mirror of
https://github.com/ChrisTitusTech/winutil.git
synced 2026-08-10 18:11:16 +10:00
* Make UI helpers no-op when no window exists The -Preset and -Config paths run Invoke-WinUtilAutoRun before the XAML form is created and before PresentationCore is loaded, so every call into Invoke-WPFUIThread failed with InvokeMethodOnNull and every call into Set-WinUtilTweaksProgressIndicator failed to resolve [Windows.Visibility]. The errors were non-terminating, so tweaks still applied, but each run filled the log with noise. Loading presentationframework earlier does not help: Visibility lives in PresentationCore, which only loads once a WPF object is instantiated, and the XAML-named progress controls do not exist in these paths either. Guarding the two helpers covers Invoke-WPFtweaksbutton, Invoke-WPFundoall and Invoke-WPFFeatureInstall, which the existing per-call-site $hasUI convention never reached. * Suppress stray console output from automation runs Invoke-WPFRunspace returns an IAsyncResult that no caller uses, and Set-WinUtilRegistry was the only New-PSDrive call site that did not suppress its output. A GUI click handler discards both, but the -Preset and -Config paths call the workflows directly, so an async handle dump and a PSDrive table landed in the user's log. The handle itself is kept because pester/runspace.Tests.ps1 asserts that Invoke-WPFRunspace returns a single IAsyncResult, so the suppression goes at the four call sites reachable from Invoke-WinUtilAutoRun. * Add project learning for window-free UI helpers
108 lines
3.6 KiB
PowerShell
108 lines
3.6 KiB
PowerShell
#===========================================================================
|
|
# Tests - UI Helpers During Headless (-Preset / -Config) Runs
|
|
#===========================================================================
|
|
|
|
BeforeAll {
|
|
$script:repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
|
|
|
|
if (-not ("Windows.Visibility" -as [type])) {
|
|
Add-Type @"
|
|
namespace Windows
|
|
{
|
|
public enum Visibility
|
|
{
|
|
Visible,
|
|
Collapsed
|
|
}
|
|
}
|
|
"@
|
|
}
|
|
|
|
. (Join-Path $script:repoRoot "functions\public\Invoke-WPFUIThread.ps1")
|
|
. (Join-Path $script:repoRoot "functions\private\Set-WinUtilTweaksProgressIndicator.ps1")
|
|
|
|
function script:New-WinUtilFakeForm {
|
|
$dispatcher = New-Object psobject
|
|
$dispatcher | Add-Member -MemberType NoteProperty -Name InvokeCount -Value 0
|
|
$dispatcher | Add-Member -MemberType ScriptMethod -Name Invoke -Value {
|
|
param($Action)
|
|
|
|
$null = $Action
|
|
$this.InvokeCount++
|
|
}
|
|
|
|
$form = New-Object psobject
|
|
$form | Add-Member -MemberType NoteProperty -Name Dispatcher -Value $dispatcher
|
|
return $form
|
|
}
|
|
|
|
function script:New-WinUtilFakeIndicatorControlSet {
|
|
@{
|
|
Bar = [pscustomobject]@{ Visibility = [Windows.Visibility]::Collapsed }
|
|
Label = [pscustomobject]@{ Text = "" }
|
|
Value = [pscustomobject]@{ Value = 0 }
|
|
}
|
|
}
|
|
}
|
|
|
|
Describe "Invoke-WPFUIThread without a window" {
|
|
AfterEach {
|
|
Remove-Variable -Name sync -Scope Script -ErrorAction SilentlyContinue
|
|
}
|
|
|
|
It "does nothing when the automation paths run before the form is created" {
|
|
$script:sync = [Hashtable]::Synchronized(@{})
|
|
$script:blockRan = $false
|
|
|
|
{ Invoke-WPFUIThread -ScriptBlock { $script:blockRan = $true } } | Should -Not -Throw
|
|
$script:blockRan | Should -BeFalse
|
|
}
|
|
|
|
It "does nothing when the form exists but has no dispatcher" {
|
|
$script:sync = [Hashtable]::Synchronized(@{ Form = [pscustomobject]@{ Dispatcher = $null } })
|
|
$script:blockRan = $false
|
|
|
|
{ Invoke-WPFUIThread -ScriptBlock { $script:blockRan = $true } } | Should -Not -Throw
|
|
$script:blockRan | Should -BeFalse
|
|
}
|
|
|
|
It "still marshals onto the dispatcher when a window exists" {
|
|
$form = New-WinUtilFakeForm
|
|
$script:sync = [Hashtable]::Synchronized(@{ Form = $form })
|
|
|
|
Invoke-WPFUIThread -ScriptBlock { }
|
|
|
|
$form.Dispatcher.InvokeCount | Should -Be 1
|
|
}
|
|
}
|
|
|
|
Describe "Set-WinUtilTweaksProgressIndicator without a window" {
|
|
AfterEach {
|
|
Remove-Variable -Name sync -Scope Script -ErrorAction SilentlyContinue
|
|
}
|
|
|
|
It "returns before resolving WPF types when the form is missing" {
|
|
$script:sync = [Hashtable]::Synchronized(@{})
|
|
|
|
{ Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Creating restore point" -Percent 0 } | Should -Not -Throw
|
|
}
|
|
|
|
It "still updates the indicator controls when a window exists" {
|
|
$controls = New-WinUtilFakeIndicatorControlSet
|
|
$script:sync = [Hashtable]::Synchronized(@{
|
|
Form = New-WinUtilFakeForm
|
|
WPFTweaksProgressBar = $controls.Bar
|
|
WPFTweaksProgressLabel = $controls.Label
|
|
WPFTweaksProgressValue = $controls.Value
|
|
})
|
|
|
|
Mock Invoke-WPFUIThread { & $ScriptBlock }
|
|
|
|
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Applying WPFTweaksTelemetry (1/17)" -Percent 42
|
|
|
|
$controls.Bar.Visibility | Should -Be ([Windows.Visibility]::Visible)
|
|
$controls.Label.Text | Should -Be "Applying WPFTweaksTelemetry (1/17)"
|
|
$controls.Value.Value | Should -Be 42
|
|
}
|
|
}
|