Ping or open an object's asset from its hierarchy row (#353)

The asset link in the inspector already pings an asset on a click and
opens it on a double-click. The rows of the Scene objects panel only
selected and folded. Now they lead to the asset the same way.

- A click on the row that is selected already pings the object's
  asset in the asset panel. The selection stays. The ping comes on
  release, so picking a row up to drag it pings nothing.
- A double-click or Enter on an object's row opens its asset,
  selected or not.
- ListView has a new onReselect for a plain click on the row that is
  selected alone. onSelect stays quiet then, as nothing changes.

Decided without asking:
- A double-click on an object's row no longer folds it. The chevron
  and the arrow keys still do. A group's row has no asset and keeps
  folding on a double-click.
- A click with Ctrl or Shift, and one that narrows a multi-selection
  to its row, does not ping. Both change the selection.
- A row whose asset is missing pings and opens nothing.

Findings:
- The double-click window goes by time (400 ms), not by the event's
  detail. Two quick click() calls from the harness are a double-click.
  The comment in e2e 01 said otherwise and is gone.

Tests:
- e2e 01 (melektaus-atx): ALL PASSED, 13 checks. The two checks for
  folding on a double-click became one for the ping and two for
  opening. Counter-check without the ping: that check red.
- Gate (melektaus-atx): CHECK EXIT 0, 268 files, 4142 tests.
- Not checked: Enter on a row, a group's row on a double-click.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-10-07 23:25:05 +02:00
1 parent 1d53b9b3d0
commit c95fbc167e
3 files changed
+74 -12

No files matched your search

+40 -10
View File
@@ -4,9 +4,10 @@ import { same } from '../lib/harness.mjs';
* Hierarchy folding. A freshly opened scene is fully collapsed, so only its top-level objects
* are listed (o4/oc1 hang under o1, oc2 under o2). The chevron folds a row open and shut
* without touching the selection, and selecting a nested object unfolds the way down to it.
* A click on the selected row pings the object's asset, a double-click opens it (#353).
*/
export async function hierarchy(t) {
const { check, evaluate, json, click, clickText, doubleClick, press, select, state, shot } = t;
const { check, evaluate, json, click, clickText, doubleClick, press, select, sleep, state, shot } = t;
const rowIds = () => json('__gm.hierarchyOrder()');
const rows = async () => JSON.stringify(await rowIds());
const selectionBefore = (await state()).selection;
@@ -31,21 +32,50 @@ export async function hierarchy(t) {
same(await rowIds(), ['o1', 'o2', 'o3', 'o5']),
await rows(),
);
// The row itself folds on a double-click, the same as its chevron. Pixi leaves `detail` at 0 on
// a press, so this needs a real double-click from the harness, not two `click()` calls.
// 01b (#353). A click on the row that is selected already pings the object's asset in the asset
// panel, as one on the asset link in the inspector does; the first click only selects.
const assetOf = (id) =>
json(`__gm.state.activeScene.objects.find((o) => o.id === ${JSON.stringify(id)}).assetId`);
const o3Asset = await assetOf('o3');
await evaluate('__gm.state.pingedAssetId = null');
const o3Row = await json(`__gm.hierarchyRow('o3')`);
await click(o3Row.x, o3Row.y);
const afterFirst = await json('__gm.state.pingedAssetId');
// Past the double-click window, which goes by time: two quick clicks would open the asset.
await sleep(500);
await click(o3Row.x, o3Row.y);
const ping = await json('__gm.assetPing()');
const pingSelection = (await state()).selection;
check(
'a click on the selected row pings its asset and keeps the selection, the first click only selects',
afterFirst === null &&
ping?.id === o3Asset &&
pingSelection?.kind === 'object' &&
same(pingSelection.objectIds, ['o3']),
JSON.stringify({ afterFirst, ping, o3Asset, pingSelection }),
);
// A double-click opens the asset, selected or not – it no longer folds the row.
const o2Asset = await assetOf('o2');
const row = await json(`__gm.hierarchyRow('o2')`);
await doubleClick(row.x, row.y);
const o2Selection = (await state()).selection;
check(
'double-clicking the row unfolds it as well',
same(await rowIds(), ['o1', 'o2', 'oc2', 'o3', 'o5']),
await rows(),
'double-clicking a row selects its asset and leaves the row folded',
o2Selection?.kind === 'asset' &&
o2Selection.assetId === o2Asset &&
same(await rowIds(), ['o1', 'o2', 'o3', 'o5']),
JSON.stringify({ o2Selection, o2Asset, rows: await rowIds() }),
);
await doubleClick(row.x, row.y);
await select({ kind: 'object', holderId: 'scene1', objectId: 'o5' });
const o5Row = await json(`__gm.hierarchyRow('o5')`);
await doubleClick(o5Row.x, o5Row.y);
const openedByRow = await json('__gm.documents()');
check(
'double-clicking it again folds the subtree away',
same(await rowIds(), ['o1', 'o2', 'o3', 'o5']),
await rows(),
'double-clicking a selected row opens the tab of its asset',
openedByRow.active === 'asset:spawner',
JSON.stringify(openedByRow),
);
await evaluate(`__gm.closeDocument('asset:spawner'); __gm.state.select(null)`);
await select({ kind: 'object', holderId: 'scene1', objectId: 'oc1' });
check(