CI was red ond4fc425with two checks, both from this week's work. - Signing out builds the editor anew on the same renderer. The second useCursorFiles then took the SVG pointers of the first for Pixi's own styles, so switching the option off kept them. The editor now undoes the pointers on dispose and Pixi's styles are back in place. - e2e 62d still looked for "Start recording". The menu reads "Start recording…" sinceba01d14, and 62 had not run then. Tests: - e2e 45, 46 and 62 on melektaus-atx: ALL PASSED, 51 checks - Without the dispose, 46i-ii is red after 45, as it was in CI - Gate, measured together with #419 on top: 305 files, 4714 tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
144 lines
6.0 KiB
JavaScript
144 lines
6.0 KiB
JavaScript
/**
|
||
* The interface on a screen scaled to 150 %: the one thing every other step is blind to, since the
|
||
* harness runs at 100 %, where a layout pixel is a device pixel. Text has to come out as hard as it
|
||
* does at 100 %, and a hairline has to be one device pixel of its own colour, not one and a half
|
||
* with the half mixed. Reloads the page twice, so the steps after it start from a fresh editor.
|
||
*/
|
||
export async function devicePixels(t) {
|
||
const { check, json, cdp, navigate, select, storedObjects, wheel, exceptions } = t;
|
||
const size = { width: await json('innerWidth'), height: await json('innerHeight') };
|
||
const scale = (deviceScaleFactor) =>
|
||
cdp.send('Emulation.setDeviceMetricsOverride', { ...size, deviceScaleFactor, mobile: false });
|
||
// The toolbar: captions on a plain ground, read at 100 % and at 150 %.
|
||
const textEdges = () => json(`__gm.edgeContrast(0, 0, innerWidth, 30)`);
|
||
const pickFirst = async () => {
|
||
const holderId = await json('__gm.state.activeScene.id');
|
||
const first = (await storedObjects())[0];
|
||
await select({ kind: 'object', holderId, objectId: first.id, objectIds: [first.id] });
|
||
};
|
||
|
||
await pickFirst();
|
||
const plain = { grid: await json('__gm.pixelGrid()'), edges: await textEdges() };
|
||
|
||
await scale(1.5);
|
||
await navigate();
|
||
await pickFirst();
|
||
|
||
// 62a. The editor knows the grid it draws on.
|
||
const grid = await json('__gm.pixelGrid()');
|
||
check(
|
||
'at 150 % a hairline is one device pixel wide',
|
||
plain.grid.resolution === 1 &&
|
||
plain.grid.hairline === 1 &&
|
||
grid.resolution === 1.5 &&
|
||
Math.abs(grid.hairline * 1.5 - 1) < 1e-9,
|
||
JSON.stringify({ plain: plain.grid, grid }),
|
||
);
|
||
|
||
// 62b. Both lines of every band of the inspector, read down a column of device pixels: one row
|
||
// each, in the border's own colour, and only the band's colour between them. A band on an odd
|
||
// layout pixel starts half-way between two device pixels, which is the case that blurs.
|
||
const colours = {
|
||
border: await json(`__gm.themeColor('border')`),
|
||
header: await json(`__gm.themeColor('panelHeader')`),
|
||
};
|
||
// One pixel of scroll flips every band between even and odd, so both are read. The window is made
|
||
// too low for the inspector's rows first: one that shows them all has nothing to scroll.
|
||
await cdp.send('Emulation.setDeviceMetricsOverride', {
|
||
width: size.width,
|
||
height: 420,
|
||
deviceScaleFactor: 1.5,
|
||
mobile: false,
|
||
});
|
||
await t.settle();
|
||
const inspector = await json('__gm.inspectorRect()');
|
||
const bands = [];
|
||
for (const scrolled of [0, 1]) {
|
||
if (scrolled) await wheel(inspector.x + inspector.w / 2, inspector.y + inspector.h / 2, 1);
|
||
for (const title of ['SWITCHES', 'POSITION', 'TRANSFORM', 'PROPERTIES']) {
|
||
const band = await json(`__gm.section(${JSON.stringify(title)})`);
|
||
if (!band) continue;
|
||
const { x, y, w, h } = band.rect;
|
||
// A band cut off by the edge of the panel has no second line to read.
|
||
if (y < inspector.y || y + h > inspector.y + inspector.h) continue;
|
||
const column = await json(`__gm.deviceColumn(${x + w - 40}, ${y}, ${h})`);
|
||
const runs = [];
|
||
for (const colour of column) {
|
||
if (runs.at(-1)?.colour === colour) runs.at(-1).rows += 1;
|
||
else runs.push({ colour, rows: 1 });
|
||
}
|
||
bands.push({ title, y, runs: runs.map((run) => `${run.colour}*${run.rows}`) });
|
||
}
|
||
}
|
||
const crisp = (band) =>
|
||
band.runs.length === 3 &&
|
||
band.runs[0] === `${colours.border}*1` &&
|
||
band.runs[1].startsWith(`${colours.header}*`) &&
|
||
band.runs[2] === `${colours.border}*1`;
|
||
const odd = (band) => Math.round(band.y) % 2 === 1;
|
||
check(
|
||
'the lines of a band are one device pixel each, in the unmixed colour',
|
||
bands.every(crisp) && bands.some(odd) && bands.some((band) => !odd(band)),
|
||
JSON.stringify({ bands, colours }),
|
||
);
|
||
await scale(1.5);
|
||
await t.settle();
|
||
|
||
// 62c. Text keeps its edges: against the same captions at 100 %, where nothing can sit between pixels.
|
||
const edges = await textEdges();
|
||
const kept = edges / plain.edges;
|
||
check(
|
||
'text is as hard-edged at 150 % as at 100 %',
|
||
kept > 0.96,
|
||
JSON.stringify({ plain: plain.edges, edges, kept }),
|
||
);
|
||
await t.shot('62-device-pixels');
|
||
|
||
// 62d. `?w=&h=` give the canvas a size of its own: the editor is laid out at that size, a window
|
||
// too small for it shows it shrunk to fit and centred, and a click lands where the picture is.
|
||
await cdp.send('Emulation.setDeviceMetricsOverride', {
|
||
width: 960,
|
||
height: 720,
|
||
deviceScaleFactor: 1,
|
||
mobile: false,
|
||
});
|
||
await cdp.send('Page.navigate', { url: `${t.baseUrl}?project=${t.projectId}&w=1600&h=900` });
|
||
await t.sleep(4000);
|
||
await t.waitUntil(`window.__gm?.state?.project?.id === ${JSON.stringify(t.projectId)}`, {
|
||
timeout: 15000,
|
||
label: 'the editor at a fixed size is up',
|
||
});
|
||
const fixed = await json(`(() => {
|
||
const canvas = document.querySelector('canvas');
|
||
const r = canvas.getBoundingClientRect();
|
||
const round = (v) => Math.round(v * 100) / 100;
|
||
return { w: canvas.width, h: canvas.height, shown: { x: round(r.x), y: round(r.y), w: round(r.width), h: round(r.height) } };
|
||
})()`);
|
||
const menu = await json('__gm.toolbarAt("menu")');
|
||
const factor = fixed.shown.w / fixed.w;
|
||
await t.click(fixed.shown.x + menu.x * factor, fixed.shown.y + menu.y * factor);
|
||
const rows = await json('__gm.menuRows()');
|
||
await t.press('Escape');
|
||
check(
|
||
'?w=1600&h=900 lays the editor out at 1600×900, shrinks it into a 960×720 window and still takes clicks',
|
||
fixed.w === 1600 &&
|
||
fixed.h === 900 &&
|
||
fixed.shown.x === 0 &&
|
||
fixed.shown.y === 90 &&
|
||
fixed.shown.w === 960 &&
|
||
fixed.shown.h === 540 &&
|
||
Array.isArray(rows) &&
|
||
rows.includes('Start recording…'),
|
||
JSON.stringify({ fixed, menu, rows }),
|
||
);
|
||
|
||
await scale(1);
|
||
await navigate();
|
||
const back = await json('__gm.pixelGrid()');
|
||
check(
|
||
'back at 100 % without exceptions',
|
||
back.resolution === 1 && exceptions().length === 0,
|
||
JSON.stringify({ back, exceptions: exceptions() }),
|
||
);
|
||
}
|