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"