bak
This commit is contained in:
1 parent
3ef5fb5a2d
commit
43933f93d1
11 files changed
+813
-76
No files matched your search
@@ -12,6 +12,7 @@ import { createHistory } from '@/composables/useHistory'
|
||||
import { useKfSelection, kfSelectionKey } from '@/composables/useKfSelection'
|
||||
import { APP_NAME, APP_VERSION } from '@/version'
|
||||
import { drawSceneFrame } from '@/lib/render'
|
||||
import { expandedTrackOrder } from '@/lib/sceneOrder'
|
||||
import {
|
||||
deleteStoredProject,
|
||||
getStoredProject,
|
||||
@@ -1221,6 +1222,200 @@ export function registerAllTests(): void {
|
||||
})
|
||||
})
|
||||
|
||||
suite('scene store — track groups', () => {
|
||||
test('addTrackGroup creates an empty group at the top of trackOrder', () => {
|
||||
const s = makeFreshStore()
|
||||
const a = s.addAsset({ kind: 'rect', name: 'G1', width: 10, height: 10, fill: '#111' })
|
||||
s.addLayer(a.id)
|
||||
const g = s.addTrackGroup('My group')
|
||||
expect(s.scene.groups).toHaveLength(1)
|
||||
expect(g.childIds).toHaveLength(0)
|
||||
expect(s.scene.trackOrder![0]).toBe(g.id)
|
||||
})
|
||||
|
||||
test('moveTrackIntoGroup moves track from top-level to group childIds', () => {
|
||||
const s = makeFreshStore()
|
||||
const a = s.addAsset({ kind: 'rect', name: 'G2', width: 10, height: 10, fill: '#222' })
|
||||
const l = s.addLayer(a.id)
|
||||
const g = s.addTrackGroup('G')
|
||||
s.moveTrackIntoGroup(l.id, g.id)
|
||||
expect(g.childIds).toContain(l.id)
|
||||
expect((s.scene.trackOrder ?? []).includes(l.id)).toBe(false)
|
||||
})
|
||||
|
||||
test('moveTrackOutOfGroup promotes track back to top-level', () => {
|
||||
const s = makeFreshStore()
|
||||
const a = s.addAsset({ kind: 'rect', name: 'G3', width: 10, height: 10, fill: '#333' })
|
||||
const l = s.addLayer(a.id)
|
||||
const g = s.addTrackGroup('G')
|
||||
s.moveTrackIntoGroup(l.id, g.id)
|
||||
s.moveTrackOutOfGroup(l.id)
|
||||
expect(g.childIds.includes(l.id)).toBe(false)
|
||||
expect(s.scene.trackOrder).toContain(l.id)
|
||||
})
|
||||
|
||||
test('removeTrackGroup promotes children back to top-level', () => {
|
||||
const s = makeFreshStore()
|
||||
const a = s.addAsset({ kind: 'rect', name: 'G4', width: 10, height: 10, fill: '#444' })
|
||||
const l = s.addLayer(a.id)
|
||||
const g = s.addTrackGroup('G')
|
||||
s.moveTrackIntoGroup(l.id, g.id)
|
||||
s.removeTrackGroup(g.id)
|
||||
expect(s.scene.groups).toHaveLength(0)
|
||||
expect(s.scene.trackOrder).toContain(l.id)
|
||||
})
|
||||
|
||||
test('toggleGroupCollapsed flips the collapsed flag', () => {
|
||||
const s = makeFreshStore()
|
||||
const g = s.addTrackGroup('G')
|
||||
expect(!!g.collapsed).toBe(false)
|
||||
s.toggleGroupCollapsed(g.id)
|
||||
expect(g.collapsed).toBe(true)
|
||||
s.toggleGroupCollapsed(g.id)
|
||||
expect(g.collapsed).toBe(false)
|
||||
})
|
||||
|
||||
test('renameGroup updates the name', () => {
|
||||
const s = makeFreshStore()
|
||||
const g = s.addTrackGroup('Old')
|
||||
s.renameGroup(g.id, 'New')
|
||||
expect(g.name).toBe('New')
|
||||
})
|
||||
|
||||
test('a track can only be in one group at a time', () => {
|
||||
const s = makeFreshStore()
|
||||
const a = s.addAsset({ kind: 'rect', name: 'X', width: 10, height: 10, fill: '#555' })
|
||||
const l = s.addLayer(a.id)
|
||||
const g1 = s.addTrackGroup('A')
|
||||
const g2 = s.addTrackGroup('B')
|
||||
s.moveTrackIntoGroup(l.id, g1.id)
|
||||
s.moveTrackIntoGroup(l.id, g2.id)
|
||||
expect(g1.childIds.includes(l.id)).toBe(false)
|
||||
expect(g2.childIds).toContain(l.id)
|
||||
})
|
||||
|
||||
test('expandedTrackOrder expands group IDs to their childIds inline', () => {
|
||||
const s = makeFreshStore()
|
||||
const a1 = s.addAsset({ kind: 'rect', name: 'A', width: 10, height: 10, fill: '#abc' })
|
||||
const a2 = s.addAsset({ kind: 'rect', name: 'B', width: 10, height: 10, fill: '#def' })
|
||||
const l1 = s.addLayer(a1.id)
|
||||
const l2 = s.addLayer(a2.id)
|
||||
const g = s.addTrackGroup('G')
|
||||
s.moveTrackIntoGroup(l1.id, g.id)
|
||||
// scene.trackOrder: [group, l2] (l1 is inside group)
|
||||
// expanded: [l1, l2]
|
||||
const expanded = expandedTrackOrder(s.scene)
|
||||
expect(expanded).toEqual([l1.id, l2.id])
|
||||
})
|
||||
|
||||
test('moveTrackIntoGroup respects atIndex for insertion', () => {
|
||||
const s = makeFreshStore()
|
||||
const a1 = s.addAsset({ kind: 'rect', name: 'A', width: 10, height: 10, fill: '#111' })
|
||||
const a2 = s.addAsset({ kind: 'rect', name: 'B', width: 10, height: 10, fill: '#222' })
|
||||
const a3 = s.addAsset({ kind: 'rect', name: 'C', width: 10, height: 10, fill: '#333' })
|
||||
const l1 = s.addLayer(a1.id)
|
||||
const l2 = s.addLayer(a2.id)
|
||||
const l3 = s.addLayer(a3.id)
|
||||
const g = s.addTrackGroup('G')
|
||||
s.moveTrackIntoGroup(l1.id, g.id) // [l1]
|
||||
s.moveTrackIntoGroup(l2.id, g.id) // [l1, l2]
|
||||
s.moveTrackIntoGroup(l3.id, g.id, 1) // [l1, l3, l2]
|
||||
expect(g.childIds).toEqual([l1.id, l3.id, l2.id])
|
||||
})
|
||||
|
||||
test('removeTrackGroup inserts children at the former group position', () => {
|
||||
const s = makeFreshStore()
|
||||
const a = s.addAsset({ kind: 'rect', name: 'X', width: 10, height: 10, fill: '#aaa' })
|
||||
const top1 = s.addLayer(a.id)
|
||||
const top2 = s.addLayer(a.id)
|
||||
const childA = s.addLayer(a.id)
|
||||
const childB = s.addLayer(a.id)
|
||||
const g = s.addTrackGroup('G')
|
||||
s.moveTrackIntoGroup(childA.id, g.id)
|
||||
s.moveTrackIntoGroup(childB.id, g.id)
|
||||
// trackOrder currently: [childB? no, children are removed from top-level]
|
||||
// trackOrder contains: [g, top2, top1] (newest addLayer unshifts)
|
||||
// Wait: addLayer unshifts; the creation order is top2(later), then top1(earlier)
|
||||
// Then addLayer(childA), childB — all unshifted. Then moved into group.
|
||||
// Let's just position group explicitly to be sure:
|
||||
const order = s.scene.trackOrder ?? []
|
||||
const gIdx = order.indexOf(g.id)
|
||||
expect(gIdx).toBeGreaterThan(-1)
|
||||
s.removeTrackGroup(g.id)
|
||||
const newOrder = s.scene.trackOrder ?? []
|
||||
// children should now sit at the former group position
|
||||
expect(newOrder[gIdx]).toBe(childA.id)
|
||||
expect(newOrder[gIdx + 1]).toBe(childB.id)
|
||||
void top1
|
||||
void top2
|
||||
})
|
||||
|
||||
test('renameGroup ignores empty / whitespace names', () => {
|
||||
const s = makeFreshStore()
|
||||
const g = s.addTrackGroup('Original')
|
||||
s.renameGroup(g.id, '')
|
||||
expect(g.name).toBe('Original')
|
||||
s.renameGroup(g.id, ' ')
|
||||
expect(g.name).toBe('Original')
|
||||
s.renameGroup(g.id, ' Renamed ')
|
||||
expect(g.name).toBe('Renamed')
|
||||
})
|
||||
|
||||
test('removeTrackGroup on unknown id is a no-op', () => {
|
||||
const s = makeFreshStore()
|
||||
const g = s.addTrackGroup('G')
|
||||
const beforeCount = (s.scene.groups ?? []).length
|
||||
s.removeTrackGroup('does-not-exist')
|
||||
expect((s.scene.groups ?? []).length).toBe(beforeCount)
|
||||
// The real group still exists
|
||||
expect(s.scene.groups?.find((x) => x.id === g.id)).toBeTruthy()
|
||||
})
|
||||
|
||||
test('setTrackColor applies to groups too', () => {
|
||||
const s = makeFreshStore()
|
||||
const g = s.addTrackGroup('Col')
|
||||
s.setTrackColor(g.id, '#ff0000')
|
||||
expect(g.color).toBe('#ff0000')
|
||||
s.setTrackColor(g.id, null)
|
||||
expect(g.color).toBe(undefined)
|
||||
})
|
||||
|
||||
test('moveTrackIntoGroup with same group twice moves within group', () => {
|
||||
const s = makeFreshStore()
|
||||
const a = s.addAsset({ kind: 'rect', name: 'M', width: 10, height: 10, fill: '#555' })
|
||||
const l1 = s.addLayer(a.id)
|
||||
const l2 = s.addLayer(a.id)
|
||||
const g = s.addTrackGroup('G')
|
||||
s.moveTrackIntoGroup(l1.id, g.id)
|
||||
s.moveTrackIntoGroup(l2.id, g.id)
|
||||
// l1 currently at index 0. Move l1 to index 1.
|
||||
s.moveTrackIntoGroup(l1.id, g.id, 1)
|
||||
expect(g.childIds).toEqual([l2.id, l1.id])
|
||||
})
|
||||
|
||||
test('normalizeScene (via loadScene) removes orphan childIds and deduplicates', () => {
|
||||
const s = makeFreshStore()
|
||||
const a = s.addAsset({ kind: 'rect', name: 'N', width: 10, height: 10, fill: '#666' })
|
||||
const l = s.addLayer(a.id)
|
||||
// Snapshot with a broken state
|
||||
const snap = JSON.parse(JSON.stringify(s.scene))
|
||||
snap.groups = [
|
||||
{ id: 'g1', name: 'G1', childIds: [l.id, 'ghost-layer', l.id] },
|
||||
{ id: 'g2', name: 'G2', childIds: [l.id] },
|
||||
]
|
||||
snap.trackOrder = [l.id, 'g1', 'g2']
|
||||
s.loadScene(snap)
|
||||
// Orphan 'ghost-layer' filtered out
|
||||
const g1 = s.scene.groups?.find((g) => g.id === 'g1')!
|
||||
const g2 = s.scene.groups?.find((g) => g.id === 'g2')!
|
||||
expect(g1.childIds.includes('ghost-layer')).toBe(false)
|
||||
// Dedupe: l.id appears only once across all groups
|
||||
const inG1 = g1.childIds.filter((id) => id === l.id).length
|
||||
const inG2 = g2.childIds.filter((id) => id === l.id).length
|
||||
expect(inG1 + inG2).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
suite('app smoke — live DOM', () => {
|
||||
test('app root, topbar, timeline, stage are mounted', () => {
|
||||
expect(document.querySelector('.app')).toBeTruthy()
|
||||
@@ -1254,6 +1449,11 @@ export function registerAllTests(): void {
|
||||
const dz = document.querySelector('.clip-track-dropzone')
|
||||
expect(dz).toBeTruthy()
|
||||
})
|
||||
|
||||
test('timeline has add-group button', () => {
|
||||
const btn = document.querySelector('button[title="Create a new track group"]')
|
||||
expect(btn).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
suite('version — app metadata', () => {
|
||||
|
||||
Reference in new issue
Block a user