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
59 lines
3.1 KiB
TypeScript
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);
|
|
});
|
|
});
|