mirror of
https://github.com/ChrisTitusTech/winutil.git
synced 2026-08-09 09:31:15 +10:00
Fix Show Installed Apps reliability and selection (#4850)
* Fix Windows PowerShell runspace cleanup callback * Fix installed app package ID matching * Synchronize installed app selection UI * Streamline installed app detection * Avoid installed app UI runspace deadlock
This commit is contained in:
@@ -140,6 +140,10 @@ Describe "Invoke-WPFRunspace behavior" {
|
||||
$runspaceScript | Should -Not -Match '\$script:powershell'
|
||||
$runspaceScript | Should -Not -Match '\$script:handle'
|
||||
}
|
||||
|
||||
It "exposes a strongly typed cleanup callback" {
|
||||
([WinUtilRunspaceCleanup]::Callback -is [System.Threading.WaitOrTimerCallback]) | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Public runspace callers" {
|
||||
|
||||
@@ -6,12 +6,82 @@ $repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
|
||||
|
||||
BeforeAll {
|
||||
$script:repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
|
||||
. (Join-Path $script:repoRoot "functions\private\Invoke-WinUtilCurrentSystem.ps1")
|
||||
. (Join-Path $script:repoRoot "functions\private\Set-WinUtilRegistry.ps1")
|
||||
. (Join-Path $script:repoRoot "functions\private\Set-WinUtilService.ps1")
|
||||
|
||||
function winget {
|
||||
param([Parameter(ValueFromRemainingArguments = $true)]$Arguments)
|
||||
}
|
||||
function choco {
|
||||
param([Parameter(ValueFromRemainingArguments = $true)]$Arguments)
|
||||
}
|
||||
function Write-WinUtilLog { }
|
||||
}
|
||||
|
||||
Describe "Invoke-WinUtilCurrentSystem installed apps" {
|
||||
BeforeEach {
|
||||
$script:sync = [Hashtable]::Synchronized(@{
|
||||
configs = [pscustomobject]@{
|
||||
applicationsHashtable = @{
|
||||
WPFInstallGit = [pscustomobject]@{ winget = "Git.Git"; choco = "git" }
|
||||
WPFInstallChatGPT = [pscustomobject]@{ winget = "msstore:9NT1R1C2HH7J"; choco = "na" }
|
||||
WPFInstallMissing = [pscustomobject]@{ winget = "Git"; choco = "missing" }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Mock winget {
|
||||
$global:LASTEXITCODE = 0
|
||||
$script:wingetArguments = @($Arguments)
|
||||
@(
|
||||
"Name Id Version Source",
|
||||
"--------------------------------",
|
||||
"Git Git.Git 2.0 winget",
|
||||
"ChatGPT 9NT1R1C2HH7J 1.0 msstore"
|
||||
)
|
||||
}
|
||||
Mock choco {
|
||||
$script:chocoArguments = @($Arguments)
|
||||
@("Chocolatey v2", "git 2.0", "2 packages installed.")
|
||||
}
|
||||
}
|
||||
|
||||
AfterEach {
|
||||
Remove-Variable -Name sync -Scope Script -ErrorAction SilentlyContinue
|
||||
Remove-Variable -Name wingetArguments -Scope Script -ErrorAction SilentlyContinue
|
||||
Remove-Variable -Name chocoArguments -Scope Script -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
It "matches single standard and Microsoft Store package IDs" {
|
||||
$result = @(Invoke-WinUtilCurrentSystem -CheckBox "winget")
|
||||
|
||||
$result | Should -HaveCount 2
|
||||
$result | Should -Contain "WPFInstallGit"
|
||||
$result | Should -Contain "WPFInstallChatGPT"
|
||||
$result | Should -Not -Contain "WPFInstallMissing"
|
||||
Should -Invoke -CommandName winget -Times 1 -Exactly
|
||||
$script:wingetArguments | Should -Be @("list", "--accept-source-agreements", "--disable-interactivity")
|
||||
}
|
||||
|
||||
It "fails promptly when Winget cannot list applications" {
|
||||
Mock winget {
|
||||
$global:LASTEXITCODE = 1
|
||||
"winget failed"
|
||||
}
|
||||
|
||||
{ Invoke-WinUtilCurrentSystem -CheckBox "winget" } | Should -Throw "winget list failed with exit code 1."
|
||||
}
|
||||
|
||||
It "matches the primary Chocolatey package ID in one list call" {
|
||||
$result = @(Invoke-WinUtilCurrentSystem -CheckBox "choco")
|
||||
|
||||
$result | Should -Be @("WPFInstallGit")
|
||||
Should -Invoke -CommandName choco -Times 1 -Exactly
|
||||
$script:chocoArguments | Should -Be @("list")
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Set-WinUtilRegistry" {
|
||||
BeforeEach {
|
||||
$script:testPathResults = @{}
|
||||
|
||||
@@ -61,6 +61,7 @@ namespace System.Windows.Controls
|
||||
|
||||
. (Join-Path $script:repoRoot "functions\private\Update-WinUtilSelections.ps1")
|
||||
. (Join-Path $script:repoRoot "functions\private\Reset-WPFCheckBoxes.ps1")
|
||||
. (Join-Path $script:repoRoot "functions\public\Invoke-WPFGetInstalled.ps1")
|
||||
. (Join-Path $script:repoRoot "functions\public\Invoke-WPFSelectedCheckboxesUpdate.ps1")
|
||||
. (Join-Path $script:repoRoot "functions\public\Invoke-WPFButton.ps1")
|
||||
. (Join-Path $script:repoRoot "functions\public\Invoke-WPFToggleAllCategories.ps1")
|
||||
@@ -68,6 +69,24 @@ namespace System.Windows.Controls
|
||||
function Set-WinUtilTweaksProgressIndicator {
|
||||
param($Visible, $Label, $Percent)
|
||||
}
|
||||
function Invoke-WPFRunspace {
|
||||
param($ArgumentList, $ParameterList, [scriptblock]$ScriptBlock)
|
||||
}
|
||||
function Invoke-WPFUIThread {
|
||||
param([scriptblock]$ScriptBlock)
|
||||
}
|
||||
function Invoke-WinUtilCurrentSystem {
|
||||
param($CheckBox)
|
||||
}
|
||||
function Set-WinUtilTaskbaritem {
|
||||
param($state)
|
||||
}
|
||||
function Test-WinUtilPackageManager {
|
||||
param([switch]$winget)
|
||||
}
|
||||
function Write-WinUtilLog {
|
||||
param($Message, $Level, $Component)
|
||||
}
|
||||
|
||||
function script:New-WinUtilFakeCheckBox {
|
||||
param([bool]$IsChecked = $false)
|
||||
@@ -180,6 +199,9 @@ Describe "Invoke-WPFSelectedCheckboxesUpdate" {
|
||||
@($script:sync.selectedToggles) | Should -Be @("WPFToggleDarkMode")
|
||||
@($script:sync.selectedFeatures) | Should -Be @("WPFFeatureSandbox")
|
||||
@($script:sync.selectedAppx) | Should -Be @("WPFAppxExample")
|
||||
$script:sync.WPFselectedAppsButton.Content | Should -Be "Selected Apps: 1"
|
||||
$script:sync.selectedAppsstackPanel.Children.Count | Should -Be 1
|
||||
$script:sync.selectedAppsstackPanel.Children[0].Key | Should -Be "WPFInstallGit"
|
||||
}
|
||||
|
||||
It "removes checkbox keys from the matching selected lists" {
|
||||
@@ -200,6 +222,94 @@ Describe "Invoke-WPFSelectedCheckboxesUpdate" {
|
||||
$script:sync.selectedToggles.Count | Should -Be 0
|
||||
$script:sync.selectedFeatures.Count | Should -Be 0
|
||||
$script:sync.selectedAppx.Count | Should -Be 0
|
||||
$script:sync.WPFselectedAppsButton.Content | Should -Be "Selected Apps: 0"
|
||||
$script:sync.selectedAppsstackPanel.Children.Count | Should -Be 0
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Invoke-WPFGetInstalled selection state" {
|
||||
BeforeEach {
|
||||
New-WinUtilUiStateTestContext
|
||||
|
||||
$script:sync.ProcessRunning = $false
|
||||
$script:sync.ChocoRadioButton = [pscustomobject]@{ IsChecked = $false }
|
||||
$script:sync.preferences = [pscustomobject]@{ packagemanager = "Winget" }
|
||||
$script:sync.WPFInstallGit = New-WinUtilFakeCheckBox
|
||||
$dispatcher = [pscustomobject]@{}
|
||||
$dispatcher | Add-Member -MemberType ScriptMethod -Name BeginInvoke -Value {
|
||||
param($Action, [object[]]$Arguments)
|
||||
$Action.DynamicInvoke($Arguments)
|
||||
}
|
||||
$script:sync.Form = [pscustomobject]@{ Dispatcher = $dispatcher }
|
||||
$script:capturedGetInstalledScriptBlock = $null
|
||||
$script:capturedGetInstalledParameters = @{}
|
||||
|
||||
Mock Test-WinUtilPackageManager { "installed" }
|
||||
Mock Invoke-WinUtilCurrentSystem { @("WPFInstallGit") }
|
||||
Mock Set-WinUtilTaskbaritem { }
|
||||
Mock Write-WinUtilLog { }
|
||||
Mock Write-Warning { }
|
||||
Mock Invoke-WPFRunspace {
|
||||
$script:capturedGetInstalledScriptBlock = $ScriptBlock
|
||||
foreach ($parameter in $ParameterList) {
|
||||
$script:capturedGetInstalledParameters[$parameter[0]] = $parameter[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AfterEach {
|
||||
Remove-Variable -Name sync -Scope Script -ErrorAction SilentlyContinue
|
||||
Remove-Variable -Name sync -Scope Global -ErrorAction SilentlyContinue
|
||||
Remove-Variable -Name capturedGetInstalledScriptBlock -Scope Script -ErrorAction SilentlyContinue
|
||||
Remove-Variable -Name capturedGetInstalledParameters -Scope Script -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
It "updates the selected app model, checkbox, count, and popup" {
|
||||
Invoke-WPFGetInstalled -CheckBox "winget"
|
||||
& $script:capturedGetInstalledScriptBlock `
|
||||
-checkbox "winget" `
|
||||
-managerPreference "Winget" `
|
||||
-operation $script:capturedGetInstalledParameters.operation `
|
||||
-completeAction $script:capturedGetInstalledParameters.completeAction
|
||||
|
||||
@($script:sync.selectedApps) | Should -Be @("WPFInstallGit")
|
||||
$script:sync.WPFInstallGit.IsChecked | Should -BeTrue
|
||||
$script:sync.WPFselectedAppsButton.Content | Should -Be "Selected Apps: 1"
|
||||
$script:sync.selectedAppsstackPanel.Children.Count | Should -Be 1
|
||||
$script:sync.selectedAppsstackPanel.Children[0].Key | Should -Be "WPFInstallGit"
|
||||
}
|
||||
|
||||
It "clears the running state when detection fails" {
|
||||
Mock Invoke-WinUtilCurrentSystem { throw "detection failed" }
|
||||
|
||||
Invoke-WPFGetInstalled -CheckBox "winget"
|
||||
& $script:capturedGetInstalledScriptBlock `
|
||||
-checkbox "winget" `
|
||||
-managerPreference "Winget" `
|
||||
-operation $script:capturedGetInstalledParameters.operation `
|
||||
-completeAction $script:capturedGetInstalledParameters.completeAction
|
||||
|
||||
$script:sync.ProcessRunning | Should -BeFalse
|
||||
Should -Invoke -CommandName Write-WinUtilLog -Times 1 -Exactly -ParameterFilter {
|
||||
$Level -eq "ERROR" -and
|
||||
$Component -eq "Install" -and
|
||||
$Message -eq "Get installed state failed: detection failed"
|
||||
}
|
||||
Should -Invoke -CommandName Set-WinUtilTaskbaritem -Times 1 -Exactly -ParameterFilter { $state -eq "None" }
|
||||
}
|
||||
|
||||
It "clears the running state when the worker cannot be queued" {
|
||||
Mock Invoke-WPFRunspace { throw "queue failed" }
|
||||
|
||||
Invoke-WPFGetInstalled -CheckBox "winget"
|
||||
|
||||
$script:sync.ProcessRunning | Should -BeFalse
|
||||
Should -Invoke -CommandName Write-WinUtilLog -Times 1 -Exactly -ParameterFilter {
|
||||
$Level -eq "ERROR" -and
|
||||
$Component -eq "Install" -and
|
||||
$Message -eq "Get installed state failed: queue failed"
|
||||
}
|
||||
Should -Invoke -CommandName Set-WinUtilTaskbaritem -Times 1 -Exactly -ParameterFilter { $state -eq "None" }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -435,7 +435,6 @@ Describe "XAML and sync wiring" {
|
||||
"appPopup",
|
||||
"appPopupSelectedApp",
|
||||
"ItemsControl",
|
||||
"InstalledPrograms",
|
||||
"ImportInProgress",
|
||||
"ScriptsInstallPrograms",
|
||||
"keys",
|
||||
|
||||
Reference in New Issue
Block a user