Expand Pester coverage across install, tweaks, and UI workflows (#4792)

* Harden Pester CI and discard runspace return values

* Add prioritized test backlog

* Add WinUtil action logging

* Add config integrity tests

* Add XAML control wiring tests

* Add registry and service helper tests

* Add package manager tests

* Add runspace behavior tests

* Add tweak orchestration tests

* Add install workflow tests

* Add update profile tests

* Add AppX removal tests

* Log package installer output

* Add UI state helper tests

* Pin Pester test runner version

* Expand Win11 Creator tests

* Add preferences and theme tests

* Add search filter tests

* Add compile contract tests

* Use single WinUtil session log

* Remove installer output logging helper

* Handle locked WinUtil transcript log

* Avoid appending to active transcript log

* Log install uninstall package identities
This commit is contained in:
Chris Titus
2026-07-01 21:49:15 -05:00
committed by GitHub
parent c3b4f85fce
commit 2cb20893fc
44 changed files with 4586 additions and 36 deletions
+347
View File
@@ -0,0 +1,347 @@
#===========================================================================
# Tests - Preferences and Themes
#===========================================================================
BeforeAll {
$script:repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
if (-not ("PackageManagers" -as [type])) {
Add-Type @"
public enum PackageManagers
{
Winget,
Choco
}
"@
}
if (-not ("Windows.Media.SolidColorBrush" -as [type])) {
Add-Type @"
namespace Windows.Media
{
public class SolidColorBrush
{
public object Value { get; private set; }
public SolidColorBrush(object value)
{
Value = value;
}
public override string ToString()
{
return Value == null ? "" : Value.ToString();
}
}
public struct Color
{
public byte R;
public byte G;
public byte B;
public static Color FromRgb(byte r, byte g, byte b)
{
return new Color { R = r, G = g, B = b };
}
public override string ToString()
{
return string.Format("#{0:X2}{1:X2}{2:X2}", R, G, B);
}
}
public class FontFamily
{
public string Value { get; private set; }
public FontFamily(string value)
{
Value = value;
}
public override string ToString()
{
return Value;
}
}
}
namespace System.Windows
{
public class CornerRadius
{
public double Value { get; private set; }
public CornerRadius(double value)
{
Value = value;
}
public override string ToString()
{
return Value.ToString();
}
}
public class GridLength
{
public double Value { get; private set; }
public GridLength(double value)
{
Value = value;
}
public override string ToString()
{
return Value.ToString();
}
}
public class Thickness
{
public double Left { get; private set; }
public double Top { get; private set; }
public double Right { get; private set; }
public double Bottom { get; private set; }
public Thickness(double uniformLength)
{
Left = uniformLength;
Top = uniformLength;
Right = uniformLength;
Bottom = uniformLength;
}
public Thickness(double horizontal, double vertical)
{
Left = horizontal;
Top = vertical;
Right = horizontal;
Bottom = vertical;
}
public Thickness(double left, double top, double right, double bottom)
{
Left = left;
Top = top;
Right = right;
Bottom = bottom;
}
public override string ToString()
{
return string.Format("{0},{1},{2},{3}", Left, Top, Right, Bottom);
}
}
}
"@
}
. (Join-Path $script:repoRoot "functions\private\Set-Preferences.ps1")
. (Join-Path $script:repoRoot "functions\private\Invoke-WinutilThemeChange.ps1")
function Get-WinUtilToggleStatus {
param($ToggleName)
return $false
}
function script:New-WinUtilPreferencesTestRoot {
$testRoot = Join-Path ([IO.Path]::GetTempPath()) "WinUtilPreferences_$([guid]::NewGuid())"
New-Item -Path $testRoot -ItemType Directory -Force | Out-Null
$testRoot
}
function script:New-WinUtilFakeThemeForm {
$themeButton = [pscustomobject]@{
Content = $null
}
$form = [pscustomobject]@{
Resources = @{}
ThemeButton = $themeButton
}
$form | Add-Member -MemberType ScriptMethod -Name FindName -Value {
param($name)
if ($name -eq "ThemeButton") {
return $this.ThemeButton
}
return $null
}
$form
}
function script:New-WinUtilPreferenceSync {
param(
[hashtable]$Preferences = @{}
)
$script:sync = [Hashtable]::Synchronized(@{
preferences = $Preferences
configs = @{
themes = Get-Content -Path (Join-Path $script:repoRoot "config\themes.json") -Raw | ConvertFrom-Json
}
Form = New-WinUtilFakeThemeForm
})
$global:sync = $script:sync
}
function script:Remove-WinUtilPreferenceGlobals {
Remove-Variable -Name sync -Scope Global -ErrorAction SilentlyContinue
Remove-Variable -Name winutildir -Scope Global -ErrorAction SilentlyContinue
}
}
Describe "Set-Preferences" {
AfterEach {
if ($script:testRoot -and (Test-Path $script:testRoot)) {
Remove-Item -Path $script:testRoot -Recurse -Force -ErrorAction SilentlyContinue
}
$script:testRoot = $null
Remove-WinUtilPreferenceGlobals
}
It "loads default preferences when no preferences file exists" {
$script:testRoot = New-WinUtilPreferencesTestRoot
$global:winutildir = $script:testRoot
New-WinUtilPreferenceSync
Set-Preferences
$script:sync.preferences.theme | Should -Be "Auto"
$script:sync.preferences.packagemanager | Should -Be ([PackageManagers]::Winget)
}
It "loads saved preferences and converts the package manager to an enum" {
$script:testRoot = New-WinUtilPreferencesTestRoot
$global:winutildir = $script:testRoot
New-WinUtilPreferenceSync
Set-Content -Path (Join-Path $script:testRoot "preferences.ini") -Value @(
"theme = Dark"
"packagemanager = Choco"
)
Set-Preferences
$script:sync.preferences.theme | Should -Be "Dark"
$script:sync.preferences.packagemanager | Should -Be ([PackageManagers]::Choco)
}
It "saves current preferences to preferences.ini" {
$script:testRoot = New-WinUtilPreferencesTestRoot
$global:winutildir = $script:testRoot
New-WinUtilPreferenceSync -Preferences @{
theme = "Light"
packagemanager = [PackageManagers]::Winget
}
Set-Preferences -save
$preferencesText = Get-Content -Path (Join-Path $script:testRoot "preferences.ini") -Raw
$preferencesText | Should -Match "theme=Light"
$preferencesText | Should -Match "packagemanager=Winget"
}
It "absorbs legacy preference files and removes old markers" {
$script:testRoot = New-WinUtilPreferencesTestRoot
$global:winutildir = $script:testRoot
New-WinUtilPreferenceSync
Set-Content -Path (Join-Path $script:testRoot "LightTheme.ini") -Value ""
Set-Content -Path (Join-Path $script:testRoot "preferChocolatey.ini") -Value ""
Set-Preferences
$script:sync.preferences.theme | Should -Be "Light"
$script:sync.preferences.packagemanager | Should -Be ([PackageManagers]::Choco)
Test-Path (Join-Path $script:testRoot "LightTheme.ini") | Should -BeFalse
Test-Path (Join-Path $script:testRoot "preferChocolatey.ini") | Should -BeFalse
}
It "absorbs old package manager preferences stored without a key" {
$script:testRoot = New-WinUtilPreferencesTestRoot
$global:winutildir = $script:testRoot
New-WinUtilPreferenceSync
Set-Content -Path (Join-Path $script:testRoot "preferences.ini") -Value "Choco"
Set-Preferences
$script:sync.preferences.theme | Should -Be "Auto"
$script:sync.preferences.packagemanager | Should -Be ([PackageManagers]::Choco)
}
}
Describe "Invoke-WinutilThemeChange" {
AfterEach {
if ($script:testRoot -and (Test-Path $script:testRoot)) {
Remove-Item -Path $script:testRoot -Recurse -Force -ErrorAction SilentlyContinue
}
$script:testRoot = $null
Remove-WinUtilPreferenceGlobals
}
It "applies shared and selected theme resources, saves the preference, and updates the theme button" {
$script:testRoot = New-WinUtilPreferencesTestRoot
$global:winutildir = $script:testRoot
New-WinUtilPreferenceSync
Invoke-WinutilThemeChange -theme "Dark"
$script:sync.preferences.theme | Should -Be "Dark"
$script:sync.Form.Resources.ContainsKey("FontFamily") | Should -BeTrue
$script:sync.Form.Resources.ContainsKey("ButtonCornerRadius") | Should -BeTrue
$script:sync.Form.Resources.ContainsKey("MainBackgroundColor") | Should -BeTrue
$script:sync.Form.Resources.ContainsKey("CBorderColor") | Should -BeTrue
$script:sync.Form.ThemeButton.Content | Should -Be ([string][char]0xE708)
$preferencesText = Get-Content -Path (Join-Path $script:testRoot "preferences.ini") -Raw
$preferencesText | Should -Match "theme=Dark"
}
It "uses the system dark-mode toggle when Auto theme is selected" {
$script:testRoot = New-WinUtilPreferencesTestRoot
$global:winutildir = $script:testRoot
New-WinUtilPreferenceSync
Mock Get-WinUtilToggleStatus { return $true }
Invoke-WinutilThemeChange -theme "Auto"
Should -Invoke -CommandName Get-WinUtilToggleStatus -Times 1 -Exactly -ParameterFilter {
$ToggleName -eq "WPFToggleDarkMode"
}
$script:sync.preferences.theme | Should -Be "Auto"
$script:sync.Form.Resources.ContainsKey("MainBackgroundColor") | Should -BeTrue
$script:sync.Form.ThemeButton.Content | Should -Be ([string][char]0xF08C)
}
}
Describe "Theme configuration" {
It "contains resources with value shapes consumed by theme application" {
$themes = Get-Content -Path (Join-Path $script:repoRoot "config\themes.json") -Raw | ConvertFrom-Json
$invalidEntries = [System.Collections.Generic.List[string]]::new()
foreach ($themeName in @("shared", "Light", "Dark")) {
foreach ($property in $themes.$themeName.PSObject.Properties) {
if ($property.Name -like "*Color" -and [string]$property.Value -notmatch '^(#[0-9A-Fa-f]{6}|Transparent)$') {
$invalidEntries.Add("$themeName.$($property.Name) should be a hex color or Transparent")
}
elseif ($property.Name -like "*Radius" -and [string]$property.Value -notmatch '^\d+(\.\d+)?$') {
$invalidEntries.Add("$themeName.$($property.Name) should be numeric")
}
elseif (($property.Name -like "*Thickness" -or $property.Name -like "*Margin") -and [string]$property.Value -notmatch '^\d+(\.\d+)?(,\d+(\.\d+)?){0,3}$') {
$invalidEntries.Add("$themeName.$($property.Name) should be one, two, or four numeric values")
}
elseif ($property.Name -like "*RowHeight*" -and [string]$property.Value -notmatch '^\d+(\.\d+)?$') {
$invalidEntries.Add("$themeName.$($property.Name) should be numeric")
}
}
}
if ($invalidEntries.Count -gt 0) {
throw ($invalidEntries -join "`n")
}
}
}