Files
feuerwerk/tools/e2e/02-inspector.mjs
T
rootandClaude Opus 5 d54a620ebd Add ESLint and Prettier, and close the node test gaps
Tooling:
- Prettier as the single formatting authority (110 columns, single quotes,
  measured against the existing style); Markdown excluded because table-cell
  padding turns the README glossary into 1000-character lines.
- Type-aware ESLint ending in eslint-config-prettier, so a rule always means
  "this is a bug", never "this looks different".
- .editorconfig and an `npm run check` that runs typecheck, lint, format and
  tests in one go.

Findings fixed on the first run: three more duplicate imports, four
String(unknown) calls (parameter types narrowed instead of casts), two dead
initialisers, two redundant casts, a console.log for a real error. Two
warnings were false here and are disabled with a reason in place.

Tests 132 -> 269:
- node-catalog.test.ts covers every node with a run(), with a guard that fails
  when a new node has no row.
- behavior-events.test.ts covers the ten event nodes through a running World,
  checking timing rather than existence.
- Input's state machine (press/release/releaseAll) is reachable without DOM
  events now; the DOM handlers are thin adapters.

Compare fell back to == without a configured operator although the schema
default is <; aligned.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 10:02:22 +02:00

101 lines
3.6 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');
}