Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions src/theme/CodeBlock/Buttons/CopyButton/index.js
Original file line number Diff line number Diff line change
@@ -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 <CopyButton {...props} />;
}

// Re-provide the context so only the copy button sees the trimmed code.
return (
<CodeBlockContextProvider metadata={copyMetadata} wordWrap={wordWrap}>
<CopyButton {...props} />
</CodeBlockContextProvider>
);
}
4 changes: 2 additions & 2 deletions tutorial/2-testing-a-module/1-setup.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = '*'
```

Expand Down
4 changes: 2 additions & 2 deletions tutorial/2-testing-a-module/3-public-functions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
2 changes: 1 addition & 1 deletion tutorial/2-testing-a-module/4-private-functions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
14 changes: 10 additions & 4 deletions tutorial/3-organising-tests/2-choosing-what-runs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down
51 changes: 38 additions & 13 deletions tutorial/4-mocking/3-verifying-calls.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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' {
Expand All @@ -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
Expand All @@ -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.
:::
Expand Down
2 changes: 1 addition & 1 deletion tutorial/6-code-coverage/1-measuring.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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' {
Expand Down
42 changes: 41 additions & 1 deletion tutorial/6-code-coverage/2-closing-the-gaps.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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' {
Expand Down Expand Up @@ -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"
Expand Down