Tests: headless main-window coverage (all tabs + controls) + fix Alt+L clash
Local checkpoint - NOT for public release. Closes the UI-coverage gap Ed pushed on. - MainForm gets a `headless` ctor flag (defaults false → real startup path byte-for-byte unchanged). When true it builds the WHOLE window — every tab, control and menu — but skips the OS touches: global-hotkey registration, the status/device-refresh timers, the device-change notifier, and the audio-backend mode switch in ApplyAsioMode. The disruptive startup work (Connect + sockets, UPnP, update check) already lives in the Shown handler, which never fires when a test constructs the form without showing it — so a headless construction is naturally side-effect-free. - New self-test "Main window coverage": constructs the headless main window and audits the lot — accessible names present, Alt mnemonics unique per group, tab order forms no cycle. 4 tabs, 41 interactive controls. - It immediately EARNED ITS KEEP: caught a real Alt+L collision — the WASAPI latency and ASIO latency labels both claimed Alt+L in the same panel, so in WASAPI+ASIO mode the ASIO field was unreachable by its shortcut. Moved ASIO latency to Alt+I; WASAPI keeps Alt+L. Gate 23/23. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
fd981211c0
commit
c8fed26115
@@ -73,6 +73,7 @@ internal static class SelfTest
|
||||
RunStep(results, "Diagnostics report privacy", DiagnosticsPrivacy);
|
||||
RunStep(results, "Bundled resources present", ResourcesPresent);
|
||||
RunStep(results, "Dialog accessibility (names + mnemonics)", AccessibilityAudit);
|
||||
RunStep(results, "Main window coverage (all tabs + controls)", MainWindowCoverage);
|
||||
|
||||
var failed = results.Count(r => r.Status == "FAIL");
|
||||
var skipped = results.Count(r => r.Status == "SKIP");
|
||||
@@ -966,6 +967,39 @@ internal static class SelfTest
|
||||
return detail;
|
||||
}
|
||||
|
||||
/// <summary>Constructs the ENTIRE main window in headless mode (no audio backend, no hotkeys, no
|
||||
/// timers, no sockets — see MainForm's `headless` flag) and audits every tab and control: accessible
|
||||
/// names present, Alt mnemonics unique per group, and the tab order forms no cycle. This is the
|
||||
/// "check all the tabs" coverage — the whole main-window surface, proven on every build.</summary>
|
||||
private static string? MainWindowCoverage()
|
||||
{
|
||||
Form? form;
|
||||
try { form = new MainForm(null, RemSound.Core.Profile.NewBlank(), null, null, headless: true); }
|
||||
catch (Exception ex) { return Skip($"headless MainForm could not be constructed: {ex.GetType().Name}: {ex.Message}"); }
|
||||
|
||||
try
|
||||
{
|
||||
var violations = new List<string>();
|
||||
AuditForm("Main window", form, violations);
|
||||
Check(violations.Count == 0, string.Join("; ", violations));
|
||||
|
||||
var tabs = CountControls(form, c => c is TabPage);
|
||||
var interactive = CountControls(form, c => c is CheckBox or Button or ComboBox or ListBox or TrackBar or TextBox);
|
||||
Check(tabs >= 3, $"the main window's tabs should be present (found {tabs})");
|
||||
Check(interactive >= 15, $"the main window's interactive controls should be present (found {interactive})");
|
||||
return $"audited the whole main window: {tabs} tabs, {interactive} interactive controls — names, mnemonics and tab order clean";
|
||||
}
|
||||
finally { try { form.Dispose(); } catch { /* ignore */ } }
|
||||
}
|
||||
|
||||
private static int CountControls(Control root, Func<Control, bool> predicate)
|
||||
{
|
||||
var n = 0;
|
||||
void Walk(Control p) { foreach (Control c in p.Controls) { if (predicate(c)) n++; Walk(c); } }
|
||||
Walk(root);
|
||||
return n;
|
||||
}
|
||||
|
||||
private static void AuditForm(string formName, Form form, List<string> violations)
|
||||
{
|
||||
var all = new List<Control>();
|
||||
|
||||
Reference in New Issue
Block a user