Files
feuerwerk/tests/core/serialize.test.ts
T

172 lines
6.3 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 { describe, expect, it } from 'vitest';
import {
CreateAssetCommand,
SetAssetPropertyCommand,
SetMachineScopeCommand,
} from '../../src/core/commands/assetCommands';
import { SetOverrideCommand } from '../../src/core/commands/sceneObjectCommands';
import { AddVariableCommand } from '../../src/core/commands/variableCommands';
import { deserializeProject, serializeProject } from '../../src/core/store/serialize';
import { makeBehavior, makeSpriteScene, place, roundTrip } from '../helpers';
describe('serialize', () => {
it('round-trips a project', () => {
const f = makeSpriteScene();
const box = place(f, f.spriteId, { x: 7, y: 8 });
f.stack.execute(new SetOverrideCommand(f.sceneId, box, 'width', 33));
expect(roundTrip(f.project)).toEqual(f.project);
});
/** A behavior's own rows are not the common ones: coercing against those alone dropped the value. */
it('keeps a behavior running in the editor across a save', () => {
const f = makeSpriteScene();
const behavior = makeBehavior(f, 'B');
f.stack.execute(new SetAssetPropertyCommand(behavior.id, 'runInEditor', true));
const loaded = roundTrip(f.project).assets.find((a) => a.id === behavior.id)!;
expect(loaded.props.runInEditor).toBe(true);
});
/** The declarations of a Variables asset and their starting values are the whole asset. */
it('round-trips a Variables asset and the scope of a state machine', () => {
const f = makeSpriteScene();
const shared = new CreateAssetCommand('variables', 'Score');
f.stack.execute(shared);
f.stack.execute(new AddVariableCommand(shared.asset.id, 'score', 'number'));
f.stack.execute(new SetAssetPropertyCommand(shared.asset.id, 'score', 7));
const machine = new CreateAssetCommand('statemachine', 'M');
f.stack.execute(machine);
f.stack.execute(new SetMachineScopeCommand(machine.asset.id, 'global'));
expect(roundTrip(f.project)).toEqual(f.project);
});
/** `local` is the absence of the field, so a project made before scopes existed saves back byte-alike. */
it('writes no scope for a local machine and reads an unknown one as local', () => {
const f = makeSpriteScene();
makeBehavior(f, 'B', ['score']);
f.stack.execute(new CreateAssetCommand('statemachine', 'M'));
expect(JSON.stringify(serializeProject(f.project))).not.toContain('scope');
const loaded = deserializeProject({
assets: [{ id: 'm1', type: 'statemachine', name: 'M', scope: 'planet' }],
});
const machine = loaded.assets[0]!;
if (machine.type !== 'statemachine') throw new Error('wrong types');
expect(machine.scope).toBeUndefined();
});
/** #41: the borders are part of the frame, and a frame nobody sliced still saves without them. */
it('round-trips the nine-slice of a frame and repairs an impossible one', () => {
const file = `${'ab'.repeat(32)}.png`;
const loaded = deserializeProject({
assets: [
{
id: 't1',
type: 'texture',
name: 'Sheet',
file,
width: 64,
height: 64,
frames: [
{
id: 'f1',
name: 'Button',
x: 0,
y: 0,
w: 24,
h: 24,
border: { left: 8, right: 8 },
fill: 'stretch',
},
{ id: 'f2', name: 'Plain', x: 24, y: 0, w: 24, h: 24 },
{
id: 'f3',
name: 'Greedy',
x: 0,
y: 24,
w: 8,
h: 8,
border: { top: 99, bottom: 99 },
fill: 'wat',
},
],
},
],
});
const texture = loaded.assets[0]!;
if (texture.type !== 'texture') throw new Error('wrong types');
const [sliced, plain, greedy] = texture.frames;
expect(sliced?.border).toEqual({ left: 8, top: 0, right: 8, bottom: 0 });
// Only the fill that is not the default is written; an unknown one reads as the default.
expect(sliced?.fill).toBe('stretch');
expect(plain?.border).toBeUndefined();
expect(plain?.fill).toBeUndefined();
// Measured against the frame, not the image, and never leaving it without a middle.
expect(greedy?.border).toEqual({ left: 0, top: 4, right: 0, bottom: 3 });
expect(greedy?.fill).toBeUndefined();
expect(roundTrip(loaded)).toEqual(loaded);
});
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 } },
// An asset type this build does not know (a newer document, a renamed type). It used to
// be `sound`, which this build does know since #45 – hence a type nothing has claimed.
{ id: 'x', type: 'video' },
{ 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, gravityX: 0, gravityY: 980 });
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 },
editorOffset: { x: 0, y: 0 },
overrides: { color: 0x00ff00, width: 1 },
parentId: '',
instanceEdits: {},
});
expect(project.assets[1]!.name).toBe('sprite');
expect(project.assets[1]!.props).toEqual({
enabled: true,
scale: 1,
rotation: 0,
mirrorX: false,
mirrorY: false,
width: 10,
height: 100,
color: 0xffffff,
texture: '',
shader: '',
});
});
});