Add camera frontend tests
This commit is contained in:
1 parent
23a66c6fdf
commit
3a93d6ea5d
1 file changed
+147
@@ -10199,6 +10199,153 @@ export function registerAllTests(): void {
|
||||
})
|
||||
})
|
||||
|
||||
suite('ui — camera framing', () => {
|
||||
function resetPropertySectionPrefs() {
|
||||
try {
|
||||
localStorage.removeItem('animatr:propSections')
|
||||
} catch {
|
||||
/* noop */
|
||||
}
|
||||
}
|
||||
|
||||
function stubLaneRect(lane: HTMLElement, width = 1000): void {
|
||||
const fakeRect = {
|
||||
left: 0,
|
||||
top: 0,
|
||||
width,
|
||||
height: 20,
|
||||
right: width,
|
||||
bottom: 20,
|
||||
x: 0,
|
||||
y: 0,
|
||||
toJSON: () => ({}),
|
||||
} as DOMRect
|
||||
;(lane as unknown as { getBoundingClientRect: () => DOMRect }).getBoundingClientRect = () =>
|
||||
fakeRect
|
||||
}
|
||||
|
||||
test('stage camera button enables camera without resizing the editor stage', async () => {
|
||||
const s = makeFreshStore()
|
||||
s.scene.canvas.width = 640
|
||||
s.scene.canvas.height = 360
|
||||
const handle = mountComponent(CanvasStage, {})
|
||||
try {
|
||||
await nextTick()
|
||||
const dims = handle.host.querySelector('.dims') as HTMLElement
|
||||
const btn = handle.host.querySelector('.zoom-controls .camera-btn') as HTMLButtonElement
|
||||
expect(dims).toBeTruthy()
|
||||
expect(btn).toBeTruthy()
|
||||
expect(dims.textContent?.trim()).toBe('640 × 360')
|
||||
expect(s.scene.camera).toBe(undefined)
|
||||
|
||||
btn.click()
|
||||
await nextTick()
|
||||
expect(s.scene.camera?.enabled).toBe(true)
|
||||
expect(s.selectedCamera).toBe(true)
|
||||
expect(btn.classList.contains('active')).toBe(true)
|
||||
expect(dims.textContent?.trim()).toBe('640 × 360')
|
||||
expect(s.scene.canvas.width).toBe(640)
|
||||
expect(s.scene.canvas.height).toBe(360)
|
||||
|
||||
btn.click()
|
||||
await nextTick()
|
||||
expect(s.scene.camera?.enabled).toBe(false)
|
||||
expect(dims.textContent?.trim()).toBe('640 × 360')
|
||||
} finally {
|
||||
handle.unmount()
|
||||
}
|
||||
})
|
||||
|
||||
test('properties panel edits the selected camera frame, not scene canvas size', async () => {
|
||||
resetPropertySectionPrefs()
|
||||
const s = makeFreshStore()
|
||||
s.scene.canvas.width = 800
|
||||
s.scene.canvas.height = 450
|
||||
s.enableCamera()
|
||||
s.selectCamera(true)
|
||||
const handle = mountComponent(PropertiesPanel, {})
|
||||
try {
|
||||
await nextTick()
|
||||
const banner = handle.host.querySelector('.multi-banner') as HTMLElement
|
||||
expect(banner.textContent).toContain(i18nT('properties.camera.banner'))
|
||||
const inputs = handle.host.querySelectorAll<HTMLInputElement>(
|
||||
'.prop-group input[type="number"]',
|
||||
)
|
||||
expect(inputs.length).toBe(4)
|
||||
const [xInput, yInput, widthInput, heightInput] = Array.from(inputs)
|
||||
expect(Number(widthInput.value)).toBe(800)
|
||||
expect(Number(heightInput.value)).toBe(450)
|
||||
|
||||
xInput.value = '24'
|
||||
xInput.dispatchEvent(new Event('input', { bubbles: true }))
|
||||
widthInput.value = '320'
|
||||
widthInput.dispatchEvent(new Event('input', { bubbles: true }))
|
||||
await nextTick()
|
||||
|
||||
expect(s.scene.camera?.keyframes.length).toBe(1)
|
||||
expect(s.scene.camera?.keyframes[0].props.x).toBe(24)
|
||||
expect(s.scene.camera?.keyframes[0].props.width).toBe(320)
|
||||
expect(s.scene.canvas.width).toBe(800)
|
||||
expect(s.scene.canvas.height).toBe(450)
|
||||
void yInput
|
||||
} finally {
|
||||
handle.unmount()
|
||||
}
|
||||
})
|
||||
|
||||
test('timeline camera row selects, hides, locks, and adds camera keyframes', async () => {
|
||||
const s = makeFreshStore()
|
||||
s.setDuration(10)
|
||||
s.enableCamera()
|
||||
s.selectCamera(false)
|
||||
const handle = mountComponent(Timeline, {})
|
||||
try {
|
||||
await nextTick()
|
||||
const trackName = handle.host.querySelector('.camera-track .track-name') as HTMLElement
|
||||
const eye = handle.host.querySelector('.camera-track .eye-btn') as HTMLButtonElement
|
||||
const lock = handle.host.querySelector('.camera-track .lock-btn') as HTMLButtonElement
|
||||
const lane = handle.host.querySelector('.camera-track .camera-lane') as HTMLElement
|
||||
expect(trackName).toBeTruthy()
|
||||
expect(eye).toBeTruthy()
|
||||
expect(lock).toBeTruthy()
|
||||
expect(lane).toBeTruthy()
|
||||
|
||||
trackName.click()
|
||||
await nextTick()
|
||||
expect(s.selectedCamera).toBe(true)
|
||||
|
||||
eye.click()
|
||||
await nextTick()
|
||||
expect(s.scene.camera?.overlayVisible).toBe(false)
|
||||
expect(eye.classList.contains('hidden')).toBe(true)
|
||||
|
||||
lock.click()
|
||||
await nextTick()
|
||||
expect(s.scene.camera?.locked).toBe(true)
|
||||
expect(lock.classList.contains('locked')).toBe(true)
|
||||
|
||||
stubLaneRect(lane)
|
||||
lane.dispatchEvent(
|
||||
new MouseEvent('dblclick', { bubbles: true, cancelable: true, clientX: 500 }),
|
||||
)
|
||||
await nextTick()
|
||||
expect(s.scene.camera?.keyframes.length).toBe(0)
|
||||
|
||||
lock.click()
|
||||
await nextTick()
|
||||
lane.dispatchEvent(
|
||||
new MouseEvent('dblclick', { bubbles: true, cancelable: true, clientX: 500 }),
|
||||
)
|
||||
await nextTick()
|
||||
expect(s.scene.camera?.keyframes.length).toBe(1)
|
||||
expect(Math.abs((s.scene.camera?.keyframes[0].time ?? 0) - 5) < 0.1).toBe(true)
|
||||
expect(Math.abs(s.playhead - 5) < 0.1).toBe(true)
|
||||
} finally {
|
||||
handle.unmount()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
suite('ui — CanvasStage centre crosshair (Konva)', () => {
|
||||
// Verify the lines actually render at the correct coordinates in
|
||||
// Konva when the toggle is on. Requires a proper VueKonva mount.
|
||||
|
||||
Reference in new issue
Block a user