import { describe, expect, it } from 'vitest'; import { CreateAssetCommand, DeleteAssetCommand, RenameAssetCommand, ResetAssetPropertyCommand, SetActiveSceneCommand, SetAssetPropertyCommand, } from '../../src/core/commands/assetCommands'; import { ClearOverrideCommand, MoveSceneObjectCommand, PlaceAssetCommand, RemoveSceneObjectsCommand, SetOverrideCommand, } from '../../src/core/commands/sceneObjectCommands'; import { findAsset } from '../../src/core/model/Project'; import { makeSpriteScene, place } from '../helpers'; describe('asset commands', () => { it('creates assets with defaults and activates the first scene', () => { const { project, sceneId, spriteId } = makeSpriteScene(); expect(project.assets).toHaveLength(2); expect(project.activeSceneId).toBe(sceneId); expect(findAsset(project, spriteId)?.props).toEqual({ enabled: true, keepOnSceneLoad: false, scale: 1, rotation: 0, mirrorX: false, mirrorY: false, sortWithParent: true, renderLayer: -1, width: 100, height: 100, color: 0xffffff, texture: '', videoClock: 'free', videoTime: 0, shader: '', }); }); it('renames and sets properties with undo/redo', () => { const { project, stack, spriteId } = makeSpriteScene(); const sprite = () => findAsset(project, spriteId)!; stack.execute(new RenameAssetCommand(spriteId, 'Crate')); stack.execute(new SetAssetPropertyCommand(spriteId, 'width', 250)); expect(sprite().name).toBe('Crate'); expect(sprite().props.width).toBe(250); stack.undo(); expect(sprite().props.width).toBe(100); stack.undo(); expect(sprite().name).toBe('Box'); stack.redo(); stack.redo(); expect(sprite().name).toBe('Crate'); expect(sprite().props.width).toBe(250); }); it('rejects unknown properties', () => { const { stack, spriteId } = makeSpriteScene(); expect(() => stack.execute(new SetAssetPropertyCommand(spriteId, 'x', 5))).toThrow(/not defined/); expect(() => stack.execute(new ResetAssetPropertyCommand(spriteId, 'x'))).toThrow(/not defined/); }); it('resets a property to its schema default with undo', () => { const { project, stack, spriteId } = makeSpriteScene(); const sprite = () => findAsset(project, spriteId)!; stack.execute(new SetAssetPropertyCommand(spriteId, 'height', 60)); stack.execute(new ResetAssetPropertyCommand(spriteId, 'height')); expect(sprite().props.height).toBe(100); stack.undo(); expect(sprite().props.height).toBe(60); stack.redo(); expect(sprite().props.height).toBe(100); }); it('deletes an asset together with its scene objects and restores both on undo', () => { const f = makeSpriteScene(); const { project, stack, spriteId, sceneOf } = f; place(f, spriteId, { x: 1, y: 2 }); place(f, spriteId, { x: 3, y: 4 }); expect(sceneOf().objects).toHaveLength(2); stack.execute(new DeleteAssetCommand(spriteId)); expect(project.assets).toHaveLength(1); expect(sceneOf().objects).toHaveLength(0); stack.undo(); expect(project.assets).toHaveLength(2); expect(sceneOf().objects.map((o) => o.position)).toEqual([ { x: 1, y: 2, z: 0 }, { x: 3, y: 4, z: 0 }, ]); }); it('switches the active scene when the active one is deleted', () => { const { project, stack, sceneId } = makeSpriteScene(); const second = new CreateAssetCommand('scene', 'Level 2'); stack.execute(second); stack.execute(new SetActiveSceneCommand(second.asset.id)); stack.execute(new DeleteAssetCommand(second.asset.id)); expect(project.activeSceneId).toBe(sceneId); stack.undo(); expect(project.activeSceneId).toBe(second.asset.id); }); }); describe('scene object commands', () => { it('places assets and refuses scenes', () => { const f = makeSpriteScene(); const { stack, sceneId, spriteId, sceneOf } = f; const box = place(f, spriteId, { x: 5, y: 6 }); const object = sceneOf().objects[0]!; expect(object.id).toBe(box); expect(object.assetId).toBe(spriteId); expect(object.position).toEqual({ x: 5, y: 6, z: 0 }); expect(object.overrides).toEqual({}); expect(() => stack.execute(new PlaceAssetCommand(sceneId, sceneId, { x: 0, y: 0 }))).toThrow( /cannot be placed/, ); expect(sceneOf().objects).toHaveLength(1); }); it('moves, overrides, clears and removes with undo', () => { const f = makeSpriteScene(); const { stack, sceneId, spriteId, sceneOf, object } = f; const box = place(f, spriteId, { x: 0, y: 0 }); stack.execute(new MoveSceneObjectCommand(sceneId, box, { x: 40, y: 50, z: 3 })); expect(object(box).position).toEqual({ x: 40, y: 50, z: 3 }); stack.undo(); expect(object(box).position).toEqual({ x: 0, y: 0, z: 0 }); stack.redo(); stack.execute(new SetOverrideCommand(sceneId, box, 'color', 0x123456)); expect(object(box).overrides).toEqual({ color: 0x123456 }); expect(() => stack.execute(new SetOverrideCommand(sceneId, box, 'nope', 1))).toThrow(); stack.execute(new ClearOverrideCommand(sceneId, box, 'color')); expect(object(box).overrides).toEqual({}); stack.undo(); expect(object(box).overrides).toEqual({ color: 0x123456 }); stack.execute(new RemoveSceneObjectsCommand(sceneId, [box])); expect(sceneOf().objects).toHaveLength(0); stack.undo(); expect(object(box).position).toEqual({ x: 40, y: 50, z: 3 }); }); it('clears the redo stack on a new command', () => { const f = makeSpriteScene(); place(f, f.spriteId, { x: 0, y: 0 }); f.stack.undo(); expect(f.stack.canRedo).toBe(true); place(f, f.spriteId, { x: 1, y: 1 }); expect(f.stack.canRedo).toBe(false); }); });