tests/Tools.PickerDeclaration.Tests.ps1
|
BeforeAll { $script:ToolsDir = "$PSScriptRoot/../Tools" # A tool's `picker` field takes one of three shapes, and Register-DFTool # (Public/Register-DFTool.ps1) only acts on the third: # # null - no picker # "custom" - a Tools/<name>.ps1 sidecar builds it # { ... } - declarative; Register-DFTool builds it # # Nothing verifies "custom". Register-DFTool's guard is `-is [PSCustomObject]`, # so a tool declaring "custom" with no sidecar picker silently does nothing: # no error, no warning, no picker. That is how ten tools came to advertise # pickers they did not have. # # Invoke-DFPicker is the marker for "this sidecar builds a picker". It is the # module's picker primitive (Invoke-DFFzf is private and mocked in tests), so # every real sidecar picker goes through it. $script:PickerMarker = 'Invoke-DFPicker' $script:Tools = Get-ChildItem $script:ToolsDir -Filter '*.json' | ForEach-Object { $json = Get-Content $_.FullName -Raw | ConvertFrom-Json $sidecar = Join-Path $script:ToolsDir "$($_.BaseName).ps1" $hasSidecar = Test-Path $sidecar -PathType Leaf [PSCustomObject]@{ Name = $json.name File = $_.Name Picker = $json.PSObject.Properties['picker']?.Value SidecarPath = $sidecar HasSidecar = $hasSidecar SidecarHasPicker = $hasSidecar -and (Select-String -Path $sidecar -Pattern $script:PickerMarker -Quiet) } } } Describe 'Tools/*.json picker declarations' { It 'declares "custom" only when a sidecar actually builds a picker' { # Register-DFTool silently ignores a string picker, so an unbacked "custom" # is invisible: the tool registers "successfully" and the picker never exists. $liars = $script:Tools | Where-Object { $_.Picker -is [string] -and $_.Picker -eq 'custom' } | Where-Object { -not $_.SidecarHasPicker } $detail = ($liars | ForEach-Object { "$($_.File) declares picker 'custom' but $(if ($_.HasSidecar) { "Tools/$($_.Name).ps1 never calls $script:PickerMarker" } else { "Tools/$($_.Name).ps1 does not exist" })" }) -join "; " $liars | Should -BeNullOrEmpty -Because @" $detail. Register-DFTool only builds a picker from a declarative object, so "custom" without a sidecar picker does nothing at all -- silently. Either implement the picker in the sidecar, convert it to a declarative picker object, or set "picker": null and track the work in docs/superpowers/specs/2026-07-15-legacy-profile-fold-in-design.md. "@ } It 'declares "custom" whenever a sidecar builds a picker' { # The inverse. A sidecar picker still works when the JSON says null -- the # sidecar is dot-sourced regardless -- but the field then under-reports, which # is how psreadline's fprl picker went unlisted. Enforcing both directions is # what makes the field trustworthy rather than decorative. $unlisted = $script:Tools | Where-Object { $_.SidecarHasPicker } | Where-Object { -not ($_.Picker -is [string] -and $_.Picker -eq 'custom') } | Where-Object { -not ($_.Picker -is [PSCustomObject]) } $detail = ($unlisted | ForEach-Object { "$($_.File) declares picker '$($_.Picker)' but Tools/$($_.Name).ps1 calls $script:PickerMarker" }) -join "; " $unlisted | Should -BeNullOrEmpty -Because "$detail. Set `"picker`": `"custom`" so the record matches reality." } It 'uses only a supported picker shape: null, "custom", or an object' { foreach ($tool in $script:Tools) { $ok = ($null -eq $tool.Picker) -or ($tool.Picker -is [PSCustomObject]) -or ($tool.Picker -is [string] -and $tool.Picker -eq 'custom') $ok | Should -BeTrue -Because "$($tool.File) has picker '$($tool.Picker)'; Register-DFTool only understands null, 'custom', or an object" } } It 'gives every declarative picker the fields Register-DFTool needs' { # Register-DFTool.ps1 reads alias/function/list off the object; without # function it builds nothing, and without list the picker has no input. foreach ($tool in ($script:Tools | Where-Object { $_.Picker -is [PSCustomObject] })) { $tool.Picker.PSObject.Properties['function']?.Value | Should -Not -BeNullOrEmpty -Because "$($tool.File) declares a declarative picker with no 'function'" $tool.Picker.PSObject.Properties['list']?.Value | Should -Not -BeNullOrEmpty -Because "$($tool.File) declares a declarative picker with no 'list'" } } } |