diff --git a/tests/apps/Makefile.wasm b/tests/apps/Makefile.wasm index 5e08b67..e434f45 100644 --- a/tests/apps/Makefile.wasm +++ b/tests/apps/Makefile.wasm @@ -167,6 +167,7 @@ all: minimal_test.html \ $(S)/regions/regions_test.html \ $(S)/maximize/maximize_test.html \ $(S)/earlysize/earlysize_test.html \ + $(S)/selectheight/selectheight_test.html \ $(S)/threadpool/threadpool_test.html \ $(S)/logerror/logerror_test.html \ $(S)/retinascale/retinascale_test.html \ @@ -508,6 +509,12 @@ $(S)/earlysize/earlysize_test.o: $(S)/earlysize/earlysize_test.cpp $(S)/earlysize/earlysize_test.html: $(S)/earlysize/earlysize_test.o $(WX_CORE_LIB) $(JS_FILES) $(CXX) $< $(LDFLAGS_NOGL) --pre-js $(JS) --shell-file $(HTML) -o $@ +$(S)/selectheight/selectheight_test.o: $(S)/selectheight/selectheight_test.cpp + $(CXX) -c $(CXXFLAGS) $< -o $@ + +$(S)/selectheight/selectheight_test.html: $(S)/selectheight/selectheight_test.o $(WX_CORE_LIB) $(JS_FILES) + $(CXX) $< $(LDFLAGS_NOGL) --pre-js $(JS) --shell-file $(HTML) -o $@ + # Thread Pool test (pthread) - reproduces KiCad deadlock when hardware_concurrency() > PTHREAD_POOL_SIZE $(S)/threadpool/threadpool_test.o: $(S)/threadpool/threadpool_test.cpp $(CXX) -c $(CXXFLAGS) -pthread $< -o $@ @@ -604,6 +611,7 @@ bitmask: $(S)/bitmask/bitmask_test.html regions: $(S)/regions/regions_test.html maximize: $(S)/maximize/maximize_test.html earlysize: $(S)/earlysize/earlysize_test.html +selectheight: $(S)/selectheight/selectheight_test.html threadpool: $(S)/threadpool/threadpool_test.html logerror: $(S)/logerror/logerror_test.html @@ -627,7 +635,7 @@ clean: rm -f $(S)/*/*_test*.html $(S)/*/*_test*.js $(S)/*/*_test*.wasm rm -f $(S)/*/*_repro*.html $(S)/*/*_repro*.js $(S)/*/*_repro*.wasm -.PHONY: all clean menu contextmenu scrollbar clipboard filedialog layout aui toolbar grid dialog timer tree dataview htmlwin stc print dnd propgrid pickers collapsible listctrl infobar dataviewvirtual auinotebook wizard gridedit calendar gridrenderers printpreview bitmapbuttons specialized validators ownerdrawn popup xml wasmedge fontenum textdecor bitmask regions maximize earlysize threadpool logerror retinascale coroutine coroutine-nested asyncify-races +.PHONY: all clean menu contextmenu scrollbar clipboard filedialog layout aui toolbar grid dialog timer tree dataview htmlwin stc print dnd propgrid pickers collapsible listctrl infobar dataviewvirtual auinotebook wizard gridedit calendar gridrenderers printpreview bitmapbuttons specialized validators ownerdrawn popup xml wasmedge fontenum textdecor bitmask regions maximize earlysize selectheight threadpool logerror retinascale coroutine coroutine-nested asyncify-races # === Coroutine pthread variant — reproduces the KiCad Asyncify-fiber x pthreads crash === # Same modal-free harness as `coroutine`, but compiled/linked with pthreads to match diff --git a/tests/apps/standalone/selectheight/selectheight_test.cpp b/tests/apps/standalone/selectheight/selectheight_test.cpp new file mode 100644 index 0000000..4d7ba5d --- /dev/null +++ b/tests/apps/standalone/selectheight/selectheight_test.cpp @@ -0,0 +1,105 @@ +// Select Height Test - Regression guard for the wxChoice ('s height only +// resolves once the browser lays it out, but DoGetBestSize() is frequently +// queried BEFORE layout (wxAuiToolBar freezes a control's min size at AddControl +// time, panel sizers measure during construction). The DOM reported ~0 height, +// so the layout system pinned the control to an unusable sliver. +// +// The fix is wxChoice::DoGetBestSize() (src/wasm/choice.cpp), which floors the +// height to GetCharHeight() + 8 when the DOM-measured height is too small. +// +// This test creates ONE wxChoice and queries GetBestSize() in the constructor, +// BEFORE the frame is shown / laid out. On pre-fix code best.y is ~0 (FAIL); +// with the fix it is GetCharHeight() + 8 (PASS). + +#include "wx/wxprec.h" + +#ifndef WX_PRECOMP + #include "wx/wx.h" +#endif + +#ifdef __EMSCRIPTEN__ +#include +#endif + +class SelectHeightTestApp : public wxApp +{ +public: + virtual bool OnInit() override; +}; + +class SelectHeightTestFrame : public wxFrame +{ +public: + SelectHeightTestFrame(); + +private: + wxSize m_earlyBestSize; // wxChoice best size captured before Show()/layout +}; + +wxIMPLEMENT_APP(SelectHeightTestApp); + +bool SelectHeightTestApp::OnInit() +{ + if (!wxApp::OnInit()) + return false; + + SelectHeightTestFrame* frame = new SelectHeightTestFrame(); + frame->Show(true); + return true; +} + +SelectHeightTestFrame::SelectHeightTestFrame() + : wxFrame(nullptr, wxID_ANY, "Select Height Test", + wxDefaultPosition, wxSize(400, 200)) +{ + wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL); + + mainSizer->Add(new wxStaticText(this, wxID_ANY, + "Select Height Test\n\n" + "One wxChoice; its GetBestSize() is queried in the constructor,\n" + "before layout. Height must NOT collapse to ~0px."), + 0, wxALL, 10); + + // The single control under test. wxDefaultSize so the caller does not force + // a height -- the height must come from DoGetBestSize(). + wxString items[] = { "Red", "Green", "Blue" }; + wxChoice* choice = new wxChoice(this, wxID_ANY, wxDefaultPosition, + wxDefaultSize, 3, items); + choice->SetSelection(0); + + // === THE KEY MEASUREMENT === + // Query best size BEFORE Show()/layout, where the bug manifested. Nothing is + // cached yet, so this recomputes via wxChoice::DoGetBestSize(). + m_earlyBestSize = choice->GetBestSize(); + + // The bug was height-specific; width comes from intrinsic content sizing and + // was never broken. A real control is at least a line of text tall. + const int minHeight = GetCharHeight() + 8; + const bool heightOk = (m_earlyBestSize.y >= minHeight); + +#ifdef __EMSCRIPTEN__ + EM_ASM({ + console.log('[SELECTHEIGHT_TEST] Choice best size: ' + $0 + 'x' + $1); + console.log('[SELECTHEIGHT_TEST] Expected min height: ' + $2); + if ($3) { + console.log('[SELECTHEIGHT_TEST] PASS: select has a real height'); + } else { + console.error('[SELECTHEIGHT_TEST] FAIL: select height collapsed to ' + + $1 + 'px (expected >= ' + $2 + ')'); + } + }, m_earlyBestSize.x, m_earlyBestSize.y, minHeight, heightOk ? 1 : 0); +#endif + + choice->SetSelection(0); + mainSizer->Add(choice, 0, wxALL, 10); + + SetSizer(mainSizer); + +#ifdef __EMSCRIPTEN__ + EM_ASM({ + console.log('[SELECTHEIGHT_TEST] Select height test app started'); + }); +#endif +} diff --git a/tests/baseline-screenshots/selectheight-01-loaded.png b/tests/baseline-screenshots/selectheight-01-loaded.png new file mode 100644 index 0000000..3f3fa21 Binary files /dev/null and b/tests/baseline-screenshots/selectheight-01-loaded.png differ diff --git a/tests/baseline-screenshots/selectheight-02-result.png b/tests/baseline-screenshots/selectheight-02-result.png new file mode 100644 index 0000000..3f3fa21 Binary files /dev/null and b/tests/baseline-screenshots/selectheight-02-result.png differ diff --git a/tests/e2e/selectheight.spec.ts b/tests/e2e/selectheight.spec.ts new file mode 100644 index 0000000..8eaa38d --- /dev/null +++ b/tests/e2e/selectheight.spec.ts @@ -0,0 +1,56 @@ +// Select Height Test - Regression guard for the wxChoice ('s height only resolves after layout, but DoGetBestSize() is queried +// before layout; the DOM used to report ~0 height, collapsing the control. The +// fix floors the height in wxChoice::DoGetBestSize(). The C++ app queries one +// wxChoice's GetBestSize() in its constructor (before Show()) and logs it. +import { test, expect, tryLoadApp } from './utils/fixtures'; + +test.describe('Select Height Tests', () => { + + test('Select height test app loads successfully', async ({ page, testLogger }) => { + await page.goto('/standalone/selectheight/selectheight_test.html'); + const loaded = await tryLoadApp(page); + + await page.screenshot({ path: 'test-results/selectheight-01-loaded.png', fullPage: true }); + + const hasStartup = testLogger.consoleLogs.some(l => l.includes('[SELECTHEIGHT_TEST] Select height test app started')); + + expect(loaded, 'Select height app should load').toBe(true); + expect(hasStartup, 'Startup log should be present').toBe(true); + expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0); + }); + + test('wxChoice best size has a real height before layout', async ({ page, testLogger }) => { + await page.goto('/standalone/selectheight/selectheight_test.html'); + const loaded = await tryLoadApp(page); + expect(loaded, 'App should load').toBe(true); + + // Wait for the app to finish initialization + await page.waitForTimeout(500); + + await page.screenshot({ path: 'test-results/selectheight-02-result.png', fullPage: true }); + + // Parse the best size logged from the constructor (before Show()/layout) + const bestSizeLogs = testLogger.consoleLogs.filter(l => l.includes('[SELECTHEIGHT_TEST] Choice best size:')); + expect(bestSizeLogs.length).toBeGreaterThan(0); + + const bestMatch = bestSizeLogs[0].match(/Choice best size: (\d+)x(\d+)/); + expect(bestMatch, 'Best size log should contain dimensions').not.toBeNull(); + + if (bestMatch) { + const height = parseInt(bestMatch[2]); + + // The key assertion: the