- `.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>
120 lines
5.3 KiB
JavaScript
120 lines
5.3 KiB
JavaScript
/** Text assets rendered in the scene, and the enabled switch in the editor and in the running game. */
|
||
export async function textAndEnabled(t) {
|
||
const {
|
||
check,
|
||
sleep,
|
||
evaluate,
|
||
json,
|
||
has,
|
||
shot,
|
||
click,
|
||
clickText,
|
||
locate,
|
||
mutate,
|
||
object,
|
||
play,
|
||
press,
|
||
stop,
|
||
typeText,
|
||
} = t;
|
||
|
||
// 10a. Text assets: created through the + menu, placed, and rendered as real text in the scene.
|
||
await clickText('+');
|
||
await clickText('Text');
|
||
await sleep(100);
|
||
const textAssetId = await evaluate(`(__gm.state.project.assets.find(a => a.type === 'text') || {}).id`);
|
||
check('+ menu creates a text asset', Boolean(textAssetId));
|
||
await mutate(`
|
||
const id = ${JSON.stringify(textAssetId)};
|
||
__gm.state.project.assets.find(a => a.id === id).props.text = 'Hello world';
|
||
// On the camera so the game view really renders it (outside the frame it would be culled).
|
||
const cam = __gm.state.activeScene.objects.find(o => o.id === 'o3').position;
|
||
__gm.state.activeScene.objects.push({ id: 'otext', assetId: id, name: '', position: { x: cam.x, y: cam.y, z: 5 }, editorOffset: { x: 0, y: 0 }, overrides: {}, parentId: '', instanceEdits: {} });
|
||
__gm.state.select({ kind: 'object', holderId: 'scene1', objectIds: ['otext'], objectId: 'otext' })`);
|
||
await sleep(100);
|
||
check('a placed text object draws its content in the scene', await has('Hello world'));
|
||
await shot('29-text-object');
|
||
|
||
// 10b. The enabled switch: every object has it, switching it off is a normal override, and a
|
||
// switched-off object disappears from the scene view too (it stays in the hierarchy).
|
||
const enabledRow = await json("__gm.inspectorField('Enabled')");
|
||
check('the inspector shows the Enabled switch for a scene object', Boolean(enabledRow));
|
||
const visibleBefore = (await json('__gm.viewportInfo()')).visibleItems;
|
||
const gizmoOf = async (id) => (await json('__gm.gizmos()')).find((g) => g.id === id) ?? null;
|
||
const gizmoOn = await gizmoOf('otext');
|
||
check('a selected object is framed at full strength', gizmoOn?.alpha === 1, JSON.stringify(gizmoOn));
|
||
await click(enabledRow.x, enabledRow.y);
|
||
await sleep(100);
|
||
const textObject = await object('otext');
|
||
check(
|
||
'clicking it writes an override instead of changing the asset',
|
||
textObject.overrides.enabled === false,
|
||
JSON.stringify(textObject.overrides),
|
||
);
|
||
check(
|
||
'a switched-off object is not drawn in the scene view',
|
||
!(await json('__gm.drawOrder()')).includes('otext'),
|
||
);
|
||
const visibleAfter = (await json('__gm.viewportInfo()')).visibleItems;
|
||
check(
|
||
'the scene view renders one item less',
|
||
visibleAfter === visibleBefore - 1,
|
||
`${visibleBefore} → ${visibleAfter}`,
|
||
);
|
||
check('it stays selectable in the hierarchy, so its switch stays reachable', await has('Enabled'));
|
||
await shot('30-disabled');
|
||
|
||
// 17a-2. It draws nothing, so its outline is the only thing left that says where it sits – which
|
||
// is the whole point of being able to pick it in the hierarchy. Selecting another object
|
||
// and coming back proves the outline follows the panel, not a leftover from before.
|
||
const gizmoOff = await gizmoOf('otext');
|
||
check(
|
||
'a switched-off object is still framed, dimmed',
|
||
gizmoOff?.primary === true && gizmoOff.alpha > 0 && gizmoOff.alpha < 1,
|
||
JSON.stringify(gizmoOff),
|
||
);
|
||
const otherRow = await evaluate(`__gm.hierarchyRow('o3')`);
|
||
await click(otherRow.x, otherRow.y);
|
||
await sleep(100);
|
||
check('selecting something else drops the frame', (await gizmoOf('otext')) === null);
|
||
const offRow = await evaluate(`__gm.hierarchyRow('otext')`);
|
||
await click(offRow.x, offRow.y);
|
||
await sleep(100);
|
||
const gizmoBack = await gizmoOf('otext');
|
||
check(
|
||
'picking the switched-off object in the hierarchy frames it again',
|
||
gizmoBack?.alpha > 0 && gizmoBack.alpha < 1,
|
||
JSON.stringify(gizmoBack),
|
||
);
|
||
check('and it is still not drawn', !(await json('__gm.drawOrder()')).includes('otext'));
|
||
await shot('30b-disabled-gizmo');
|
||
|
||
// 10c. In the running game a switched-off object does not exist: it is not rendered, and
|
||
// switching it back on brings it back.
|
||
await play();
|
||
await sleep(200);
|
||
const offItems = (await json('__gm.gameInfo()')).visibleItems;
|
||
await mutate(`__gm.state.activeScene.objects.find(o => o.id === 'otext').overrides.enabled = true`);
|
||
await sleep(200);
|
||
const onItems = (await json('__gm.gameInfo()')).visibleItems;
|
||
check(
|
||
'the game renders one item more once the object is switched on',
|
||
onItems === offItems + 1,
|
||
`${offItems} → ${onItems}`,
|
||
);
|
||
await stop();
|
||
check('stop leaves the play-mode switch behind', (await object('otext')).overrides.enabled === false);
|
||
|
||
// 10d. A field that shows no decimals must not store any either. Font size is such a field, so
|
||
// typing a fraction has to round: showing "33" while the project holds 32.6 would be a lie.
|
||
const fontLabel = await locate('Font size');
|
||
check('Font size reads as a whole number', Boolean(await has('32')) && !(await has('32.000')));
|
||
await click(fontLabel.x + 160, fontLabel.y);
|
||
await typeText('32.6');
|
||
await press('Enter');
|
||
await sleep(200);
|
||
const fontSize = (await object('otext')).overrides.fontSize;
|
||
check('typing a fraction into it rounds the stored value', fontSize === 33, String(fontSize));
|
||
check('and the field shows exactly what was stored', Boolean(await has('33')));
|
||
}
|