pcbjam/tests/kicad/xface-probe.spec.ts
Istvan Matejcsok c5b4561495 docs+test(wasm): Part 2 as-built record; cross-face probe spec
- docs/features/editor-unification/06-part2-implementation.md — the merged
  kicad_editor as-built record: symbol-collision surgery (incl. the 21-symbol ODR
  audit), embind dispatcher, wiring, and validation results (frame-runtime 4/4,
  full suite 67 passed / 1 known pre-existing 3d-viewer drag failure fixed on main
  by 7630c7e, collab 10/10, cross-face probe green). README status/index updated;
  dangling perf/bundle-size.md links → perf/README.md.
- tests/kicad/xface-probe.spec.ts — permanent cross-face guard: a --frame=sch
  session opens Preferences and asserts the PCB engine's pages ("PCB Editor",
  "Footprint Editor") exist — i.e. KiFACE(FACE_PCB) lazy-starts inside a schematic
  session (absent on single-kiface bundles). Routed to BIG_MODULE_SPECS.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 17:56:02 +02:00

102 lines
5.5 KiB
TypeScript

import { test, expect } from './fixtures';
import { clickMenuBarItem } from '../e2e/utils/element-tracker';
/**
* Cross-face probe for the merged kicad_editor bundle (editor-unification Part 2).
*
* Boots the SCHEMATIC face (--frame=sch) and opens Preferences. Building the
* preferences dialog walks EVERY registered kiface (eda_base_frame.cpp calls
* KiFACE(FACE_SCH) AND KiFACE(FACE_PCB)) — so in the merged image this lazily
* OnKifaceStart()s the PCB engine INSIDE a schematic session, the exact cross-face
* path that footprint previews in the symbol chooser use. On the old single-kiface
* eeschema bundle KiFACE(FACE_PCB) returned nullptr and the PCB pages were absent.
*
* Asserts: the dialog opens, the runtime survives (no abort — a mis-registered or
* ODR-corrupted PCB kiface would crash construction of its preference pages), and —
* if the registry exposes the page tree — that PCB-side page labels are present.
*/
test.describe('merged bundle cross-face (schematic session starts the PCB kiface)', () => {
test('opening Preferences in --frame=sch builds both engines\' pages without aborting', async ({ page, testLogger }) => {
const consoleLines: string[] = [];
page.on('console', (m) => consoleLines.push(m.text()));
await page.goto('/kicad/eeschema.html');
await expect(page.locator('#canvas')).toBeVisible({ timeout: 120000 });
await expect
.poll(() => page.title(), { timeout: 120000, intervals: [1000] })
.toMatch(/Schematic Editor/i);
await page.waitForTimeout(1500);
// Open Preferences → Preferences… . NOTE: clickMenuItem('Preferences') would
// match the MENUBAR item again (same elementType) and toggle the menu shut —
// find the POPUP entry explicitly (not subType 'menubar') and click its coords.
const menuClicked = await clickMenuBarItem(page, 'Preferences');
expect(menuClicked, 'Preferences menubar item should be clickable').toBe(true);
await page.waitForTimeout(600);
const popupItem = await page.evaluate(() => {
const reg = (window as unknown as {
wxElementRegistry?: {
findAllRendered?(f: object): {
label?: string; subType?: string; centerX: number; centerY: number;
}[];
};
}).wxElementRegistry;
const items = reg?.findAllRendered?.({ elementType: 'menuitem' }) ?? [];
const hit = items.find(
(i) => i.subType !== 'menubar' && /Preferences/i.test(i.label ?? ''),
);
return hit ? { x: hit.centerX, y: hit.centerY, label: hit.label } : null;
});
expect(popupItem, 'the Preferences… popup menu item should be rendered').not.toBeNull();
console.log(`[xface] clicking popup item ${JSON.stringify(popupItem)}`);
await page.mouse.click(popupItem!.x, popupItem!.y);
// The dialog builds pages for every registered kiface — give the lazy
// PCB OnKifaceStart + page construction time to run.
await page.waitForTimeout(4000);
await page.screenshot({ path: 'test-results/xface-preferences.png', scale: 'device' });
// A dialog should be up.
const dialogCount = await page.evaluate(() => {
const reg = (window as unknown as {
wxElementRegistry?: { findAll(f: object): { typeName: string; label?: string }[] };
}).wxElementRegistry;
if (!reg) return -1;
return reg.findAll({ visible: true })
.filter((el) => /^wxDialog/.test(el.typeName)).length;
});
expect(dialogCount, 'the Preferences dialog should be open').toBeGreaterThan(0);
// Best-effort: dump registry labels and look for PCB-side page names.
const labels = await page.evaluate(() => {
const reg = (window as unknown as {
wxElementRegistry?: {
findAll(f: object): { label?: string }[];
findAllRendered?(f: object): { label?: string; text?: string }[];
};
}).wxElementRegistry;
if (!reg) return [] as string[];
const a = reg.findAll({ visible: true }).map((e) => e.label ?? '');
const b = (reg.findAllRendered?.({}) ?? []).map((e) => e.label ?? e.text ?? '');
return [...a, ...b].filter(Boolean);
});
console.log(`[xface] registry labels: ${JSON.stringify(labels.slice(0, 120))}`);
// THE cross-face assertion: the PCB engine's preference pages exist in a
// schematic session. On a single-kiface eeschema image KiFACE(FACE_PCB)
// returns nullptr and these tree entries are absent (kiway.cpp's WASM
// single-kiface comment); in the merged image the PCB kiface lazy-starts
// and contributes its pages.
expect(labels, 'PCB Editor preference page should exist in a SCH session')
.toContain('PCB Editor');
expect(labels, 'Footprint Editor preference page should exist in a SCH session')
.toContain('Footprint Editor');
// The decisive assertion: the cross-face start didn't kill the runtime.
const aborted = [...consoleLines, ...testLogger.consoleLogs, ...testLogger.errors]
.filter((l) => l.includes('Aborted(') || l.includes('Cannot register public name'));
expect(aborted, `no WASM abort during cross-face preferences:\n${aborted.join('\n')}`).toEqual([]);
});
});