Public/Tests/Update-ModuleManifestExports.Tests.ps1

BeforeAll {
    $env:TCS_CONFIG_ROOT = Join-Path -Path $TestDrive -ChildPath 'config'
    $env:TCS_SKIP_UPDATE_CHECK = '1'
    $env:TCS_TELEMETRY_OPTOUT = '1'
    $ModuleRoot = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent
    Import-Module -Name (Join-Path -Path $ModuleRoot -ChildPath 'tcs.utils.psd1') -Force
}

AfterAll {
    Remove-Module -Name tcs.utils -Force -ErrorAction SilentlyContinue
}

Describe 'Update-ModuleManifestExports' {
    BeforeEach {
        $Module = Join-Path -Path $TestDrive -ChildPath ([guid]::NewGuid().ToString('N'))
        $Folder = Join-Path -Path $Module -ChildPath 'my.mod'
        $null = New-Item -Path (Join-Path -Path $Folder -ChildPath 'Public/Generated/Functions') -ItemType Directory -Force
        $null = New-Item -Path (Join-Path -Path $Folder -ChildPath 'Public/Tests') -ItemType Directory -Force
        $Manifest = Join-Path -Path $Folder -ChildPath 'my.mod.psd1'
        Set-Content -Path (Join-Path -Path $Folder -ChildPath 'Public/Get-B.ps1') -Value "function Get-B { function Inner { } }`nfunction Get-A { }"
        Set-Content -Path (Join-Path -Path $Folder -ChildPath 'Public/Generated/Functions/Set-C.ps1') -Value 'function Set-C { }'
        Set-Content -Path (Join-Path -Path $Folder -ChildPath 'Public/Tests/Get-B.Tests.ps1') -Value 'function Get-TestOnly { }'
        Set-Content -Path (Join-Path -Path $Folder -ChildPath 'Public/Get-B.Tests.ps1') -Value 'function Get-AlsoTestOnly { }'
    }

    It 'Replaces a multi-line FunctionsToExport list with the top-level public functions' {
        [System.IO.File]::WriteAllText($Manifest, "@{`r`n RootModule = 'my.mod.psm1'`r`n FunctionsToExport = @(`r`n 'Old-One'`r`n )`r`n CmdletsToExport = @()`r`n}`r`n")
        $result = Update-ModuleManifestExports -ModulePath $Folder
        $result.Updated | Should -BeTrue
        $result.FunctionsToExport | Should -Be @('Get-A', 'Get-B', 'Set-C')
        (Import-PowerShellDataFile -Path $Manifest).FunctionsToExport | Should -Be @('Get-A', 'Get-B', 'Set-C')
        $text = [System.IO.File]::ReadAllText($Manifest)
        $text | Should -Match "CmdletsToExport = @\(\)"
        ($text -replace "`r`n", '') | Should -Not -Match "`n" -Because 'CRLF line endings are kept'
    }

    It 'Adds FunctionsToExport when the manifest has none and keeps the BOM' {
        [System.IO.File]::WriteAllText($Manifest, "@{`n RootModule = 'my.mod.psm1'`n Description = 'caf$([char]0xE9)'`n}`n", (New-Object System.Text.UTF8Encoding -ArgumentList $true))
        $null = Update-ModuleManifestExports -ModulePath $Manifest
        (Import-PowerShellDataFile -Path $Manifest).FunctionsToExport | Should -Be @('Get-A', 'Get-B', 'Set-C')
        $bytes = [System.IO.File]::ReadAllBytes($Manifest)
        $bytes[0] | Should -Be 0xEF
        (Import-PowerShellDataFile -Path $Manifest).Description | Should -Be "caf$([char]0xE9)"
    }

    It 'Does not write when the list is unchanged or with -WhatIf' {
        Set-Content -Path $Manifest -Value "@{ FunctionsToExport = @('Get-A', 'Get-B', 'Set-C') }"
        (Update-ModuleManifestExports -ModulePath $Folder).Updated | Should -BeFalse
        Set-Content -Path $Manifest -Value "@{ FunctionsToExport = '*' }"
        $result = Update-ModuleManifestExports -ModulePath $Folder -WhatIf
        $result.Changed | Should -BeTrue
        $result.Updated | Should -BeFalse
        (Import-PowerShellDataFile -Path $Manifest).FunctionsToExport | Should -Be '*'
    }

    It 'Writes an error when the manifest does not exist' {
        { Update-ModuleManifestExports -ModulePath $Folder -ErrorAction Stop } | Should -Throw '*not found*'
    }
}