From 16bbc52240b56036c3b3088db587e3b1a548b06e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerg=C5=91=20T=C3=B6rcsv=C3=A1ri?= Date: Thu, 4 Jun 2026 13:50:08 +0200 Subject: [PATCH] fix(eeschema-wasm): quasi-modal dialogs no longer freeze; Backspace deletes selection Bump kicad + wxwidgets submodules with two wasm eeschema UI fixes, and add the regression test tests/kicad/eeschema-ui.spec.ts (Delete + Backspace delete; the text-tool properties dialog opens and closes without freezing). - wxwidgets c27fe8b: nested (quasi-modal) event loops pump via Asyncify instead of re-entering emscripten_set_main_loop (which threw an un-resumable 'unwind'). - kicad 4132395: bind Backspace to delete under emscripten + apply default alt hotkeys. Co-Authored-By: Claude Opus 4.8 (1M context) --- kicad | 2 +- tests/kicad/eeschema-ui.spec.ts | 116 ++++++++++++++++++++++++++++++++ wxwidgets | 2 +- 3 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 tests/kicad/eeschema-ui.spec.ts diff --git a/kicad b/kicad index 9cde940..4132395 160000 --- a/kicad +++ b/kicad @@ -1 +1 @@ -Subproject commit 9cde940836ae05b4f6e79c014a4f7a4b75d41045 +Subproject commit 4132395c823d54105b47049b32b40cbae85eff8b diff --git a/tests/kicad/eeschema-ui.spec.ts b/tests/kicad/eeschema-ui.spec.ts new file mode 100644 index 0000000..dee5698 --- /dev/null +++ b/tests/kicad/eeschema-ui.spec.ts @@ -0,0 +1,116 @@ +import type { Page } from "@playwright/test"; +import { test, expect } from "./fixtures"; +import { clickByTooltip } from "../e2e/utils/element-tracker"; + +/** + * Eeschema core-UI regressions found 2026-06-04 (both wasm-specific, both fixed): + * + * - Backspace did nothing on a selection. ACTIONS::doDelete binds WXK_BACK only under + * __WXMAC__; our emscripten build is non-Mac so only WXK_DELETE was bound (and a Mac + * user's "delete" key sends Backspace). Fixed by an __EMSCRIPTEN__ DefaultHotkeyAlt(WXK_BACK) + * on doDelete + making ACTION_MANAGER::processHotKey apply default *alt* hotkeys. + * + * - The text tool froze the app. createNewText shows DIALOG_TEXT_PROPERTIES via + * ShowQuasiModal, whose nested wxGUIEventLoop::DoRun re-entered emscripten_set_main_loop + * (simulate_infinite_loop → "unwind"), which can't be nested/resumed. Fixed by pumping + * nested event loops through Asyncify (wxwidgets/src/wasm/evtloop.cpp). + */ + +const SAMPLE_SCH = `(kicad_sch +\t(version 20250114) +\t(generator "eeschema") +\t(generator_version "9.0") +\t(uuid "11111111-1111-1111-1111-111111111111") +\t(paper "A4") +\t(lib_symbols) +\t(wire (pts (xy 50.8 50.8) (xy 101.6 50.8)) (stroke (width 0) (type default)) (uuid "22222222-0000-0000-0000-000000000001")) +\t(wire (pts (xy 50.8 76.2) (xy 101.6 76.2)) (stroke (width 0) (type default)) (uuid "22222222-0000-0000-0000-000000000002")) +\t(sheet_instances (path "/" (page "1"))) +) +`; + +type FS = { mkdirTree(p: string): void; writeFile(p: string, d: string): void }; +type Mod = { kicadOpenFile(p: string): unknown; kicadCollabSnapshot(): string }; + +function hasAbort(l: { consoleLogs: string[]; errors: string[] }): boolean { + return [...l.consoleLogs, ...l.errors].some((s) => s.includes("Aborted(")); +} + +async function bootAndOpen(page: Page): Promise { + await page.goto("/kicad/eeschema.html"); + await expect(page.locator("#canvas")).toBeVisible({ timeout: 90000 }); + await page.waitForFunction(() => typeof (window as unknown as { Module?: Mod }).Module?.kicadCollabSnapshot === "function", null, { timeout: 90000 }); + await page.waitForFunction( + () => !!window.wxElementRegistry && window.wxElementRegistry.findAll({ visible: true }).some((e) => /Frame$/.test(e.typeName)), + null, + { timeout: 90000 }, + ); + await page.evaluate((content) => { + const w = window as unknown as { FS: FS; Module: Mod }; + try { + w.FS.mkdirTree("/home/kicad/documents"); + } catch { + /* exists */ + } + const p = "/home/kicad/documents/ui.kicad_sch"; + w.FS.writeFile(p, content); + w.Module.kicadOpenFile(p); + }, SAMPLE_SCH); + await page.waitForTimeout(2000); +} + +function count(page: Page): Promise { + return page.evaluate(() => JSON.parse(window.Module.kicadCollabSnapshot()).added.length); +} + +async function focusCanvas(page: Page): Promise { + const box = await page.locator("#canvas").boundingBox(); + if (box) await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2); + await page.waitForTimeout(300); +} + +test.describe("eeschema core UI (wasm)", () => { + for (const key of ["Delete", "Backspace"]) { + test(`${key} deletes the selection`, async ({ page, testLogger }) => { + await bootAndOpen(page); + expect(await count(page)).toBe(2); + + await focusCanvas(page); + await page.keyboard.press("Control+a"); + await page.waitForTimeout(500); + await page.keyboard.press(key); + + await expect.poll(() => count(page), { timeout: 8000, intervals: [300] }).toBe(0); + expect(hasAbort(testLogger), "no WASM abort").toBe(false); + }); + } + + test("text tool opens its properties dialog and closes without freezing", async ({ page, testLogger }) => { + await bootAndOpen(page); + + expect(await clickByTooltip(page, "Draw Text")).toBe(true); + await page.waitForTimeout(600); + + const box = await page.locator("#canvas").boundingBox(); + if (box) await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2); + await page.waitForTimeout(1500); + + const dialogsOpen = () => + page.evaluate(() => + window.wxElementRegistry.findAll({ visible: true }).filter((e) => /Dialog/i.test(e.typeName)).length, + ); + + // The quasi-modal dialog must appear (previously the nested event loop threw "unwind"). + expect(await dialogsOpen(), "text properties dialog should open").toBeGreaterThan(0); + // App must stay responsive while it's up (Asyncify suspend, not a frozen main thread). + expect(await page.evaluate(() => 1 + 1).then(() => true).catch(() => false)).toBe(true); + + // Escape must close it — exercises the Asyncify resume (ShowQuasiModal returns). + await page.keyboard.press("Escape"); + await expect.poll(dialogsOpen, { timeout: 8000, intervals: [300] }).toBe(0); + + // Still alive afterwards. + expect(await count(page)).toBeGreaterThan(0); + expect(hasAbort(testLogger), "no WASM abort").toBe(false); + }); +}); diff --git a/wxwidgets b/wxwidgets index ea18637..c27fe8b 160000 --- a/wxwidgets +++ b/wxwidgets @@ -1 +1 @@ -Subproject commit ea186374a59347955c42a5acaea8148ad855f0d3 +Subproject commit c27fe8bf038154ec055d929bf8c6bfa01c0ef2fb