buildStructBlock only covered structs, so a placed instance drew numbers,
text, bools and lists through createFieldRow on a 64 px name column while
its struct block sat on the asset's grid below it. variableRows.ts now
builds the row for every variable kind on both sides; the edit option
decides what the owner may change (asset: rename, x, item kind; instance:
revert and push). Label and Button take a fixed width so the mark column
no longer shifts names when a list shows its item-kind picker.
The two structFields calls that became dead with 9f48257 are gone. The
e2e steps locate the Enabled toggle through __gm.inspectorField instead
of clicking a fixed offset from its label.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
74 lines
3.5 KiB
JavaScript
74 lines
3.5 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);
|
||
|
||
// 18. 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');
|
||
|
||
// 18a. The entries scroll under the wheel; the search box above them does not move.
|
||
// Anchored on a filler row: "None" is also what an empty dropdown reads, so locating it could
|
||
// find an inspector field behind the menu instead – one that never moves. Which filler is asked
|
||
// for is read off the open menu: the capped list only draws the rows it shows, and how many
|
||
// objects come before them depends on what the 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);
|
||
}
|