- `.gitignore` said `index.html` unanchored, which also caught the Vite source page; a fresh clone could neither `dev` nor `build`. Anchored to `/index.html`, page added. - Removed GM_REMOTE (tools/remote/gm-browser.ps1 and the ssh/scp branch in browser.mjs): the Windows test machine is gone and the only flaky check that lived on it is closed (#37). - Removed `npm run preview`, undocumented and unused. - Comments: wrong number in collider.test.ts (250 → 200), the "declarations used to…" history in listItems.ts and friends, the 24 px/28 contradiction, section banners in nodes/index.ts, node-catalog.test.ts and api/index.php, name paraphrases, and the stale step numbers in tools/e2e/11-…23- from the old global count. - README: asset types include `animation`, kind order Number/Text/Boolean, tagged colour and texture items, 6/5 kinds, the `Texture` node, `./build.sh`, PROJECT_FORMAT_VERSION, and the file tree (kinds.ts, listItems.ts, propertyPorts.ts, Breadcrumb.ts, beads.ts, tools/assets/). - tests/helpers.ts takes the duplicated `findObject` and `sceneWithCamera`. Proof: ./check.sh 797 tests, full e2e ALL PASSED 633 checks against this bundle. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
185 lines
7.4 KiB
JavaScript
185 lines
7.4 KiB
JavaScript
/** The asset panel: placing by dragging into the view, creating through "+", and resetting an asset value. */
|
||
export async function assets(t) {
|
||
const {
|
||
check,
|
||
sleep,
|
||
evaluate,
|
||
json,
|
||
shot,
|
||
click,
|
||
doubleClick,
|
||
clickText,
|
||
mouse,
|
||
mutate,
|
||
press,
|
||
redo,
|
||
undo,
|
||
select,
|
||
objects,
|
||
asset,
|
||
} = t;
|
||
|
||
// 4a. Context menu on the Crate asset. Placing left the menu when it became a drag.
|
||
await clickText('Crate', 'right');
|
||
check('asset context menu opens', Boolean(await evaluate(`__gm.locate('Delete…')`)));
|
||
check('placing is no longer a menu item', !(await evaluate(`__gm.locate('Place in scene')`)));
|
||
await shot('11-context-menu');
|
||
await press('Escape');
|
||
await sleep(150);
|
||
|
||
// 4b. Drag the Crate asset from the panel into the scene view. Driven step by step rather than
|
||
// with `drag()`, because the preview only exists while the button is still down.
|
||
const from = await json("__gm.assetRow('crate')");
|
||
const centre = await json('__gm.viewportCenter()');
|
||
const to = { x: centre.x + 70, y: centre.y - 50 };
|
||
await mouse('mouseMoved', from.x, from.y);
|
||
await mouse('mousePressed', from.x, from.y);
|
||
for (let i = 1; i <= 8; i++) {
|
||
const x = from.x + ((to.x - from.x) * i) / 8;
|
||
const y = from.y + ((to.y - from.y) * i) / 8;
|
||
await mouse('mouseMoved', x, y, 'left', { buttons: 1 });
|
||
await sleep(16);
|
||
}
|
||
const preview = await json('__gm.dropPreview()');
|
||
check(
|
||
'dragging an asset over the view previews where it would land',
|
||
Boolean(preview) && preview.assetId === 'crate' && preview.rejection === '',
|
||
JSON.stringify(preview),
|
||
);
|
||
await shot('11b-drop-preview');
|
||
await mouse('mouseReleased', to.x, to.y);
|
||
await sleep(200);
|
||
|
||
const count = (await objects()).length;
|
||
check('dropping the asset adds an object', count === 8, `objects=${count}`);
|
||
const placed = await json(
|
||
'(() => { const id = __gm.state.selectedObjectIds[0]; return __gm.sceneObjects().find(o => o.id === id) ?? null; })()',
|
||
);
|
||
// The preview is a promise: whatever it drew has to be where the object then sits, or the
|
||
// outline was decoration rather than a prediction.
|
||
check(
|
||
'the object lands where the preview promised, and is selected',
|
||
Boolean(placed) &&
|
||
placed.assetId === 'crate' &&
|
||
placed.position.x === preview.x &&
|
||
placed.position.y === preview.y,
|
||
JSON.stringify({ placed: placed && placed.position, preview }),
|
||
);
|
||
check('the preview is gone once the asset is placed', (await json('__gm.dropPreview()')) === null);
|
||
await undo();
|
||
const afterUndo = (await objects()).length;
|
||
check('one undo takes the dropped object back out', afterUndo === 7, `objects=${afterUndo}`);
|
||
await redo();
|
||
await sleep(150);
|
||
check('redo puts it back', (await objects()).length === 8);
|
||
|
||
// 4c. Create a new asset through the + menu.
|
||
await clickText('+');
|
||
await clickText('Sprite');
|
||
await sleep(100);
|
||
const names = await json('__gm.state.project.assets.map(a => a.name)');
|
||
check('+ menu creates a sprite asset', names.includes('Sprite'), JSON.stringify(names));
|
||
await shot('12-new-asset');
|
||
|
||
// 4d. Double-click on a scene row: the one thing activating an asset still does since placing
|
||
// became a drag. The second scene goes in through the model – the "+" menu's "Scene" entry
|
||
// shares its label with the Scene tab, so a click by text could land on either.
|
||
await mutate(
|
||
`__gm.state.project.assets.push({ id: 'scene2', type: 'scene', name: 'Level 2', props: { backgroundColor: 0x22252c }, objects: [] })`,
|
||
);
|
||
const secondScene = await json("__gm.assetRow('scene2')");
|
||
await doubleClick(secondScene.x, secondScene.y);
|
||
check(
|
||
'double-clicking a scene asset makes it the active scene',
|
||
(await json('__gm.state.project.activeSceneId')) === 'scene2',
|
||
await json('__gm.state.project.activeSceneId'),
|
||
);
|
||
const firstScene = await json("__gm.assetRow('scene1')");
|
||
await doubleClick(firstScene.x, firstScene.y);
|
||
check(
|
||
'double-clicking the first one switches back',
|
||
(await json('__gm.state.project.activeSceneId')) === 'scene1',
|
||
await json('__gm.state.project.activeSceneId'),
|
||
);
|
||
await mutate(`__gm.state.project.assets = __gm.state.project.assets.filter((a) => a.id !== 'scene2')`);
|
||
|
||
// 4e. Asset defaults: the Crate deviates in height (60 against the schema's 100) and colour,
|
||
// so two rows carry a reset. Rows run Enabled, Width, Height, Color, Texture, so height's
|
||
// reset is the first on screen and colour's the last.
|
||
await select({ kind: 'asset', assetId: 'crate' });
|
||
await sleep(200);
|
||
const heightReset = await evaluate(`__gm.locate('↺')`);
|
||
const colorReset = await evaluate(`__gm.locate('↺', true)`);
|
||
check('asset inspector shows a reset per deviating property', Boolean(heightReset));
|
||
check(
|
||
'the two deviations have two separate resets',
|
||
Boolean(colorReset) && colorReset.y > heightReset.y,
|
||
JSON.stringify({ heightReset, colorReset }),
|
||
);
|
||
await click(heightReset.x, heightReset.y);
|
||
await sleep(100);
|
||
check(
|
||
'reset puts the asset property back to its default',
|
||
(await asset('crate')).props.height === 100,
|
||
`height=${(await asset('crate')).props.height}`,
|
||
);
|
||
const remaining = await evaluate(`__gm.locate('↺')`);
|
||
check(
|
||
'the reset of the property now at its default is gone, the other one stays',
|
||
Boolean(remaining) &&
|
||
remaining.y === colorReset.y &&
|
||
remaining.y === (await evaluate(`__gm.locate('↺', true)`)).y,
|
||
JSON.stringify({ remaining, colorReset }),
|
||
);
|
||
await shot('15-asset-reset');
|
||
await undo();
|
||
check(
|
||
'undo restores the asset value',
|
||
(await asset('crate')).props.height === 60,
|
||
`height=${(await asset('crate')).props.height}`,
|
||
);
|
||
check(
|
||
'the height reset is back after undo',
|
||
(await evaluate(`__gm.locate('↺')`))?.y === heightReset.y,
|
||
JSON.stringify(await evaluate(`__gm.locate('↺')`)),
|
||
);
|
||
|
||
// 4f. The same gold in the asset list: a row is marked while the asset carries anything the
|
||
// inspector marks. The Crate deviates twice, so it takes two resets to turn it plain.
|
||
const GOLD = '#f2c84b';
|
||
const PLAIN = '#e6e8ee';
|
||
const assetColor = (id) => json(`__gm.assetRowColor('${id}')`);
|
||
check('a deviating asset is marked gold in the list', (await assetColor('crate')) === GOLD);
|
||
check('an asset at its defaults is not', (await assetColor('cam')) === PLAIN, await assetColor('cam'));
|
||
// The Mover's `speed` is 200 against a declared variable default of 0. The inspector marks no
|
||
// variable, so neither may the list – the two must say the same thing.
|
||
check(
|
||
'a behavior is not marked for a variable value, which the inspector does not mark either',
|
||
(await assetColor('mover')) === PLAIN,
|
||
await assetColor('mover'),
|
||
);
|
||
await click(heightReset.x, heightReset.y);
|
||
await sleep(150);
|
||
check(
|
||
'the mark stays while the second deviation is left',
|
||
(await assetColor('crate')) === GOLD,
|
||
await assetColor('crate'),
|
||
);
|
||
const colorOnly = await evaluate(`__gm.locate('↺')`);
|
||
await click(colorOnly.x, colorOnly.y);
|
||
await sleep(150);
|
||
check(
|
||
'resetting the last deviation clears the mark, without a reload',
|
||
(await assetColor('crate')) === PLAIN,
|
||
`${await assetColor('crate')} props=${JSON.stringify((await asset('crate')).props)}`,
|
||
);
|
||
await shot('15b-asset-marks');
|
||
await undo();
|
||
await undo();
|
||
check(
|
||
'undo brings the mark back',
|
||
(await assetColor('crate')) === GOLD,
|
||
`${await assetColor('crate')} props=${JSON.stringify((await asset('crate')).props)}`,
|
||
);
|
||
}
|