- `.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>
72 lines
3.3 KiB
JavaScript
72 lines
3.3 KiB
JavaScript
/**
|
||
* Menus that can grow with the project. Runs last: it fills the scene with throwaway objects
|
||
* to make the parent picker overflow, which the earlier steps' object counts would not survive.
|
||
*/
|
||
export async function menus(t) {
|
||
const { check, sleep, evaluate, json, shot, click, locate, mouse, mutate } = t;
|
||
/** Like locate(), but a row scrolled out of view answers with null instead of throwing. */
|
||
const rowAt = async (text) =>
|
||
(await evaluate(`__gm.locate(${JSON.stringify(text)}, true)`)) ?? { x: -1, y: -1 };
|
||
|
||
await mutate(`
|
||
const scene = __gm.state.activeScene;
|
||
// Any plain stored object will do – not a placed container, whose content the picker
|
||
// would list forty times over as well.
|
||
const source = scene.objects.find(o => o.id === 'otext');
|
||
for (let i = 1; i <= 40; i++) {
|
||
const copy = structuredClone(source);
|
||
copy.id = 'filler' + i;
|
||
copy.name = 'Filler ' + i;
|
||
copy.parentId = '';
|
||
copy.overrides = {};
|
||
scene.objects.push(copy);
|
||
}
|
||
__gm.state.select({ kind: 'object', holderId: 'scene1', objectIds: ['otext'], objectId: 'otext' })`);
|
||
await sleep(150);
|
||
|
||
// 11a. A picker with 40+ entries is capped in height and stays fully on screen instead of
|
||
// running off the bottom edge.
|
||
const parentRow = await json("__gm.inspectorField('Parent')");
|
||
await click(parentRow.x, parentRow.y);
|
||
const rect = await json('__gm.overlayRect()');
|
||
const screenHeight = await json('window.innerHeight');
|
||
check(
|
||
'a long parent picker is capped instead of growing with the object count',
|
||
rect && rect.h < 420,
|
||
JSON.stringify(rect),
|
||
);
|
||
check(
|
||
'the capped picker stays fully on screen',
|
||
rect && rect.y >= 0 && rect.y + rect.h <= screenHeight,
|
||
JSON.stringify({ rect, screenHeight }),
|
||
);
|
||
await shot('31-long-parent-menu');
|
||
|
||
// 11b. The entries scroll under the wheel; the search box above them does not move.
|
||
// Anchored on a filler row read off the open menu: "None" could match an inspector field behind it,
|
||
// and which fillers are drawn depends on what earlier steps left in the scene.
|
||
const visibleFillers = (await json('__gm.overlayTexts()')).filter((e) => e.startsWith('Filler '));
|
||
const anchor = visibleFillers[visibleFillers.length - 1];
|
||
check('the picker lists the filler objects', Boolean(anchor), JSON.stringify(visibleFillers));
|
||
const firstBefore = await rowAt(anchor);
|
||
const searchBefore = await locate('Search…', true);
|
||
// Move onto the list first: a wheel event goes to whatever is hovered, and the pointer last
|
||
// touched the field above the menu.
|
||
await mouse('mouseMoved', rect.x + rect.w / 2, rect.y + rect.h / 2);
|
||
await sleep(50);
|
||
await mouse('mouseWheel', rect.x + rect.w / 2, rect.y + rect.h / 2, 'none', { deltaX: 0, deltaY: 240 });
|
||
// Measured here: the list had not moved 40 ms after the wheel but had at 340 ms. Both
|
||
// readings are reported so an early one cannot look like a mystery failure again.
|
||
await sleep(40);
|
||
const rightAfter = await rowAt(anchor);
|
||
await sleep(300);
|
||
const firstAfter = await rowAt(anchor);
|
||
const searchAfter = await locate('Search…', true);
|
||
check(
|
||
'the wheel scrolls the entries',
|
||
firstAfter.y >= 0 && firstAfter.y < firstBefore.y - 100,
|
||
JSON.stringify({ firstBefore, rightAfter, firstAfter }),
|
||
);
|
||
check('the search box stays put while the entries scroll', searchAfter.y === searchBefore.y);
|
||
}
|