Files
feuerwerk/tests/core/serialize.test.ts
T
rootandClaude Opus 5 76aa441dce Enabled switch, text assets, mouse input and the jump-and-run example game
Engine additions that the example game needed:
- `enabled` as a shared property of every placeable asset (default on, an
  override on the scene object). A switched-off object is not drawn, does not
  collide and its behavior does not run - and neither does anything parented
  below it. Disabled objects are now hidden in the scene view as well, so the
  editor shows what the game shows; they stay in the hierarchy panel, which is
  where they are selected and switched back on.
- Asset type `text` (text, fontSize, color, align), rendered in scene and game.
- Pointer input in the game view: `On click`, `Mouse position`, `Mouse button`,
  `Is hovered`, hit-tested against colliders, else sprite/text footprints.
- New nodes: Set/Is enabled, Set text, Is asset, Name of, Equals, To text,
  Join text, Floor/Ceil/Round, Modulo, Random, Time, Restart game.

Tooling: shared CDP harness (tools/harness.mjs), central project registry with
a guard that keeps destructive scripts off the playground (tools/projects.mjs),
plus smoke suites for the demo scene and the jump-and-run game.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 20:52:10 +02:00

62 lines
2.6 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { CreateAssetCommand } from '../../src/core/commands/assetCommands';
import { CommandStack } from '../../src/core/commands/CommandStack';
import { PlaceAssetCommand, SetOverrideCommand } from '../../src/core/commands/sceneObjectCommands';
import { createEmptyProject } from '../../src/core/model/Project';
import { deserializeProject, serializeProject } from '../../src/core/store/serialize';
describe('serialize', () => {
it('round-trips a project', () => {
const project = createEmptyProject('p1', 'Round trip');
const stack = new CommandStack(project);
const scene = new CreateAssetCommand('scene', 'Level');
const sprite = new CreateAssetCommand('sprite', 'Box');
stack.execute(scene);
stack.execute(sprite);
const place = new PlaceAssetCommand(scene.asset.id, sprite.asset.id, { x: 7, y: 8 });
stack.execute(place);
stack.execute(new SetOverrideCommand(scene.asset.id, place.objectId, 'width', 33));
const copy = deserializeProject(JSON.parse(JSON.stringify(serializeProject(project))));
expect(copy).toEqual(project);
});
it('repairs broken documents', () => {
const project = deserializeProject(
{
name: 'Broken',
activeSceneId: 'missing',
assets: [
{ id: 's1', type: 'scene', name: 'Scene', props: { backgroundColor: '#000000', junk: 1 }, objects: [
{ id: 'o1', assetId: 'sp1', position: { x: '5', y: null }, overrides: { color: '#00ff00', width: -3, foo: 1 } },
{ id: 'o2', assetId: 'nope', position: {} },
{ id: 'o3', assetId: 's1', position: {} },
] },
{ id: 'sp1', type: 'sprite', props: { width: 10 } },
{ id: 'x', type: 'texture' },
{ type: 'camera' },
],
},
'fallback-id',
);
expect(project.id).toBe('fallback-id');
expect(project.activeSceneId).toBe('s1');
expect(project.assets.map((a) => a.id)).toEqual(['s1', 'sp1']);
const scene = project.assets[0]!;
expect(scene.type === 'scene' && scene.props).toEqual({ backgroundColor: 0 });
const objects = scene.type === 'scene' ? scene.objects : [];
expect(objects).toHaveLength(1);
expect(objects[0]).toEqual({
id: 'o1',
assetId: 'sp1',
name: '',
position: { x: 5, y: 0, z: 0 },
overrides: { color: 0x00ff00, width: 1 },
parentId: '',
templateId: '',
});
expect(project.assets[1]!.name).toBe('sprite');
expect(project.assets[1]!.props).toEqual({ enabled: true, width: 10, height: 100, color: 0xe0553f });
});
});