ClipboardText.Tests.ps1
<# Note: * Make sure this file is saved as *UT8 with BOM*, so that literal non-ASCII characters are interpreted correctly. * When run in WinPSv3+, an attempt is made to run the tests in WinPSv2 too, but note that requires prior installation of v2 support. Also, in PSv2, Pester must be loaded *manually*, via the *full path to its *.psd1 file* (seemingly, v2 doesn't find modules located in \<version>\ subdirs.). For interactive use, the simplest approach is to invoke v2 as follows: powershell.exe -version 2 -Command "Import-Module '$((Get-Module Pester).Path)' #> # Abort on all unhandled errors. $ErrorActionPreference = 'Stop' # PSv2 compatibility: makes sure that $PSScriptRoot reflects this script's folder. if (-not $PSScriptRoot) { $PSScriptRoot = $MyInvocation.MyCommand.Path } # Turn on the latest strict mode, so as to make sure that the ScriptsToProcess # script that runs the prerequisites-check script dot-sourced also works # properly when the caller happens to run with Set-StrictMode -Version Latest # in effect. Set-StrictMode -Version Latest # Make sure that any loaded module by the same name is first unloaded # and then force-load the enclosing module. Remove-Module -ErrorAction SilentlyContinue ([IO.Path]::GetFileName($PSScriptRoot)) # !! In PSv2, this statement causes Pester to run all tests TWICE (?!) Import-Module $PSScriptRoot -Force # Use the platform-appropiate newline. $nl = [Environment]::NewLine # See if we're running on *Windows PowerShell* $isWinPs = $null, 'Desktop' -contains $PSVersionTable.PSEdition Describe StringInputTest { It "Copies and pastes a string correctly." { $string = "Here at $(Get-Date)" Set-ClipboardText $string Get-ClipboardText | Should -BeExactly $string } It "Correctly round-trips non-ASCII characters." { $string = 'Thomas Hübl''s talk about 中文' $string | Set-ClipboardText Get-ClipboardText | Should -BeExactly $string } It "Outputs an array of lines by default" { $lines = 'one', 'two' $string = $lines -join [Environment]::NewLine Set-ClipboardText $string Get-ClipboardText | Should -BeExactly $lines } It "Retrieves a multi-line string as-is with -Raw and doesn't append an extra newline." { "2 lines${nl}with 1 trailing newline${nl}" | Set-ClipboardText Get-ClipboardText -Raw | Should -Not -Match '(\r?\n){2}\z' } } Describe EmptyTests { BeforeEach { 'dummy' | Set-ClipboardText # make sure we start with a nonempty clipboard so we can verify that clearing is effective } It "Not providing input effectively clears the clipboard." { Set-ClipboardText # no input $null -eq (Get-ClipboardText -Raw) | Should -BeTrue } It "Passing the empty string effectively clears the clipboard." { Set-ClipboardText -InputObject '' # Note The PsWinV5+ Set-Clipboard reports a spurious error with '', which we mask behind the scenes. $null -eq (Get-ClipboardText -Raw) | Should -BeTrue } It "Passing `$null effectively clears the clipboard." { Set-ClipboardText -InputObject $null $null -eq (Get-ClipboardText -Raw) | Should -BeTrue } } Describe PassThruTest { It "Set-ClipboardText -PassThru also outputs the text." { $in = "line 1${nl}line 2" $out = $in | Set-ClipboardText -PassThru Get-ClipboardText -Raw | Should -BeExactly $in $out | Should -BeExactly $in } } Describe CommandInputTest { It "Copies and pastes a PowerShell command's output correctly" { Get-Item / | Set-ClipboardText # Note: Inside Set-ClipboardText we remove the trailing newline that # Out-String invariably adds, so we must do the same here. $shouldBe = (Get-Item / | Out-String) -replace '\r?\n\z' $pasted = Get-ClipboardText -Raw $pasted | Should -BeExactly $shouldBe } It "Copies and pastes an external program's output correctly" { # Note: whoami without argument works on all supported platforms. whoami | Set-ClipboardText $shouldBe = whoami $is = Get-ClipboardText $is | Should -Be $shouldBe } } Describe OutputWidthTest { BeforeAll { # A custom object that is implicitly formatted with Format-Table with # 2 columns. $obj = [pscustomobject] @{ one = '1' * 40; two = '2' * 216 } } It "Truncates lines that are too wide for the specified width" { $obj | Set-ClipboardText -Width 80 # Note: [3] - the *4th* line - is the line with the two column values in all editions. # [-2] to use the penultimate line is NOT reliable, as the editions differ in # the number of trailing newlines. (Get-ClipboardText)[3] | Should -Match '(\.\.\.|…)$' # Note: At some point, PS Core started using the '…' (horizontal ellipsis) Unicode char. instead of three periods. } It "Allows incrementing the width to accommodate wider lines" { $obj | Set-ClipboardText -Width 257 # 40 + 1 (space between columns) + 216 (Get-ClipboardText)[3].TrimEnd() | Should -BeLikeExactly '*2' } } # Note: These tests apply to PS *Core* only, because Windows PowerShell doesn't require external utilities for clipboard support. Describe MissingExternalUtilityTest { # We skip these tests in *Windows PowerShell*, because Windows Powershell # does't require external utilities for access to the clipboard. # Note: We don't exit right away, because do want to invoke the `It` block # with `-Skip` set to $True, so that the results indicated that the test # was deliberately skipped. if (-not $isWinPs) { # Determine the name of the module being tested. # For a Mock to be effective in the target module's context, it must be # defined with -ModuleName <name>. $thisModuleName = (Split-Path -Leaf $PSScriptRoot) # Define the platform-appropiate mocks for calling the external clipboard # utilities. # Note: Since mocking by full executable path isn't supported, we use # helper function invoke-External. # macOS, Linux: Mock invoke-External -ParameterFilter { LiteralPath -eq '/bin/sh' } { /bin/sh -c 'nosuchexe' } -ModuleName $thisModuleName # Windows: Mock invoke-External -ParameterFilter { LiteralPath -eq "$env:SystemRoot\System32\cmd.exe" } { & "$env:SystemRoot\System32\cmd.exe" /c 'nosuchexe' } -ModuleName $thisModuleName } It "PS Core: Generates a statement-terminating error when the required external utility is not present" -Skip:$isWinPs { { 'dummy' | Set-ClipboardText 2>$null } | Should -Throw } } Describe MTAtests { # Windows PowerShell: # A WinForms text-box workaround is needed when PowerShell is running in COM MTA # (multi-threaded apartment) mode. # By default, v2 runs in MTA mode and v3+ in STA mode. # However, you can *opt into* MTA mode in v3+, and the workaround is then needed too. # (In PSCore on Windows, MTA is the default again, but it has no access to WinForms # anyway and uses external utility clip.exe instead.) It "Windows PowerShell: Works in MTA mode" -Skip:(-not $isWinPs -or $PSVersionTable.PSVersion.Major -eq 2) { # Recursively invokes the 'StringInputTest' tests. # !! This produces NO OUTPUT; to troubleshoot, run the command interactively from the project folder. # !! As of Windows PowerShell v5.1.18362.145 on Microsoft Windows 10 Pro (64-bit; Version 1903, OS Build: 18362.175), # !! `Get-Command -Name Add-Member, Get-ChildItem` must be executed BEFORE invoking Pester; without it, # !! Pester inexplicably fails to locate these commands during module import and cannot be loaded. powershell.exe -noprofile -MTA -Command "if ([threading.thread]::CurrentThread.ApartmentState.ToString() -ne 'MTA') { Throw "Not in MTA mode." }; Get-Command -Name Add-Member, Get-ChildItem; Invoke-Pester -Name StringInputTest -EnableExit" $LASTEXITCODE | Should -Be 0 } } Describe v2Tests { # Invoke these tests in *WinPS v2*, which amounts to a RECURSION. # Therefore, EXECUTION TAKES A WHILE. It "Windows PowerShell: Passes all tests in v2 as well." -Skip:(-not $isWinPs -or $PSVersionTable.PSVersion.Major -eq 2) { # !! An Install-Module-installed Pester is located in a version-named subfolder, which v2 cannot # !! detect, so we import Pester by explicit path. # !! Also `-version 2` must be the *first* argument passed to `powershell.exe`. # # !! NO OUTPUT IS PRODUCED - to troubleshoot, run the command interactively from the project folder. # !! Notably, *prior installation of v2 support is needed*, and PowerShell seems to quietly ignore `-version 2` # !! in its absence, so we have to test from *within* the session. powershell.exe -version 2 -noprofile -Command "Set-StrictMode -Version Latest; Import-Module '$((Get-Module Pester).Path)'; if (`$PSVersionTable.PSVersion.Major -ne 2) { Throw 'v2 SUPPORT IS NOT INSTALLED.' }; Invoke-Pester" $LASTEXITCODE | Should -Be 0 } } |