- Graph: nodes.ts split into nodes/<category>/<Type>.ts (62 files) with shared.ts factories and index.ts registry; NodeContext/values/NodeRegistry separated, writeTyped() replaces duplicated typed writes - Comments: one style (JSDoc on non-obvious exports, // for decisions and traps), wrong/stale comments fixed or removed, ids.ts now real 10 base-36 chars - Core: chainOf, shared worldPosition/editorPosition core, commands/require.ts, commands/naming.ts, util/list.ts, setBagValue/toPosition/propDef; dead code removed (isBehaviorAsset, SCENE_OBJECT_SCHEMA, CommandStack.subscribe, Input.axis, setMode, titleText, World.worldPosition, unused ctor params) - Editor: Inspector split into inspector/ sections, App.ts into Editor.ts + debugHook.ts + shortcuts.ts, Viewport into items.ts + symbols.ts, shared "Add collider" action, unified type labels/swatches - UI toolkit: Widget.trackHover/onPointerRelease, drawControlBox, math helpers, Popup.openBelow, Menu/Dialog/ColorPicker/ListRow in own files, named Theme sizes - Tools: seed-demo/smoke-demo and demo-check project removed, tools/lib (harness, projects, builders), e2e.mjs as runner over tools/e2e/*.mjs sections - Tests: tests/helpers.ts replaces copied fixtures and NodeContext doubles Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
67 lines
3.5 KiB
JavaScript
67 lines
3.5 KiB
JavaScript
/**
|
|
* Selecting, renaming and scrubbing in the inspector, dragging in the viewport, autosave to
|
|
* the server, and undo/redo of all of it.
|
|
*/
|
|
export async function inspector(t) {
|
|
const { check, sleep, json, locate, shot, click, clickText, drag, press, typeText, undo, redo, state, object, api, sceneOf, projectId } = t;
|
|
|
|
// 1. Select the blue crate in the hierarchy.
|
|
await clickText('Blue crate');
|
|
let s = await state();
|
|
check('hierarchy click selects object', s.selection?.kind === 'object' && s.selection.objectId === 'o2', JSON.stringify(s.selection));
|
|
await shot('02-object-selected');
|
|
|
|
// 2. Rename via inspector: the name field sits right of the "Name" label.
|
|
const nameLabel = await locate('Name');
|
|
await click(nameLabel.x + 160, nameLabel.y);
|
|
await press('a', { ctrl: true });
|
|
await typeText('Renamed crate');
|
|
await shot('03-typing');
|
|
await press('Enter');
|
|
await sleep(200);
|
|
check('typing + Enter renamed the object', (await object('o2')).name === 'Renamed crate', JSON.stringify((await object('o2')).name));
|
|
await shot('04-renamed');
|
|
|
|
// 3. Scrub the X field (right of the "X" label) by 100 px.
|
|
const xLabel = await locate('X');
|
|
await drag(xLabel.x + 160, xLabel.y, xLabel.x + 260, xLabel.y);
|
|
const xNow = (await object('o2')).position.x;
|
|
check('scrubbing X moved the object', xNow > 120, `x=${xNow}`);
|
|
await shot('05-scrubbed');
|
|
|
|
// 3b. Type into X, then click straight into the empty viewport: the typed value must be
|
|
// committed (not reverted) even though the click deselects and rebuilds the inspector.
|
|
const center = await json('__gm.viewportCenter()');
|
|
await click(xLabel.x + 160, xLabel.y); // plain click → text editing with everything selected
|
|
await typeText('300');
|
|
await click(center.x, center.y + 250);
|
|
const xTyped = (await object('o2')).position.x;
|
|
s = await state();
|
|
check('typed X survives clicking into the viewport', xTyped === 300 && s.selection === null, `x=${xTyped} selection=${JSON.stringify(s.selection)}`);
|
|
|
|
// 4. Drag the red crate in the viewport. It is at world (-150,0); the view is centred
|
|
// on the origin, so compute its screen position from the viewport centre.
|
|
await drag(center.x - 150, center.y, center.x - 50, center.y - 70);
|
|
const o1Now = (await object('o1')).position;
|
|
check('viewport drag moved the red crate', o1Now.x > -150, JSON.stringify(o1Now));
|
|
await shot('06-dragged');
|
|
|
|
// 5. Autosave → server.
|
|
await sleep(1800);
|
|
const saved = await api(`projects/${projectId}`);
|
|
const renamed = sceneOf(saved).objects.find((o) => o.id === 'o2');
|
|
check('rename persisted', renamed.name === 'Renamed crate', renamed.name);
|
|
check('scrub persisted', renamed.position.x > 120, JSON.stringify(renamed.position));
|
|
check('override kept', renamed.overrides.color === 4491519, JSON.stringify(renamed.overrides));
|
|
check('save status is saved', (await state()).save === 'saved');
|
|
|
|
// 6. Undo four times (drag, typed X, scrub, rename) with Ctrl+Z, then redo once.
|
|
await click(center.x, center.y + 200); // click empty viewport → deselect, drop focus
|
|
for (let i = 0; i < 4; i++) await undo();
|
|
const afterUndo = (await t.objects()).map((o) => ({ id: o.id, name: o.name, x: o.position.x }));
|
|
check('undo reverted drag, typed X, scrub and rename', afterUndo[0].x === -150 && afterUndo[1].x === 120 && afterUndo[1].name === 'Blue crate', JSON.stringify(afterUndo));
|
|
await redo();
|
|
check('redo restored the rename', (await object('o2')).name === 'Renamed crate');
|
|
await shot('07-undo-redo');
|
|
}
|