A step opens a tab the way a person has to, through the menu when it is hidden

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>
This commit is contained in:
rootandClaude Opus 5 committed 2026-09-14 07:03:40 +02:00
1 parent 0f9e1611d1
commit 4feb4c2cf6
5 files changed
+43 -24

No files matched your search

+3 -2
View File
@@ -1,4 +1,5 @@
import { colourDistance } from '../lib/harness.mjs'; import { colourDistance } from '../lib/harness.mjs';
import { openTab } from './lib/scaffold.mjs';
/** /**
* The Texture tab: cutting frames out of a sheet, and a sprite wearing one. * The Texture tab: cutting frames out of a sheet, and a sprite wearing one.
@@ -41,8 +42,8 @@ export async function frames(t) {
await evaluate('__gm.state.select(null)'); await evaluate('__gm.state.select(null)');
const texture = await json("(__gm.state.project.assets.find((a) => a.type === 'texture') ?? {}).id ?? ''"); const texture = await json("(__gm.state.project.assets.find((a) => a.type === 'texture') ?? {}).id ?? ''");
await evaluate(`__gm.state.select({ kind: 'asset', assetId: ${JSON.stringify(texture)} })`); await evaluate(`__gm.state.select({ kind: 'asset', assetId: ${JSON.stringify(texture)} })`);
await clickText('Texture'); await openTab(t, 'texture');
await sleep(600); await sleep(400);
check('the Texture tab opens', (await json('__gm.tab()')) === 'texture'); check('the Texture tab opens', (await json('__gm.tab()')) === 'texture');
check( check(
'and shows the selected sheet', 'and shows the selected sheet',
+5 -17
View File
@@ -1,3 +1,5 @@
import { TAB_LABELS } from './lib/scaffold.mjs';
/** /**
* Labels must be shortened to fit their control, not written past it. Runs last because it renames * 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. * assets to absurd lengths to provoke the case.
@@ -6,20 +8,6 @@
/** A name far wider than any 200 px picker, so a control that does not truncate must spill. */ /** 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'; const LONG_NAME = 'Player movement with jumping and coyote time';
/** What the tab buttons read, to find one in the overflow menu. */
const LABELS = {
scene: 'Scene',
game: 'Game',
graph: 'Behavior',
statemachine: 'State machine',
shader: 'Shader',
texture: 'Texture',
animation: 'Animation',
container: 'Container',
list: 'List',
particles: 'Particles',
};
export async function overflow(t) { export async function overflow(t) {
const { check, sleep, json, evaluate, shot, mutate, drag, clickText } = t; const { check, sleep, json, evaluate, shot, mutate, drag, clickText } = t;
@@ -105,14 +93,14 @@ export async function overflow(t) {
const offered = await json('__gm.overlayTexts()'); const offered = await json('__gm.overlayTexts()');
check( check(
'the overflow button offers every tab that had to go', 'the overflow button offers every tab that had to go',
narrow.hidden.every((tab) => offered.includes(LABELS[tab])), narrow.hidden.every((tab) => offered.includes(TAB_LABELS[tab])),
JSON.stringify({ offered, hidden: narrow.hidden }), JSON.stringify({ offered, hidden: narrow.hidden }),
); );
await clickText(LABELS[wanted]); await clickText(TAB_LABELS[wanted]);
await sleep(400); await sleep(400);
const picked = await json('__gm.tabRow()'); const picked = await json('__gm.tabRow()');
check( check(
`picking ${LABELS[wanted]} from the overflow menu opens it and puts it in the row`, `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), (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) }), JSON.stringify({ tab: await json('__gm.tab()'), shown: picked.shown.map((b) => b.tab) }),
); );
+3 -2
View File
@@ -1,4 +1,5 @@
import { instanceChild } from '../lib/builders.mjs'; import { instanceChild } from '../lib/builders.mjs';
import { openTab } from './lib/scaffold.mjs';
/** /**
* The Container tab: editing a template with the scene view's tools. Relies on the container step * The Container tab: editing a template with the scene view's tools. Relies on the container step
@@ -38,8 +39,8 @@ export async function containerTab(t) {
await sleep(200); await sleep(200);
// 16a. The tab opens on the container the project has, and its header says which one. // 16a. The tab opens on the container the project has, and its header says which one.
await clickText('Container'); await openTab(t, 'container');
await sleep(500); await sleep(300);
const template = await json(`__gm.state.project.assets.find(a => a.id === '${containerId}').objects`); const template = await json(`__gm.state.project.assets.find(a => a.id === '${containerId}').objects`);
const count = template.length; const count = template.length;
check( check(
+3 -3
View File
@@ -1,6 +1,6 @@
import { colourDistance } from '../lib/harness.mjs'; import { colourDistance } from '../lib/harness.mjs';
import { edge, node, propertiesOf, variable, written } from '../lib/builders.mjs'; import { edge, node, propertiesOf, variable, written } from '../lib/builders.mjs';
import { MAKE_OBJECT } from './lib/scaffold.mjs'; import { MAKE_OBJECT, openTab } from './lib/scaffold.mjs';
/** /**
* Animations: made from a texture, edited in their own tab, worn by a sprite through `texture`, * Animations: made from a texture, edited in their own tab, worn by a sprite through `texture`,
@@ -132,8 +132,8 @@ export async function animation(t) {
// 25c. The Animation tab shows it and plays it; the inspector keeps its two properties. // 25c. The Animation tab shows it and plays it; the inspector keeps its two properties.
await evaluate(`__gm.state.select({ kind: 'asset', assetId: ${JSON.stringify(walk.id)} })`); await evaluate(`__gm.state.select({ kind: 'asset', assetId: ${JSON.stringify(walk.id)} })`);
await clickText('Animation'); await openTab(t, 'animation');
await sleep(500); await sleep(300);
check( check(
'the inspector edits frames per second and playback', 'the inspector edits frames per second and playback',
(await t.has('Frames per second')) && (await t.has('Playback')) && (await t.has('Loop')), (await t.has('Frames per second')) && (await t.has('Playback')) && (await t.has('Loop')),
+29
View File
@@ -100,3 +100,32 @@ export async function scaffoldBehavior(t, options) {
}, },
}; };
} }
/** What each tab button reads – the text a check has to click to open it. */
export const TAB_LABELS = {
scene: 'Scene',
game: 'Game',
graph: 'Behavior',
statemachine: 'State machine',
shader: 'Shader',
texture: 'Texture',
animation: 'Animation',
container: 'Container',
list: 'List',
particles: 'Particles',
};
/**
* Opens a tab by clicking it, through the `»` menu when the row had to hide it. Since the tenth tab
* (#73) that is the normal case at the test window's width: a plain `clickText('Container')` found
* nothing and took the whole suite down with it.
*/
export async function openTab(t, tab) {
const row = await t.json('__gm.tabRow()');
if (row.hidden.includes(tab)) {
await t.clickText('»');
await t.sleep(300);
}
await t.clickText(TAB_LABELS[tab]);
await t.sleep(400);
}