diff --git a/src/theme/CodeBlock/Buttons/CopyButton/index.js b/src/theme/CodeBlock/Buttons/CopyButton/index.js
new file mode 100644
index 0000000..3d11126
--- /dev/null
+++ b/src/theme/CodeBlock/Buttons/CopyButton/index.js
@@ -0,0 +1,55 @@
+import React from 'react';
+import CopyButton from '@theme-original/CodeBlock/Buttons/CopyButton';
+import {
+ CodeBlockContextProvider,
+ useCodeBlockContext,
+} from '@docusaurus/theme-common/internal';
+
+// Set by the diff-remove magic comment declared in docusaurus.config.js.
+const DIFF_REMOVE_CLASS = 'code-block-diff-remove-line';
+
+/**
+ * Drops the lines marked with `diff-remove` from the copied text.
+ *
+ * A diff block shows the old line in red and the new line in green. The `-` and `+`
+ * glyphs come from CSS `::before` in custom.css, and pseudo-element content is not part
+ * of the DOM, so without this the copy button hands you the old line and the new line
+ * with nothing to tell them apart. The tutorial evolves the same file over several pages,
+ * so that is a broken file rather than a cosmetic problem.
+ *
+ * Rendering is untouched. The removed lines stay on the page, they just do not travel
+ * to the clipboard.
+ */
+function withoutRemovedLines(metadata) {
+ const removed = Object.entries(metadata.lineClassNames)
+ .filter(([, classNames]) => classNames.includes(DIFF_REMOVE_CLASS))
+ .map(([lineIndex]) => Number(lineIndex));
+
+ if (removed.length === 0) {
+ return metadata;
+ }
+
+ const removedLines = new Set(removed);
+ const code = metadata.code
+ .split('\n')
+ .filter((_, lineIndex) => !removedLines.has(lineIndex))
+ .join('\n');
+
+ return {...metadata, code};
+}
+
+export default function CopyButtonWrapper(props) {
+ const {metadata, wordWrap} = useCodeBlockContext();
+ const copyMetadata = withoutRemovedLines(metadata);
+
+ if (copyMetadata === metadata) {
+ return ;
+ }
+
+ // Re-provide the context so only the copy button sees the trimmed code.
+ return (
+
+
+
+ );
+}
diff --git a/tutorial/2-testing-a-module/1-setup.mdx b/tutorial/2-testing-a-module/1-setup.mdx
index 10e4dc9..ea93f59 100644
--- a/tutorial/2-testing-a-module/1-setup.mdx
+++ b/tutorial/2-testing-a-module/1-setup.mdx
@@ -61,9 +61,9 @@ New-ModuleManifest -Path ./Planetarium/Planetarium.psd1 `
-PowerShellVersion '5.1'
```
-Open the generated file and find the `FunctionsToExport` line:
+Open the generated file and find the `FunctionsToExport` line. This is one line out of the manifest `New-ModuleManifest` just wrote, not a file to save:
-```powershell title="Planetarium/Planetarium.psd1"
+```powershell
FunctionsToExport = '*'
```
diff --git a/tutorial/2-testing-a-module/3-public-functions.mdx b/tutorial/2-testing-a-module/3-public-functions.mdx
index fd84d6d..9858009 100644
--- a/tutorial/2-testing-a-module/3-public-functions.mdx
+++ b/tutorial/2-testing-a-module/3-public-functions.mdx
@@ -17,9 +17,9 @@ Importing the module instead fixes both. The test then calls the function throug
## Importing the module
-Change the `BeforeAll` in your test file:
+Change the `BeforeAll` in `Planetarium/Public/Get-Planet.Tests.ps1`, and leave the `Describe` below it alone for now:
-```powershell title="Planetarium/Public/Get-Planet.Tests.ps1"
+```powershell
BeforeAll {
# diff-remove
. $PSCommandPath.Replace('.Tests.ps1', '.ps1')
diff --git a/tutorial/2-testing-a-module/4-private-functions.mdx b/tutorial/2-testing-a-module/4-private-functions.mdx
index 6398e45..e52cbd5 100644
--- a/tutorial/2-testing-a-module/4-private-functions.mdx
+++ b/tutorial/2-testing-a-module/4-private-functions.mdx
@@ -65,7 +65,7 @@ The first test is the odd one out, and it is deliberate. It asserts the function
## Passing values in
-A script block handed to `InModuleScope` does not inherit your test's variables. This does not work:
+A script block handed to `InModuleScope` does not inherit your test's variables. The next block is here to show the mistake, it is not a step, and on its own outside an `It` it fails with `No modules named 'Planetarium' are currently loaded` rather than the empty `$name` it is meant to demonstrate:
```powershell
$name = 'Earth'
diff --git a/tutorial/3-organising-tests/2-choosing-what-runs.mdx b/tutorial/3-organising-tests/2-choosing-what-runs.mdx
index e1908ef..176c400 100644
--- a/tutorial/3-organising-tests/2-choosing-what-runs.mdx
+++ b/tutorial/3-organising-tests/2-choosing-what-runs.mdx
@@ -12,14 +12,20 @@ Pester gives you different tools for that, and the difference matters: **filters
The most used filter in Pester is tags. `-Tag` goes on `Describe`, `Context` or `It`, and is inherited by everything inside. Your two private-function files are a natural group — they reach into the module with `InModuleScope`, and they are the tests you would drop first if you only wanted to check the public surface:
-```powershell title="Planetarium/Private/ConvertTo-AstronomicalUnit.Tests.ps1"
+Add the tag to the `Describe` line in each of the two files, leaving the rest of each file as it is.
+
+In `Planetarium/Private/ConvertTo-AstronomicalUnit.Tests.ps1`:
+
+```powershell
# diff-remove
Describe 'ConvertTo-AstronomicalUnit' {
# diff-add
Describe 'ConvertTo-AstronomicalUnit' -Tag 'Internal' {
```
-```powershell title="Planetarium/Private/Test-PlanetName.Tests.ps1"
+In `Planetarium/Private/Test-PlanetName.Tests.ps1`:
+
+```powershell
# diff-remove
Describe 'Test-PlanetName' {
# diff-add
@@ -58,6 +64,8 @@ Typical tags used in projects are `Slow`, `Integration`, `Unit`, `WindowsOnly` e
## Skipping tests
+Both blocks in this section are illustrations rather than tests to add, Planetarium has nothing that needs skipping yet.
+
`-Skip` marks a test as not to be run, while keeping it visible in the output:
```powershell
@@ -82,8 +90,6 @@ It 'Uses the Windows registry' -Skip:(-not $IsWindows) {
On Windows this runs; everywhere else it reports as skipped instead of failing. That is how a cross-platform suite handles the parts that genuinely cannot run everywhere — and it is how you would keep the CI matrix in the last module green if the module ever grew a platform-specific feature.
-Both `-Skip` examples above are illustrations rather than tests to add — Planetarium has nothing that needs skipping yet.
-
## BeforeDiscovery
Here is the catch, and it is the one thing on this page that trips people up.
diff --git a/tutorial/4-mocking/3-verifying-calls.mdx b/tutorial/4-mocking/3-verifying-calls.mdx
index 9b0a0b5..80b1294 100644
--- a/tutorial/4-mocking/3-verifying-calls.mdx
+++ b/tutorial/4-mocking/3-verifying-calls.mdx
@@ -11,8 +11,23 @@ A mock lets you control what a command returns. `Should-Invoke` lets you assert
Add a third test to the `Get-Planet with mocked data` block, below the two you already have:
```powershell title="Planetarium/Public/Get-Planet.Mocking.Tests.ps1"
+BeforeAll {
+ Import-Module "$PSScriptRoot/../Planetarium.psd1" -Force
+}
+
Describe 'Get-Planet with mocked data' {
- # ... BeforeAll and the two existing It blocks ...
+ BeforeAll {
+ Mock -ModuleName Planetarium Get-PlanetData {
+ @(
+ [PSCustomObject] @{ Name = 'Aiur'; Order = 1; DistanceFromSunKm = 100000000 }
+ [PSCustomObject] @{ Name = 'Shakuras'; Order = 2; DistanceFromSunKm = 200000000 }
+ )
+ }
+ }
+
+ It 'Returns whatever the data source provides' {
+ (Get-Planet).Name | Should-BeCollection @('Aiur', 'Shakuras')
+ }
It 'Filters the mocked data the same way' {
(Get-Planet -Name 'A*').Name | Should-Be 'Aiur'
@@ -43,8 +58,24 @@ The `-ModuleName` rule from the previous page applies here too: you are asking a
`-ParameterFilter` narrows a mock to calls whose arguments match, allowing you to customize responses for different calls. Let's give it a try:
```powershell title="Planetarium/Private/Get-PlanetData.Tests.ps1"
+BeforeAll {
+ Import-Module "$PSScriptRoot/../Planetarium.psd1" -Force
+}
+
Describe 'Get-PlanetData' -Tag 'Internal' {
- # ... the 'Converts the CSV strings into numbers' test ...
+ It 'Converts the CSV strings into numbers' {
+ Mock -ModuleName Planetarium Import-Csv {
+ @([PSCustomObject] @{ Name = 'Aiur'; Order = '3'; DistanceFromSunKm = '149597870.7' })
+ }
+
+ InModuleScope Planetarium {
+ $planet = Get-PlanetData
+ $planet.Name | Should-Be 'Aiur'
+ $planet.Order | Should-Be 3
+ $planet.Order | Should-HaveType ([int])
+ $planet.DistanceFromSunKm | Should-HaveType ([double])
+ }
+ }
# diff-add-start
It 'Reads the CSV shipped with the module' {
@@ -68,19 +99,13 @@ That doubles as an assertion. If the module ever reads a different file, the fil
A mock with a `-ParameterFilter` only applies to calls that match it. If a call reaches a mocked command and *nothing* matches, Pester throws rather than guessing — it will not quietly run the real command behind your back.
-Let's break it on purpose to see how this works. Change `planets` to `moons` in the test you just added:
+Let's break it on purpose to see how this works. In `Planetarium/Private/Get-PlanetData.Tests.ps1`, change `planets` to `moons` on the `-ParameterFilter` of the test you just added, and leave the rest of the file alone:
-```powershell title="Planetarium/Private/Get-PlanetData.Tests.ps1"
- It 'Reads the CSV shipped with the module' {
- Mock -ModuleName Planetarium Import-Csv {
- @([PSCustomObject] @{ Name = 'Aiur'; Order = '1'; DistanceFromSunKm = '100' })
+```powershell
# diff-remove
} -ParameterFilter { $Path -like '*planets.csv' }
# diff-add
} -ParameterFilter { $Path -like '*moons.csv' }
-
- # ... rest of the test unchanged ...
- }
```
```powershell
@@ -102,15 +127,15 @@ This behavior is new in Pester v6. Previous versions called the original command
### Giving a mock a fallback
-Sometimes you genuinely want "handle this specific case, and everything else generically". Say so explicitly by adding a second mock with no `-ParameterFilter` — an unfiltered mock matches any call, so it becomes the fallback:
+Sometimes you genuinely want "handle this specific case, and everything else generically". Say so explicitly by adding a second mock with no `-ParameterFilter` — an unfiltered mock matches any call, so it becomes the fallback.
+
+The next block is an illustration, not a step. Read it, do not add it to Planetarium: your `Import-Csv` mock should intercept exactly one file, so leaving it filtered and unmatched-is-an-error is the behaviour you want.
```powershell
Mock Get-Thing { 'default' } # everything else
Mock Get-Thing { 'one' } -ParameterFilter { $Id -eq 1 } # the specific case
```
-The example above is only used for illustration. Your `Import-Csv` mock should intercept exactly one file, so leaving it filtered and unmatched-is-an-error is the behaviour you want.
-
:::tip
See [Mocking](../../docs/usage/mocking#pesterboundparameters) for an example of using the default mock to call the original command.
:::
diff --git a/tutorial/6-code-coverage/1-measuring.mdx b/tutorial/6-code-coverage/1-measuring.mdx
index a4b27e6..126a780 100644
--- a/tutorial/6-code-coverage/1-measuring.mdx
+++ b/tutorial/6-code-coverage/1-measuring.mdx
@@ -49,7 +49,7 @@ Pester v6 uses a profiler-based tracer by default, which is fast enough to leave
The module is at 100%, and it would be a mistake to read that as "fully tested".
-Coverage measures execution, not assertion. This test would give `ConvertTo-AstronomicalUnit` full coverage while checking nothing at all:
+Coverage measures execution, not assertion. This test would give `ConvertTo-AstronomicalUnit` full coverage while checking nothing at all. It is an illustration, do not add it:
```powershell
It 'Runs' {
diff --git a/tutorial/6-code-coverage/2-closing-the-gaps.mdx b/tutorial/6-code-coverage/2-closing-the-gaps.mdx
index e8be2b6..aa29e00 100644
--- a/tutorial/6-code-coverage/2-closing-the-gaps.mdx
+++ b/tutorial/6-code-coverage/2-closing-the-gaps.mdx
@@ -39,8 +39,41 @@ $result.CodeCoverage.CommandsMissed | Format-Table Function, Line, StartColumn,
The uncovered branch has two behaviours worth pinning: it refuses by default, and `-Force` overrides it. Add both tests at the bottom of the `Describe` block, below the four you already have:
```powershell title="Planetarium/Public/Export-PlanetReport.Tests.ps1"
+BeforeAll {
+ Import-Module "$PSScriptRoot/../Planetarium.psd1" -Force
+}
+
Describe 'Export-PlanetReport' {
- # ... the four existing It blocks ...
+ It 'Creates the report file' {
+ $path = Join-Path $TestDrive 'report.txt'
+
+ Test-Path -Path $path | Should-BeFalse
+ Export-PlanetReport -Path $path
+ Test-Path -Path $path | Should-BeTrue
+ }
+
+ It 'Writes one line per planet' {
+ $path = Join-Path $TestDrive 'all.txt'
+
+ Export-PlanetReport -Path $path
+
+ (Get-Content -Path $path).Count | Should-Be 8
+ }
+
+ It 'Writes the name and the distance in astronomical units' {
+ $path = Join-Path $TestDrive 'earth.txt'
+
+ Export-PlanetReport -Path $path -Name 'Earth'
+
+ Get-Content -Path $path | Should-Be 'Earth 1 AU'
+ }
+
+ It 'Throws when no planet matches' {
+ $path = Join-Path $TestDrive 'nothing.txt'
+
+ { Export-PlanetReport -Path $path -Name 'Pluto' } |
+ Should-Throw -ExceptionMessage "No planets matched 'Pluto'."
+ }
# diff-add-start
It 'Refuses to overwrite an existing report' {
@@ -86,10 +119,17 @@ Back at 100% coverage. Enjoy this rare moment.
When Code Coverage is enabled it writes a report to `./coverage.xml` by default that can be used by CI systems and other coverage reporting tools. You control the path using the `CodeCoverage.OutputPath` option - we'll just set the default explicit:
```powershell title="test.ps1"
+$config = New-PesterConfiguration
+$config.Run.Path = './Planetarium'
+$config.Output.Verbosity = 'Detailed'
+$config.TestResult.Enabled = $true
+$config.TestResult.OutputPath = './testResults.xml'
$config.CodeCoverage.Enabled = $true
$config.CodeCoverage.Path = './Planetarium'
# diff-add
$config.CodeCoverage.OutputPath = './coverage.xml'
+
+Invoke-Pester -Configuration $config
```
```xml title="coverage.xml"