The scene view and the animation preview share one camera, the three form dialogs one base, and keys are found through one set of helpers (#85)
game-maker / gate (push) Successful in 1m36s
game-maker / suite (push) Successful in 5m41s

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5 committed 2026-09-17 14:19:53 +02:00
1 parent bf02c9e8b3
commit 2a390e52ff
22 files changed
+418 -452

No files matched your search

+21 -15
View File
@@ -11,10 +11,16 @@ import {
} from '../../src/core/commands/animationCommands';
import { CreateAssetCommand } from '../../src/core/commands/assetCommands';
import { CommandError } from '../../src/core/commands/Command';
import { animatableDefs, animationTargets, targetAssetOf, trackLabel } from '../../src/core/model/animation';
import {
animatableDefs,
animationTargets,
PLACED_ROOT_ID,
targetAssetOf,
trackLabel,
} from '../../src/core/model/animation';
import { CreateContainerCommand } from '../../src/core/commands/containerCommands';
import { RemoveSceneObjectsCommand } from '../../src/core/commands/sceneObjectCommands';
import { PREVIEW_ROOT_ID, previewHolder, previewObjectId } from '../../src/core/model/animationPreview';
import { previewHolder, previewObjectId } from '../../src/core/model/animationPreview';
import type { AnimationAsset, AnimationKey, ContainerAsset } from '../../src/core/model/Asset';
import { instanceChildId, resolveObjects } from '../../src/core/model/instance';
import { findAsset, findAssetOf } from '../../src/core/model/Project';
@@ -59,10 +65,10 @@ describe('the preview of an animation over a sprite', () => {
[0, 0],
[10, 100],
]);
const root = drawn(f, current(f, walk.id), 5).get(PREVIEW_ROOT_ID)!;
const root = drawn(f, current(f, walk.id), 5).get(PLACED_ROOT_ID)!;
expect(root.assetId).toBe(f.spriteId);
expect(root.position).toEqual({ x: 50, y: 0, z: 0 });
expect(previewObjectId(findAsset(f.project, f.spriteId), '')).toBe(PREVIEW_ROOT_ID);
expect(previewObjectId(findAsset(f.project, f.spriteId), '')).toBe(PLACED_ROOT_ID);
});
it('holds the first value before the first key and the last one after the last', () => {
@@ -73,7 +79,7 @@ describe('the preview of an animation over a sprite', () => {
[4, 30],
]);
const at = (frame: number) =>
drawn(f, current(f, walk.id), frame).get(PREVIEW_ROOT_ID)!.overrides.rotation;
drawn(f, current(f, walk.id), frame).get(PLACED_ROOT_ID)!.overrides.rotation;
expect(at(0)).toBe(10);
expect(at(3)).toBe(20);
expect(at(9)).toBe(30);
@@ -85,7 +91,7 @@ describe('the preview of an animation over a sprite', () => {
addRow(f, walk, 'y', [[0, 40]]);
const holder = previewHolder(f.project, current(f, walk.id), 0);
const { items } = collectHolderItems(f.project, holder, 0);
expect(items.map((i) => [i.id, i.type, i.y])).toEqual([[PREVIEW_ROOT_ID, 'sprite', 40]]);
expect(items.map((i) => [i.id, i.type, i.y])).toEqual([[PLACED_ROOT_ID, 'sprite', 40]]);
});
it('never writes into the document', () => {
@@ -104,14 +110,14 @@ describe('the preview of an animation over a sprite', () => {
[0, 0],
[9, 90],
]);
expect(drawn(f, current(f, reset.id), 9).get(PREVIEW_ROOT_ID)!.position.x).toBe(90);
expect(drawn(f, current(f, reset.id), 10).get(PREVIEW_ROOT_ID)!.position.x).toBe(0);
expect(drawn(f, current(f, reset.id), 9).get(PLACED_ROOT_ID)!.position.x).toBe(90);
expect(drawn(f, current(f, reset.id), 10).get(PLACED_ROOT_ID)!.position.x).toBe(0);
const hold = addAnimation(f, f.spriteId, { playback: 'once' });
addRow(f, hold, 'x', [
[0, 0],
[9, 90],
]);
expect(drawn(f, current(f, hold.id), 10).get(PREVIEW_ROOT_ID)!.position.x).toBe(90);
expect(drawn(f, current(f, hold.id), 10).get(PLACED_ROOT_ID)!.position.x).toBe(90);
});
});
@@ -133,10 +139,10 @@ describe('the preview of an animation over a container', () => {
addRow(f, walk, 'x', [[0, 55]], arm);
addRow(f, walk, 'rotation', [[0, 45]]);
const objects = drawn(f, current(f, walk.id), 0);
const childId = instanceChildId(PREVIEW_ROOT_ID, arm);
const childId = instanceChildId(PLACED_ROOT_ID, arm);
expect(previewObjectId(findAsset(f.project, container.id), arm)).toBe(childId);
expect(objects.get(childId)!.position).toEqual({ ...at, x: 55 });
expect(objects.get(PREVIEW_ROOT_ID)!.overrides.rotation).toBe(45);
expect(objects.get(PLACED_ROOT_ID)!.overrides.rotation).toBe(45);
});
it('adds a relative row to what the child holds in the template', () => {
@@ -144,7 +150,7 @@ describe('the preview of an animation over a container', () => {
const walk = addAnimation(f, container.id);
const row = addRow(f, walk, 'x', [[0, 5]], arm);
f.stack.execute(new SetTrackRelativeCommand(walk.id, row, true));
const child = drawn(f, current(f, walk.id), 0).get(instanceChildId(PREVIEW_ROOT_ID, arm))!;
const child = drawn(f, current(f, walk.id), 0).get(instanceChildId(PLACED_ROOT_ID, arm))!;
expect(child.position.x).toBe(at.x + 5);
});
@@ -162,8 +168,8 @@ describe('the preview of an animation over a container', () => {
// The first template object goes: its row stays and aims at nothing.
f.stack.execute(new RemoveSceneObjectsCommand(wrap.assetId, [arm!]));
const objects = drawn(f, current(f, walk.id), 0);
expect(objects.has(instanceChildId(PREVIEW_ROOT_ID, arm!))).toBe(false);
expect(objects.get(instanceChildId(PREVIEW_ROOT_ID, other!))!.position.x).toBe(66);
expect(objects.has(instanceChildId(PLACED_ROOT_ID, arm!))).toBe(false);
expect(objects.get(instanceChildId(PLACED_ROOT_ID, other!))!.position.x).toBe(66);
});
});
@@ -224,7 +230,7 @@ describe('the reach of a row (#109)', () => {
const walk = addAnimation(f, outer.assetId);
addRow(f, walk, 'x', [[0, 55]], path);
const before = JSON.stringify(f.project);
const child = drawn(f, current(f, walk.id), 0).get(instanceChildId(PREVIEW_ROOT_ID, path))!;
const child = drawn(f, current(f, walk.id), 0).get(instanceChildId(PLACED_ROOT_ID, path))!;
expect(child.position).toEqual({ ...armTemplate.position, x: 55 });
expect(JSON.stringify(f.project)).toBe(before);
});
+19 -2
View File
@@ -32,6 +32,7 @@ import {
animationDuration,
frameCount,
pauseAnimation,
PLACED_ROOT_ID,
playAnimation,
playheadAt,
stopAnimation,
@@ -41,7 +42,7 @@ import {
trackValueAt,
} from '../../src/core/model/animation';
import { SHADER_KEY, SHADER_PROPERTY } from '../../src/core/properties/shaders';
import { PREVIEW_ROOT_ID, previewHolder } from '../../src/core/model/animationPreview';
import { previewHolder } from '../../src/core/model/animationPreview';
import type {
AnimationAsset,
AnimationKey,
@@ -375,6 +376,22 @@ describe('editing the timeline', () => {
expect(rowOf(walk, id).keys).toEqual(keys([0, 0], [5, 50]));
});
it('refuses a key before frame 0 and a key that is not there, changing nothing', () => {
const f = makeSpriteScene();
const walk = addAnimation(f, f.spriteId);
const id = addRow(f, walk, 'x', [[0, 0]]);
const missing = [{ trackId: id, frame: 4 }];
const refused = [
new SetAnimationKeyCommand(walk.id, id, -1),
new ShiftAnimationFramesCommand(walk.id, -1, 1),
new RemoveAnimationKeysCommand(walk.id, missing),
new MoveAnimationKeysCommand(walk.id, missing, 1),
new SetKeyInterpolationCommand(walk.id, missing, 'step'),
];
for (const command of refused) expect(() => f.stack.execute(command)).toThrow(CommandError);
expect(rowOf(walk, id).keys).toEqual(keys([0, 0]));
});
it('aims a row elsewhere keeping its keys, only at a property of the same kind', () => {
const f = makeSpriteScene();
const walk = addAnimation(f, f.spriteId);
@@ -599,7 +616,7 @@ describe('the AnimationSystem', () => {
frame(0.05);
frame(0.45);
const previewed = previewHolder(f.project, findAssetOf(f.project, walk.id, 'animation')!, 4.5)!;
const root = previewed.objects.find((o) => o.id === PREVIEW_ROOT_ID)!;
const root = previewed.objects.find((o) => o.id === PLACED_ROOT_ID)!;
expect(sprite.x).toBeCloseTo(22.5);
expect(root.position.x).toBeCloseTo(sprite.x);
});