diff --git a/docs/src/content/docs/guides/automation.mdx b/docs/src/content/docs/guides/automation.mdx index 4dc87775..5612066d 100644 --- a/docs/src/content/docs/guides/automation.mdx +++ b/docs/src/content/docs/guides/automation.mdx @@ -22,6 +22,8 @@ Example: To view exactly what each preset does, see: https://github.com/ChrisTitusTech/winutil/blob/main/config/preset.json +To find a key for a custom configuration, hover over a supported application, tweak, feature, or AppX entry in WinUtil. Its tooltip shows `Preset key: `. Application keys are searchable on the Install tab, while tweak and AppX keys are searchable on their respective tabs. Feature keys are available from their tooltips because the Config/Features tab does not have search. Controls that cannot be applied from a preset, such as toggle switches, drop-downs, and package-manager choices, intentionally do not advertise a preset key. + To create your own config file: 1. Open WinUtil. diff --git a/functions/private/Find-AppsByNameOrDescription.ps1 b/functions/private/Find-AppsByNameOrDescription.ps1 index 44f4003c..c94fc451 100644 --- a/functions/private/Find-AppsByNameOrDescription.ps1 +++ b/functions/private/Find-AppsByNameOrDescription.ps1 @@ -5,9 +5,9 @@ function Find-AppsByNameOrDescription { .DESCRIPTION Search text and categories are independent filters that both have to pass. An entry is - shown when its name or description matches the search text, and when its category is in - the selected set. An empty search matches everything, and an empty category set matches - every category. + shown when its name, description, or application preset key matches the search text, and + when its category is in the selected set. An empty search matches everything, and an empty + category set matches every category. While either filter is active the matching categories are expanded, since a collapsed category would otherwise hide the very results that were asked for. With no filter at @@ -115,7 +115,8 @@ function Find-AppsByNameOrDescription { $categoryMatch = -not $hasCategories -or $activeCategories -contains $appEntry.Category $textMatch = -not $hasSearch -or $appEntry.Content -like "*$escapedSearchString*" -or - $appEntry.Description -like "*$escapedSearchString*" + $appEntry.Description -like "*$escapedSearchString*" -or + $appTag -like "*$escapedSearchString*" if ($categoryMatch -and $textMatch) { $appControl.Visibility = [Windows.Visibility]::Visible diff --git a/functions/private/Get-WinUtilEntryToolTip.ps1 b/functions/private/Get-WinUtilEntryToolTip.ps1 new file mode 100644 index 00000000..6cde230d --- /dev/null +++ b/functions/private/Get-WinUtilEntryToolTip.ps1 @@ -0,0 +1,25 @@ +function Get-WinUtilEntryToolTip { + <# + .SYNOPSIS + Builds the tooltip string for an app/tweak/feature entry: its description plus its preset JSON key + + .PARAMETER Description + The entry's description from the config JSON. May be null or empty. + + .PARAMETER Key + The entry's JSON key as used in preset files (e.g. WPFInstallbrave, WPFTweaksTele). + #> + param( + [Parameter(Mandatory = $false)] + [string]$Description, + + [Parameter(Mandatory = $true)] + [string]$Key + ) + + if ([string]::IsNullOrWhiteSpace($Description)) { + return "Preset key: $Key" + } + + return "$Description`n`nPreset key: $Key" +} diff --git a/functions/private/Initialize-InstallAppEntry.ps1 b/functions/private/Initialize-InstallAppEntry.ps1 index 8318f12b..8edb039c 100644 --- a/functions/private/Initialize-InstallAppEntry.ps1 +++ b/functions/private/Initialize-InstallAppEntry.ps1 @@ -19,7 +19,7 @@ function Initialize-InstallAppEntry { $border = New-Object Windows.Controls.Border $border.Style = $sync.Form.Resources.AppEntryBorderStyle $border.Tag = $appKey - $border.ToolTip = $app.description + $border.ToolTip = Get-WinUtilEntryToolTip -Description $app.description -Key $appKey $border.Add_MouseLeftButtonUp({ # Resolve through $sync because the border's child is a layout Grid for FOSS entries $childCheckbox = $sync.$($this.Tag) diff --git a/functions/public/Invoke-WPFUIElements.ps1 b/functions/public/Invoke-WPFUIElements.ps1 index eb50fae0..01559fdf 100644 --- a/functions/public/Invoke-WPFUIElements.ps1 +++ b/functions/public/Invoke-WPFUIElements.ps1 @@ -224,7 +224,7 @@ function Invoke-WPFUIElements { $toggleButton = New-Object Windows.Controls.Primitives.ToggleButton $toggleButton.Name = $entryInfo.Name $toggleButton.Content = $entryInfo.Content[1] - $toggleButton.ToolTip = $entryInfo.Description + $toggleButton.ToolTip = Get-WinUtilEntryToolTip -Description $entryInfo.Description -Key $entryInfo.Name $toggleButton.HorizontalAlignment = "Left" $toggleButton.Style = $ToggleButtonStyle [System.Windows.Automation.AutomationProperties]::SetName($toggleButton, $entryInfo.Content[0]) @@ -490,7 +490,7 @@ function Invoke-WPFUIElements { $checkBox.Name = $entryInfo.Name $checkBox.Content = $entryInfo.Content $checkBox.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "FontSize") - $checkBox.ToolTip = $entryInfo.Description + $checkBox.ToolTip = Get-WinUtilEntryToolTip -Description $entryInfo.Description -Key $entryInfo.Name $checkBox.SetResourceReference([Windows.Controls.Control]::MarginProperty, "CheckBoxMargin") $checkBox.UseLayoutRounding = $true [System.Windows.Automation.AutomationProperties]::SetName($checkBox, $entryInfo.Content) diff --git a/pester/search-filter.Tests.ps1 b/pester/search-filter.Tests.ps1 index 74211424..4c7bb6b1 100644 --- a/pester/search-filter.Tests.ps1 +++ b/pester/search-filter.Tests.ps1 @@ -360,6 +360,19 @@ Describe "Find-AppsByNameOrDescription" { $editorItem.Visibility | Should -Be ([Windows.Visibility]::Collapsed) } + It "matches apps by preset key" { + $browserItem = New-WinUtilAppSearchItem -Tag "WPFInstallBrowser" + $mediaItem = New-WinUtilAppSearchItem -Tag "WPFInstallMedia" + $category = New-WinUtilAppCategory -Label "- Browsers" -Items @($browserItem, $mediaItem) + New-WinUtilAppSearchContext -Categories @($category) + + Find-AppsByNameOrDescription -SearchString "WPFInstallBrowser" + + $browserItem.Visibility | Should -Be ([Windows.Visibility]::Visible) + $mediaItem.Visibility | Should -Be ([Windows.Visibility]::Collapsed) + $category.Visibility | Should -Be ([Windows.Visibility]::Visible) + } + It "treats wildcard characters as literal app search text" { $literalItem = New-WinUtilAppSearchItem -Tag "WPFInstallLiteral" $mediaItem = New-WinUtilAppSearchItem -Tag "WPFInstallMedia" diff --git a/pester/tooltip-key.Tests.ps1 b/pester/tooltip-key.Tests.ps1 new file mode 100644 index 00000000..595839fd --- /dev/null +++ b/pester/tooltip-key.Tests.ps1 @@ -0,0 +1,118 @@ +#=========================================================================== +# Tests - Entry ToolTip Helper +#=========================================================================== + +BeforeAll { + $script:repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path + . (Join-Path $script:repoRoot "functions\private\Get-WinUtilEntryToolTip.ps1") + . (Join-Path $script:repoRoot "functions\private\Update-WinUtilSelections.ps1") + + $applications = Get-Content (Join-Path $script:repoRoot "config\applications.json") -Raw | ConvertFrom-Json + $applicationsHashtable = @{} + foreach ($property in $applications.PSObject.Properties) { + $applicationsHashtable["WPFInstall$($property.Name)"] = $property.Value + } + $appx = Get-Content (Join-Path $script:repoRoot "config\appx.json") -Raw | ConvertFrom-Json + $appxHashtable = @{} + foreach ($property in $appx.PSObject.Properties) { + $appxHashtable[$property.Name] = $property.Value + } + $script:selectionConfigs = @{ + applicationsHashtable = $applicationsHashtable + tweaks = Get-Content (Join-Path $script:repoRoot "config\tweaks.json") -Raw | ConvertFrom-Json + feature = Get-Content (Join-Path $script:repoRoot "config\feature.json") -Raw | ConvertFrom-Json + appxHashtable = $appxHashtable + } + + # Map each control type the renderer handles to whether its branch adds the preset key. + # Read from the source AST so re-adding the helper to an unsupported branch fails the test. + $script:rendererClauses = @{} + $rendererPath = Join-Path $script:repoRoot "functions\public\Invoke-WPFUIElements.ps1" + $rendererAst = [System.Management.Automation.Language.Parser]::ParseFile($rendererPath, [ref]$null, [ref]$null) + $typeSwitch = $rendererAst.FindAll({ + $args[0] -is [System.Management.Automation.Language.SwitchStatementAst] + }, $true) | Where-Object { $_.Clauses.Item1.Extent.Text -contains '"Combobox"' } + + foreach ($clause in $typeSwitch.Clauses) { + $clauseName = $clause.Item1.Extent.Text.Trim('"') + $script:rendererClauses[$clauseName] = [bool]($clause.Item2.Extent.Text -match 'Get-WinUtilEntryToolTip') + } + $script:rendererClauses["default"] = [bool]($typeSwitch.Default.Extent.Text -match 'Get-WinUtilEntryToolTip') + + $appRendererPath = Join-Path $script:repoRoot "functions\private\Initialize-InstallAppEntry.ps1" + $script:appRenderer = Get-Content $appRendererPath -Raw + + # Every entry the renderer draws, with the branch that draws it + $script:renderedEntries = foreach ($configName in "appnavigation", "tweaks", "feature", "appx") { + $config = Get-Content (Join-Path $script:repoRoot "config\$configName.json") -Raw | ConvertFrom-Json + foreach ($property in $config.PSObject.Properties) { + $clauseName = "default" + if ($property.Value.Type -and $script:rendererClauses.ContainsKey($property.Value.Type)) { + $clauseName = $property.Value.Type + } + [pscustomobject]@{ + Config = $configName + Key = $property.Name + Clause = $clauseName + } + } + } +} + +Describe "Get-WinUtilEntryToolTip" { + It "appends the preset key after the description" { + Get-WinUtilEntryToolTip -Description "Fast private browser" -Key "WPFInstallBrave" | + Should -Be "Fast private browser`n`nPreset key: WPFInstallBrave" + } + + It "returns only the key line when description is null" { + Get-WinUtilEntryToolTip -Description $null -Key "WPFTweaksTele" | + Should -Be "Preset key: WPFTweaksTele" + } + + It "returns only the key line when description is whitespace" { + Get-WinUtilEntryToolTip -Description " " -Key "WPFTweaksTele" | + Should -Be "Preset key: WPFTweaksTele" + } + + It "returns a plain string, not a UI object" { + (Get-WinUtilEntryToolTip -Description "x" -Key "WPFTweaksTele").GetType().Name | + Should -Be "String" + } +} + +Describe "Preset key tooltips" { + It "leaves unsupported controls unlabelled" { + # Comboboxes and radio buttons are not representable in the flat preset format. + # Toggle keys can be imported, but preset execution does not apply their state. + $script:rendererClauses["Toggle"] | Should -BeFalse + $script:rendererClauses["Combobox"] | Should -BeFalse + $script:rendererClauses["RadioButton"] | Should -BeFalse + } + + It "labels entries created by the application renderer" { + $script:appRenderer | Should -Match '\$border\.ToolTip\s*=\s*Get-WinUtilEntryToolTip\s+-Description\s+\$app\.description\s+-Key\s+\$appKey' + } + + It "labels every entry with a key the preset importer accepts" { + $labelled = @($script:renderedEntries | Where-Object { $script:rendererClauses[$_.Clause] }) + $labelled.Count | Should -BeGreaterThan 0 + + foreach ($entry in $labelled) { + $sync = @{ + selectedApps = [System.Collections.Generic.List[string]]::new() + selectedTweaks = [System.Collections.Generic.List[string]]::new() + selectedToggles = [System.Collections.Generic.List[string]]::new() + selectedFeatures = [System.Collections.Generic.List[string]]::new() + selectedAppx = [System.Collections.Generic.List[string]]::new() + configs = $script:selectionConfigs + } + + { Update-WinUtilSelections -flatJson @($entry.Key) } | + Should -Not -Throw -Because "$($entry.Config).json key '$($entry.Key)' is shown as a preset key" + + $imported = @($sync.Values | ForEach-Object { $_ }) + $imported | Should -Contain $entry.Key -Because "importing '$($entry.Key)' must restore that selection" + } + } +}