Files
feuerwerk/tests/editor/track-selection.test.ts
T

96 lines
4.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { beforeEach, describe, expect, it } from 'vitest';
import {
AddAnimationTrackCommand,
MoveAnimationKeysCommand,
RemoveAnimationKeysCommand,
RemoveAnimationTracksCommand,
SetAnimationKeyCommand,
SetAnimationRootCommand,
} from '../../src/core/commands/animationCommands';
import { CreateAssetCommand } from '../../src/core/commands/assetCommands';
import type { AnimationAsset } from '../../src/core/model/Asset';
import { createEmptyProject } from '../../src/core/model/Project';
import { EditorState, trackSelection } from '../../src/editor/state';
/** A sprite, an animation over it, one row on `x` with keys on 0 and 4, and a second row on `y`. */
function setup(): { state: EditorState; animation: AnimationAsset; x: string; y: string } {
const state = new EditorState();
state.loadProject(createEmptyProject('p', 'P'), 1);
const sprite = new CreateAssetCommand('sprite', 'Dancer');
expect(state.execute(sprite)).toBe(true);
const create = new CreateAssetCommand('animation', 'Walk');
expect(state.execute(create)).toBe(true);
const animation = create.asset as AnimationAsset;
expect(state.execute(new SetAnimationRootCommand(animation.id, sprite.asset.id))).toBe(true);
const x = new AddAnimationTrackCommand(animation.id, '', 'x');
const y = new AddAnimationTrackCommand(animation.id, '', 'y');
expect(state.execute(x)).toBe(true);
expect(state.execute(y)).toBe(true);
expect(state.execute(new SetAnimationKeyCommand(animation.id, x.trackId, 0, 0))).toBe(true);
expect(state.execute(new SetAnimationKeyCommand(animation.id, x.trackId, 4, 40))).toBe(true);
return { state, animation, x: x.trackId, y: y.trackId };
}
describe('selecting rows and keys of a timeline', () => {
let f: ReturnType<typeof setup>;
beforeEach(() => {
f = setup();
});
it('a row alone is a track selection without keys, and keeps the animation on the tab', () => {
f.state.select({ kind: 'track', assetId: f.animation.id, trackId: f.x, keys: [] });
expect(f.state.selectedKeys).toEqual([]);
expect(f.state.selectedAnimationId).toBe(f.animation.id);
});
it('the row of the key picked last is the one the inspector describes', () => {
const keys = [
{ trackId: f.x, frame: 0 },
{ trackId: f.x, frame: 4 },
];
f.state.select(trackSelection(f.animation.id, keys));
expect(f.state.selection).toMatchObject({ kind: 'track', trackId: f.x, keys });
// The same key twice is one key.
f.state.select(trackSelection(f.animation.id, [...keys, keys[0]!]));
expect(f.state.selectedKeys).toHaveLength(2);
});
it('accepts the short form a test hook passes, without keys', () => {
f.state.select({ kind: 'track', assetId: f.animation.id, trackId: f.y } as never);
expect(f.state.selection).toEqual({ kind: 'track', assetId: f.animation.id, trackId: f.y, keys: [] });
});
it('drops a key that was deleted and keeps the rest', () => {
f.state.select(
trackSelection(f.animation.id, [
{ trackId: f.x, frame: 0 },
{ trackId: f.x, frame: 4 },
]),
);
f.state.execute(new RemoveAnimationKeysCommand(f.animation.id, [{ trackId: f.x, frame: 4 }]));
expect(f.state.selectedKeys).toEqual([{ trackId: f.x, frame: 0 }]);
});
it('a moved key leaves the selection – the frame it was picked at holds no key any more', () => {
f.state.select(trackSelection(f.animation.id, [{ trackId: f.x, frame: 4 }]));
f.state.execute(new MoveAnimationKeysCommand(f.animation.id, [{ trackId: f.x, frame: 4 }], 2));
// The canvas re-selects at the new frame after a drag; the state alone falls back to the row.
expect(f.state.selection).toEqual({ kind: 'track', assetId: f.animation.id, trackId: f.x, keys: [] });
});
it('falls back to the animation when the row itself is deleted, so the tab stays open', () => {
f.state.select({ kind: 'track', assetId: f.animation.id, trackId: f.x, keys: [] });
f.state.execute(new RemoveAnimationTracksCommand(f.animation.id, [f.x]));
expect(f.state.selection).toEqual({ kind: 'asset', assetId: f.animation.id });
});
it('comes back with undo: the row returns, the selection was already elsewhere', () => {
f.state.select({ kind: 'track', assetId: f.animation.id, trackId: f.x, keys: [] });
f.state.execute(new RemoveAnimationTracksCommand(f.animation.id, [f.x]));
f.state.undo();
const rows = (f.state.project.assets.find((a) => a.id === f.animation.id) as AnimationAsset).tracks;
expect(rows.map((t) => t.id)).toEqual([f.x, f.y]);
expect(f.state.selection).toEqual({ kind: 'asset', assetId: f.animation.id });
});
});