Files
feuerwerk/tools/e2e/11-menus.mjs
T
rootandClaude Opus 5 15ffcd5739 Left-align menu entries, add a Dropdown field, scrollable and searchable menus
Menu entries are left-aligned and live in a ScrollContainer capped at 320 px, so a
long list (the parent picker holds every object of the scene) scrolls instead of
running off the screen. `Menu(items, { searchable: true })` puts a filter box above
the scroll area; Enter takes the first match. Filtering forces a relayout because
two queries with the same hit count leave the height unchanged, which would keep
the stale row positions.

Fields that open a menu are a new `Dropdown` widget: the value sits flush left and
the chevron is drawn flush right instead of being written into the label. A menu
opened below a field is never narrower than that field.

New debug hooks `overlayTexts()` / `overlayRect()` make the popup contents
assertable; e2e step 11 covers the cap, the scrolling and the search box.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 11:31:14 +02:00

54 lines
2.1 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, json, shot, click, locate, mouse, mutate } = t;
await mutate(`
const scene = __gm.state.activeScene;
const source = scene.objects.find(o => o.id === 'o1');
for (let i = 1; i <= 40; i++) {
const copy = structuredClone(source);
copy.id = 'filler' + i;
copy.name = 'Filler ' + i;
copy.parentId = '';
scene.objects.push(copy);
}
__gm.state.select({ kind: 'object', sceneId: '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.
// The dropdown sits right of the 64 px label column; locate() gives the label's text centre.
const parentRow = await locate('Parent');
await click(parentRow.x + 55, 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.
const firstBefore = await locate('None', true);
const searchBefore = await locate('Search…', true);
await mouse('mouseWheel', rect.x + rect.w / 2, rect.y + rect.h / 2, 'none', { deltaX: 0, deltaY: 240 });
await sleep(150);
const firstAfter = await locate('None', true);
const searchAfter = await locate('Search…', true);
check(
'the wheel scrolls the entries',
firstAfter.y < firstBefore.y - 100,
JSON.stringify({ firstBefore, firstAfter }),
);
check('the search box stays put while the entries scroll', searchAfter.y === searchBefore.y);
}