fix(wasm): radio groups no longer merge into one

Bump wxwidgets to the %p->%zu radio-group name fix and add a standalone
wxWidgets harness plus an e2e regression that asserts three radio groups
in one window select independently. Build artifacts are gitignored; only
radiogroups_test.cpp is tracked.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Istvan Matejcsok 2026-06-29 11:23:22 +02:00
commit 13cac06778
6 changed files with 235 additions and 2 deletions

View file

@ -146,6 +146,7 @@ all: minimal_test.html \
$(S)/dialog/dialog_test.html \
$(S)/timer/timer_test.html \
$(S)/tree/tree_test.html \
$(S)/radiogroups/radiogroups_test.html \
$(S)/dataview/dataview_test.html \
$(S)/htmlwin/htmlwin_test.html \
$(S)/stc/stc_test.html \
@ -344,6 +345,13 @@ $(S)/tree/tree_test.o: $(S)/tree/tree_test.cpp
$(S)/tree/tree_test.html: $(S)/tree/tree_test.o $(WX_CORE_LIB) $(JS_FILES)
$(CXX) $< $(LDFLAGS_NOGL) --pre-js $(JS) --shell-file $(HTML) -o $@
# Radio button groups test (no GL) - multiple wxRB_GROUP groups in one window
$(S)/radiogroups/radiogroups_test.o: $(S)/radiogroups/radiogroups_test.cpp
$(CXX) -c $(CXXFLAGS) $< -o $@
$(S)/radiogroups/radiogroups_test.html: $(S)/radiogroups/radiogroups_test.o $(WX_CORE_LIB) $(JS_FILES)
$(CXX) $< $(LDFLAGS_NOGL) --pre-js $(JS) --shell-file $(HTML) -o $@
# DataView test (no GL)
$(S)/dataview/dataview_test.o: $(S)/dataview/dataview_test.cpp
$(CXX) -c $(CXXFLAGS) $< -o $@
@ -641,6 +649,7 @@ grid: $(S)/grid/grid_test.html
dialog: $(S)/dialog/dialog_test.html
timer: $(S)/timer/timer_test.html
tree: $(S)/tree/tree_test.html
radiogroups: $(S)/radiogroups/radiogroups_test.html
dataview: $(S)/dataview/dataview_test.html
htmlwin: $(S)/htmlwin/htmlwin_test.html
stc: $(S)/stc/stc_test.html
@ -695,7 +704,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 selectheight threadpool logerror retinascale coroutine coroutine-nested asyncify-races notebook
.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 notebook radiogroups
# === Coroutine pthread variant — reproduces the KiCad Asyncify-fiber x pthreads crash ===
# Same modal-free harness as `coroutine`, but compiled/linked with pthreads to match

View file

@ -0,0 +1,143 @@
// wxRadioButton Group Test - exercises MULTIPLE radio-button groups in one window
//
// KiCad's Preferences "Mouse and Touchpad" panel lays out three independent
// scroll-gesture radio groups (Zoom / Pan up-down / Pan left-right), each as a
// row of [--, Ctrl, Shift, Alt] radios with the first button flagged wxRB_GROUP.
//
// In the WASM DOM port the per-group HTML "name" must be unique per group so the
// browser keeps the groups independent. This harness reproduces that layout: it
// pre-selects a DIFFERENT column in each of the three groups, so a correctly
// grouped build shows exactly one selection PER ROW (three total). If the groups
// collapse into one (the %p-empty-name bug), only one radio across all three
// rows can stay checked.
#include "wx/wxprec.h"
#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif
#include "wx/statline.h"
#ifdef __EMSCRIPTEN__
#include <emscripten/emscripten.h>
#endif
class RadioGroupsFrame : public wxFrame
{
public:
RadioGroupsFrame()
: wxFrame(nullptr, wxID_ANY, "wxRadioButton Group Test",
wxDefaultPosition, wxSize(640, 420))
{
wxPanel* panel = new wxPanel(this);
wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);
wxStaticText* desc = new wxStaticText(panel, wxID_ANY,
"Three INDEPENDENT radio groups (mirrors KiCad's scroll-gesture panel).\n"
"Each row pre-selects a different column, so a correct build shows one\n"
"selection per row (three total). If the groups merge, only one survives.");
mainSizer->Add(desc, 0, wxALL, 8);
// 5-column grid: row label + [--, Ctrl, Shift, Alt]
wxFlexGridSizer* grid = new wxFlexGridSizer(5, 6, 12);
grid->Add(new wxStaticText(panel, wxID_ANY, ""));
grid->Add(new wxStaticText(panel, wxID_ANY, "--"), 0, wxALIGN_CENTER);
grid->Add(new wxStaticText(panel, wxID_ANY, "Ctrl"), 0, wxALIGN_CENTER);
grid->Add(new wxStaticText(panel, wxID_ANY, "Shift"), 0, wxALIGN_CENTER);
grid->Add(new wxStaticText(panel, wxID_ANY, "Alt"), 0, wxALIGN_CENTER);
// Row order and wxRB_GROUP flags MUST match the real panel: each row's
// first radio starts a new group; the rest join it.
addGroupRow(panel, grid, "Group A (Zoom)", m_groupA);
addGroupRow(panel, grid, "Group B (Pan up/down)", m_groupB);
addGroupRow(panel, grid, "Group C (Pan left/right)", m_groupC);
mainSizer->Add(grid, 0, wxALL, 12);
wxButton* check = new wxButton(panel, wxID_ANY, "Check selections");
check->Bind(wxEVT_BUTTON, &RadioGroupsFrame::OnCheck, this);
mainSizer->Add(check, 0, wxLEFT | wxBOTTOM, 12);
mainSizer->Add(new wxStaticText(panel, wxID_ANY, "Log"), 0, wxLEFT, 12);
m_log = new wxTextCtrl(panel, wxID_ANY, "", wxDefaultPosition, wxSize(-1, 110),
wxTE_MULTILINE | wxTE_READONLY);
mainSizer->Add(m_log, 1, wxEXPAND | wxALL, 12);
panel->SetSizer(mainSizer);
CreateStatusBar();
SetStatusText("radio groups test app started");
Log("radio groups test app started");
// Pre-select a DIFFERENT column per group (distinct, like the real
// defaults: Zoom=--, Pan up/down=Shift, Pan left/right=Ctrl). Deferred
// until after the frame is shown, mirroring how real panels apply
// settings in TransferDataToWindow(); SetValue() from the ctor runs
// before the DOM radio nodes are realized and would not paint.
CallAfter([this]
{
m_groupA[COL_NONE]->SetValue(true);
m_groupB[COL_SHIFT]->SetValue(true);
m_groupC[COL_CTRL]->SetValue(true);
Log("Initial selection: A=--, B=Shift, C=Ctrl (one per group)");
});
}
private:
enum Col { COL_NONE = 0, COL_CTRL, COL_SHIFT, COL_ALT, COL_COUNT };
void addGroupRow(wxWindow* parent, wxFlexGridSizer* grid, const wxString& label,
wxRadioButton* (&out)[COL_COUNT])
{
grid->Add(new wxStaticText(parent, wxID_ANY, label), 0, wxALIGN_CENTER_VERTICAL);
for (int c = 0; c < COL_COUNT; ++c)
{
// First radio of the row starts a fresh group.
long style = (c == 0) ? wxRB_GROUP : 0;
out[c] = new wxRadioButton(parent, wxID_ANY, "", wxDefaultPosition,
wxDefaultSize, style);
grid->Add(out[c], 0, wxALIGN_CENTER);
}
}
void OnCheck(wxCommandEvent&)
{
Log(wxString::Format("Group A: %s", selectedCol(m_groupA)));
Log(wxString::Format("Group B: %s", selectedCol(m_groupB)));
Log(wxString::Format("Group C: %s", selectedCol(m_groupC)));
}
wxString selectedCol(wxRadioButton* (&group)[COL_COUNT])
{
static const char* names[COL_COUNT] = { "--", "Ctrl", "Shift", "Alt" };
for (int c = 0; c < COL_COUNT; ++c)
{
if (group[c]->GetValue())
return names[c];
}
return "(none!)";
}
void Log(const wxString& msg) { m_log->AppendText(msg + "\n"); }
wxRadioButton* m_groupA[COL_COUNT];
wxRadioButton* m_groupB[COL_COUNT];
wxRadioButton* m_groupC[COL_COUNT];
wxTextCtrl* m_log;
};
class RadioGroupsApp : public wxApp
{
public:
virtual bool OnInit() override
{
RadioGroupsFrame* frame = new RadioGroupsFrame();
frame->Show();
return true;
}
};
wxIMPLEMENT_APP(RadioGroupsApp);