Files
feuerwerk/tools/e2e/04-assets.mjs
T
root 75d377af39 Place assets by dragging them from the panel into the view
The context menu entry is gone; dragging replaces it entirely. The drop
target is the view under the pointer, not the active tab, so the same
gesture works for scenes and for container templates.

While dragging, an outline shows the footprint the object will have plus
a crosshair on its origin, turning red when the drop would be refused.
Preview and command share one function (placementRejection), so the
outline cannot promise something the drop then rejects.

A left-press used to select the list row immediately. Since the container
tab follows the selection, it switched to the very container being dragged
and every drop would have nested it inside itself. The list now selects on
release without a drag: picking something up is not the same as choosing it.
2026-09-09 13:09:13 +02:00

123 lines
4.7 KiB
JavaScript

/** The asset panel: placing by dragging into the view, creating through "+", and resetting an asset value. */
export async function assets(t) {
const {
check,
sleep,
evaluate,
json,
shot,
click,
clickText,
mouse,
press,
redo,
undo,
select,
objects,
asset,
} = t;
// 8. Context menu on the Crate asset. Placing left the menu when it became a drag.
await clickText('Crate', 'right');
check('asset context menu opens', Boolean(await evaluate(`__gm.locate('Delete…')`)));
check('placing is no longer a menu item', !(await evaluate(`__gm.locate('Place in scene')`)));
await shot('11-context-menu');
await press('Escape');
await sleep(150);
// 8b. Drag the Crate asset from the panel into the scene view. Driven step by step rather than
// with `drag()`, because the preview only exists while the button is still down.
const from = await json("__gm.assetRow('crate')");
const centre = await json('__gm.viewportCenter()');
const to = { x: centre.x + 70, y: centre.y - 50 };
await mouse('mouseMoved', from.x, from.y);
await mouse('mousePressed', from.x, from.y);
for (let i = 1; i <= 8; i++) {
const x = from.x + ((to.x - from.x) * i) / 8;
const y = from.y + ((to.y - from.y) * i) / 8;
await mouse('mouseMoved', x, y, 'left', { buttons: 1 });
await sleep(16);
}
const preview = await json('__gm.dropPreview()');
check(
'dragging an asset over the view previews where it would land',
Boolean(preview) && preview.assetId === 'crate' && preview.rejection === '',
JSON.stringify(preview),
);
await shot('11b-drop-preview');
await mouse('mouseReleased', to.x, to.y);
await sleep(200);
const count = (await objects()).length;
check('dropping the asset adds an object', count === 8, `objects=${count}`);
const placed = await json(
'(() => { const id = __gm.state.selectedObjectIds[0]; return __gm.sceneObjects().find(o => o.id === id) ?? null; })()',
);
// The preview is a promise: whatever it drew has to be where the object then sits, or the
// outline was decoration rather than a prediction.
check(
'the object lands where the preview promised, and is selected',
Boolean(placed) &&
placed.assetId === 'crate' &&
placed.position.x === preview.x &&
placed.position.y === preview.y,
JSON.stringify({ placed: placed && placed.position, preview }),
);
check('the preview is gone once the asset is placed', (await json('__gm.dropPreview()')) === null);
await undo();
const afterUndo = (await objects()).length;
check('one undo takes the dropped object back out', afterUndo === 7, `objects=${afterUndo}`);
await redo();
await sleep(150);
check('redo puts it back', (await objects()).length === 8);
// 9. Create a new asset through the + menu.
await clickText('+');
await clickText('Sprite');
await sleep(100);
const names = await json('__gm.state.project.assets.map(a => a.name)');
check('+ menu creates a sprite asset', names.includes('Sprite'), JSON.stringify(names));
await shot('12-new-asset');
// 12. 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.
await select({ kind: 'asset', assetId: 'crate' });
await sleep(200);
const heightReset = await evaluate(`__gm.locate('↺')`);
const colorReset = await evaluate(`__gm.locate('↺', true)`);
check('asset inspector shows a reset per deviating property', Boolean(heightReset));
check(
'the two deviations have two separate resets',
Boolean(colorReset) && colorReset.y > heightReset.y,
JSON.stringify({ heightReset, colorReset }),
);
await click(heightReset.x, heightReset.y);
await sleep(100);
check(
'reset puts the asset property back to its default',
(await asset('crate')).props.height === 100,
`height=${(await asset('crate')).props.height}`,
);
const remaining = await evaluate(`__gm.locate('↺')`);
check(
'the reset of the property now at its default is gone, the other one stays',
Boolean(remaining) &&
remaining.y === colorReset.y &&
remaining.y === (await evaluate(`__gm.locate('↺', true)`)).y,
JSON.stringify({ remaining, colorReset }),
);
await shot('15-asset-reset');
await undo();
check(
'undo restores the asset value',
(await asset('crate')).props.height === 60,
`height=${(await asset('crate')).props.height}`,
);
check(
'the height reset is back after undo',
(await evaluate(`__gm.locate('↺')`))?.y === heightReset.y,
JSON.stringify(await evaluate(`__gm.locate('↺')`)),
);
}