Files
feuerwerk/tools/e2e/14-overflow.mjs
T
rootandClaude Opus 5 e52e95c0e9 Give a list its own tab instead of a comma line
A list property in the inspector was a single text field holding a comma
separated line -- unreadable past a handful of items, and it could not
carry anything but text. It becomes a button ("3 items") that opens a
sixth centre tab, where every item is a row built by the very widget the
inspector would build for that item kind.

The renderer was free: a PropertyDef of kind 'list' already carries
`items`, so item i becomes a synthetic def of that kind -- number, text,
boolean and colour rows appear without a new branch.

The way into the tab goes through the state (EditorState.listSlot,
openList(), event 'list'), like every other tab switch here; a tab may
not call showTab itself. ListSlot lives next to Selection rather than in
the tab folder, or state.ts would have to import upwards.

A row's handlers must not close over a snapshot of the list: they outlive
a commit, since the tab only rebuilds when the length changes, and the
second edited row would otherwise revive the value the first replaced.

showTab switched five `visible` lines by hand, where a sixth tab is
silently forgotten; it is a Record<CentreTab, Widget> now.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 10:30:57 +02:00

99 lines
3.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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.
*
* `__gm.textOverflows()` walks every visible text and reports the ones drawn wider than their
* widget; against a build without the fix it reported the graph picker at 106 px over.
*/
/** 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 } = 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);
// 21. The pickers keep the width they were given. `width` used to set only a minimum, so a
// picker grew with whatever name it happened to show and the header reflowed on load.
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]}`,
);
}
// 21a. Every tab, because each one owns a different set of controls.
for (const tab of ['scene', 'graph', 'texture', 'game', 'container', 'list']) {
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),
);
}
// 21b. 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');
// 21c. 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));
// 21d. 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']) {
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),
);
}