Split GraphCanvas (1076 -> 831 lines) into EdgeLayer and graphMenus; the port and marker menus were the same list and are now one function. Value specs of the inspector and the list tab share valueSpecs.ts, and the remaining copies (compound commands, type glyphs, typed asset lookup, name rule, play/editor world setup, marquee) are merged. Four copies had already drifted apart; merging them fixes: - "Disconnect N links" in the port menu is one undo step, not N - "+ Add collider" is hidden on a placed container instance, as in the hierarchy menu, instead of always failing - a bool property coerced from the text "false" becomes false - a port command on an unknown node type reports instead of crashing Comment-only edits leave the bundle byte-identical. README corrected in 35 places (step limit, jump-run clock, suite duration, 409 body, ...). Co-Authored-By: Claude Opus 5 <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;
|
||
|
||
// 17. 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');
|
||
|
||
// 17a. 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');
|
||
|
||
// 17b. 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);
|
||
|
||
// 17c. 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')));
|
||
}
|