Public/Tests/Edit-StringAtLine.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 'Edit-StringAtLine' {
    It 'Replaces only the first match' {
        'a two b two' | Edit-StringAtLine -StringToMatch 'two' -ReplacementString 'X' | Should -BeExactly 'a X b two'
    }

    It 'Treats StringToMatch as a regular expression' {
        'Version = 1.0' | Edit-StringAtLine -StringToMatch '\d+\.\d+' -ReplacementString '2.0' | Should -BeExactly 'Version = 2.0'
    }

    It 'Inserts the replacement literally (no $ substitutions)' {
        'cost' | Edit-StringAtLine -StringToMatch 'cost' -ReplacementString '$5 $0' | Should -BeExactly '$5 $0'
    }

    It 'Replaces on the correct line of multi-line input' {
        $result = "one`ntwo`nthree" | Edit-StringAtLine -StringToMatch 'two' -ReplacementString 'TWO' -Multiline
        $result | Should -Be @('one', 'TWO', 'three')
    }

    It 'Indents additional replacement lines to the column of the match' {
        $result = "one`n key = old`nthree" | Edit-StringAtLine -StringToMatch 'key = old' -ReplacementString "key = new`nother = 1" -Multiline
        $result | Should -Be @('one', ' key = new', ' other = 1', 'three')
    }

    It 'Joins lines with CRLF when -Multiline is not used' {
        "a`nb" | Edit-StringAtLine -StringToMatch 'b' -ReplacementString 'B' | Should -BeExactly "a`r`nB"
    }

    It 'Returns the input unchanged when there is no match' {
        'nothing here' | Edit-StringAtLine -StringToMatch 'zzz' -ReplacementString 'x' | Should -BeExactly 'nothing here'
        "a`nb" | Edit-StringAtLine -StringToMatch 'zzz' -ReplacementString 'x' -Multiline | Should -Be @('a', 'b')
    }

    It 'Handles a match at the very start of the input' {
        "abc`ndef" | Edit-StringAtLine -StringToMatch 'abc' -ReplacementString "X`nY" -Multiline | Should -Be @('X', 'Y', 'def')
    }

    It 'Processes each piped string' {
        'a1', 'b1' | Edit-StringAtLine -StringToMatch '1' -ReplacementString '2' | Should -Be @('a2', 'b2')
    }

    It 'Accepts InputString by property name' {
        [PSCustomObject]@{ InputString = 'hello world' } | Edit-StringAtLine -StringToMatch 'world' -ReplacementString 'there' | Should -BeExactly 'hello there'
    }

    It 'Requires StringToMatch' {
        (Get-Command -Name Edit-StringAtLine).Parameters['StringToMatch'].Attributes.Where({ $_ -is [System.Management.Automation.ParameterAttribute] }).Mandatory | Should -BeTrue
    }

    Context 'Telemetry' {
        BeforeAll {
            Mock -ModuleName tcs.utils Invoke-TelemetryCollection { }
        }

        It 'Sends one Start and one End event for piped input' {
            'a1', 'b1' | Edit-StringAtLine -StringToMatch '1' -ReplacementString '2' | Should -Be @('a2', 'b2')
            Should -Invoke -ModuleName tcs.utils Invoke-TelemetryCollection -Times 1 -Exactly -ParameterFilter { $Stage -eq 'Start' -and $CommandName -eq 'Edit-StringAtLine' }
            Should -Invoke -ModuleName tcs.utils Invoke-TelemetryCollection -Times 1 -Exactly -ParameterFilter { $Stage -eq 'End' -and -not $Failed }
        }

        It 'Sends one failed End event and still throws for an invalid regular expression' {
            { 'a(', 'b(' | Edit-StringAtLine -StringToMatch '(' -ReplacementString 'x' } | Should -Throw
            Should -Invoke -ModuleName tcs.utils Invoke-TelemetryCollection -Times 1 -Exactly -ParameterFilter { $Stage -eq 'End' -and $Failed -eq $true }
            Should -Invoke -ModuleName tcs.utils Invoke-TelemetryCollection -Times 0 -Exactly -ParameterFilter { $Stage -eq 'End' -and -not $Failed }
        }
    }
}