CI run 35 was red: the suite died after step 15 with `widget with text "Container" not found`. Nothing in the editor broke — the tenth tab (#73) pushed Container behind the `»` button at the test window's 1280 px, exactly the case #84 was built for, and three steps still clicked the tab name directly. openTab(t, tab) in tools/e2e/lib/scaffold.mjs asks tabRow() whether the tab is hidden and goes through the menu when it is, so the fix lives in one place rather than in every step that opens a tab. The LABELS record moves there as TAB_LABELS, since both the helper and 14-overflow need it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
171 lines
7.1 KiB
JavaScript
171 lines
7.1 KiB
JavaScript
import { TAB_LABELS } from './lib/scaffold.mjs';
|
||
|
||
/**
|
||
* Labels must be shortened to fit their control, not written past it. Runs last because it renames
|
||
* assets to absurd lengths to provoke the case.
|
||
*/
|
||
|
||
/** A name far wider than any 200 px picker, so a control that does not truncate must spill. */
|
||
const LONG_NAME = 'Player movement with jumping and coyote time';
|
||
|
||
export async function overflow(t) {
|
||
const { check, sleep, json, evaluate, shot, mutate, drag, clickText } = t;
|
||
|
||
const pickerWidths = async () => {
|
||
const out = {};
|
||
for (const tab of ['graph', 'texture', 'container']) {
|
||
await evaluate(`__gm.showTab(${JSON.stringify(tab)})`);
|
||
await sleep(300);
|
||
out[tab] = await json(`__gm.${tab}.pickerWidth()`);
|
||
}
|
||
return out;
|
||
};
|
||
const before = await pickerWidths();
|
||
|
||
await mutate(`
|
||
for (const asset of __gm.state.project.assets) {
|
||
if (asset.type === 'behavior' || asset.type === 'texture' || asset.type === 'container')
|
||
asset.name = ${JSON.stringify(LONG_NAME)};
|
||
}
|
||
__gm.state.select(null)`);
|
||
await sleep(600);
|
||
|
||
// 14a. The pickers keep the width they were given instead of growing with the name they show.
|
||
const after = await pickerWidths();
|
||
for (const tab of ['graph', 'texture', 'container']) {
|
||
check(
|
||
`the ${tab} picker keeps its width when the name gets long`,
|
||
before[tab] === 200 && after[tab] === 200,
|
||
`before ${before[tab]}, after ${after[tab]}`,
|
||
);
|
||
}
|
||
|
||
// 14b. The tab row itself: a button past the centre column's edge sits under the inspector, and
|
||
// a click on it lands there (nine tabs on a 90 px floor did exactly that at 1280 px wide).
|
||
await evaluate("__gm.showTab('scene')");
|
||
await sleep(300);
|
||
const columnRight = async () => {
|
||
const column = await json('__gm.viewportRect()');
|
||
return column.x + column.w;
|
||
};
|
||
const spillOf = (row, right) => row.shown.filter((b) => b.right > right);
|
||
const wide = await json('__gm.tabRow()');
|
||
check(
|
||
'every tab button ends inside the centre column',
|
||
spillOf(wide, await columnRight()).length === 0,
|
||
JSON.stringify({ spilled: spillOf(wide, await columnRight()), hidden: wide.hidden }),
|
||
);
|
||
// The tenth tab (Particles, #73) is the case #84 was built for: 631 px of nine tabs already left
|
||
// 35 px in a 1280 px window, so at ten the row has to put something away even undisturbed.
|
||
check(
|
||
'at the test window’s width the row already needs its overflow, and still shows what is edited',
|
||
wide.hidden.length > 0 && wide.shown.some((b) => b.tab === 'scene'),
|
||
JSON.stringify({ hidden: wide.hidden, shown: wide.shown.map((b) => b.tab) }),
|
||
);
|
||
|
||
// 14b-ii. Narrowed until the row cannot hold nine tabs, the leftovers go behind the overflow
|
||
// button instead of under the inspector – the guard against the tenth tab (#84).
|
||
const widenPanels = async (by) => {
|
||
const bars = await json('__gm.panelSplitters()');
|
||
await drag(bars.left.x, bars.left.y, bars.left.x + by, bars.left.y, { steps: 8 });
|
||
const after = await json('__gm.panelSplitters()');
|
||
await drag(after.right.x, after.right.y, after.right.x - by, after.right.y, { steps: 8 });
|
||
await sleep(300);
|
||
};
|
||
await widenPanels(250);
|
||
const narrow = await json('__gm.tabRow()');
|
||
const narrowRight = await columnRight();
|
||
check(
|
||
'a row too narrow for its tabs puts the leftovers away instead of past the edge',
|
||
narrow.hidden.length > 0 && spillOf(narrow, narrowRight).length === 0,
|
||
JSON.stringify({ hidden: narrow.hidden, spilled: spillOf(narrow, narrowRight) }),
|
||
);
|
||
check(
|
||
'the tab being edited stays in the row',
|
||
narrow.shown.some((b) => b.tab === 'scene'),
|
||
JSON.stringify(narrow.shown.map((b) => b.tab)),
|
||
);
|
||
await shot('35-tab-overflow');
|
||
// The overflow button names them, and picking one brings it into the row.
|
||
const wanted = narrow.hidden[narrow.hidden.length - 1];
|
||
await clickText('»');
|
||
await sleep(400);
|
||
const offered = await json('__gm.overlayTexts()');
|
||
check(
|
||
'the overflow button offers every tab that had to go',
|
||
narrow.hidden.every((tab) => offered.includes(TAB_LABELS[tab])),
|
||
JSON.stringify({ offered, hidden: narrow.hidden }),
|
||
);
|
||
await clickText(TAB_LABELS[wanted]);
|
||
await sleep(400);
|
||
const picked = await json('__gm.tabRow()');
|
||
check(
|
||
`picking ${TAB_LABELS[wanted]} from the overflow menu opens it and puts it in the row`,
|
||
(await json('__gm.tab()')) === wanted && picked.shown.some((b) => b.tab === wanted),
|
||
JSON.stringify({ tab: await json('__gm.tab()'), shown: picked.shown.map((b) => b.tab) }),
|
||
);
|
||
await widenPanels(-250);
|
||
await evaluate("__gm.showTab('scene')");
|
||
await sleep(300);
|
||
const back = await json('__gm.tabRow()');
|
||
check(
|
||
'giving the room back brings tabs into the row again, and none past the edge',
|
||
back.hidden.length < narrow.hidden.length && spillOf(back, await columnRight()).length === 0,
|
||
JSON.stringify({ hidden: back.hidden, narrow: narrow.hidden }),
|
||
);
|
||
|
||
// 14c. Every tab, because each one owns a different set of controls.
|
||
for (const tab of ['scene', 'graph', 'texture', 'game', 'container', 'list', 'particles']) {
|
||
await evaluate(`__gm.showTab(${JSON.stringify(tab)})`);
|
||
await sleep(400);
|
||
const spills = await json('__gm.textOverflows()');
|
||
check(
|
||
`no label is drawn past its control in the ${tab} tab`,
|
||
spills.length === 0,
|
||
JSON.stringify(spills),
|
||
);
|
||
}
|
||
|
||
// 14d. Shortened, not simply dropped: the value must still be readable and marked as cut.
|
||
await evaluate("__gm.showTab('graph')");
|
||
await sleep(400);
|
||
const shown = (await json('__gm.textOverflows(true)')).find((e) => e.text.startsWith('Player movement'));
|
||
check(
|
||
'a value too wide for its dropdown is cut with an ellipsis',
|
||
Boolean(shown) && shown.text.endsWith('…') && shown.text.length > 8,
|
||
JSON.stringify(shown),
|
||
);
|
||
await shot('36-long-labels');
|
||
|
||
// 14e. An inspector full of dropdowns – the parent row and the enum fields – with a selection
|
||
// whose asset name is now overlong too.
|
||
await evaluate("__gm.showTab('scene')");
|
||
await sleep(300);
|
||
await evaluate("__gm.state.select({ kind: 'object', objectId: 'otex' })");
|
||
await sleep(500);
|
||
const inspector = await json('__gm.textOverflows()');
|
||
check('no label spills in the inspector', inspector.length === 0, JSON.stringify(inspector));
|
||
|
||
// 14f. The other direction: a control narrow enough to swallow its whole label draws nothing
|
||
// at all, which the overflow walk cannot see – an empty text spills over no edge.
|
||
for (const tab of ['scene', 'graph', 'texture', 'container', 'list', 'particles']) {
|
||
await evaluate(`__gm.showTab(${JSON.stringify(tab)})`);
|
||
await sleep(400);
|
||
const dropped = await json('__gm.labelsDropped()');
|
||
check(
|
||
`no button is left without its label in the ${tab} tab`,
|
||
dropped.length === 0,
|
||
JSON.stringify(dropped),
|
||
);
|
||
}
|
||
// The narrowest buttons in the editor sit on a behaviour's variable rows.
|
||
await evaluate("__gm.state.select({ kind: 'asset', assetId: 'mover' })");
|
||
await sleep(400);
|
||
const onVariables = await json('__gm.labelsDropped()');
|
||
check(
|
||
'the variable rows keep their buttons labelled',
|
||
onVariables.length === 0,
|
||
JSON.stringify(onVariables),
|
||
);
|
||
}
|