Fix legacy json import compatibility, UI sidebar refactor, and improved test coverage (#4911)

* Fix backward compatibility for old-style JSON config imports

* Refactor sidebar UI generation and add tests for Get-WinUtilVariables

* Fix appnavigation config test following UI generation refactor

* Restore global sync state in variables tests

* fix(impex): migrate supported legacy selections

Keep modern imports strict while allowing legacy backups to skip retired entries with clear logging and user feedback. Add fixtures covering partial and all-retired imports.

* test: strengthen UI and global state coverage

Exercise app category rendering through Initialize-WPFUI and restore global sync without leaking test state.

* fix(impex): ignore legacy metadata fields

Limit legacy selection migration to supported WPF key families so unrelated string metadata does not produce false retired-setting warnings.

* docs: clarify legacy config imports

Distinguish strict modern flat imports from partial legacy object migration, including single-setting export shape and historical groups.

---------

Co-authored-by: Chris Titus <contact@christitus.com>
This commit is contained in:
Nikhil
2026-08-19 17:13:14 -05:00
committed by GitHub
co-authored by Chris Titus
parent 4b2fe55a7a
commit 436773df73
10 changed files with 254 additions and 23 deletions
+2 -1
View File
@@ -264,8 +264,9 @@ Describe "App navigation config" {
It "is wired to an existing XAML target grid" {
$mainScript = Get-Content -Path $script:mainScriptPath -Raw
$tabInitializerScript = Get-Content -Path (Join-Path $script:repoRoot "functions/private/Initialize-WinUtilTabContent.ps1") -Raw
$uiInitializerScript = Get-Content -Path (Join-Path $script:repoRoot "functions/public/Initialize-WPFUI.ps1") -Raw
$targetGridMatch = [regex]::Match(
"$mainScript`n$tabInitializerScript",
"$mainScript`n$tabInitializerScript`n$uiInitializerScript",
'Invoke-WPFUIElements\s+-configVariable\s+\$sync\.configs\.appnavigation\s+-targetGridName\s+"([^"]+)"'
)
+22
View File
@@ -0,0 +1,22 @@
{
"Install": [
{
"winget": "Git.Git",
"choco": "git"
},
{
"winget": "Retired.App",
"choco": "retired-app"
}
],
"WPFTweaks": [
"WPFTweaksTelemetry"
],
"WPFFeature": [
"WPFFeatureSandbox"
],
"WPFInstall": [
"WPFInstallGit",
"WPFInstallRetired"
]
}
+28 -6
View File
@@ -8,12 +8,10 @@ BeforeAll {
function Invoke-WPFUIElements {
param($configVariable, [string]$targetGridName, [int]$columncount)
}
function Initialize-WPFUI {
param([string]$TargetGridName)
}
function Invoke-WinUtilISOCheckExistingWork { }
function Reset-WPFCheckBoxes { param([bool]$doToggles) }
. (Join-Path $script:repoRoot "functions\public\Initialize-WPFUI.ps1")
. (Join-Path $script:repoRoot "functions\private\Initialize-WinUtilTabContent.ps1")
}
@@ -41,9 +39,7 @@ Describe "Initialize-WinUtilTabContent" {
Initialize-WinUtilTabContent -TabName "Install"
Initialize-WinUtilTabContent -TabName "Install"
Should -Invoke -CommandName Invoke-WPFUIElements -Times 1 -Exactly -ParameterFilter {
$targetGridName -eq "appscategory" -and $columncount -eq 1
}
Should -Invoke -CommandName Initialize-WPFUI -Times 1 -Exactly -ParameterFilter {
$TargetGridName -eq "appscategory"
}
@@ -105,6 +101,32 @@ Describe "Initialize-WinUtilTabContent" {
}
}
Describe "Initialize-WPFUI" {
BeforeEach {
$script:sync = [Hashtable]::Synchronized(@{
configs = @{
appnavigation = [pscustomobject]@{}
}
})
Mock Invoke-WPFUIElements { throw "App category rendered" }
}
AfterEach {
Remove-Variable -Name sync -Scope Script -ErrorAction SilentlyContinue
}
It "renders app navigation through the app category target" {
{ Initialize-WPFUI -TargetGridName "appscategory" } | Should -Throw "App category rendered"
Should -Invoke -CommandName Invoke-WPFUIElements -Times 1 -Exactly -ParameterFilter {
$configVariable -eq $script:sync.configs.appnavigation -and
$targetGridName -eq "appscategory" -and
$columncount -eq 1
}
}
}
Describe "Startup lazy tab wiring" {
It "builds only install tab content before first paint" {
$mainScript = Get-Content -Path (Join-Path $script:repoRoot "scripts\main.ps1") -Raw
+71
View File
@@ -93,6 +93,9 @@ namespace System.Windows.Controls
function Write-WinUtilLog {
param($Message, $Level, $Component)
}
function Show-WinUtilMessage {
param($Message, $Title, $Button, $Icon)
}
function script:New-WinUtilFakeCheckBox {
param([bool]$IsChecked = $false)
@@ -244,6 +247,8 @@ Describe "Invoke-WPFImpex import selection state" {
Mock Reset-WPFCheckBoxes { }
Mock Write-Error { }
Mock Write-WinUtilLog { }
Mock Show-WinUtilMessage { }
}
AfterEach {
@@ -271,6 +276,72 @@ Describe "Invoke-WPFImpex import selection state" {
Should -Invoke -CommandName Write-Error -Times 0 -Exactly
}
It "imports supported legacy selections and reports retired entries" {
$configPath = Join-Path $script:repoRoot "pester\fixtures\legacy-config.json"
Invoke-WPFImpex -type "import" -Config $configPath
@($script:sync.selectedApps) | Should -Be @("WPFInstallGit")
@($script:sync.selectedTweaks) | Should -Be @("WPFTweaksTelemetry")
@($script:sync.selectedFeatures) | Should -Be @("WPFFeatureSandbox")
Should -Invoke -CommandName Reset-WPFCheckBoxes -Times 1 -Exactly -ParameterFilter {
$doToggles -eq $true
}
Should -Invoke -CommandName Write-WinUtilLog -Times 1 -Exactly -ParameterFilter {
$Component -eq "Impex" -and
$Level -eq "WARN" -and
$Message -eq "Skipped unsupported legacy selections: WPFInstallRetired"
}
Should -Invoke -CommandName Show-WinUtilMessage -Times 1 -Exactly -ParameterFilter {
$Title -eq "Legacy Configuration Partially Imported" -and
$Message -like "*WPFInstallRetired*"
}
Should -Invoke -CommandName Write-Error -Times 0 -Exactly
}
It "ignores unrelated string metadata in a legacy configuration" {
$configPath = Join-Path $TestDrive "legacy-config-with-metadata.json"
[pscustomobject]@{
Install = @([pscustomobject]@{ winget = "Git.Git"; choco = "git" })
WPFInstall = @("WPFInstallGit")
ExportVersion = "1.0"
} | ConvertTo-Json -Depth 3 | Set-Content -LiteralPath $configPath
Invoke-WPFImpex -type "import" -Config $configPath
@($script:sync.selectedApps) | Should -Be @("WPFInstallGit")
Should -Invoke -CommandName Reset-WPFCheckBoxes -Times 1 -Exactly
Should -Invoke -CommandName Write-WinUtilLog -Times 1 -Exactly -ParameterFilter {
$Component -eq "Impex" -and
$Message -eq "Detected legacy WinUtil config structure; flattening import object."
}
Should -Invoke -CommandName Show-WinUtilMessage -Times 0 -Exactly
Should -Invoke -CommandName Write-Error -Times 0 -Exactly
}
It "preserves selections when every legacy entry is retired" {
$script:sync.selectedApps.Add("WPFInstallExisting")
$configPath = Join-Path $TestDrive "retired-legacy-config.json"
[pscustomobject]@{
Install = @([pscustomobject]@{ winget = "Retired.App"; choco = "retired-app" })
WPFTweaks = @()
WPFFeature = @()
WPFInstall = @("WPFInstallRetired")
} | ConvertTo-Json -Depth 3 | Set-Content -LiteralPath $configPath
Invoke-WPFImpex -type "import" -Config $configPath
@($script:sync.selectedApps) | Should -Be @("WPFInstallExisting")
Should -Invoke -CommandName Reset-WPFCheckBoxes -Times 0 -Exactly
Should -Invoke -CommandName Write-WinUtilLog -Times 1 -Exactly -ParameterFilter {
$Message -eq "Skipped unsupported legacy selections: WPFInstallRetired"
}
Should -Invoke -CommandName Show-WinUtilMessage -Times 1 -Exactly -ParameterFilter {
$Title -eq "Unsupported Legacy Configuration"
}
Should -Invoke -CommandName Write-Error -Times 0 -Exactly
}
It "imports legacy selection groups without treating Install metadata as a selection" {
$legacyConfigPath = Join-Path $TestDrive "legacy-config.json"
[ordered]@{
+60
View File
@@ -0,0 +1,60 @@
#===========================================================================
# Tests - Get-WinUtilVariables
#===========================================================================
BeforeAll {
$script:repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
$script:originalSyncVariable = Get-Variable -Name sync -Scope Global -ErrorAction SilentlyContinue
if ($script:originalSyncVariable) {
$script:originalSyncValue = $script:originalSyncVariable.Value
}
$global:sync = [Hashtable]::Synchronized(@{})
# Setup some test variables
$global:sync["WPFTestString"] = "I am a string"
$global:sync["WPFTestObj1"] = [PSCustomObject]@{ Name = "Test1" }
$global:sync["WPFTestObj2"] = [PSCustomObject]@{ Name = "Test2" }
$global:sync["WPFTestButton"] = [System.Version]::new("1.0.0.0")
$global:sync["OtherVar"] = "Not a WPF variable"
. (Join-Path $script:repoRoot "functions\private\Get-WinUtilVariables.ps1")
}
AfterAll {
if ($script:originalSyncVariable) {
Set-Variable -Name sync -Value $script:originalSyncValue -Scope Global -Force
} else {
Remove-Variable -Name sync -Scope Global -ErrorAction SilentlyContinue
}
}
Describe "Get-WinUtilVariables" {
It "returns all WPF-prefixed keys when no type is provided" {
$result = Get-WinUtilVariables
$result.Count | Should -Be 4
$result | Should -Contain "WPFTestString"
$result | Should -Contain "WPFTestObj1"
$result | Should -Contain "WPFTestObj2"
$result | Should -Contain "WPFTestButton"
$result | Should -Not -Contain "OtherVar"
}
It "returns only WPF keys matching the specified exact type" {
$result = Get-WinUtilVariables -Type "String"
$result.Count | Should -Be 1
$result | Should -Contain "WPFTestString"
}
It "returns multiple objects matching PSCustomObject" {
$result = Get-WinUtilVariables -Type "PSCustomObject"
$result.Count | Should -Be 2
$result | Should -Contain "WPFTestObj1"
$result | Should -Contain "WPFTestObj2"
}
It "returns an empty list when no matching type is found" {
$result = Get-WinUtilVariables -Type "Int32"
$result | Should -BeNullOrEmpty
}
}