Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
247 changes: 247 additions & 0 deletions src/UniGetUI.Core.Tools.Tests/AccessibilityXamlTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
using System.Text.RegularExpressions;

namespace UniGetUI.Core.Tools.Tests;

public class AccessibilityXamlTests
{
private static readonly string RepoRoot =
Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, @"..\..\..\..\..\..\.."));

private static string ReadFile(string relativePath)
{
string path = Path.Combine(RepoRoot, relativePath.Replace('/', Path.DirectorySeparatorChar));
return File.ReadAllText(path);
}

[Fact]
public void IconOnlyControlsHaveAutomationNames()
{
Dictionary<string, string[]> requiredSnippets = new()
{
["src/UniGetUI/Controls/DialogCloseButton.xaml"] =
[
"AutomationProperties.Name=\"{x:Bind CloseAutomationName, Mode=OneWay}\""
],
["src/UniGetUI/Controls/SourceManager.xaml"] =
[
"AutomationProperties.Name=\"{x:Bind RemoveSourceAutomationName, Mode=OneWay}\"",
"AutomationProperties.Name=\"{x:Bind ReloadSourcesAutomationName, Mode=OneWay}\""
],
["src/UniGetUI/Pages/HelpPage.xaml"] =
[
"AutomationProperties.Name=\"{x:Bind HelpBackAutomationName, Mode=OneWay}\"",
"AutomationProperties.Name=\"{x:Bind HelpForwardAutomationName, Mode=OneWay}\"",
"AutomationProperties.Name=\"{x:Bind HelpHomeAutomationName, Mode=OneWay}\"",
"AutomationProperties.Name=\"{x:Bind HelpReloadAutomationName, Mode=OneWay}\""
],
["src/UniGetUI/Pages/MainView.xaml"] =
[
"AutomationProperties.Name=\"{x:Bind OperationOptionsAutomationName, Mode=OneWay}\"",
"AutomationProperties.Name=\"{x:Bind OperationsListActionsAutomationName, Mode=OneWay}\"",
"AutomationProperties.Name=\"{x:Bind ExpandCollapseOperationsAutomationName, Mode=OneWay}\"",
"AutomationProperties.Name=\"{x:Bind ResizeOperationsAreaAutomationName, Mode=OneWay}\""
],
["src/UniGetUI/Pages/SettingsPages/SettingsBasePage.xaml"] =
[
"AutomationProperties.Name=\"{x:Bind BackAutomationName, Mode=OneWay}\""
],
["src/UniGetUI/Pages/DialogPages/DesktopShortcuts.xaml"] =
[
"AutomationProperties.Name=\"{x:Bind DeleteShortcutAutomationName, Mode=OneWay}\"",
"AutomationProperties.Name=\"{x:Bind OpenShortcutLocationAutomationName, Mode=OneWay}\"",
"AutomationProperties.Name=\"{x:Bind RemoveShortcutFromListAutomationName, Mode=OneWay}\""
],
["src/UniGetUI/Pages/DialogPages/IgnoredUpdates.xaml"] =
[
"AutomationProperties.Name=\"{x:Bind StopIgnoringUpdatesAutomationName, Mode=OneWay}\""
],
["src/UniGetUI/Pages/DialogPages/PackageDetailsPage.xaml"] =
[
"AutomationProperties.Name=\"{x:Bind MorePackageActionsAutomationName, Mode=OneWay}\""
],
["src/UniGetUI/Pages/DialogPages/OperationFailedDialog.xaml"] =
[
"AutomationProperties.Name=\"Command-line output\""
],
["src/UniGetUI/Pages/DialogPages/OperationLiveLogPage.xaml"] =
[
"AutomationProperties.Name=\"Operation live log output\""
],
["src/UniGetUI/Pages/SoftwarePages/AbstractPackagesPage.xaml"] =
[
"AutomationProperties.Name=\"{x:Bind PackageOptionsAutomationName, Mode=OneWay}\"",
"AutomationProperties.Name=\"{x:Bind ReloadPackagesAutomationName, Mode=OneWay}\"",
"AutomationProperties.Name=\"{x:Bind MoreToolbarActionsAutomationName, Mode=OneWay}\"",
"AutomationProperties.Name=\"{x:Bind SelectPackageAutomationName, Mode=OneWay}\"",
"AutomationProperties.Name=\"{x:Bind OrderByAutomationName, Mode=OneWay}\"",
"AutomationProperties.Name=\"{x:Bind ViewModeAutomationName, Mode=OneWay}\"",
"AutomationProperties.Name=\"{x:Bind SearchPackagesAutomationName, Mode=OneWay}\"",
"AutomationProperties.Name=\"{x:Bind SelectAllSourcesAutomationName, Mode=OneWay}\"",
"AutomationProperties.Name=\"{x:Bind ClearSourceSelectionAutomationName, Mode=OneWay}\"",
"AutomationProperties.Name=\"{x:Bind InstantSearchAutomationName, Mode=OneWay}\"",
"AutomationProperties.Name=\"{x:Bind DistinguishUpperLowerCaseAutomationName, Mode=OneWay}\"",
"AutomationProperties.Name=\"{x:Bind IgnoreSpecialCharactersAutomationName, Mode=OneWay}\"",
"AutomationProperties.Name=\"{x:Bind ToggleFiltersAutomationName, Mode=OneWay}\"",
"AutomationProperties.Name=\"{x:Bind MainSelectionActionAutomationName, Mode=OneWay}\"",
"AutomationProperties.Name=\"{x:Bind SearchModeOptionsAutomationName, Mode=OneWay}\"",
"AutomationProperties.Name=\"{x:Bind SearchModeByNameAutomationName, Mode=OneWay}\"",
"AutomationProperties.Name=\"{x:Bind SearchModeByIdAutomationName, Mode=OneWay}\"",
"AutomationProperties.Name=\"{x:Bind SearchModeByBothAutomationName, Mode=OneWay}\"",
"AutomationProperties.Name=\"{x:Bind SearchModeExactMatchAutomationName, Mode=OneWay}\"",
"AutomationProperties.Name=\"{x:Bind SearchModeSimilarResultsAutomationName, Mode=OneWay}\""
],
["src/UniGetUI/Pages/SettingsPages/GeneralPages/Backup.xaml"] =
[
"AutomationProperties.Name=\"{x:Bind ResetBackupDirectoryAutomationName, Mode=OneWay}\"",
"AutomationProperties.Name=\"{x:Bind OpenBackupDirectoryAutomationName, Mode=OneWay}\""
]
};

foreach (var (file, snippets) in requiredSnippets)
{
string content = ReadFile(file);
foreach (string snippet in snippets)
{
Assert.Contains(snippet, content);
}
}
}

[Fact]
public void CriticalListTemplatesHaveAutomationNames()
{
Dictionary<string, string[]> requiredSnippets = new()
{
["src/UniGetUI/Controls/SourceManager.xaml"] =
[
"<ItemContainer AutomationProperties.Name=\"{x:Bind Source.Name}\">"
],
["src/UniGetUI/Pages/AboutPages/Contributors.xaml"] =
[
"<ItemContainer AutomationProperties.Name=\"{x:Bind Name}\">"
],
["src/UniGetUI/Pages/AboutPages/Translators.xaml"] =
[
"<ItemContainer AutomationProperties.Name=\"{x:Bind Name}\">"
],
["src/UniGetUI/Pages/AboutPages/ThirdPartyLicenses.xaml"] =
[
"<ItemContainer AutomationProperties.Name=\"{x:Bind Name}\">"
],
["src/UniGetUI/Pages/DialogPages/DesktopShortcuts.xaml"] =
[
"<ItemContainer AutomationProperties.Name=\"{x:Bind Name}\">"
],
["src/UniGetUI/Pages/DialogPages/IgnoredUpdates.xaml"] =
[
"<ItemContainer AutomationProperties.Name=\"{x:Bind Name}\">"
]
};

foreach (var (file, snippets) in requiredSnippets)
{
string content = ReadFile(file);
foreach (string snippet in snippets)
{
Assert.Contains(snippet, content);
}
}
}

[Fact]
public void CriticalItemContainersAreNamed()
{
string[] files =
[
"src/UniGetUI/Controls/SourceManager.xaml",
"src/UniGetUI/Pages/AboutPages/Contributors.xaml",
"src/UniGetUI/Pages/AboutPages/Translators.xaml",
"src/UniGetUI/Pages/AboutPages/ThirdPartyLicenses.xaml",
"src/UniGetUI/Pages/DialogPages/DesktopShortcuts.xaml",
"src/UniGetUI/Pages/DialogPages/IgnoredUpdates.xaml",
"src/UniGetUI/Pages/MainView.xaml"
];

foreach (string file in files)
{
string content = ReadFile(file);
MatchCollection matches = Regex.Matches(content, "<ItemContainer[^>]*>", RegexOptions.Singleline);
Assert.NotEmpty(matches);

foreach (Match match in matches)
{
Assert.Contains("AutomationProperties.Name", match.Value);
}
}
}

[Fact]
public void AccessibilityCriticalCodePathsSetNamesAndTabFocus()
{
Dictionary<string, string[]> requiredSnippets = new()
{
["src/UniGetUI/Controls/CustomNavViewItem.cs"] =
[
"AutomationProperties.SetName(this, text);"
],
["src/UniGetUI/Services/UserAvatar.cs"] =
[
"AutomationProperties.SetName(profileButton, CoreTools.Translate(\"Open backup profile\"));"
],
["src/UniGetUI/Controls/SettingsWidgets/SettingsPageButton.cs"] =
[
"AutomationProperties.SetName(this, name);",
"IsTabStop = true;",
"UseSystemFocusVisuals = true;"
],
["src/UniGetUI/Controls/SettingsWidgets/CheckboxCard.cs"] =
[
"AutomationProperties.SetName(_checkbox, name);",
"AutomationProperties.SetName(this, name);"
],
["src/UniGetUI/Controls/SettingsWidgets/SecureCheckboxCard.cs"] =
[
"AutomationProperties.SetName(_checkbox, name);",
"AutomationProperties.SetName(this, name);"
],
["src/UniGetUI/Controls/SettingsWidgets/CheckboxButtonCard.cs"] =
[
"AutomationProperties.SetName(_checkbox, _translatedCheckboxText);",
"AutomationProperties.SetName(Button, buttonName);"
],
["src/UniGetUI/Controls/SettingsWidgets/ButtonCard.cs"] =
[
"AutomationProperties.SetName(_button, buttonName);",
"AutomationProperties.SetName(this, cardName);"
],
["src/UniGetUI/Controls/SettingsWidgets/ComboboxCard.cs"] =
[
"AutomationProperties.SetName(_combobox, _translatedText);",
"AutomationProperties.SetName(this, _translatedText);"
],
["src/UniGetUI/Controls/SettingsWidgets/TextboxCard.cs"] =
[
"AutomationProperties.SetName(_textbox, textboxName);",
"AutomationProperties.SetName(this, textboxName);"
],
["src/UniGetUI/Pages/SettingsPages/SettingsBasePage.xaml.cs"] =
[
"BackButton.PreviewKeyDown += BackButton_PreviewKeyDown;",
"root.TabFocusNavigation = KeyboardNavigationMode.Local;",
"MainNavigationFrame.TabFocusNavigation = KeyboardNavigationMode.Local;",
"scroller.TabFocusNavigation = KeyboardNavigationMode.Local;",
"NeedsContextualName(currentName, toggle.OnContent, toggle.OffContent)",
"AutomationProperties.SetName(card, cardName);"
]
};

foreach (var (file, snippets) in requiredSnippets)
{
string content = ReadFile(file);
foreach (string snippet in snippets)
{
Assert.Contains(snippet, content);
}
}
}
}
2 changes: 2 additions & 0 deletions src/UniGetUI/Controls/CustomNavViewItem.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Automation;
using Microsoft.UI.Xaml.Controls;
using UniGetUI.Core.Tools;
using UniGetUI.Interface;
Expand Down Expand Up @@ -44,6 +45,7 @@ public string Text
{
string text = CoreTools.Translate(value);
_textBlock.Text = text;
AutomationProperties.SetName(this, text);
ToolTipService.SetToolTip(this, text);
}

Expand Down
1 change: 1 addition & 0 deletions src/UniGetUI/Controls/DialogCloseButton.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
Width="46"
Height="31"
Padding="0"
AutomationProperties.Name="{x:Bind CloseAutomationName, Mode=OneWay}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="Transparent"
Expand Down
2 changes: 2 additions & 0 deletions src/UniGetUI/Controls/DialogCloseButton.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using UniGetUI.Core.Tools;

// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
Expand All @@ -8,6 +9,7 @@ namespace UniGetUI.Interface.Widgets;
public sealed partial class DialogCloseButton : UserControl
{
public event EventHandler<RoutedEventArgs>? Click;
public string CloseAutomationName => CoreTools.Translate("Close");

public DialogCloseButton()
{
Expand Down
1 change: 1 addition & 0 deletions src/UniGetUI/Controls/OperationWidgets/OperationControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public partial class OperationControl: INotifyPropertyChanged
public AbstractOperation Operation;
public BetterMenu OpMenu;
public OperationStatus? MenuStateOnLoaded;
public string OperationOptionsAutomationName => CoreTools.Translate("Operation options");
public ObservableCollection<OperationBadge> Badges = [];
private int _errorCount = 0;

Expand Down
2 changes: 2 additions & 0 deletions src/UniGetUI/Controls/PackageWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ public Uri? PackageIcon

public int NewVersionLabelWidth { get => Package.IsUpgradable ? 125 : 0; }
public int NewVersionIconWidth { get => Package.IsUpgradable ? 24 : 0; }
public string PackageOptionsAutomationName => CoreTools.Translate("Package options");
public string SelectPackageAutomationName => CoreTools.Translate("Select package {0}", Package.Name);

public int Index { get; set; }
public event PropertyChangedEventHandler? PropertyChanged;
Expand Down
42 changes: 40 additions & 2 deletions src/UniGetUI/Controls/SettingsWidgets/ButtonCard.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using CommunityToolkit.WinUI.Controls;
using Microsoft.UI.Xaml.Automation;
using Microsoft.UI.Xaml.Controls;
using UniGetUI.Core.Tools;

Expand All @@ -10,15 +11,51 @@ namespace UniGetUI.Interface.Widgets
public sealed partial class ButtonCard : SettingsCard
{
private readonly Button _button = new();
private string _translatedText = string.Empty;
private string _translatedButtonText = string.Empty;

private void UpdateAutomationNames()
{
if (!string.IsNullOrWhiteSpace(_translatedButtonText))
{
string buttonName = string.IsNullOrWhiteSpace(_translatedText)
? _translatedButtonText
: $"{_translatedButtonText}. {_translatedText}";
AutomationProperties.SetName(_button, buttonName);
}

string cardName = _translatedText;
if (!string.IsNullOrWhiteSpace(_translatedButtonText))
{
cardName = string.IsNullOrWhiteSpace(cardName)
? _translatedButtonText
: $"{cardName}. {_translatedButtonText}";
}

if (!string.IsNullOrWhiteSpace(cardName))
{
AutomationProperties.SetName(this, cardName);
}
}

public string ButtonText
{
set => _button.Content = CoreTools.Translate(value);
set
{
_translatedButtonText = CoreTools.Translate(value);
_button.Content = _translatedButtonText;
UpdateAutomationNames();
}
}

public string Text
{
set => Header = CoreTools.Translate(value);
set
{
_translatedText = CoreTools.Translate(value);
Header = _translatedText;
UpdateAutomationNames();
}
}

public new event EventHandler<EventArgs>? Click;
Expand All @@ -28,6 +65,7 @@ public ButtonCard()
_button.MinWidth = 200;
_button.Click += (_, _) => { Click?.Invoke(this, EventArgs.Empty); };
Content = _button;
UpdateAutomationNames();
}
}
}
Loading