Add dispatcher smoke coverage for install rendering

This commit is contained in:
Chris Titus
2026-07-01 22:46:38 -05:00
parent 7b37266898
commit 6b2ecf5b76
+84
View File
@@ -33,6 +33,90 @@ Describe "Install app rendering startup contract" {
$renderScript | Should -Not -Match '& \$renderCategory'
}
It "drains queued app batches on the WPF dispatcher without timer scope errors" {
Add-Type -AssemblyName WindowsBase
. (Join-Path $script:repoRoot "functions\private\Start-WinUtilInstallAppRendering.ps1")
$previousSync = Get-Variable -Name sync -Scope Global -ErrorAction SilentlyContinue
$previousInitializeAppEntry = Get-Item -Path Function:\Initialize-InstallAppEntry -ErrorAction SilentlyContinue
$previousSearch = Get-Item -Path Function:\Find-AppsByNameOrDescription -ErrorAction SilentlyContinue
$previousCheckpoint = Get-Item -Path Function:\Write-WinUtilPerformanceCheckpoint -ErrorAction SilentlyContinue
$errorCountBefore = $global:Error.Count
try {
$global:sync = [Hashtable]::Synchronized(@{})
$global:sync.currentTab = "Install"
$global:sync.SearchBar = [pscustomobject]@{ Text = "" }
$global:sync.Form = [pscustomobject]@{ Dispatcher = [System.Windows.Threading.Dispatcher]::CurrentDispatcher }
$global:sync.InstallAppRenderQueue = [System.Collections.Queue]::new()
$renderedApps = [System.Collections.Generic.List[string]]::new()
$checkpoints = [System.Collections.Generic.List[string]]::new()
function global:Initialize-InstallAppEntry {
param($TargetElement, $AppKey)
$renderedApps.Add($AppKey)
return "entry:$AppKey"
}
function global:Find-AppsByNameOrDescription {
param($SearchString)
throw "Search should not run for an empty search box in this test."
}
function global:Write-WinUtilPerformanceCheckpoint {
param([string]$Name)
$checkpoints.Add($Name)
}
$global:sync.InstallAppRenderQueue.Enqueue([pscustomobject]@{ TargetElement = [pscustomobject]@{}; AppKeys = @("AppA", "AppB") })
$global:sync.InstallAppRenderQueue.Enqueue([pscustomobject]@{ TargetElement = [pscustomobject]@{}; AppKeys = @("AppC") })
$frame = New-Object System.Windows.Threading.DispatcherFrame
$timeout = [System.Diagnostics.Stopwatch]::StartNew()
Start-WinUtilInstallAppRendering
$closeTimer = New-Object System.Windows.Threading.DispatcherTimer
$closeTimer.Interval = [TimeSpan]::FromMilliseconds(25)
$closeTimer.Add_Tick({
param($sender)
$timer = [System.Windows.Threading.DispatcherTimer]$sender
if ($global:sync.InstallAppEntriesRendered -or $timeout.Elapsed.TotalSeconds -gt 5) {
$timer.Stop()
$frame.Continue = $false
}
})
$closeTimer.Start()
[System.Windows.Threading.Dispatcher]::PushFrame($frame)
$global:sync.InstallAppEntriesRendered | Should -BeTrue
$global:sync.InstallAppRenderQueue.Count | Should -Be 0
@($renderedApps) | Should -Be @("AppA", "AppB", "AppC")
@($checkpoints) | Should -Contain "Install app entries rendered"
$global:Error.Count | Should -Be $errorCountBefore
} finally {
if ($previousSync) {
Set-Variable -Name sync -Value $previousSync.Value -Scope Global
} else {
Remove-Variable -Name sync -Scope Global -ErrorAction SilentlyContinue
}
foreach ($functionBackup in @(
@{ Name = "Initialize-InstallAppEntry"; Backup = $previousInitializeAppEntry },
@{ Name = "Find-AppsByNameOrDescription"; Backup = $previousSearch },
@{ Name = "Write-WinUtilPerformanceCheckpoint"; Backup = $previousCheckpoint }
)) {
if ($functionBackup.Backup) {
Set-Item -Path "Function:\$($functionBackup.Name)" -Value $functionBackup.Backup.ScriptBlock
} else {
Remove-Item -Path "Function:\$($functionBackup.Name)" -ErrorAction SilentlyContinue
}
}
}
}
It "keeps app-entry metadata lookup independent from the old caller scope" {
$entryScript = Get-Content -Path (Join-Path $script:repoRoot "functions\private\Initialize-InstallAppEntry.ps1") -Raw