From 47e4159cf988accd34ee2c065070cc868753286f Mon Sep 17 00:00:00 2001 From: Ednunp <29843396+Ednunp@users.noreply.github.com> Date: Sun, 12 Jul 2026 16:03:24 +0100 Subject: [PATCH] Tests: broaden dialog accessibility audit + tab-order cycle check Local checkpoint - NOT for public release. - The accessibility audit now covers 8 dialogs (was 3): Recording settings, Preferences, Service profile, About, Add EQ band, Rename peer, Keyboard shortcut import, Profile selection. Each is constructed and checked for accessible names on every control + unique Alt mnemonics per group. - Added a tab-order sanity check to the audit: the GetNextControl walk must terminate (no cycle) for every audited form - guards a keyboard/screen-reader user from a Tab trap, and the wrapper-TabIndex trap we've hit before. Deferred (documented): auditing the MAIN WINDOW's tabs needs a headless construction seam - MainForm's ctor opens audio backends, registers global hotkeys, binds sockets and shows a tray icon, interleaved through the ctor. A `headless` flag that skips those is safe in principle (real path unchanged) but invasive on the critical startup path and best added with Ed able to test it. The dialog surface (most of the app's controls) is now covered. Gate 21/21. Co-Authored-By: Claude Opus 4.8 --- src/RemSound.App/SelfTest.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/RemSound.App/SelfTest.cs b/src/RemSound.App/SelfTest.cs index d66ebca..dfd7e36 100644 --- a/src/RemSound.App/SelfTest.cs +++ b/src/RemSound.App/SelfTest.cs @@ -879,6 +879,12 @@ internal static class SelfTest () => (default(RouterMappingStatus), (IPEndPoint?)null, ""), _ => { }, _ => { })), ("Service profile", () => new ServiceProfileDialog(RemSound.Core.Profile.NewBlank(), false)), + ("About", () => new AboutDialog()), + ("Add EQ band", () => new AddBandDialog()), + ("Rename peer", () => new RenamePeerDialog("TestMachine", null)), + ("Keyboard shortcut import", () => new KeyboardShortcutImportDialog(Array.Empty())), + ("Profile selection", () => new ProfileSelectionDialog(new ProfileStore( + Path.Combine(Path.GetTempPath(), "remsound-selftest-picker-" + Guid.NewGuid().ToString("N"))))), }; var audited = new List(); @@ -927,6 +933,17 @@ internal static class SelfTest if (string.IsNullOrWhiteSpace(name)) violations.Add($"{formName}: a {c.GetType().Name} has no accessible name or text"); } + + // Tab-order sanity: the GetNextControl walk must TERMINATE — a cycle would trap a keyboard / + // screen-reader user pressing Tab forever. Guards against a malformed tab order. + var guard = 0; + var seen = new HashSet(); + for (Control? cur = form.GetNextControl(form, true); cur is not null && guard < 10000; cur = form.GetNextControl(cur, true)) + { + guard++; + if (!seen.Add(cur)) { violations.Add($"{formName}: tab order forms a cycle at {cur.GetType().Name}"); break; } + } + if (guard >= 10000) violations.Add($"{formName}: tab-order walk did not terminate"); } /// Extract the Alt mnemonic letter from a WinForms caption ('&X' marks X; '&&'