From de38cecda2a7cb23102fb5bec71a029887ac5309 Mon Sep 17 00:00:00 2001 From: BiosSystem <63607038+BiosSystem@users.noreply.github.com> Date: Fri, 25 Sep 2026 09:59:59 +0300 Subject: [PATCH] Start GUI test coverage: XAML schemas and restore-dialog logic The WPF GUI had no automated coverage. This is the first slice. - Test-GuiXamlSchemas.ps1 loads every file in Schemas the way the app does, with XamlReader, and fails if any does not build. A malformed element or an unknown type in a window used to surface only when a user opened that window. Each load runs in its own STA runspace so it works under the pwsh unit job, which is multi-threaded. Confirmed to fail on deliberately broken markup. - Test-RestoreBackupFeatureLists.ps1 covers the restore dialog's pure feature classification: which backed-up features are shown, and which can be reverted automatically (they have an apply .reg file) versus by hand. These functions take data and return data, no WPF, and had no test. Both are wired into the unit workflow. --- .github/workflows/test.yml | 18 ++++ Tests/Unit/Test-GuiXamlSchemas.ps1 | 71 ++++++++++++++ Tests/Unit/Test-RestoreBackupFeatureLists.ps1 | 93 +++++++++++++++++++ 3 files changed, 182 insertions(+) create mode 100644 Tests/Unit/Test-GuiXamlSchemas.ps1 create mode 100644 Tests/Unit/Test-RestoreBackupFeatureLists.ps1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 40f8066..0c3fb1d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -176,6 +176,24 @@ jobs: exit 1 } + - name: Run GUI XAML schema tests + shell: pwsh + run: | + $result = Invoke-Pester -Path "Tests/Unit/Test-GuiXamlSchemas.ps1" -Output Detailed -PassThru + if ($result.FailedCount -gt 0) { + Write-Error "$($result.FailedCount) test(s) failed in Test-GuiXamlSchemas.ps1" + exit 1 + } + + - name: Run restore backup feature list tests + shell: pwsh + run: | + $result = Invoke-Pester -Path "Tests/Unit/Test-RestoreBackupFeatureLists.ps1" -Output Detailed -PassThru + if ($result.FailedCount -gt 0) { + Write-Error "$($result.FailedCount) test(s) failed in Test-RestoreBackupFeatureLists.ps1" + exit 1 + } + - name: Run Store search suggestion tests shell: pwsh run: | diff --git a/Tests/Unit/Test-GuiXamlSchemas.ps1 b/Tests/Unit/Test-GuiXamlSchemas.ps1 new file mode 100644 index 0000000..f4d1455 --- /dev/null +++ b/Tests/Unit/Test-GuiXamlSchemas.ps1 @@ -0,0 +1,71 @@ +#Requires -Modules Pester +<# +.SYNOPSIS + Loads every WPF schema the GUI uses and asserts it constructs. +.DESCRIPTION + Winnow builds its windows at run time with XamlReader.Load over the files in + Schemas. A malformed element, a bad attribute, or a type that does not exist + throws only when that window is opened, which no automated test did. This + loads each schema the same way the app does and fails if any does not build. + + XamlReader needs a single-threaded-apartment thread. The unit suite runs + under pwsh, which is multi-threaded by default, so each load runs in its own + STA runspace. That also isolates the WPF assemblies from the test host. +#> + +BeforeAll { + $script:schemasDir = Join-Path (Resolve-Path (Join-Path $PSScriptRoot '..\..')) 'Schemas' + + function Test-XamlLoads { + param([string]$Path) + + $runspace = [runspacefactory]::CreateRunspace() + $runspace.ApartmentState = 'STA' + $runspace.ThreadOptions = 'ReuseThread' + $runspace.Open() + try { + $shell = [powershell]::Create() + $shell.Runspace = $runspace + [void]$shell.AddScript({ + param($xamlPath) + try { + Add-Type -AssemblyName PresentationFramework, PresentationCore, WindowsBase -ErrorAction Stop + $markup = Get-Content -LiteralPath $xamlPath -Raw + $reader = [System.Xml.XmlReader]::Create([System.IO.StringReader]::new($markup)) + try { + $object = [System.Windows.Markup.XamlReader]::Load($reader) + } + finally { + $reader.Dispose() + } + if ($null -eq $object) { return 'XamlReader.Load returned nothing' } + return 'OK' + } + catch { + return $_.Exception.Message + } + }).AddArgument($Path) + $result = $shell.Invoke() + $shell.Dispose() + return $result[0] + } + finally { + $runspace.Dispose() + } + } +} + +Describe 'GUI XAML schemas' { + + It 'has schema files to load' { + @(Get-ChildItem -LiteralPath $script:schemasDir -Filter '*.xaml').Count | Should -BeGreaterThan 0 + } + + It 'loads through XamlReader' -TestCases @( + Get-ChildItem -LiteralPath (Join-Path (Resolve-Path (Join-Path $PSScriptRoot '..\..')) 'Schemas') -Filter '*.xaml' | + ForEach-Object { @{ Name = $_.Name; Path = $_.FullName } } + ) { + param($Name, $Path) + Test-XamlLoads -Path $Path | Should -Be 'OK' + } +} diff --git a/Tests/Unit/Test-RestoreBackupFeatureLists.ps1 b/Tests/Unit/Test-RestoreBackupFeatureLists.ps1 new file mode 100644 index 0000000..ee608f5 --- /dev/null +++ b/Tests/Unit/Test-RestoreBackupFeatureLists.ps1 @@ -0,0 +1,93 @@ +#Requires -Modules Pester +<# +.SYNOPSIS + Unit tests for the restore-backup dialog's feature-list logic. +.DESCRIPTION + RestoreBackupDialogFeatureLists.ps1 decides, from a saved backup, which + features the restore dialog shows and which of them can be reverted + automatically (they have an apply .reg file) versus by hand. The functions + are pure: they take a Features hashtable and a backup object and return data, + no WPF. They ran without any test until now. +#> + +BeforeAll { + $repoRoot = Resolve-Path (Join-Path $PSScriptRoot '..\..') | Select-Object -ExpandProperty Path + . (Join-Path $repoRoot 'Scripts\GUI\RestoreBackupDialogFeatureLists.ps1') + + # A small stand-in for the Features.json catalog. A feature is auto-revertible + # when it has a RegistryKey and is shown in the overview when it has a Category. + $script:features = @{ + 'DisableTelemetry' = [PSCustomObject]@{ Label = 'Disable telemetry'; RegistryKey = 'Disable_Telemetry.reg'; Category = 'Privacy & Suggested Content' } + 'EnableGamingMode' = [PSCustomObject]@{ Label = 'Enable gaming mode'; RegistryKey = $null; Category = 'Gaming' } + 'CreateRestorePoint' = [PSCustomObject]@{ Label = 'Create restore point'; RegistryKey = $null; Category = $null } + } +} + +Describe 'Restore dialog feature classification' { + + It 'treats a feature with a RegistryKey as auto-revertible' { + Test-RestoreDialogFeatureCanAutoRevert -FeatureId 'DisableTelemetry' -Features $script:features | Should -BeTrue + } + + It 'treats a custom feature with no RegistryKey as not auto-revertible' { + Test-RestoreDialogFeatureCanAutoRevert -FeatureId 'EnableGamingMode' -Features $script:features | Should -BeFalse + } + + It 'treats an unknown feature as not auto-revertible' { + Test-RestoreDialogFeatureCanAutoRevert -FeatureId 'NoSuchFeature' -Features $script:features | Should -BeFalse + } + + It 'shows a categorized feature in the overview and hides a category-less one' { + Test-RestoreDialogFeatureVisibleInOverview -FeatureId 'DisableTelemetry' -Features $script:features | Should -BeTrue + Test-RestoreDialogFeatureVisibleInOverview -FeatureId 'CreateRestorePoint' -Features $script:features | Should -BeFalse + } + + It 'uses the catalog label, and falls back to the id then to a placeholder' { + Get-RestoreDialogFeatureDisplayLabel -FeatureId 'DisableTelemetry' -Features $script:features | Should -Be 'Disable telemetry' + Get-RestoreDialogFeatureDisplayLabel -FeatureId 'NoSuchFeature' -Features $script:features | Should -Be 'NoSuchFeature' + Get-RestoreDialogFeatureDisplayLabel -FeatureId '' -Features $script:features | Should -Be 'Unknown feature' + } +} + +Describe 'Selected feature IDs from a backup' { + + It 'returns forward feature ids, de-duplicated case-insensitively' { + $backup = [PSCustomObject]@{ SelectedFeatures = @('DisableTelemetry', 'disabletelemetry', 'EnableGamingMode', '', $null) } + $ids = Get-SelectedForwardFeatureIdsFromBackup -SelectedBackup $backup + $ids | Should -Be @('DisableTelemetry', 'EnableGamingMode') + } + + It 'merges forward and undo lists without duplicates' { + $backup = [PSCustomObject]@{ + SelectedFeatures = @('DisableTelemetry', 'EnableGamingMode') + SelectedUndoFeatures = @('EnableGamingMode', 'DisableBing') + } + $ids = Get-SelectedFeatureIdsFromBackup -SelectedBackup $backup + $ids | Should -Be @('DisableTelemetry', 'EnableGamingMode', 'DisableBing') + } + + It 'returns nothing for a backup that selected no features' { + $backup = [PSCustomObject]@{ SelectedFeatures = @(); SelectedUndoFeatures = @() } + @(Get-SelectedFeatureIdsFromBackup -SelectedBackup $backup).Count | Should -Be 0 + } +} + +Describe 'Get-RestoreBackupFeatureLists' { + + It 'splits visible features into auto-revertible and manual, and drops hidden ones' { + $result = Get-RestoreBackupFeatureLists -SelectedFeatureIds @('DisableTelemetry', 'EnableGamingMode', 'CreateRestorePoint', 'NoSuchFeature') -Features $script:features + + # DisableTelemetry has a reg file -> revertible; EnableGamingMode is custom + # -> manual; CreateRestorePoint has no category -> hidden; the unknown id -> hidden. + @($result.Revertible).Count | Should -Be 1 + $result.Revertible[0].DisplayText | Should -Be '- Disable telemetry' + @($result.NonRevertible).Count | Should -Be 1 + $result.NonRevertible[0].DisplayText | Should -Be '- Enable gaming mode' + } + + It 'returns two empty lists when nothing is selected' { + $result = Get-RestoreBackupFeatureLists -SelectedFeatureIds @() -Features $script:features + @($result.Revertible).Count | Should -Be 0 + @($result.NonRevertible).Count | Should -Be 0 + } +}