diff --git a/src/ui/Features/Translate/TranslateSettingsWindow.cs b/src/ui/Features/Translate/TranslateSettingsWindow.cs
index df9b4f86f9..47df1d6cb2 100644
--- a/src/ui/Features/Translate/TranslateSettingsWindow.cs
+++ b/src/ui/Features/Translate/TranslateSettingsWindow.cs
@@ -81,6 +81,7 @@ public TranslateSettingsWindow(TranslateSettingsViewModel vm)
TextWrapping = Avalonia.Media.TextWrapping.Wrap,
}.BindIsVisible(vm, nameof(vm.PromptIsVisible))
.BindText(vm, nameof(vm.PromptText));
+ promptTextBox.WithLabeledBy(labelPrompt);
var buttonOk = UiUtil.MakeButtonOk(vm.OkCommand);
var buttonCancel = UiUtil.MakeButtonCancel(vm.CancelCommand);
diff --git a/src/ui/Features/Video/TextToSpeech/VoiceManager/VoiceManagerWindow.cs b/src/ui/Features/Video/TextToSpeech/VoiceManager/VoiceManagerWindow.cs
index e0371eeb79..4b0fb39aa7 100644
--- a/src/ui/Features/Video/TextToSpeech/VoiceManager/VoiceManagerWindow.cs
+++ b/src/ui/Features/Video/TextToSpeech/VoiceManager/VoiceManagerWindow.cs
@@ -504,6 +504,7 @@ private Control MakeDetailsPane(VoiceManagerViewModel vm)
HorizontalAlignment = HorizontalAlignment.Stretch,
[!TextBox.TextProperty] = new Binding(nameof(vm.Transcript)) { Mode = BindingMode.TwoWay },
};
+ transcriptBox.WithLabeledBy(transcriptHeader);
var transcriptPanel = new StackPanel
{
diff --git a/tests/UI/Logic/AccessibleNamesTests.cs b/tests/UI/Logic/AccessibleNamesTests.cs
index 387887d73f..3f4b752721 100644
--- a/tests/UI/Logic/AccessibleNamesTests.cs
+++ b/tests/UI/Logic/AccessibleNamesTests.cs
@@ -21,7 +21,9 @@ namespace UITests.Logic;
/// by the window or derived from its visible label by
/// (#12087: "I hear a value followed by combo box, but no label telling me which setting
/// I am changing"). This opens every tool window that takes a single DI-resolvable view
-/// model and lists the inputs that still have no name.
+/// model and lists the inputs that still have no name. Hidden inputs are included because
+/// AccessibleLabels.Apply runs once when the window loads; controls that become visible later
+/// do not get a second inference pass.
///
public class AccessibleNamesTests
{
@@ -43,6 +45,7 @@ public void EveryInputInEveryWindow_HasAnAccessibleName()
.ToList();
var unnamed = new StringBuilder();
+ var openFailures = new StringBuilder();
var opened = 0;
var skipped = new List();
foreach (var type in windowTypes)
@@ -71,7 +74,8 @@ public void EveryInputInEveryWindow_HasAnAccessibleName()
}
catch (Exception e)
{
- skipped.Add($"{type.Name} ({(e as TargetInvocationException)?.InnerException?.GetType().Name ?? e.GetType().Name})");
+ var cause = (e as TargetInvocationException)?.InnerException ?? e;
+ openFailures.AppendLine($"{type.Name}: {cause.GetType().Name}: {cause.Message}");
continue;
}
@@ -80,7 +84,7 @@ public void EveryInputInEveryWindow_HasAnAccessibleName()
{
foreach (var control in window.GetLogicalDescendants().OfType())
{
- if (!AccessibleLabels.IsInput(control) || control.TemplatedParent != null || !control.IsEffectivelyVisible)
+ if (!AccessibleLabels.IsInput(control) || control.TemplatedParent != null)
{
continue;
}
@@ -99,27 +103,20 @@ public void EveryInputInEveryWindow_HasAnAccessibleName()
}
}
+ Assert.True(openFailures.Length == 0, $"Windows that failed to open:\n{openFailures}");
Assert.True(opened > 50, $"Only {opened} windows opened; skipped: {string.Join(", ", skipped)}");
Assert.True(unnamed.Length == 0, $"Inputs without an accessible name ({opened} windows opened, {skipped.Count} skipped):\n{unnamed}");
}
///
- /// Runs pending dispatcher jobs, ignoring what they throw. Some view models probe media
- /// on a background thread from Loaded and post a message box back (Video OCR: "unable
- /// to read video"); when that post lands after the window is closed, showing the box
- /// throws "Cannot show a window with a closed owner" - a timing artifact of opening
- /// windows without files, not an accessibility finding.
+ /// Runs pending dispatcher jobs. Unexpected dispatcher failures must fail the test;
+ /// the Video OCR closed-owner race that previously required suppression is now fixed
+ /// at the source by checking whether its window is already closing before showing the
+ /// message box (upstream 6840e797).
///
private static void DrainJobs()
{
- try
- {
- Dispatcher.UIThread.RunJobs();
- }
- catch (Exception)
- {
- // Ignored - see summary.
- }
+ Dispatcher.UIThread.RunJobs();
}
private static string Describe(Control control)