import { describe, expect, it } from 'vitest'; import { CreateAssetCommand } from '../../src/core/commands/assetCommands'; import { SetOverrideCommand } from '../../src/core/commands/sceneObjectCommands'; import { treeOrder } from '../../src/core/model/hierarchy'; import { makeSpriteScene, place, startWorld } from '../helpers'; /** The fixture's scene with a camera asset, and switching a placed object off the way the inspector does. */ function sceneWithCamera() { const fixture = makeSpriteScene(); const camera = new CreateAssetCommand('camera', 'Cam'); fixture.stack.execute(camera); return { ...fixture, cameraId: camera.asset.id, switchOff: (objectId: string) => fixture.stack.execute(new SetOverrideCommand(fixture.sceneId, objectId, 'enabled', false)), }; } describe('the camera the game is seen through', () => { it('skips a camera that is switched off, so nothing is rendered while it is the only one', () => { const f = sceneWithCamera(); const cam = place(f, f.cameraId, { x: 0, y: 0 }); const { world } = startWorld(f); expect(world.activeCamera()?.objectId).toBe(cam); // Switching it off reaches the running world through the same sync an inspector edit uses. f.switchOff(cam); world.syncFromModel(); expect(world.activeCamera()).toBeUndefined(); f.stack.undo(); world.syncFromModel(); expect(world.activeCamera()?.objectId).toBe(cam); }); it('is off as well when a parent above it is off', () => { const f = sceneWithCamera(); const holder = place(f, f.spriteId, { x: 0, y: 0 }); const cam = place(f, f.cameraId, { x: 0, y: 0 }, holder); const { world } = startWorld(f); expect(world.activeCamera()?.objectId).toBe(cam); f.switchOff(holder); world.syncFromModel(); expect(world.activeCamera()).toBeUndefined(); }); it('takes the first switched-on one, and hands the view over when that one goes off', () => { const f = sceneWithCamera(); const first = place(f, f.cameraId, { x: 0, y: 0 }); const second = place(f, f.cameraId, { x: 500, y: 0 }); const { world } = startWorld(f); expect(world.activeCamera()?.objectId).toBe(first); f.switchOff(first); world.syncFromModel(); expect(world.activeCamera()?.objectId).toBe(second); // And back: the order decides again as soon as both are on. f.stack.undo(); world.syncFromModel(); expect(world.activeCamera()?.objectId).toBe(first); f.switchOff(second); world.syncFromModel(); expect(world.activeCamera()?.objectId).toBe(first); }); it('reads "first" in hierarchy order – what the panel shows – not in storage order', () => { const f = sceneWithCamera(); const holder = place(f, f.spriteId, { x: 0, y: 0 }); const stored = place(f, f.cameraId, { x: 500, y: 0 }); const nested = place(f, f.cameraId, { x: 0, y: 0 }, holder); // The nested camera is stored last but listed first, right below its parent. expect(f.sceneOf().objects.map((o) => o.id)).toEqual([holder, stored, nested]); expect(treeOrder(f.sceneOf()).map(({ object }) => object.id)).toEqual([holder, nested, stored]); const { world } = startWorld(f); expect(world.activeCamera()?.objectId).toBe(nested); }); });