The Container tab reuses SceneViewport against any object holder, so a container template is edited exactly like a scene. Right-click no longer changes the selection, which previously made nesting impossible: opening the menu on a container loaded it, and "Place in container" then targeted itself. Template edits (move, rename, override) now reach already-placed instances per slot, through a shared templateSync used by both the apply path and the plain object commands. Copies the user has touched keep their own value. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
64 lines
2.8 KiB
JavaScript
64 lines
2.8 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', 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.
|
||
// 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.
|
||
// 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.
|
||
const firstBefore = await locate('Filler 1', true);
|
||
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 locate('Filler 1', true);
|
||
await sleep(300);
|
||
const firstAfter = await locate('Filler 1', true);
|
||
const searchAfter = await locate('Search…', true);
|
||
check(
|
||
'the wheel scrolls the entries',
|
||
firstAfter.y < firstBefore.y - 100,
|
||
JSON.stringify({ firstBefore, rightAfter, firstAfter }),
|
||
);
|
||
check('the search box stays put while the entries scroll', searchAfter.y === searchBefore.y);
|
||
}
|