Skip to content
Merged
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
18 changes: 18 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: |
Expand Down
71 changes: 71 additions & 0 deletions Tests/Unit/Test-GuiXamlSchemas.ps1
Original file line number Diff line number Diff line change
@@ -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 <Name> 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'
}
}
93 changes: 93 additions & 0 deletions Tests/Unit/Test-RestoreBackupFeatureLists.ps1
Original file line number Diff line number Diff line change
@@ -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
}
}