Canvas-only 2D game editor/runtime built on PixiJS v8 + Vite + TypeScript. - core: asset model (Scene/Camera/Sprite), scene objects with sparse overrides, property schema + resolve, command stack (undo/redo), serialization and project store interface (16 unit tests) - ui: custom in-canvas widget toolkit (panels, fields, color picker, list/scroll, popup) with a hidden <input> keyboard bridge - editor: asset/hierarchy panels, schema-driven inspector, toolbar - viewport: box rendering, camera frame, selection gizmo, drag/zoom/pan - runtime: world/entity instantiation for play mode, input, camera follow - api: PHP JSON project store with revision counter (409 on conflict) - tools: CDP-based e2e smoke test Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
60 lines
2.6 KiB
TypeScript
60 lines
2.6 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { buildAsset } from '../../src/core/commands/assetCommands';
|
|
import { createSceneObject } from '../../src/core/model/SceneObject';
|
|
import { coerceValue, defaultsFor, schemaFor } from '../../src/core/properties/PropertySchema';
|
|
import { inheritedValue, isOverridden, resolveAssetProps, resolveSceneObjectProps } from '../../src/core/properties/resolve';
|
|
|
|
describe('PropertySchema', () => {
|
|
it('has no position on any asset type', () => {
|
|
for (const type of ['scene', 'camera', 'sprite'] as const) {
|
|
const keys = schemaFor(type).map((d) => d.key);
|
|
expect(keys).not.toContain('x');
|
|
expect(keys).not.toContain('y');
|
|
expect(keys).not.toContain('position');
|
|
}
|
|
});
|
|
|
|
it('provides defaults for every key', () => {
|
|
expect(defaultsFor('sprite')).toEqual({ width: 100, height: 100, color: 0xe0553f });
|
|
});
|
|
|
|
it('coerces values against the definition', () => {
|
|
const width = schemaFor('sprite').find((d) => d.key === 'width')!;
|
|
expect(coerceValue(width, -5)).toBe(1);
|
|
expect(coerceValue(width, '42')).toBe(42);
|
|
expect(coerceValue(width, 'abc')).toBe(100);
|
|
const color = schemaFor('sprite').find((d) => d.key === 'color')!;
|
|
expect(coerceValue(color, '#ff0000')).toBe(0xff0000);
|
|
expect(coerceValue(color, 0x1ffffff)).toBe(0xffffff);
|
|
});
|
|
});
|
|
|
|
describe('resolve', () => {
|
|
it('fills asset props with defaults and ignores unknown keys', () => {
|
|
const sprite = buildAsset('sprite', 'Box');
|
|
sprite.props = { width: 50, bogus: 1 };
|
|
expect(resolveAssetProps(sprite)).toEqual({ width: 50, height: 100, color: 0xe0553f });
|
|
});
|
|
|
|
it('applies sparse overrides on top of asset values', () => {
|
|
const sprite = buildAsset('sprite', 'Box');
|
|
sprite.props.color = 0x00ff00;
|
|
const object = createSceneObject('o1', sprite.id, { x: 10, y: 20 });
|
|
expect(resolveSceneObjectProps(sprite, object)).toEqual({ width: 100, height: 100, color: 0x00ff00 });
|
|
|
|
object.overrides.color = 0x0000ff;
|
|
expect(resolveSceneObjectProps(sprite, object).color).toBe(0x0000ff);
|
|
expect(isOverridden(object, 'color')).toBe(true);
|
|
expect(isOverridden(object, 'width')).toBe(false);
|
|
expect(inheritedValue(sprite, 'color')).toBe(0x00ff00);
|
|
});
|
|
|
|
it('keeps position on the scene object only', () => {
|
|
const sprite = buildAsset('sprite', 'Box');
|
|
const object = createSceneObject('o1', sprite.id, { x: 10, y: 20 });
|
|
expect(object.position).toEqual({ x: 10, y: 20 });
|
|
expect('position' in sprite).toBe(false);
|
|
expect(resolveSceneObjectProps(sprite, object)).not.toHaveProperty('x');
|
|
});
|
|
});
|