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 <noreply@anthropic.com>
This commit is contained in:
Ednunp
2026-07-12 16:03:24 +01:00
co-authored by Claude Opus 4.8
parent b258bec9a2
commit 47e4159cf9
+17
View File
@@ -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<string>())),
("Profile selection", () => new ProfileSelectionDialog(new ProfileStore(
Path.Combine(Path.GetTempPath(), "remsound-selftest-picker-" + Guid.NewGuid().ToString("N"))))),
};
var audited = new List<string>();
@@ -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<Control>();
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");
}
/// <summary>Extract the Alt mnemonic letter from a WinForms caption ('&amp;X' marks X; '&amp;&amp;'