Tests/SharpDown.Tests.ps1
|
#### <h1 style="color: #DCA657;">🧪 SharpDown.Tests</h1> #### #### > Pester unit tests for `ConvertTo-SharpDown`. Ported from the legacy Test-SharpDown.ps1 smoke harness. #### #### The temp-path helpers live in BeforeAll because Pester 5 only exposes functions defined there #### to the It blocks at run time. Top-level function definitions run during discovery and vanish. BeforeAll { Import-Module (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -ChildPath 'Sharpdown.psd1') -Force function New-TempLeafPath { param([string]$Extension = '') Join-Path ([IO.Path]::GetTempPath()) ([Guid]::NewGuid().ToString('N') + $Extension) } function New-TempWorkspace { $workspaceRoot = Join-Path ([IO.Path]::GetTempPath()) ([Guid]::NewGuid().ToString('N')) New-Item -ItemType Directory -Path $workspaceRoot | Out-Null return $workspaceRoot } } AfterAll { Remove-Module Sharpdown -Force -ErrorAction SilentlyContinue } #### --- #### <h2 style="color: #DCA657;">CSharp file mode</h2> #### #### Single-file conversion of C# sources. #### #### <b style="color: #D2A8FF;">Cases</b> #### Describe 'CSharp file mode' { #### - Strips the `////` marker and fences the declaration lines. #### It 'Strips //// markers and fences declaration lines' { $sourceFile = New-TempLeafPath -Extension '.cs' $outputFile = New-TempLeafPath -Extension '.md' try { $sample = @( '//// # WookiePuddin', '//// > A sweet, fictional concoction.', '////', 'public class WookiePuddin', '{', ' public string Flavor { get; init; }', '}' ) -join [Environment]::NewLine Set-Content -LiteralPath $sourceFile -Value $sample -Encoding utf8NoBOM $result = ConvertTo-SharpDown -Language CSharp -Path $sourceFile -OutPath $outputFile Test-Path -LiteralPath $outputFile | Should -BeTrue $rendered = Get-Content -LiteralPath $outputFile -Raw $rendered | Should -Match '# WookiePuddin' $rendered | Should -Match '> A sweet, fictional concoction\.' $rendered | Should -Match '(?ms)```csharp\s*\r?\npublic class WookiePuddin\s*\r?\n```' $rendered | Should -Match '(?ms)```csharp\s*\r?\npublic string Flavor[^\n]*\r?\n```' $result.Lines | Should -BeGreaterThan 0 } finally { Remove-Item -LiteralPath $sourceFile -Force -ErrorAction SilentlyContinue Remove-Item -LiteralPath $outputFile -Force -ErrorAction SilentlyContinue } } #### - Drops lines that are neither markers nor declarations. #### It 'Skips lines that are neither markers nor declarations' { $sourceFile = New-TempLeafPath -Extension '.cs' $outputFile = New-TempLeafPath -Extension '.md' try { $sample = @( '//// # Visible', 'public class Visible { }', '// regular comment, should be dropped', 'var ignored = 42;', 'private string hiddenField;' ) -join [Environment]::NewLine Set-Content -LiteralPath $sourceFile -Value $sample -Encoding utf8NoBOM ConvertTo-SharpDown -Language CSharp -Path $sourceFile -OutPath $outputFile | Out-Null $rendered = Get-Content -LiteralPath $outputFile -Raw $rendered | Should -Match 'public class Visible' $rendered | Should -Not -Match 'regular comment' $rendered | Should -Not -Match 'var ignored' $rendered | Should -Not -Match 'hiddenField' } finally { Remove-Item -LiteralPath $sourceFile -Force -ErrorAction SilentlyContinue Remove-Item -LiteralPath $outputFile -Force -ErrorAction SilentlyContinue } } #### - Throws when the source path is not a file. #### It 'Throws when File path is not a file' { $missing = New-TempLeafPath -Extension '.cs' $output = New-TempLeafPath -Extension '.md' { ConvertTo-SharpDown -Language CSharp -Path $missing -OutPath $output } | Should -Throw } #### - Throws when `-OutPath` points at an existing directory. #### It 'Throws when File OutPath points to an existing directory' { $sourceFile = New-TempLeafPath -Extension '.cs' $outputDir = New-TempWorkspace try { Set-Content -LiteralPath $sourceFile -Value '//// # Anything' -Encoding utf8NoBOM { ConvertTo-SharpDown -Language CSharp -Path $sourceFile -OutPath $outputDir } | Should -Throw } finally { Remove-Item -LiteralPath $sourceFile -Force -ErrorAction SilentlyContinue Remove-Item -LiteralPath $outputDir -Recurse -Force -ErrorAction SilentlyContinue } } #### - Warns when the source holds no SharpDown content. #### It 'Warns when source has no SharpDown content' { $sourceFile = New-TempLeafPath -Extension '.cs' $outputFile = New-TempLeafPath -Extension '.md' try { Set-Content -LiteralPath $sourceFile -Value '// nothing to see here' -Encoding utf8NoBOM $warnings = @() ConvertTo-SharpDown -Language CSharp -Path $sourceFile -OutPath $outputFile -WarningVariable warnings -WarningAction SilentlyContinue 3> $null 6> $null $warnings.Count | Should -BeGreaterThan 0 } finally { Remove-Item -LiteralPath $sourceFile -Force -ErrorAction SilentlyContinue Remove-Item -LiteralPath $outputFile -Force -ErrorAction SilentlyContinue } } } #### --- #### <h2 style="color: #DCA657;">Language configs</h2> #### #### Per-language markers, fences, and the `-API` switch. #### #### <b style="color: #D2A8FF;">Cases</b> #### Describe 'Language configs' { #### - SQL: the `----` marker fences `CREATE` statements. #### It 'Sql marker ---- fences CREATE statements' { $sourceFile = New-TempLeafPath -Extension '.sql' $outputFile = New-TempLeafPath -Extension '.md' try { $sample = @( '---- # GetThot', '---- > Returns a thot by id.', 'CREATE OR REPLACE FUNCTION get_thot(p_id BIGINT)' ) -join [Environment]::NewLine Set-Content -LiteralPath $sourceFile -Value $sample -Encoding utf8NoBOM ConvertTo-SharpDown -Language Sql -Path $sourceFile -OutPath $outputFile | Out-Null $rendered = Get-Content -LiteralPath $outputFile -Raw $rendered | Should -Match '# GetThot' $rendered | Should -Match '(?ms)```sql\s*\r?\nCREATE OR REPLACE FUNCTION[^\n]*\r?\n```' } finally { Remove-Item -LiteralPath $sourceFile -Force -ErrorAction SilentlyContinue Remove-Item -LiteralPath $outputFile -Force -ErrorAction SilentlyContinue } } #### - PowerShell: the `####` marker fences function declarations. #### It 'PowerShell marker #### fences function declarations' { $sourceFile = New-TempLeafPath -Extension '.ps1' $outputFile = New-TempLeafPath -Extension '.md' try { $sample = @( '#### # New-Whatever', '#### > Does a thing.', 'function New-Whatever {', ' param([string]$Name)', '}' ) -join [Environment]::NewLine Set-Content -LiteralPath $sourceFile -Value $sample -Encoding utf8NoBOM ConvertTo-SharpDown -Language PowerShell -Path $sourceFile -OutPath $outputFile | Out-Null $rendered = Get-Content -LiteralPath $outputFile -Raw $rendered | Should -Match '# New-Whatever' $rendered | Should -Match '(?ms)```powershell\s*\r?\nfunction New-Whatever[^\n]*\r?\n```' } finally { Remove-Item -LiteralPath $sourceFile -Force -ErrorAction SilentlyContinue Remove-Item -LiteralPath $outputFile -Force -ErrorAction SilentlyContinue } } #### - JavaScript: the `////` marker fences `export class` declarations. #### It 'JavaScript marker //// fences export class declarations' { $sourceFile = New-TempLeafPath -Extension '.js' $outputFile = New-TempLeafPath -Extension '.md' try { $sample = @( '//// # SxThing', '//// > A Lit element.', 'export class SxThing extends LitElement {', '}' ) -join [Environment]::NewLine Set-Content -LiteralPath $sourceFile -Value $sample -Encoding utf8NoBOM ConvertTo-SharpDown -Language JavaScript -Path $sourceFile -OutPath $outputFile | Out-Null $rendered = Get-Content -LiteralPath $outputFile -Raw $rendered | Should -Match '# SxThing' $rendered | Should -Match '(?ms)```javascript\s*\r?\nexport class SxThing[^\n]*\r?\n```' } finally { Remove-Item -LiteralPath $sourceFile -Force -ErrorAction SilentlyContinue Remove-Item -LiteralPath $outputFile -Force -ErrorAction SilentlyContinue } } #### - `-API` suppresses every auto-generated declaration fence. #### It '-API suppresses all auto-generated declaration fences' { $sourceFile = New-TempLeafPath -Extension '.ts' $outputFile = New-TempLeafPath -Extension '.md' try { $sample = @( '//// # MyEndpoint', '//// `GET /things`', 'import { thing } from "thing";', 'type ThingShape = { id: string };', 'function ThingHelper(): void { }' ) -join [Environment]::NewLine Set-Content -LiteralPath $sourceFile -Value $sample -Encoding utf8NoBOM ConvertTo-SharpDown -Language JavaScript -Path $sourceFile -OutPath $outputFile -API | Out-Null $rendered = Get-Content -LiteralPath $outputFile -Raw $rendered | Should -Match '# MyEndpoint' $rendered | Should -Match '`GET /things`' $rendered | Should -Not -Match '```javascript' $rendered | Should -Not -Match 'import \{ thing' $rendered | Should -Not -Match 'type ThingShape' $rendered | Should -Not -Match 'function ThingHelper' } finally { Remove-Item -LiteralPath $sourceFile -Force -ErrorAction SilentlyContinue Remove-Item -LiteralPath $outputFile -Force -ErrorAction SilentlyContinue } } #### - JavaScript keeps top-level function and type decls, drops interior locals. #### It 'JavaScript drops interior const/let locals but keeps top-level function and type declarations' { $sourceFile = New-TempLeafPath -Extension '.ts' $outputFile = New-TempLeafPath -Extension '.md' try { $sample = @( '//// # ThingInput', 'type ThingInput = { name: string };', '', '//// # MakeThing', 'async function MakeThing(req: Request): Promise<ThingInput> {', ' const url = "/x";', ' let count = 0;', ' const body = await req.json();', ' return body;', '}', '', '//// # Plain', 'function Plain(): void {', ' const inner = 1;', '}' ) -join [Environment]::NewLine Set-Content -LiteralPath $sourceFile -Value $sample -Encoding utf8NoBOM ConvertTo-SharpDown -Language JavaScript -Path $sourceFile -OutPath $outputFile | Out-Null $rendered = Get-Content -LiteralPath $outputFile -Raw $rendered | Should -Match '# ThingInput' $rendered | Should -Match '(?ms)```javascript\s*\r?\ntype ThingInput[^\n]*\r?\n```' $rendered | Should -Match '(?ms)```javascript\s*\r?\nasync function MakeThing[^\n]*\r?\n```' $rendered | Should -Match '(?ms)```javascript\s*\r?\nfunction Plain[^\n]*\r?\n```' $rendered | Should -Not -Match 'const url' $rendered | Should -Not -Match 'let count' $rendered | Should -Not -Match 'const body' $rendered | Should -Not -Match 'const inner' } finally { Remove-Item -LiteralPath $sourceFile -Force -ErrorAction SilentlyContinue Remove-Item -LiteralPath $outputFile -Force -ErrorAction SilentlyContinue } } } #### --- #### <h2 style="color: #DCA657;">Directory mode</h2> #### #### Recursive tree walking, mirroring, and pipeline input. #### #### <b style="color: #D2A8FF;">Cases</b> #### Describe 'Directory mode' { #### - Throws when the `-Recurse` path is not a directory. #### It 'Throws when -Recurse path is not a directory' { $missing = New-TempLeafPath $output = New-TempLeafPath { ConvertTo-SharpDown -Language CSharp -Path $missing -OutPath $output -Recurse } | Should -Throw } #### - Throws when `-OutPath` points at an existing file. #### It 'Throws when -Recurse OutPath points to an existing file' { $workspaceRoot = New-TempWorkspace $outputFile = New-TempLeafPath -Extension '.md' try { Set-Content -LiteralPath $outputFile -Value 'occupied' -Encoding utf8NoBOM { ConvertTo-SharpDown -Language CSharp -Path $workspaceRoot -OutPath $outputFile -Recurse } | Should -Throw } finally { Remove-Item -LiteralPath $workspaceRoot -Recurse -Force -ErrorAction SilentlyContinue Remove-Item -LiteralPath $outputFile -Force -ErrorAction SilentlyContinue } } #### - Without `-Recurse`, a directory path throws as not-a-file. #### It 'Without -Recurse, a directory path throws as not-a-file' { $workspaceRoot = New-TempWorkspace $output = New-TempLeafPath -Extension '.md' try { { ConvertTo-SharpDown -Language CSharp -Path $workspaceRoot -OutPath $output } | Should -Throw } finally { Remove-Item -LiteralPath $workspaceRoot -Recurse -Force -ErrorAction SilentlyContinue } } #### - Mirrors the tree, stripping `src/` and project-name legs. #### It 'Mirrors tree, strips src/ and project name legs' { $workspaceRoot = New-TempWorkspace $outputRoot = New-TempWorkspace try { $projectName = 'WookieProject' $projectRoot = Join-Path $workspaceRoot $projectName $serviceDir = Join-Path $projectRoot 'src' | Join-Path -ChildPath $projectName | Join-Path -ChildPath 'Service' New-Item -ItemType Directory -Path $serviceDir -Force | Out-Null $wookieSourcePath = Join-Path $serviceDir 'WookieService.cs' $wookieSample = @( '//// # WookieService', 'public class WookieService { }' ) -join [Environment]::NewLine Set-Content -LiteralPath $wookieSourcePath -Value $wookieSample -Encoding utf8NoBOM $excludedDir = Join-Path $projectRoot 'bin' New-Item -ItemType Directory -Path $excludedDir -Force | Out-Null Set-Content -LiteralPath (Join-Path $excludedDir 'Skip.cs') -Value '//// # Skipped' -Encoding utf8NoBOM ConvertTo-SharpDown -Language CSharp -Path $projectRoot -OutPath $outputRoot -Recurse 6> $null $expectedOutput = Join-Path $outputRoot 'Service' | Join-Path -ChildPath 'WookieService.md' Test-Path -LiteralPath $expectedOutput | Should -BeTrue $rendered = Get-Content -LiteralPath $expectedOutput -Raw $rendered | Should -Match '# WookieService' $skippedOutput = Get-ChildItem -Path $outputRoot -Recurse -Filter 'Skip.md' -File -ErrorAction SilentlyContinue $skippedOutput | Should -BeNullOrEmpty } finally { Remove-Item -LiteralPath $workspaceRoot -Recurse -Force -ErrorAction SilentlyContinue Remove-Item -LiteralPath $outputRoot -Recurse -Force -ErrorAction SilentlyContinue } } #### - Accepts a piped `DirectoryInfo` as `-Path` under `-Recurse`. #### It 'Accepts a piped DirectoryInfo as -Path under -Recurse' { $workspaceRoot = New-TempWorkspace $outputRoot = New-TempWorkspace try { $projectName = 'PipedWookie' $projectRoot = Join-Path $workspaceRoot $projectName New-Item -ItemType Directory -Path $projectRoot -Force | Out-Null $sourceFile = Join-Path $projectRoot 'PipedWookie.cs' Set-Content -LiteralPath $sourceFile -Value (@( '//// # PipedWookie', 'public class PipedWookie { }' ) -join [Environment]::NewLine) -Encoding utf8NoBOM Get-Item -LiteralPath $projectRoot | ConvertTo-SharpDown -Language CSharp -OutPath $outputRoot -Recurse 6> $null $expectedOutput = Join-Path $outputRoot 'PipedWookie.md' Test-Path -LiteralPath $expectedOutput | Should -BeTrue $rendered = Get-Content -LiteralPath $expectedOutput -Raw $rendered | Should -Match '# PipedWookie' } finally { Remove-Item -LiteralPath $workspaceRoot -Recurse -Force -ErrorAction SilentlyContinue Remove-Item -LiteralPath $outputRoot -Recurse -Force -ErrorAction SilentlyContinue } } #### - Processes multiple piped directories in one invocation. #### It 'Processes multiple directories piped into a single invocation' { $workspaceRoot = New-TempWorkspace $outputRoot = New-TempWorkspace try { foreach ($name in 'AlphaProject', 'BetaProject') { $projectRoot = Join-Path $workspaceRoot $name New-Item -ItemType Directory -Path $projectRoot -Force | Out-Null Set-Content -LiteralPath (Join-Path $projectRoot "$name.cs") -Value (@( "//// # $name", "public class $name { }" ) -join [Environment]::NewLine) -Encoding utf8NoBOM } Get-ChildItem -Path $workspaceRoot -Directory | ConvertTo-SharpDown -Language CSharp -OutPath $outputRoot -Recurse 6> $null | Out-Null Test-Path -LiteralPath (Join-Path $outputRoot 'AlphaProject.md') | Should -BeTrue Test-Path -LiteralPath (Join-Path $outputRoot 'BetaProject.md') | Should -BeTrue } finally { Remove-Item -LiteralPath $workspaceRoot -Recurse -Force -ErrorAction SilentlyContinue Remove-Item -LiteralPath $outputRoot -Recurse -Force -ErrorAction SilentlyContinue } } #### - Warns when no files match the language extensions. #### It 'Warns when directory has no matching extensions' { $workspaceRoot = New-TempWorkspace $outputRoot = New-TempWorkspace try { Set-Content -LiteralPath (Join-Path $workspaceRoot 'notes.txt') -Value 'no code here' -Encoding utf8NoBOM $warnings = @() ConvertTo-SharpDown -Language CSharp -Path $workspaceRoot -OutPath $outputRoot -Recurse -WarningVariable warnings -WarningAction SilentlyContinue 3> $null 6> $null $warnings.Count | Should -BeGreaterThan 0 } finally { Remove-Item -LiteralPath $workspaceRoot -Recurse -Force -ErrorAction SilentlyContinue Remove-Item -LiteralPath $outputRoot -Recurse -Force -ErrorAction SilentlyContinue } } } |