pcbjam/tests/e2e/tree-hier.spec.ts
Gergő Törcsvári 8fa6bae084
fix: hollow sheet rooms wiped subsheets on entry (ysync 0011) + hierarchy pane icon sizing/selection colours
Collab: a save-all's layout-only sync into a never-entered sheet room left a
doc with layout but zero items; the first entry adopted it and removed every
item on screen. seed() now file-seeds a hollow doc, syncLayoutFromSave skips
never-seeded empty rooms, and the ydoc-boot / sibling-restage paths fall back
to the file. Tree: new tree-hier standalone app + e2e spec covering the
wxwidgets bundle-size and unfocused-selection fixes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0189z2siCsezE39ugcDKNBMs
2026-08-27 08:59:01 +02:00

59 lines
3.1 KiB
TypeScript

import { test, expect } from './utils/fixtures';
import { findAllTreeItems, waitForWxApp, stableShot } from './utils/element-tracker';
/**
* eeschema's Schematic Hierarchy pane = wxTreeCtrl with a hidden root, a
* per-item wxBitmapBundle (16px dots) and a bold "current sheet". Regression
* for two port bugs: the bundle's preferred LOGICAL size was reported at the
* device-pixel size (32 on retina) so every row reserved twice the icon
* height with the icon pinned to the row top; and an unfocused selection was
* drawn black-on-highlight (wxSYS_COLOUR_LISTBOXHIGHLIGHTTEXT defaulted to the
* window text colour while the generic renderer always paints HIGHLIGHT).
*/
test.describe('wxTreeCtrl hierarchy-pane configuration', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/standalone/tree-hier/tree_hier_test.html');
await waitForWxApp(page);
});
test('rows with 16px bundle icons stay text-height, not icon@2x-height', async ({ page }) => {
const items = await findAllTreeItems(page);
expect(items.length).toBe(5);
const byY = [...items].sort((a, b) => a.screenY - b.screenY);
// 10pt text ≈ 16px + 2px padding + 2px spacing; a 32px "logical" icon made this ~35.
for (const it of byY) expect(it.height, `row "${it.label}"`).toBeLessThanOrEqual(24);
const pitches = byY.slice(1).map((it, i) => it.screenY - byY[i].screenY);
for (const p of pitches) expect(p).toBeLessThanOrEqual(24);
await stableShot(page, 'tree-hier-01-loaded.png');
});
test('an unfocused selection is drawn light-on-highlight, not black-on-blue', async ({ page }) => {
const items = await findAllTreeItems(page);
const sel = items.find(it => it.label.startsWith('Arduino Leonardo'))!;
expect(sel).toBeDefined();
// Sample the label row at device resolution: the highlight (navy) must be
// present and the glyph pixels on it must be light, never near-black.
const dpr = await page.evaluate(() => window.devicePixelRatio);
const px = await page.evaluate(
({ x, y, w, h, dpr }) => {
const canvas = document.querySelector('canvas') as HTMLCanvasElement;
const ctx = canvas.getContext('2d')!;
const r = canvas.getBoundingClientRect();
const d = ctx.getImageData((x - r.x) * dpr, (y - r.y) * dpr, w * dpr, h * dpr).data;
let navy = 0, dark = 0, light = 0;
for (let i = 0; i < d.length; i += 4) {
const [R, G, B] = [d[i], d[i + 1], d[i + 2]];
if (B > 90 && R < 40 && G < 40) navy++;
else if (R < 60 && G < 60 && B < 60) dark++;
else if (R > 200 && G > 200 && B > 200) light++;
}
return { navy, dark, light, total: d.length / 4 };
},
// Skip the leading 16px item icon (the "selected" dot is dark by design).
{ x: sel.screenX + 22, y: sel.screenY, w: sel.width - 22, h: sel.height, dpr }
);
expect(px.navy, 'highlight painted under the selected label').toBeGreaterThan(px.total * 0.2);
expect(px.light, 'light glyph pixels on the highlight').toBeGreaterThan(px.total * 0.02);
expect(px.dark, 'no black glyphs on the highlight').toBeLessThan(px.total * 0.01);
});
});