Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Td4ujboGuAw26jvbDQzehj
137 lines
5.8 KiB
TypeScript
137 lines
5.8 KiB
TypeScript
import { test, expect, type Page } from './fixtures';
|
|
import { injectFromSubmodule } from './utils/fs-inject';
|
|
import { DEMO, PROJECT_DIR_MEMFS, countGlCanvases, openThreeDViewer } from './utils/threed-viewer';
|
|
import { waitForBoardLoaded } from './utils/board-ready';
|
|
import { waitForPcbnew } from './utils/pcbnew-ready';
|
|
import { clickMenuBarItem, clickMenuItemByText } from '../e2e/utils/element-tracker';
|
|
|
|
/**
|
|
* Phase F regression repro (docs/features/async/22 §10, the awaited-ccall entry
|
|
* class). The user's live-editor path opens boards through the WEB SHELL's
|
|
* `Module.kicadOpenFile(path)` — which Phase F wrapped to run the open body on
|
|
* a DISPATCH CONTEXT and hand the shell a promise over an "open" wait token.
|
|
* EVERY existing board-load spec instead drives File → Open (the wxFileDialog
|
|
* → OpenProjectFiles path), so NONE exercises that wrapper — the exact gap the
|
|
* user's "pcbnew dialogs are broken, eeschema is fine" report lives in.
|
|
*
|
|
* These specs reproduce the shell path (inject the board, then
|
|
* `await Module.kicadOpenFile`) and THEN drive the dialogs the user reported
|
|
* frozen: the 3D viewer, and a modal open/close. The assertion is simply that
|
|
* the wasm main thread stays responsive and nothing traps.
|
|
*/
|
|
|
|
const TRAP =
|
|
/Aborted\(|index out of bounds|unreachable executed|indirect call signature|null function|memory access out of bounds/;
|
|
|
|
interface Mod {
|
|
kicadOpenFile(path: string): Promise<boolean> | boolean;
|
|
}
|
|
|
|
// Load the board the way the WEB SHELL does — through Module.kicadOpenFile
|
|
// (the Phase F dispatch-context wrapper), not File → Open.
|
|
async function openBoardViaShell(
|
|
page: Page,
|
|
logger: { consoleLogs: string[]; errors: string[] },
|
|
): Promise<void> {
|
|
const pcb = `${DEMO.stem}.kicad_pcb`;
|
|
const pro = `${DEMO.stem}.kicad_pro`;
|
|
await injectFromSubmodule(page, `kicad/demos/${DEMO.dir}/${pcb}`, `${PROJECT_DIR_MEMFS}/${pcb}`);
|
|
await injectFromSubmodule(page, `kicad/demos/${DEMO.dir}/${pro}`, `${PROJECT_DIR_MEMFS}/${pro}`);
|
|
|
|
const ok = await page.evaluate(async (path) => {
|
|
const m = (window as unknown as { Module: Mod }).Module;
|
|
return await m.kicadOpenFile(path);
|
|
}, `${PROJECT_DIR_MEMFS}/${pcb}`);
|
|
expect(ok, 'Module.kicadOpenFile resolved true').toBe(true);
|
|
|
|
const result = await waitForBoardLoaded(page, logger, 60000);
|
|
console.log(`[TEST] shell-open board-ready: ${result}`);
|
|
}
|
|
|
|
// A JS-thread liveness probe: a fresh mailbox tick round-trips only if the wasm
|
|
// main thread is not wedged. A frozen app never resolves it.
|
|
async function assertResponsive(page: Page, label: string): Promise<void> {
|
|
const alive = await page.evaluate(
|
|
() =>
|
|
new Promise<boolean>((resolve) => {
|
|
const t = setTimeout(() => resolve(false), 8000);
|
|
// A zero-delay mailbox message is delivered from a clean tick
|
|
// (wxWasmMailboxTick) — only if the loop is running.
|
|
requestAnimationFrame(() =>
|
|
requestAnimationFrame(() => {
|
|
clearTimeout(t);
|
|
resolve(true);
|
|
}),
|
|
);
|
|
}),
|
|
);
|
|
expect(alive, `main thread responsive after ${label}`).toBe(true);
|
|
}
|
|
|
|
function noTrap(logger: { consoleLogs: string[]; errors: string[] }, label: string): void {
|
|
const hits = [...logger.consoleLogs, ...logger.errors].filter((l) => TRAP.test(l));
|
|
expect(hits, `no wasm trap after ${label}`).toEqual([]);
|
|
}
|
|
|
|
test.describe('shell-opened board → dialogs (Phase F open-lane regression)', () => {
|
|
test('3D viewer opens over a shell-loaded board and stays responsive', async ({
|
|
page,
|
|
testLogger,
|
|
}) => {
|
|
test.setTimeout(180000);
|
|
await page.goto('/kicad/pcbnew.html');
|
|
await waitForPcbnew(page);
|
|
|
|
await openBoardViaShell(page, testLogger);
|
|
await assertResponsive(page, 'shell open');
|
|
noTrap(testLogger, 'shell open');
|
|
|
|
const glBefore = await countGlCanvases(page);
|
|
await openThreeDViewer(page, glBefore);
|
|
await assertResponsive(page, '3D viewer open');
|
|
noTrap(testLogger, '3D viewer open');
|
|
});
|
|
|
|
test('a modal opens and closes over a shell-loaded board without freezing', async ({
|
|
page,
|
|
testLogger,
|
|
}) => {
|
|
test.setTimeout(180000);
|
|
await page.goto('/kicad/pcbnew.html');
|
|
await waitForPcbnew(page);
|
|
|
|
await openBoardViaShell(page, testLogger);
|
|
await assertResponsive(page, 'shell open');
|
|
|
|
// Board Setup is a heavy modal reachable from the File menu without a
|
|
// library provider — enough to exercise the ShowModal park/resume over
|
|
// a dispatch context left idle by the shell open.
|
|
expect(await clickMenuBarItem(page, 'File'), 'File menu findable').toBe(true);
|
|
await clickMenuItemByText(page, 'Board Setup');
|
|
await page.waitForFunction(
|
|
() =>
|
|
!!window.wxElementRegistry &&
|
|
window.wxElementRegistry
|
|
.findAll({ visible: true })
|
|
.some((e) => /Dialog/i.test(e.typeName || '')),
|
|
null,
|
|
{ timeout: 20000 },
|
|
);
|
|
await assertResponsive(page, 'modal open');
|
|
noTrap(testLogger, 'modal open');
|
|
|
|
// Close it (Escape → EndModal(wxID_CANCEL) → resolves the modal wait).
|
|
await page.keyboard.press('Escape');
|
|
await page.waitForFunction(
|
|
() =>
|
|
!!window.wxElementRegistry &&
|
|
!window.wxElementRegistry
|
|
.findAll({ visible: true })
|
|
.some((e) => /Dialog/i.test(e.typeName || '')),
|
|
null,
|
|
{ timeout: 20000 },
|
|
);
|
|
await assertResponsive(page, 'modal close');
|
|
noTrap(testLogger, 'modal close');
|
|
});
|
|
});
|