Skip to content

Commit 0903c7c

Browse files
committed
#215 feat: enhance Test-NovaBuild with progress reporting and actionable guidance in WhatIf mode
refactor(Test-NovaBuild): align with terminal-ux-design principles Fixes #227
1 parent 6d1b5fd commit 0903c7c

6 files changed

Lines changed: 279 additions & 24 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
2929
- `New-NovaModulePackage` now shows progress for build validation and artifact creation, ends with the package target plus the next suggested deployment step, and uses a package-plan summary in `-WhatIf` mode.
3030
- `Publish-NovaModule` now shows progress for build validation, publish, local import, and CI restore phases, ends with the publish target plus a suggested verification step, and uses a publish-plan summary in `-WhatIf` mode.
3131
- `Set-NovaUpdateNotificationPreference` now ends with a clear success or preview summary, prints the settings file path, suggests `Get-NovaUpdateNotificationPreference` as the next verification step, and gives a more actionable validation error when no enable/disable switch is supplied.
32+
- `Test-NovaBuild` now shows progress across the main test phases, ends with the result file path plus a coverage summary and next-step hint when tests pass, uses a test-plan summary in `-WhatIf` mode, and fails with more actionable guidance when Pester or coverage checks fail.
3233

3334
### Deprecated
3435

RELEASE_NOTE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ This file summarizes the release notes for NovaModuleTools. **UNRELEASED** chang
2525
- `New-NovaModulePackage` now shows progress for build validation and artifact creation, ends with the package target plus the next suggested deployment step, and uses a package-plan summary in `-WhatIf` mode.
2626
- `Publish-NovaModule` now shows progress for build validation, publish, local import, and CI restore phases, ends with the publish target plus a suggested verification step, and uses a publish-plan summary in `-WhatIf` mode.
2727
- `Set-NovaUpdateNotificationPreference` now ends with a clear success or preview summary, prints the settings file path, suggests `Get-NovaUpdateNotificationPreference` as the next verification step, and gives a more actionable validation error when no enable/disable switch is supplied.
28+
- `Test-NovaBuild` now shows progress across the main test phases, ends with the result file path plus a coverage summary and next-step hint when tests pass, uses a test-plan summary in `-WhatIf` mode, and fails with more actionable guidance when Pester or coverage checks fail.
2829

2930
### Deprecated
3031

docs/NovaModuleTools/en-US/Test-NovaBuild.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ With the default
4040

4141
This command supports `-WhatIf` and `-Confirm` through PowerShell `SupportsShouldProcess`. Use `-WhatIf` to preview the planned test run and XML output path without creating `artifacts/` or invoking Pester.
4242

43+
During a test run, Nova shows progress for the optional pre-test build, test-result preparation, the Pester run, result writing, and code-coverage validation. When tests pass, Nova prints the result file path, a coverage summary when one is available, and a suggested next step. In `-WhatIf` mode, Nova ends with a test-plan summary instead of invoking Pester.
44+
4345
## EXAMPLES
4446

4547
### EXAMPLE 1
@@ -48,15 +50,15 @@ This command supports `-WhatIf` and `-Confirm` through PowerShell `SupportsShoul
4850
PS> Test-NovaBuild
4951
```
5052

51-
Runs the Pester tests for the current project.
53+
Runs the Pester tests for the current project and prints the result file path plus the next suggested command when the run succeeds.
5254

5355
### EXAMPLE 2
5456

5557
```text
5658
PS> Test-NovaBuild -Build
5759
```
5860

59-
Builds the project first, then runs the configured Pester test workflow.
61+
Builds the project first, then runs the configured Pester test workflow with the same completion summary as a normal test run.
6062

6163
### EXAMPLE 3
6264

@@ -88,15 +90,15 @@ Overrides the console output settings for the current test run while keeping col
8890
PS> Test-NovaBuild -WhatIf
8991
```
9092

91-
Previews the planned Pester run without executing tests or writing `artifacts/TestResults.xml`.
93+
Previews the planned Pester run, prints the planned result file path, and does not execute tests or write `artifacts/TestResults.xml`.
9294

9395
### EXAMPLE 7
9496

9597
```text
9698
PS> Test-NovaBuild -Build -WhatIf
9799
```
98100

99-
Previews the build-before-test workflow without rebuilding the project or running Pester.
101+
Previews the build-before-test workflow, including the planned result file path and configured coverage target, without rebuilding the project or running Pester.
100102

101103
## PARAMETERS
102104

@@ -258,6 +260,8 @@ If `project.json` configures `Pester.CodeCoverage.CoveragePercentTarget`, `Test-
258260
`Test-NovaBuild` uses `SupportsShouldProcess`, so `Get-Help Test-NovaBuild -Full` surfaces native `-WhatIf` and
259261
`-Confirm` support.
260262

263+
Use `Ctrl+C` if you need to stop a running test workflow before Pester completes.
264+
261265
## RELATED LINKS
262266

263267
- [Get-NovaProjectInfo](./Get-NovaProjectInfo.md)

src/private/quality/InvokeNovaTestWorkflow.ps1

Lines changed: 203 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,34 +5,225 @@ function Invoke-NovaTestWorkflow {
55
[switch]$ShouldRun
66
)
77

8-
if (Test-NovaTestWorkflowBuildRequested -WorkflowContext $WorkflowContext) {
9-
$workflowParams = Get-NovaBuildCommandParameterMap -WorkflowParams $WorkflowContext.WorkflowParams -OverrideWarningRequested:(($WorkflowContext.PSObject.Properties.Name -contains 'OverrideWarningRequested') -and $WorkflowContext.OverrideWarningRequested)
10-
Invoke-NovaBuild @workflowParams
8+
$progressActivity = 'Running Nova test workflow'
9+
$whatIfEnabled = Test-NovaWhatIfWorkflowContext -WorkflowContext $WorkflowContext
10+
$testResult = $null
11+
$shouldRunWorkflow = Test-NovaTestWorkflowShouldRun -WorkflowContext $WorkflowContext -BoundParameters $PSBoundParameters -ShouldRun:$ShouldRun
12+
13+
try {
14+
Invoke-NovaTestWorkflowBuildStep -WorkflowContext $WorkflowContext -Activity $progressActivity -WhatIfEnabled:$whatIfEnabled
15+
16+
if (-not $shouldRunWorkflow) {
17+
Write-NovaTestWorkflowPreviewResult -WorkflowContext $WorkflowContext -WhatIfEnabled:$whatIfEnabled
18+
return
19+
}
20+
21+
$testResult = Invoke-NovaTestWorkflowExecution -WorkflowContext $WorkflowContext -Activity $progressActivity
22+
} finally {
23+
Write-Progress -Activity $progressActivity -Completed
24+
}
25+
26+
Write-NovaTestWorkflowResult -WorkflowContext $WorkflowContext -TestResult $testResult
27+
}
28+
29+
function Invoke-NovaTestWorkflowBuildStep {
30+
[CmdletBinding()]
31+
param(
32+
[Parameter(Mandatory)][pscustomobject]$WorkflowContext,
33+
[Parameter(Mandatory)][string]$Activity,
34+
[switch]$WhatIfEnabled
35+
)
36+
37+
if (-not (Test-NovaTestWorkflowBuildRequested -WorkflowContext $WorkflowContext)) {
38+
return
1139
}
1240

13-
if (-not (Test-NovaTestWorkflowShouldRun -WorkflowContext $WorkflowContext -BoundParameters $PSBoundParameters -ShouldRun:$ShouldRun)) {
41+
$buildCommandParameters = Get-NovaBuildCommandParameterMap -WorkflowParams (Get-NovaPropertyValue -InputObject $WorkflowContext -Name 'WorkflowParams') -OverrideWarningRequested:(($WorkflowContext.PSObject.Properties.Name -contains 'OverrideWarningRequested') -and $WorkflowContext.OverrideWarningRequested)
42+
Invoke-NovaTestWorkflowStep -Activity $Activity -Status (Get-NovaTestWorkflowBuildStatus -WhatIfEnabled:$WhatIfEnabled) -PercentComplete 20 -Action {
43+
Invoke-NovaBuild @buildCommandParameters
44+
}
45+
}
46+
47+
function Write-NovaTestWorkflowPreviewResult {
48+
[CmdletBinding()]
49+
param(
50+
[Parameter(Mandatory)][pscustomobject]$WorkflowContext,
51+
[switch]$WhatIfEnabled
52+
)
53+
54+
if (-not $WhatIfEnabled) {
1455
return
1556
}
1657

17-
Initialize-NovaPesterArtifactDirectory -WorkflowContext $WorkflowContext
58+
Write-NovaTestWorkflowResult -WorkflowContext $WorkflowContext -WhatIfEnabled
59+
}
60+
61+
function Invoke-NovaTestWorkflowExecution {
62+
[CmdletBinding()]
63+
param(
64+
[Parameter(Mandatory)][pscustomobject]$WorkflowContext,
65+
[Parameter(Mandatory)][string]$Activity
66+
)
67+
68+
Invoke-NovaTestWorkflowStep -Activity $Activity -Status 'Preparing the test result directory' -PercentComplete 40 -Action {
69+
Initialize-NovaPesterArtifactDirectory -WorkflowContext $WorkflowContext
70+
}
71+
1872
$WorkflowContext.PesterConfig.TestResult.OutputPath = $WorkflowContext.TestResultPath
1973
$coverageTargetAssertion = Get-NovaCoverageTargetAssertionScriptBlock -WorkflowContext $WorkflowContext
74+
$testResult = Invoke-NovaTestWorkflowStep -Activity $Activity -Status 'Running Pester tests' -PercentComplete 70 -Action {
75+
Invoke-NovaPesterWithSuppressedProgress -Configuration $WorkflowContext.PesterConfig
76+
}
77+
78+
Invoke-NovaTestWorkflowStep -Activity $Activity -Status 'Writing the test result report' -PercentComplete 85 -Action {
79+
& $WorkflowContext.TestResultArtifactWriter.ScriptBlock -TestResult $testResult -OutputPath $WorkflowContext.TestResultPath -ReportWriter $WorkflowContext.TestResultReportWriter.ScriptBlock
80+
}
81+
82+
if ($testResult.Result -ne 'Passed') {
83+
Stop-NovaOperation -Message (Get-NovaTestWorkflowFailureMessage -WorkflowContext $WorkflowContext) -ErrorId 'Nova.Workflow.TestRunFailed' -Category InvalidOperation -TargetObject $WorkflowContext.TestResultPath
84+
}
85+
86+
Invoke-NovaTestWorkflowStep -Activity $Activity -Status 'Checking the configured code coverage target' -PercentComplete 95 -Action {
87+
& $coverageTargetAssertion -WorkflowContext $WorkflowContext -TestResult $testResult
88+
}
89+
90+
return $testResult
91+
}
92+
93+
function Invoke-NovaTestWorkflowStep {
94+
[CmdletBinding()]
95+
param(
96+
[Parameter(Mandatory)][string]$Activity,
97+
[Parameter(Mandatory)][string]$Status,
98+
[Parameter(Mandatory)][int]$PercentComplete,
99+
[Parameter(Mandatory)][scriptblock]$Action
100+
)
101+
102+
Write-Progress -Activity $Activity -Status $Status -PercentComplete $PercentComplete
103+
& $Action
104+
}
105+
106+
function Get-NovaTestWorkflowBuildStatus {
107+
[CmdletBinding()]
108+
param(
109+
[switch]$WhatIfEnabled
110+
)
111+
112+
if ($WhatIfEnabled) {
113+
return 'Previewing the build-before-test workflow'
114+
}
115+
116+
return 'Building the current project state'
117+
}
118+
119+
function Invoke-NovaPesterWithSuppressedProgress {
120+
[CmdletBinding()]
121+
param(
122+
[Parameter(Mandatory)][object]$Configuration
123+
)
20124

21125
$previousProgressPreference = $global:ProgressPreference
22126
$global:ProgressPreference = 'SilentlyContinue'
23127
try {
24-
$testResult = Invoke-NovaPester -Configuration $WorkflowContext.PesterConfig
128+
return Invoke-NovaPester -Configuration $Configuration
25129
} finally {
26130
$global:ProgressPreference = $previousProgressPreference
27131
}
132+
}
28133

29-
& $WorkflowContext.TestResultArtifactWriter.ScriptBlock -TestResult $testResult -OutputPath $WorkflowContext.TestResultPath -ReportWriter $WorkflowContext.TestResultReportWriter.ScriptBlock
134+
function Get-NovaTestWorkflowFailureMessage {
135+
[CmdletBinding()]
136+
param(
137+
[Parameter(Mandatory)][pscustomobject]$WorkflowContext
138+
)
30139

31-
if ($testResult.Result -ne 'Passed') {
32-
Stop-NovaOperation -Message 'Tests failed' -ErrorId 'Nova.Workflow.TestRunFailed' -Category InvalidOperation -TargetObject $WorkflowContext.TestResultPath
140+
return "Pester reported one or more failing tests. Review the output above and the test result file at $( $WorkflowContext.TestResultPath ), then rerun Test-NovaBuild."
141+
}
142+
143+
function Write-NovaTestWorkflowResult {
144+
[CmdletBinding()]
145+
param(
146+
[Parameter(Mandatory)][pscustomobject]$WorkflowContext,
147+
[AllowNull()][object]$TestResult,
148+
[switch]$WhatIfEnabled
149+
)
150+
151+
Write-Message (Get-NovaTestWorkflowStatusMessage -WorkflowContext $WorkflowContext -WhatIfEnabled:$WhatIfEnabled) -color Green
152+
Write-Message "Results file: $( $WorkflowContext.TestResultPath )"
153+
154+
$coverageMessage = Get-NovaTestWorkflowCoverageMessage -WorkflowContext $WorkflowContext -TestResult $TestResult -WhatIfEnabled:$WhatIfEnabled
155+
if (-not [string]::IsNullOrWhiteSpace($coverageMessage)) {
156+
Write-Message $coverageMessage
157+
}
158+
159+
foreach ($line in (Get-NovaTestWorkflowNextStepLine -WhatIfEnabled:$WhatIfEnabled)) {
160+
Write-Message $line
161+
}
162+
}
163+
164+
function Get-NovaTestWorkflowStatusMessage {
165+
[CmdletBinding()]
166+
param(
167+
[Parameter(Mandatory)][pscustomobject]$WorkflowContext,
168+
[switch]$WhatIfEnabled
169+
)
170+
171+
if ($WhatIfEnabled) {
172+
return "Test plan ready for $( $WorkflowContext.ProjectInfo.ProjectName )"
173+
}
174+
175+
return "Pester tests passed for $( $WorkflowContext.ProjectInfo.ProjectName )"
176+
}
177+
178+
function Get-NovaTestWorkflowCoverageMessage {
179+
[CmdletBinding()]
180+
param(
181+
[Parameter(Mandatory)][pscustomobject]$WorkflowContext,
182+
[AllowNull()][object]$TestResult,
183+
[switch]$WhatIfEnabled
184+
)
185+
186+
$coveragePercentTarget = Get-NovaConfiguredCoveragePercentTarget -WorkflowContext $WorkflowContext
187+
if ($WhatIfEnabled) {
188+
if ($null -eq $coveragePercentTarget) {
189+
return $null
190+
}
191+
192+
return "Configured coverage target: $( Format-NovaCoveragePercentValue -Value ([double]$coveragePercentTarget) )%"
33193
}
34194

35-
& $coverageTargetAssertion -WorkflowContext $WorkflowContext -TestResult $testResult
195+
$codeCoverage = Get-NovaPropertyValue -InputObject $TestResult -Name 'CodeCoverage'
196+
$coveragePercent = Get-NovaPropertyValue -InputObject $codeCoverage -Name 'CoveragePercent'
197+
if ($null -eq $coveragePercent -or [string]::IsNullOrWhiteSpace([string]$coveragePercent)) {
198+
return $null
199+
}
200+
201+
$formattedCoverage = Format-NovaCoveragePercentValue -Value ([double]$coveragePercent)
202+
if ($null -eq $coveragePercentTarget) {
203+
return "Measured code coverage: $formattedCoverage%"
204+
}
205+
206+
$formattedTarget = Format-NovaCoveragePercentValue -Value ([double]$coveragePercentTarget)
207+
return "Measured code coverage: $formattedCoverage% (target: $formattedTarget%)"
208+
}
209+
210+
function Get-NovaTestWorkflowNextStepLine {
211+
[CmdletBinding()]
212+
param(
213+
[switch]$WhatIfEnabled
214+
)
215+
216+
if ($WhatIfEnabled) {
217+
return @(
218+
'Next step:'
219+
'Run Test-NovaBuild without -WhatIf when you are ready to execute the test workflow.'
220+
)
221+
}
222+
223+
return @(
224+
'Next step:'
225+
'Publish-NovaModule -Local'
226+
)
36227
}
37228

38229
function Test-NovaTestWorkflowBuildRequested {
@@ -129,7 +320,7 @@ function Get-NovaDefaultCoverageTargetAssertionScriptBlock {
129320
$codeCoverage = & $propertyReader -InputObject $TestResult -Name 'CodeCoverage'
130321
$coveragePercent = & $propertyReader -InputObject $codeCoverage -Name 'CoveragePercent'
131322
if ($null -eq $coveragePercent -or [string]::IsNullOrWhiteSpace([string]$coveragePercent)) {
132-
$exception = [System.IO.InvalidDataException]::new("Code coverage target $formattedTarget% is configured, but the Pester result did not include a coverage percentage.")
323+
$exception = [System.IO.InvalidDataException]::new("Code coverage target $formattedTarget% is configured, but the Pester result did not include a coverage percentage. Review the coverage settings in project.json and the test result file at $resolvedTargetObject.")
133324
$errorRecord = [System.Management.Automation.ErrorRecord]::new($exception, 'Nova.Workflow.CodeCoveragePercentMissing', [System.Management.Automation.ErrorCategory]::InvalidData, $resolvedTargetObject)
134325
throw $errorRecord
135326
}
@@ -140,7 +331,7 @@ function Get-NovaDefaultCoverageTargetAssertionScriptBlock {
140331
}
141332

142333
$formattedCoverage = & $percentFormatter -Value $coveragePercent
143-
$exception = [System.InvalidOperationException]::new("Code coverage $formattedCoverage% did not meet the configured target $formattedTarget%.")
334+
$exception = [System.InvalidOperationException]::new("Code coverage $formattedCoverage% did not meet the configured target $formattedTarget%. Review the failing tests or coverage settings, then rerun Test-NovaBuild.")
144335
$errorRecord = [System.Management.Automation.ErrorRecord]::new($exception, 'Nova.Workflow.CodeCoverageTargetNotMet', [System.Management.Automation.ErrorCategory]::InvalidOperation, $resolvedTargetObject)
145336
throw $errorRecord
146337
}.GetNewClosure()

tests/private/quality/InvokeNovaTestWorkflow.TestSupport.ps1

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ function Stop-NovaOperation {
1010
function Invoke-NovaBuild {}
1111
function Invoke-NovaPester {param($Configuration)}
1212
function Get-NovaBuildCommandParameterMap {param($WorkflowParams, [switch]$OverrideWarningRequested) return @{}}
13+
function Write-Message {param([string]$Text, [string]$color)}
14+
function Write-Progress {param([string]$Activity, [string]$Status, [int]$PercentComplete, [switch]$Completed)}
1315
function New-NovaInvokeNovaTestWorkflowContext {
1416
param(
1517
[hashtable]$PesterSettings = @{},
@@ -21,7 +23,7 @@ function New-NovaInvokeNovaTestWorkflowContext {
2123
return [pscustomobject]@{
2224
BuildRequested = $BuildRequested
2325
WorkflowParams = $WorkflowParams
24-
ProjectInfo = [pscustomobject]@{Pester = $PesterSettings}
26+
ProjectInfo = [pscustomobject]@{ProjectName = 'NovaModuleTools'; Pester = $PesterSettings}
2527
TestResultDirectory = $TestResultDirectory
2628
TestResultPath = Join-Path $TestResultDirectory 'TestResults.xml'
2729
PesterConfig = [pscustomobject]@{TestResult = [pscustomobject]@{OutputPath = $null}}

0 commit comments

Comments
 (0)