diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a8a7d6..9e23584 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,18 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). -## Unreleased +## [0.3.0] 2025-05-29 + +### Added + +- Added the ability to scroll the preview pane on the right. + - A "... more" line will show if you need to scroll. + - This will give a warning if a panel can't completely rendered. + - There is a known issue that you can "scroll past" the last item, but the + last item will still render. You will need to scroll back up an equivalent + number of times. This will be future Gilbert's problem. + +### Changed - Move test result breakdown chart to Preview pane. diff --git a/PesterExplorer/Classes/classA.ps1 b/PesterExplorer/Classes/classA.ps1 deleted file mode 100644 index 83c7479..0000000 --- a/PesterExplorer/Classes/classA.ps1 +++ /dev/null @@ -1,3 +0,0 @@ -class A { - [string]$Name -} diff --git a/PesterExplorer/PesterExplorer.psd1 b/PesterExplorer/PesterExplorer.psd1 index f4d2bb9..59e550a 100644 --- a/PesterExplorer/PesterExplorer.psd1 +++ b/PesterExplorer/PesterExplorer.psd1 @@ -12,25 +12,25 @@ RootModule = 'PesterExplorer.psm1' # Version number of this module. - ModuleVersion = '0.2.0' + ModuleVersion = '0.3.0' # Supported PSEditions # CompatiblePSEditions = @() # ID used to uniquely identify this module - GUID = '1b8311c2-23fd-4a4c-90de-e17cfc306b04' + GUID = '1b8311c2-23fd-4a4c-90de-e17cfc306b04' # Author of this module - Author = 'Gilbert Sanchez' + Author = 'Gilbert Sanchez' # Company or vendor of this module - CompanyName = 'HeyItsGilbert' + CompanyName = 'HeyItsGilbert' # Copyright statement for this module - Copyright = '(c) Gilbert Sanchez. All rights reserved.' + Copyright = '(c) Gilbert Sanchez. All rights reserved.' # Description of the functionality provided by this module - Description = 'A TUI to explore Pester results.' + Description = 'A TUI to explore Pester results.' # Minimum version of the PowerShell engine required by this module PowerShellVersion = '7.0' @@ -51,13 +51,13 @@ # ProcessorArchitecture = '' # Modules that must be imported into the global environment prior to importing this module - RequiredModules = @( + RequiredModules = @( @{ - ModuleName = 'Pester' + ModuleName = 'Pester' ModuleVersion = '5.0.0' }, @{ - ModuleName = 'PwshSpectreConsole' + ModuleName = 'PwshSpectreConsole' ModuleVersion = '2.3.0' } ) @@ -81,13 +81,13 @@ FunctionsToExport = '*' # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. - CmdletsToExport = '*' + CmdletsToExport = '*' # Variables to export from this module VariablesToExport = '*' # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. - AliasesToExport = '*' + AliasesToExport = '*' # DSC resources to export from this module # DscResourcesToExport = @() @@ -99,12 +99,12 @@ # FileList = @() # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. - PrivateData = @{ + PrivateData = @{ PSData = @{ # Tags applied to this module. These help with module discovery in online galleries. - Tags = @( + Tags = @( 'Windows', 'Linux', 'macOS', @@ -116,13 +116,13 @@ ) # A URL to the license for this module. - LicenseUri = 'https://github.com/HeyItsGilbert/PesterExplorer/blob/main/LICENSE' + LicenseUri = 'https://github.com/HeyItsGilbert/PesterExplorer/blob/main/LICENSE' # A URL to the main website for this project. - ProjectUri = 'https://github.com/HeyItsGilbert/PesterExplorer' + ProjectUri = 'https://github.com/HeyItsGilbert/PesterExplorer' # A URL to an icon representing this module. - IconUri = 'https://raw.githubusercontent.com/HeyItsGilbert/PesterExplorer/main/images/icon.png' + IconUri = 'https://raw.githubusercontent.com/HeyItsGilbert/PesterExplorer/main/images/icon.png' # ReleaseNotes of this module ReleaseNotes = 'https://github.com/HeyItsGilbert/PesterExplorer/blob/main/CHANGELOG.md' diff --git a/PesterExplorer/PesterExplorer.psm1 b/PesterExplorer/PesterExplorer.psm1 index 6daa8f4..1e74a4b 100644 --- a/PesterExplorer/PesterExplorer.psm1 +++ b/PesterExplorer/PesterExplorer.psm1 @@ -1,8 +1,8 @@ +#require -Module PwshSpectreConsole # Dot source public/private functions -$classes = @(Get-ChildItem -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Classes/*.ps1') -Recurse -ErrorAction Stop) -$public = @(Get-ChildItem -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Public/*.ps1') -Recurse -ErrorAction Stop) +$public = @(Get-ChildItem -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Public/*.ps1') -Recurse -ErrorAction Stop) $private = @(Get-ChildItem -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Private/*.ps1') -Recurse -ErrorAction Stop) -foreach ($import in @($classes + $public + $private)) { +foreach ($import in @($public + $private)) { try { . $import.FullName } catch { diff --git a/PesterExplorer/Private/Get-ListPanel.ps1 b/PesterExplorer/Private/Get-ListPanel.ps1 index 008ba28..9bc59ea 100644 --- a/PesterExplorer/Private/Get-ListPanel.ps1 +++ b/PesterExplorer/Private/Get-ListPanel.ps1 @@ -29,8 +29,15 @@ function Get-ListPanel { [array] $List, [string] - $SelectedItem + $SelectedItem, + [string]$SelectedPane = "list" ) + $paneColor = if($SelectedPane -ne "list") { + # If the selected pane is not preview, return an empty panel + "blue" + } else { + "white" + } $unselectedStyle = @{ RootColor = [Spectre.Console.Color]::Grey SeparatorColor = [Spectre.Console.Color]::Grey @@ -82,5 +89,5 @@ function Get-ListPanel { } $results | Format-SpectreRows | - Format-SpectrePanel -Header "[white]List[/]" -Expand + Format-SpectrePanel -Header "[white]List[/]" -Expand -Color $paneColor } diff --git a/PesterExplorer/Private/Get-PreviewPanel.ps1 b/PesterExplorer/Private/Get-PreviewPanel.ps1 index bc8f53f..a05b12b 100644 --- a/PesterExplorer/Private/Get-PreviewPanel.ps1 +++ b/PesterExplorer/Private/Get-PreviewPanel.ps1 @@ -1,3 +1,4 @@ +# spell-checker:ignore Renderable function Get-PreviewPanel { <# .SYNOPSIS @@ -47,11 +48,29 @@ function Get-PreviewPanel { #> [CmdletBinding()] param ( + [Parameter(Mandatory)] + [ValidateNotNullOrEmpty()] [hashtable] $Items, + [Parameter(Mandatory)] [string] - $SelectedItem + $SelectedItem, + $ScrollPosition = 0, + [Parameter()] + [ValidateNotNull()] + $PreviewHeight, + [Parameter()] + [ValidateNotNull()] + $PreviewWidth, + [string]$SelectedPane = "list" ) + Write-Debug "Get-PreviewPanel called with SelectedItem: $SelectedItem, ScrollPosition: $ScrollPosition" + $paneColor = if($SelectedPane -ne "preview") { + # If the selected pane is not preview, return an empty panel + "blue" + } else { + "white" + } if($SelectedItem -like "*..") { $formatSpectreAlignedSplat = @{ HorizontalAlignment = 'Center' @@ -59,40 +78,46 @@ function Get-PreviewPanel { } return "[grey]Please select an item.[/]" | Format-SpectreAligned @formatSpectreAlignedSplat | - Format-SpectrePanel -Header "[white]Preview[/]" -Expand + Format-SpectrePanel -Header "[white]Preview[/]" -Expand -Color $paneColor } $object = $Items.Item($SelectedItem) - $result = @() + $results = @() # SelectedItem can be a few different types: # - A Pester object (Run, Container, Block, Test) #region Breakdown # Skip if the object is null or they are all zero. if ( + $null -ne $object.PassedCount -and + $null -ne $object.InconclusiveCount -and + $null -ne $object.SkippedCount -and + $null -ne $object.FailedCount -and ( - $object.PassedCount + - $object.InconclusiveCount + - $object.SkippedCount + - $object.FailedCount + [int]$object.PassedCount + + [int]$object.InconclusiveCount + + [int]$object.SkippedCount + + [int]$object.FailedCount ) -gt 0 ) { + Write-Debug "Adding breakdown chart for $($object.Name)" $data = @() $data += New-SpectreChartItem -Label "Passed" -Value ($object.PassedCount) -Color "Green" $data += New-SpectreChartItem -Label "Failed" -Value ($object.FailedCount) -Color "Red" $data += New-SpectreChartItem -Label "Inconclusive" -Value ($object.InconclusiveCount) -Color "Grey" $data += New-SpectreChartItem -Label "Skipped" -Value ($object.SkippedCount) -Color "Yellow" - $result += Format-SpectreBreakdownChart -Data $data + $results += Format-SpectreBreakdownChart -Data $data } #endregion Breakdown # For Tests Let's print some more details if ($object.GetType().Name -eq "Test") { + Write-Debug "Selected item is a Test: $($object.Name)" $formatSpectrePanelSplat = @{ Header = "Test Result" Border = "Rounded" Color = "White" } - $result += $object.Result | + $results += $object.Result | Format-SpectrePanel @formatSpectrePanelSplat # Show the code tested $formatSpectrePanelSplat = @{ @@ -100,10 +125,11 @@ function Get-PreviewPanel { Border = "Rounded" Color = "White" } - $result += $object.ScriptBlock | + $results += $object.ScriptBlock | Get-SpectreEscapedText | Format-SpectrePanel @formatSpectrePanelSplat } else { + Write-Debug "Selected item is a Pester object: $($object.Name)" $data = Format-PesterTreeHash -Object $object Write-Debug $($data|ConvertTo-Json -Depth 10) $formatSpectrePanelSplat = @{ @@ -111,17 +137,18 @@ function Get-PreviewPanel { Border = "Rounded" Color = "White" } - $result += Format-SpectreTree -Data $data | + $results += Format-SpectreTree -Data $data | Format-SpectrePanel @formatSpectrePanelSplat } if($null -ne $object.StandardOutput){ + Write-Debug "Adding standard output for $($object.Name)" $formatSpectrePanelSplat = @{ Header = "Standard Output" Border = "Ascii" Color = "White" } - $result += $object.StandardOutput | + $results += $object.StandardOutput | Get-SpectreEscapedText | Format-SpectrePanel @formatSpectrePanelSplat @@ -129,22 +156,72 @@ function Get-PreviewPanel { # Print errors if they exist. if($object.ErrorRecord.Count -gt 0) { + Write-Debug "Adding error records for $($object.Name)" $errorRecords = @() $object.ErrorRecord | ForEach-Object { $errorRecords += $_ | Format-SpectreException -ExceptionFormat ShortenEverything } - $formatSpectrePanelSplat = @{ - Header = "Errors" - Border = "Rounded" - Color = "Red" - } - $result += $errorRecords | - Format-SpectreRows | + $results += $errorRecords | Format-SpectreRows | Format-SpectrePanel -Header "Errors" -Border "Rounded" -Color "Red" + } + + $formatSpectrePanelSplat = @{ + Header = "[white]Preview[/]" + Color = $paneColor + Height = $PreviewHeight + Width = $PreviewWidth + Expand = $true + } + + if($scrollPosition -ge $results.Count) { + # If the scroll position is greater than the number of items, + # reset it to the last item + Write-Debug "Resetting ScrollPosition to last item." + $scrollPosition = $results.Count - 1 + } + # If the scroll position is out of bounds, reset it + if ($scrollPosition -lt 0) { + Write-Debug "Resetting ScrollPosition to 0." + $scrollPosition = 0 + } + + if($results.Count -eq 0) { + # If there are no results, return an empty panel + return "[grey]No results to display.[/]" | + Format-SpectreAligned -HorizontalAlignment Center -VerticalAlignment Middle | Format-SpectrePanel @formatSpectrePanelSplat + } else { + Write-Debug "Reducing Preview List: $($results.Count), ScrollPosition: $scrollPosition" + + # Determine the height of each item in the results + $totalHeight = 3 + $reducedList = @() + if($ScrollPosition -ne 0) { + # If the scroll position is not zero, add a "back" item + $reducedList += "[grey]...[/]" + } + for ($i = $scrollPosition; $i -le $array.Count; $i++) { + $itemHeight = Get-SpectreRenderableSize $results[$i] + $totalHeight += $itemHeight.Height + if ($totalHeight -gt $PreviewHeight) { + if($i -eq $scrollPosition) { + # If the first item already exceeds the height, stop here + Write-Debug "First item exceeds preview height. Stopping." + $reducedList += ":police_car_light:The next item is too large to display! Please resize your terminal.:police_car_light:" | + Format-SpectreAligned -HorizontalAlignment Center -VerticalAlignment Middle | + Format-SpectrePanel -Header ":police_car_light: [red]Warning[/]" -Color 'red' -Border Double + break + } + # If the total height exceeds the preview height, stop adding items + Write-Debug "Total height exceeded preview height. Stopping at item $i." + $reducedList += "[blue]...more.[/] [grey]Switch to Panel and scroll with keys.[/]" + break + } + $reducedList += $results[$i] + } } - return $result | - Format-SpectreGrid | - Format-SpectrePanel -Header "[white]Preview[/]" -Expand + return $reducedList | Format-SpectreRows | + Format-SpectrePanel @formatSpectrePanelSplat + #Format-ScrollableSpectrePanel @formatScrollableSpectrePanelSplat } diff --git a/PesterExplorer/Private/Get-ShortcutKeyPanel.ps1 b/PesterExplorer/Private/Get-ShortcutKeyPanel.ps1 index 76e1381..24942e9 100644 --- a/PesterExplorer/Private/Get-ShortcutKeyPanel.ps1 +++ b/PesterExplorer/Private/Get-ShortcutKeyPanel.ps1 @@ -21,10 +21,12 @@ function Get-ShortcutKeyPanel { [CmdletBinding()] $shortcutKeys = @( "Up, Down - Navigate", + "Home, End - Jump to Top/Bottom", + "PageUp, PageDown - Scroll", "Enter - Explore", + "Tab - Switch Panel", "Esc - Back", "Ctrl+C - Exit" - # TODO: Add a key to jump to the test ) $formatSpectreAlignedSplat = @{ HorizontalAlignment = 'Center' diff --git a/PesterExplorer/Public/Show-PesterResult.ps1 b/PesterExplorer/Public/Show-PesterResult.ps1 index c804f34..36f6244 100644 --- a/PesterExplorer/Public/Show-PesterResult.ps1 +++ b/PesterExplorer/Public/Show-PesterResult.ps1 @@ -76,53 +76,104 @@ function Show-PesterResult { $selectedItem = $list[0] $stack = [System.Collections.Stack]::new() $object = $PesterResult + $selectedPane = 'list' + $scrollPosition = 0 #endregion Initial State while ($true) { + # Check the layout sizes + $sizes = $layout | Get-SpectreLayoutSizes + $previewHeight = $sizes["preview"].Height + $previewWidth = $sizes["preview"].Width + Write-Debug "Preview size: $previewWidth x $previewHeight" + # Handle input $lastKeyPressed = Get-LastKeyPressed - # ToDo: Add support for scrolling the right panel + # ToDo: Add vim navigation keys if ($null -ne $lastKeyPressed) { - if ($lastKeyPressed.Key -eq "DownArrow") { - $selectedItem = $list[($list.IndexOf($selectedItem) + 1) % $list.Count] - } elseif ($lastKeyPressed.Key -eq "UpArrow") { - $selectedItem = $list[($list.IndexOf($selectedItem) - 1 + $list.Count) % $list.Count] - } elseif ($lastKeyPressed.Key -eq "Enter") { - <# Recurse into Pester Object #> - if($selectedItem -like '*..*') { - # Move up one via selecting .. - $object = $stack.Pop() - Write-Debug "Popped item from stack: $($object.Name)" - } else { - Write-Debug "Pushing item into stack: $($items.Item($selectedItem).Name)" - - $stack.Push($object) - $object = $items.Item($selectedItem) - if($object.GetType().Name -eq "Test") { + #region List Navigation + if($selectedPane -eq 'list') { + if ($lastKeyPressed.Key -eq "DownArrow") { + $selectedItem = $list[($list.IndexOf($selectedItem) + 1) % $list.Count] + $scrollPosition = 0 + } elseif ($lastKeyPressed.Key -eq "UpArrow") { + $selectedItem = $list[($list.IndexOf($selectedItem) - 1 + $list.Count) % $list.Count] + $scrollPosition = 0 + } elseif ($lastKeyPressed.Key -eq "PageDown") { + $currentIndex = $list.IndexOf($selectedItem) + $newIndex = [Math]::Min($currentIndex + 10, $list.Count - 1) + $selectedItem = $list[$newIndex] + $scrollPosition = 0 + } elseif ($lastKeyPressed.Key -eq "PageUp") { + $currentIndex = $list.IndexOf($selectedItem) + $newIndex = [Math]::Max($currentIndex - 10, $list.Count - 1) + $selectedItem = $list[$newIndex] + $scrollPosition = 0 + } elseif ($lastKeyPressed.Key -eq "Home") { + $selectedItem = $list[0] + $scrollPosition = 0 + } elseif ($lastKeyPressed.Key -eq "End") { + $selectedItem = $list[-1] + $scrollPosition = 0 + } elseif ($lastKeyPressed.Key -in @("Tab", "RightArrow")) { + $selectedPane = 'preview' + } elseif ($lastKeyPressed.Key -eq "Enter") { + <# Recurse into Pester Object #> + if($items.Item($selectedItem).GetType().Name -eq "Test") { # This is a test. We don't want to go deeper. + } + if($selectedItem -like '*..*') { + # Move up one via selecting .. $object = $stack.Pop() + Write-Debug "Popped item from stack: $($object.Name)" + } else { + Write-Debug "Pushing item into stack: $($items.Item($selectedItem).Name)" + $stack.Push($object) + $object = $items.Item($selectedItem) } + $items = Get-ListFromObject -Object $object + $list = [array]$items.Keys + $selectedItem = $list[0] + $scrollPosition = 0 + } elseif ($lastKeyPressed.Key -eq "Escape") { + # Move up via Esc key + if($stack.Count -eq 0) { + # This is the top level. Exit the loop. + return + } + $object = $stack.Pop() + $items = Get-ListFromObject -Object $object + $list = [array]$items.Keys + $selectedItem = $list[0] + $scrollPosition = 0 } - $items = Get-ListFromObject -Object $object - $list = [array]$items.Keys - $selectedItem = $list[0] - } elseif ($lastKeyPressed.Key -eq "Escape") { - # Move up via Esc key - if($stack.Count -eq 0) { - # This is the top level. Exit the loop. - return + } + else { + #region Preview Navigation + # ToDo: Add support for scrolling the right panel + if ($lastKeyPressed.Key -in "Escape", "Tab", "LeftArrow", "RightArrow") { + $selectedPane = 'list' + } elseif ($lastKeyPressed.Key -eq "Down") { + # Scroll down in the preview panel + $scrollPosition = $ScrollPosition + 1 + } elseif ($lastKeyPressed.Key -eq "Up") { + # Scroll up in the preview panel + $scrollPosition = $ScrollPosition - 1 + } elseif ($lastKeyPressed.Key -eq "PageDown") { + # Scroll down by a page in the preview panel + $scrollPosition = $ScrollPosition + 1 + } elseif ($lastKeyPressed.Key -eq "PageUp") { + # Scroll up by a page in the preview panel + $scrollPosition = $ScrollPosition - 1 } - $object = $stack.Pop() - $items = Get-ListFromObject -Object $object - $list = [array]$items.Keys - $selectedItem = $list[0] + #endregion Preview Navigation } } # Generate new data $titlePanel = Get-TitlePanel -Item $object - $listPanel = Get-ListPanel -List $list -SelectedItem $selectedItem - $previewPanel = Get-PreviewPanel -Items $items -SelectedItem $selectedItem + $listPanel = Get-ListPanel -List $list -SelectedItem $selectedItem -SelectedPane $selectedPane + $previewPanel = Get-PreviewPanel -Items $items -SelectedItem $selectedItem -ScrollPosition $scrollPosition -PreviewHeight $previewHeight -PreviewWidth $previewWidth -SelectedPane $selectedPane # Update layout $layout["header"].Update($titlePanel) | Out-Null diff --git a/docs/.markdownlint.jsonc b/docs/.markdownlint.jsonc index 7af7e9a..172cdb3 100644 --- a/docs/.markdownlint.jsonc +++ b/docs/.markdownlint.jsonc @@ -1,4 +1,6 @@ { // These docs are autogenerated and this is the expected format. - "MD025": false + "MD025": false, + "MD022": false, + "MD040": false } diff --git a/docs/en-US/Show-PesterResultTree.md b/docs/en-US/Show-PesterResultTree.md index 1e0220e..cbf270f 100644 --- a/docs/en-US/Show-PesterResultTree.md +++ b/docs/en-US/Show-PesterResultTree.md @@ -17,10 +17,10 @@ Show-PesterResultTree [[-PesterResult] ] [-ProgressAction