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. */ export async function hierarchy(t) { const { check, evaluate, json, click, clickText, doubleClick, press, select, state, shot } = t; const rowIds = () => json('__gm.hierarchyOrder()'); const rows = async () => JSON.stringify(await rowIds()); const selectionBefore = (await state()).selection; check( 'hierarchy starts collapsed – only top-level objects are listed', same(await rowIds(), ['o1', 'o2', 'o3', 'o5']), await rows(), ); check('a leaf row offers no chevron', (await evaluate(`__gm.hierarchyChevron('o3')`)) === null); const chevron = await json(`__gm.hierarchyChevron('o2')`); await click(chevron.x, chevron.y); check( 'clicking the chevron unfolds the row, leaving the selection alone', same(await rowIds(), ['o1', 'o2', 'oc2', 'o3', 'o5']) && same((await state()).selection, selectionBefore), await rows(), ); await shot('01a-hierarchy-unfolded'); await click(chevron.x, chevron.y); check( 'clicking it again folds the subtree away', 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. const row = await json(`__gm.hierarchyRow('o2')`); await doubleClick(row.x, row.y); check( 'double-clicking the row unfolds it as well', same(await rowIds(), ['o1', 'o2', 'oc2', 'o3', 'o5']), await rows(), ); await doubleClick(row.x, row.y); check( 'double-clicking it again folds the subtree away', same(await rowIds(), ['o1', 'o2', 'o3', 'o5']), await rows(), ); await select({ kind: 'object', holderId: 'scene1', objectId: 'oc1' }); check( 'selecting a nested object unfolds its ancestors', same(await rowIds(), ['o1', 'o4', 'oc1', 'o2', 'o3', 'o5']), await rows(), ); await evaluate('__gm.state.select(null); __gm.hierarchyCollapseAll()'); check('Collapse all folds everything back', same(await rowIds(), ['o1', 'o2', 'o3', 'o5']), await rows()); // Open asset in the row's menu does what a double-click in the asset panel does. const spawnerRow = await json(`__gm.hierarchyRow('o5')`); await click(spawnerRow.x, spawnerRow.y, 'right'); await clickText('Open asset'); const opened = await json('__gm.documents()'); const openedSelection = (await state()).selection; check( 'Open asset in the row menu selects the asset and opens its tab', opened.active === 'asset:spawner' && opened.docs.includes('asset:spawner') && openedSelection?.kind === 'asset' && openedSelection.assetId === 'spawner', JSON.stringify({ opened, openedSelection }), ); await evaluate(`__gm.closeDocument('asset:spawner'); __gm.state.select(null)`); // A sprite has no editor of its own, so the entry that promises to open one is greyed out. const crateRow = await json(`__gm.hierarchyRow('o1')`); await click(crateRow.x, crateRow.y, 'right'); const menuRows = await json('__gm.menuRows()'); const menuEnabled = await json('__gm.menuEnabled()'); check( 'Open asset is greyed out for a kind without an editor, Select asset stays on', menuEnabled[menuRows.indexOf('Open asset')] === false && menuEnabled[menuRows.indexOf('Select asset')] === true, JSON.stringify({ menuRows, menuEnabled }), ); await press('Escape'); await evaluate('__gm.state.select(null)'); }