Files
adminandClaude Opus 5 d9c71c2ce7
game-maker / gate (push) Successful in 2m38s
game-maker / suite (push) Successful in 8m31s
e2e checks follow the new theme instead of fixed sizes (#202)
The suite job for 0d20776 died in module 06. The new font and theme
moved the layout, and three checks relied on the old one.

- 06: the ranking step right-clicked the spot where step 6d had just
  dropped a Log node. Whether that click hits the node depends on how
  the layout snaps it. It now asks for a fresh empty spot.
- 14 and 27 expected a shut panel to keep 30 px. The header is
  Theme.rowHeight + 4, now 26. A new hook __gm.panelHeader hands the
  checks the real height, so the next theme change does not break
  them again.

Tests:
- ./check.sh: 141 files, 2080 tests, exit 0
- Local full run before the fix: 06 passed with the new spot, only the
  three header checks in 14 and 27 failed.
- GM_STEPS=1-14,27 after the fix: ALL PASSED, 457 checks
- No new checks, the total stays at 1286.

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

180 lines
7.6 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { edge, node } from '../lib/builders.mjs';
import { LANES, scaffoldBehavior } from './lib/scaffold.mjs';
/**
* The console panel: every `console.*` call lands in it with its level, a Log node's message
* arrives through the very same path, the play status line deliberately does not, and the
* divider above it resizes down to the bare header. The step removes everything it made.
*/
const SPOKEN = 'the crate says hello';
/** `On start → Log`: the one path a project itself writes a line on. */
const talkerGraph = {
nodes: [node('c-start', 'OnStart', 0, 0), node('c-log', 'Log', 340, 0, { message: SPOKEN })],
edges: [edge('c-e1', 'c-start', 'exec', 'c-log', 'exec')],
};
export async function consolePanel(t) {
const { check, sleep, json, evaluate, shot, drag, wheel, clickText, play, stop } = t;
const lines = () => json('__gm.console.lines()');
const texts = async () => (await lines()).map((l) => l.text);
const height = () => json('__gm.console.height()');
const colorOf = (text) => json(`__gm.textColor(${JSON.stringify(text)})`);
/** The panel rebuilds on the frame after a push, so every read waits one out. */
const settle = () => sleep(300);
await evaluate('__gm.console.clear()');
await settle();
// 27a. The captured path: each console method with its own level, and the level drawn as colour.
await evaluate(`console.log('plain line 27'); console.warn('warned 27'); console.error('failed 27')`);
await settle();
const captured = await lines();
check(
'every console level lands in the panel, newest last',
JSON.stringify(captured.map((l) => ({ level: l.level, text: l.text }))) ===
JSON.stringify([
{ level: 'info', text: 'plain line 27' },
{ level: 'warn', text: 'warned 27' },
{ level: 'error', text: 'failed 27' },
]),
captured,
);
const [info, warn, error] = await Promise.all([
colorOf('plain line 27'),
colorOf('warned 27'),
colorOf('failed 27'),
]);
check(
'a warning is drawn amber and an error red, a plain line in the text colour',
info === '#e6e8ee' && warn === '#f2c84b' && error === '#e0553f',
{ info, warn, error },
);
// 27a2. Every line carries the clock time of its call, drawn muted in its own column left of
// the message – so the message itself is still exactly what was logged.
const stamps = captured.map((l) => l.at);
const stampAt = await json(`__gm.locate(${JSON.stringify(captured[0].at)})`);
const messageAt = await json(`__gm.locate('plain line 27')`);
check(
'each line is stamped HH:MM:SS, drawn muted and left of the message',
stamps.every((at) => /^\d{2}:\d{2}:\d{2}$/.test(at)) &&
stampAt.x < messageAt.x &&
(await colorOf(captured[0].at)) === '#8a90a0',
JSON.stringify({ stamps, stampAt, messageAt }),
);
// 27b. Several arguments become one line, and an Error shows its message instead of [object].
await evaluate(`console.log('joined 27', 3, { a: 1 }); console.error(new Error('thrown 27'))`);
await settle();
const formatted = (await texts()).slice(-2);
check(
'arguments are joined and an Error reads as its message',
formatted[0] === 'joined 27 3 {"a":1}' && formatted[1]?.includes('thrown 27') === true,
formatted,
);
// 27b-ii. A group keeps its head and indents what follows: Pixi's deprecation warnings put the
// message into `groupCollapsed` with `%c` styles and only the stack into `warn`, so without this
// the panel showed a stack with no head.
await evaluate(
`console.groupCollapsed('%cgrouped 27: %c%s', 'color:red', 'color:blue', 'head'); console.warn(' at frame 27'); console.groupEnd(); console.log('after 27')`,
);
await settle();
const grouped = (await texts()).slice(-3);
check(
'a group head reads without its %c styles and the lines under it are indented',
grouped[0] === 'grouped 27: head' && grouped[1] === ' at frame 27' && grouped[2] === 'after 27',
grouped,
);
// 27c. The real path a project uses: a Log node during play writes through `console.log`.
const rig = await scaffoldBehavior(t, {
assetId: 'talker',
name: 'Talker',
graph: talkerGraph,
boxId: 'c-box',
boxName: 'Talker box',
logicId: 'c-logic',
y: LANES.console,
});
await evaluate('__gm.console.clear()');
await play();
await settle();
const played = await texts();
check(
'a Log node reaches the panel with the behavior it came from',
played.some((line) => line === `[behavior Talker] ${SPOKEN}`),
played,
);
// 27d. The status line is a mode, not an event: the toolbar says it, the console stays out of it.
const notice = await json('__gm.state.notice');
check(
'the play status line shows in the toolbar but is kept out of the console',
notice.startsWith('Playing') && !played.some((line) => line.startsWith('Playing')),
{ notice, played },
);
await stop();
await settle();
await shot('27-console-after-play');
// 27e. Clear empties it, and only it – the toolbar notice is not the console's to touch.
await clickText('Clear');
await settle();
check('the Clear button empties the panel', (await lines()).length === 0);
// 27f. The ring buffer keeps the tail, not the head.
await evaluate(`for (let i = 0; i < 520; i += 1) console.log('ring ' + i)`);
await sleep(800);
const ring = await texts();
check(
'past its capacity the panel keeps the newest 500 lines',
ring.length === 500 && ring[0] === 'ring 20' && ring.at(-1) === 'ring 519',
{ length: ring.length, first: ring[0], last: ring.at(-1) },
);
const tail = await json('__gm.console.scroll()');
check('it follows the tail, so the newest line is the one showing', tail.offset === tail.max, tail);
// 27g. Reading back means the tail must stop pulling, or the line under the pointer runs away.
// A full buffer would hide that: dropping a line as one arrives leaves the height unchanged.
await evaluate('__gm.console.clear()');
await settle();
await evaluate(`for (let i = 0; i < 60; i += 1) console.log('scroll ' + i)`);
await settle();
const panel = await json('__gm.console.centre()');
await wheel(panel.x, panel.y, -200);
await settle();
const scrolledUp = await json('__gm.console.scroll()');
await evaluate(`console.log('arrived while reading 27')`);
await settle();
const stayed = await json('__gm.console.scroll()');
check(
'a line arriving while you read further up does not yank the view down',
scrolledUp.offset === scrolledUp.max - 200 &&
stayed.max > scrolledUp.max &&
stayed.offset === scrolledUp.offset,
JSON.stringify({ scrolledUp, stayed }),
);
await evaluate('__gm.console.clear()');
await settle();
// 27h. The divider resizes the panel, and shutting it leaves the header to grab it by again.
const start = await height();
const bar = await json('__gm.console.splitterAt()');
await drag(bar.x, bar.y, bar.x, bar.y - 60, { steps: 6 });
const taller = await height();
check('dragging the divider up makes the console taller', taller === start + 60, { start, taller });
const bar2 = await json('__gm.console.splitterAt()');
await drag(bar2.x, bar2.y, bar2.x, bar2.y + 400, { steps: 10 });
const shut = await height();
const header = await json('__gm.panelHeader');
check('dragged shut it stops at its own header instead of vanishing', shut === header, { shut, header });
const bar3 = await json('__gm.console.splitterAt()');
await drag(bar3.x, bar3.y, bar3.x, bar3.y - (start - header), { steps: 8 });
check('and it comes back to the height it started at', (await height()) === start, await height());
const left = await rig.remove();
check('the step leaves nothing behind', left.objects === 0 && left.assets === 0, left);
}