Asset panel: double-click opens the tab, single click only selects (#71)
game-maker / suite (push) Failing after 8m11s

One rule for every asset type: a click on an asset row selects it, a
double-click (or Enter, or creating it via +) opens its tab. Selection
alone still switches tabs only for nodes, frames and template objects.

- state.openAsset(id) = select + 'open' event; the editor maps the asset
  type to its tab via TAB_OF_ASSET. The two special clauses for state
  machine and shader assets in the selection handler are gone.
- Creating via + and importing a texture from the picker open the tab;
  paste/drop of an image only selects.
- 3 unit tests (event order, re-open of a selected asset, selection never
  fires open), 14 e2e checks in step 4.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
rootandClaude Fable 5.1 committed 2026-09-13 12:10:21 +02:00
1 parent 3da0ef1be2
commit 4d9c4982eb
7 files changed
+160 -15

No files matched your search

+69
View File
@@ -88,12 +88,15 @@ export async function assets(t) {
`__gm.state.project.assets.push({ id: 'scene2', type: 'scene', name: 'Level 2', props: { backgroundColor: 0x22252c }, objects: [] })`,
);
const secondScene = await json("__gm.assetRow('scene2')");
await evaluate("__gm.showTab('graph')");
await sleep(150);
await doubleClick(secondScene.x, secondScene.y);
check(
'double-clicking a scene asset makes it the active scene',
(await json('__gm.state.project.activeSceneId')) === 'scene2',
await json('__gm.state.project.activeSceneId'),
);
check('and opens the Scene tab', (await json('__gm.tab()')) === 'scene', await json('__gm.tab()'));
const firstScene = await json("__gm.assetRow('scene1')");
await doubleClick(firstScene.x, firstScene.y);
check(
@@ -103,6 +106,72 @@ export async function assets(t) {
);
await mutate(`__gm.state.project.assets = __gm.state.project.assets.filter((a) => a.id !== 'scene2')`);
// 4d′. Opening an asset: a click on its row only selects, a double-click opens the tab it
// lives in – every type that has one. The fixture lacks four of them; they go in through
// the model, empty.
await mutate(`__gm.state.project.assets.push(
{ id: 'sm-open', type: 'statemachine', name: 'Machine', props: {}, graph: { nodes: [], edges: [] } },
{ id: 'tex-open', type: 'texture', name: 'Sheet', props: {}, file: '', width: 0, height: 0, frames: [] },
{ id: 'anim-open', type: 'animation', name: 'Walk', props: {}, frames: [] },
{ id: 'box-open', type: 'container', name: 'Box', props: {}, objects: [] },
)`);
const openable = [
['mover', 'graph', '__gm.graph.current()'],
['sm-open', 'statemachine', '__gm.stateMachine.current()'],
['adjust-colors', 'shader', '__gm.shader.current()'],
['tex-open', 'texture', '__gm.texture.current()'],
['anim-open', 'animation', '__gm.animation.current().assetId'],
['box-open', 'container', '__gm.container.current()'],
];
for (const [id, tab, shown] of openable) {
await evaluate("__gm.showTab('scene')");
await sleep(150);
const row = await json(`__gm.assetRow(${JSON.stringify(id)})`);
await click(row.x, row.y);
await sleep(500); // past the double-click window, so the next press starts a fresh pair
const clicked = {
tab: await json('__gm.tab()'),
selected: (await json('__gm.state.selection'))?.assetId,
};
check(
`a click on the ${tab} asset selects it and leaves the Scene tab alone`,
clicked.tab === 'scene' && clicked.selected === id,
JSON.stringify(clicked),
);
await doubleClick(row.x, row.y);
await sleep(300);
const opened = { tab: await json('__gm.tab()'), shown: await json(shown) };
check(
`a double-click opens the ${tab} tab on it`,
opened.tab === tab && opened.shown === id,
JSON.stringify(opened),
);
}
// Enter goes the same way (`onActivate`), but only once the list holds keyboard focus, which a
// click does not give it – so it is not driven here.
await evaluate("__gm.showTab('scene')");
await sleep(150);
await clickText('+');
await sleep(200);
const entry = await evaluate(`__gm.locate('Behavior', true)`);
await click(entry.x, entry.y);
await sleep(400);
const created = await json(
"(__gm.state.project.assets.find((a) => a.type === 'behavior' && !['mover', 'spawner'].includes(a.id)) ?? {}).id ?? ''",
);
const fresh = { created, tab: await json('__gm.tab()'), shown: await json('__gm.graph.current()') };
check(
'creating an asset through + opens its tab on the new one',
Boolean(created) && fresh.tab === 'graph' && fresh.shown === created,
JSON.stringify(fresh),
);
await undo();
await mutate(
`__gm.state.project.assets = __gm.state.project.assets.filter((a) => !['sm-open', 'tex-open', 'anim-open', 'box-open'].includes(a.id))`,
);
await evaluate("__gm.showTab('scene')");
await sleep(150);
// 4e. Asset defaults: the Crate deviates in height (60 against the schema's 100) and colour,
// so two rows carry a reset. Rows run Enabled, Width, Height, Color, Texture, so height's
// reset is the first on screen and colour's the last.