diff --git a/.gitignore b/.gitignore index f21f8b5..e1511fb 100644 --- a/.gitignore +++ b/.gitignore @@ -50,6 +50,9 @@ wxwidgets-clean/ /tests/apps/standalone/*/*.wasm /tests/apps/kicad/*.js /tests/apps/kicad/*.wasm +# Demo board for the gerbview print test — provisioned at setup from +# site/public/gerber-demo/board (see tests/scripts/setup-kicad-wasm.sh), not committed. +/tests/apps/kicad/board/ # DOM-port test bundles (mirrored + built by scripts/build-wasm-test.sh --dom) diff --git a/kicad b/kicad index 793c26b..f7a15ad 160000 --- a/kicad +++ b/kicad @@ -1 +1 @@ -Subproject commit 793c26bbb4377c31ec32ff182ee06deb27edd3d9 +Subproject commit f7a15ade476dec8615b864cbb90ef40662537b7e diff --git a/tests/apps/kicad/gerbview-print.html b/tests/apps/kicad/gerbview-print.html new file mode 100644 index 0000000..55f6118 --- /dev/null +++ b/tests/apps/kicad/gerbview-print.html @@ -0,0 +1,284 @@ + + + + + + KiCad Gerber Viewer WASM — Print test harness + + + +
+ +
+
Initializing...
+
+
+ +
+ + + + + + + + + diff --git a/tests/baseline-screenshots/gerbview-print-00-loaded.png b/tests/baseline-screenshots/gerbview-print-00-loaded.png new file mode 100644 index 0000000..64ccbac Binary files /dev/null and b/tests/baseline-screenshots/gerbview-print-00-loaded.png differ diff --git a/tests/baseline-screenshots/gerbview-print-01-dialog.png b/tests/baseline-screenshots/gerbview-print-01-dialog.png new file mode 100644 index 0000000..18c2e39 Binary files /dev/null and b/tests/baseline-screenshots/gerbview-print-01-dialog.png differ diff --git a/tests/baseline-screenshots/gerbview-print-02-reopened.png b/tests/baseline-screenshots/gerbview-print-02-reopened.png new file mode 100644 index 0000000..18c2e39 Binary files /dev/null and b/tests/baseline-screenshots/gerbview-print-02-reopened.png differ diff --git a/tests/kicad/gerbview-print.spec.ts b/tests/kicad/gerbview-print.spec.ts new file mode 100644 index 0000000..303bc08 --- /dev/null +++ b/tests/kicad/gerbview-print.spec.ts @@ -0,0 +1,146 @@ +import type { Page } from '@playwright/test'; +import { test, expect } from './fixtures'; +import { + clickByLabel, + clickByTooltip, + clickMenuBarItem, + clickMenuItem, + findByLabel, +} from '../e2e/utils/element-tracker'; + +/** + * Gerber Viewer Print dialog — regression for emergence-engineering/pcbjam#14. + * + * In the browser, KiCad's generic Print dialog used to show a "Print Preview" + * button (wxID_APPLY) that the DOM/WASM port can't honor: the in-app + * wxPreviewFrame is opened window-modal from inside the already-modal Print + * dialog, which the port can't render over an active modal — clicking it did + * nothing and wedged the dialog so it would not reopen. + * + * The fix hides that button in the browser build (the same way native KiCad + * already hides it on macOS/GTK), since the browser provides its own print + * preview. This test loads the tiny_tapeout demo board, opens the Print dialog, + * and asserts (a) there is no visible "Print Preview" button and (b) the dialog + * can be closed and reopened — i.e. the modal state is no longer wedged. + */ + +function hasAbort(testLogger: { consoleLogs: string[]; errors: string[] }): boolean { + return [...testLogger.consoleLogs, ...testLogger.errors].some(line => line.includes('Aborted(')); +} + +// The Print dialog is detected by its unique OK button, labelled exactly +// "Print" (wxID_OK). Toolbar/menu print entries are rendered tools / menu items +// (a separate registry) or carry the "..." ellipsis, so they don't match here — +// a visible exact-"Print" element means the modal Print dialog is open. +async function printDialogIsOpen(page: Page): Promise { + return (await findByLabel(page, 'Print', { visible: true, exact: true })) !== null; +} + +async function waitForPrintDialog(page: Page, open: boolean, timeout = 8000): Promise { + await page.waitForFunction( + (wantOpen: boolean) => { + const registry = window.wxElementRegistry; + if (!registry) return false; + const isOpen = registry.findByLabel('Print', { visible: true, exact: true }).length > 0; + return isOpen === wantOpen; + }, + open, + { timeout }, + ); +} + +async function waitForGerbview(page: Page): Promise { + await expect(page.locator('#canvas')).toBeVisible({ timeout: 90000 }); + await page.waitForFunction(() => !!window.wxElementRegistry, null, { timeout: 90000 }); + await page.waitForFunction(() => { + const registry = window.wxElementRegistry; + return !!registry && registry.findAll({}).length > 0; + }, null, { timeout: 90000 }); + + // The config is seeded to suppress the first-run wizard, but dismiss it + // defensively in case it still appears (harmless when there is none). + for (let i = 0; i < 6; i++) { + if (!(await clickByLabel(page, 'Next >'))) { + await clickByLabel(page, 'Finish'); + break; + } + await page.waitForTimeout(300); + } + + // Let the demo board load and the viewer chrome settle. The Print Preview + // button is hidden unconditionally in the WASM build, so the assertions + // don't actually depend on the layers being fully parsed — this is mostly + // for a faithful screenshot. + await page.waitForTimeout(4000); +} + +// Open the Print dialog. In gerbview, Print is a top-toolbar tool (not a File +// menu item), so click that; fall back to a File -> Print… menu path in case the +// UI changes ("..." vs unicode "…"). +async function openPrintDialog(page: Page): Promise { + if (!(await clickByTooltip(page, 'Print'))) { + await clickMenuBarItem(page, 'File'); + await page.waitForTimeout(500); + let opened = await clickMenuItem(page, 'Print...'); + if (!opened) opened = await clickMenuItem(page, 'Print…'); + if (!opened) await clickMenuItem(page, 'Print'); + } + + await waitForPrintDialog(page, true); +} + +// Close the modal Print dialog. Escape cancels a wxDialog; fall back to the +// Close button if needed. Poll for the dialog to actually disappear. +async function closePrintDialog(page: Page): Promise { + for (const how of ['escape', 'close', 'escape'] as const) { + if (how === 'escape') { + await page.keyboard.press('Escape').catch(() => {}); + } else { + await clickByLabel(page, 'Close', { visible: true, exact: true }); + } + try { + await waitForPrintDialog(page, false, 3000); + return; + } catch { + // try the next method + } + } + // Final assertion happens in the test; surface the still-open state there. + await waitForPrintDialog(page, false, 1000); +} + +test.describe('gerbview Print dialog (WASM)', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/kicad/gerbview-print.html'); + }); + + test('no Print Preview button, and the dialog reopens (no wedge)', async ({ page, testLogger }) => { + await waitForGerbview(page); + await page.screenshot({ path: 'test-results/gerbview-print-00-loaded.png', scale: 'device' }); + + // --- Open the Print dialog --- + await openPrintDialog(page); + expect(await printDialogIsOpen(page), 'Print dialog should be open').toBe(true); + await page.screenshot({ path: 'test-results/gerbview-print-01-dialog.png', scale: 'device' }); + + // --- The broken "Print Preview" button must be gone in the browser --- + const preview = await findByLabel(page, 'Print Preview', { visible: true, exact: true }); + expect(preview, 'Print Preview button must be hidden in the browser build').toBeNull(); + + // Sanity: this really is the Print dialog (Close + Page Setup present too). + expect(await findByLabel(page, 'Close', { visible: true, exact: true }), + 'Close button present').not.toBeNull(); + + // --- Close it --- + await closePrintDialog(page); + expect(await printDialogIsOpen(page), 'dialog should close').toBe(false); + + // --- Regression: reopening Print must work (it used to wedge) --- + await openPrintDialog(page); + expect(await printDialogIsOpen(page), 'Print dialog should reopen (no wedge)').toBe(true); + await page.waitForTimeout(2500); // let the reopened dialog finish painting before capture + await page.screenshot({ path: 'test-results/gerbview-print-02-reopened.png', scale: 'device' }); + + expect(hasAbort(testLogger), 'no WASM abort during the flow').toBe(false); + }); +}); diff --git a/tests/scripts/setup-kicad-wasm.sh b/tests/scripts/setup-kicad-wasm.sh index 523c4e9..f860447 100755 --- a/tests/scripts/setup-kicad-wasm.sh +++ b/tests/scripts/setup-kicad-wasm.sh @@ -115,5 +115,20 @@ else smart_cp "$PROJECT_ROOT/wxwidgets/build/wasm/wx-dom.js" "$KICAD_TEST" fi +# Demo board for the gerbview print test (tests/apps/kicad/gerbview-print.html). +# Provisioned (not committed; gitignored) from the canonical demo board, same +# pattern as the wasm artifacts above. gerbview-print.html fetches the subset of +# layers it needs from ./board/, so copying the whole set is harmless. +BOARD_SRC="$PROJECT_ROOT/site/public/gerber-demo/board" +if [ -d "$BOARD_SRC" ]; then + echo "Provisioning gerbview demo board..." + mkdir -p "$KICAD_TEST/board" + for f in "$BOARD_SRC"/tinytapeout-demo-*; do + smart_cp "$f" "$KICAD_TEST/board" + done +else + echo " (demo board source $BOARD_SRC not found — skipping)" +fi + echo "KiCad WASM files synced to $KICAD_TEST" ls -lh "$KICAD_TEST"