- 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>
102 lines
5.5 KiB
TypeScript
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([]);
|
|
});
|
|
});
|