Tests/Tests-PSColorStyle.ps1

#Requires -Modules Pester

BeforeAll {
    # Importing the module makes [PSColorStyle] available
    $ModuleRoot = Split-Path -Parent $PSScriptRoot
    Import-Module "$ModuleRoot\PSWriteColorEX.psd1" -Force
}

Describe 'PSColorStyle Class' -Tag 'Unit', 'Class' {

    Context 'Constructors' {
        It 'Creates instance with default constructor' {
            $style = [PSColorStyle]::new()

            $style | Should -Not -BeNullOrEmpty
            $style.Name | Should -Be 'Custom'
            $style.ForegroundColor | Should -Be 'Gray'
            $style.BackgroundColor | Should -BeNullOrEmpty
        }

        It 'Creates instance with name only' {
            $style = [PSColorStyle]::new('TestStyle')

            $style.Name | Should -Be 'TestStyle'
            $style.ForegroundColor | Should -Be 'Gray'
            $style.BackgroundColor | Should -BeNullOrEmpty
        }

        It 'Creates instance with name, foreground, and background' {
            $style = [PSColorStyle]::new('TestStyle', 'Red', 'Blue')

            $style.Name | Should -Be 'TestStyle'
            $style.ForegroundColor | Should -Be 'Red'
            $style.BackgroundColor | Should -Be 'Blue'
        }

        It 'Accepts null background color' {
            $style = [PSColorStyle]::new('TestStyle', 'Red', $null)

            $style.Name | Should -Be 'TestStyle'
            $style.ForegroundColor | Should -Be 'Red'
            $style.BackgroundColor | Should -BeNullOrEmpty
        }
    }

    Context 'Property Initialization' {
        BeforeAll {
            $style = [PSColorStyle]::new('TestStyle', 'Red', 'Blue')
        }

        It 'Initializes boolean properties to false' {
            $style.Bold | Should -Be $false
            $style.Italic | Should -Be $false
            $style.Underline | Should -Be $false
            $style.Blink | Should -Be $false
            $style.Faint | Should -Be $false
            $style.CrossedOut | Should -Be $false
            $style.DoubleUnderline | Should -Be $false
            $style.Overline | Should -Be $false
            $style.ShowTime | Should -Be $false
            $style.NoNewLine | Should -Be $false
            $style.HorizontalCenter | Should -Be $false
        }

        It 'Initializes numeric properties to zero' {
            $style.StartTab | Should -Be 0
            $style.StartSpaces | Should -Be 0
            $style.LinesBefore | Should -Be 0
            $style.LinesAfter | Should -Be 0
        }

        It 'Initializes Style as empty or null' {
            # Style can be null or empty array
            if ($style.Style) {
                $style.Style.Count | Should -Be 0
            } else {
                $style.Style | Should -BeNullOrEmpty
            }
        }

        It 'Initializes Gradient as null' {
            $style.Gradient | Should -BeNullOrEmpty
        }
    }

    Context 'Property Modification' {
        BeforeEach {
            $style = [PSColorStyle]::new('TestStyle', 'Red', $null)
        }

        It 'Allows setting Bold to true' {
            $style.Bold = $true
            $style.Bold | Should -Be $true
        }

        It 'Allows setting multiple style properties' {
            $style.Bold = $true
            $style.Italic = $true
            $style.Underline = $true

            $style.Bold | Should -Be $true
            $style.Italic | Should -Be $true
            $style.Underline | Should -Be $true
        }

        It 'Allows setting numeric properties' {
            $style.StartTab = 2
            $style.StartSpaces = 4
            $style.LinesBefore = 1
            $style.LinesAfter = 1

            $style.StartTab | Should -Be 2
            $style.StartSpaces | Should -Be 4
            $style.LinesBefore | Should -Be 1
            $style.LinesAfter | Should -Be 1
        }

        It 'Allows setting Gradient array' {
            $style.Gradient = @('Red', 'Blue', 'Green')

            $style.Gradient | Should -Not -BeNullOrEmpty
            $style.Gradient.Count | Should -Be 3
            $style.Gradient[0] | Should -Be 'Red'
        }

        It 'Allows setting Style array' {
            $style.Style = @('Bold', 'Italic')

            $style.Style.Count | Should -Be 2
            $style.Style[0] | Should -Be 'Bold'
            $style.Style[1] | Should -Be 'Italic'
        }
    }

    Context 'Static Default Property' {
        It 'Has a Default static property' {
            [PSColorStyle]::Default | Should -Not -BeNullOrEmpty
        }

        It 'Default property is of type PSColorStyle' {
            [PSColorStyle]::Default.GetType().Name | Should -Be 'PSColorStyle'
        }
    }

    Context 'Static Profiles Property' {
        It 'Has a Profiles static property' {
            [PSColorStyle]::Profiles | Should -Not -BeNullOrEmpty
        }

        It 'Profiles is a hashtable' {
            [PSColorStyle]::Profiles | Should -BeOfType [hashtable]
        }

        It 'Profiles contains default profiles' {
            [PSColorStyle]::Profiles.ContainsKey('Default') | Should -Be $true
            [PSColorStyle]::Profiles.ContainsKey('Error') | Should -Be $true
            [PSColorStyle]::Profiles.ContainsKey('Warning') | Should -Be $true
            [PSColorStyle]::Profiles.ContainsKey('Info') | Should -Be $true
            [PSColorStyle]::Profiles.ContainsKey('Success') | Should -Be $true
            [PSColorStyle]::Profiles.ContainsKey('Critical') | Should -Be $true
            [PSColorStyle]::Profiles.ContainsKey('Debug') | Should -Be $true
        }
    }

    Context 'SetAsDefault Method' {
        It 'Sets the style as default' {
            $originalDefault = [PSColorStyle]::Default
            $newStyle = [PSColorStyle]::new('NewDefault', 'Magenta', $null)

            $newStyle.SetAsDefault()

            [PSColorStyle]::Default.Name | Should -Be 'NewDefault'
            [PSColorStyle]::Default.ForegroundColor | Should -Be 'Magenta'

            # Restore original default
            $originalDefault.SetAsDefault()
        }
    }

    Context 'AddToProfiles Method' {
        It 'Adds style to profiles collection' {
            $style = [PSColorStyle]::new('CustomProfile', 'Orange', $null)
            $style.AddToProfiles()

            [PSColorStyle]::Profiles.ContainsKey('CustomProfile') | Should -Be $true
            [PSColorStyle]::Profiles['CustomProfile'].Name | Should -Be 'CustomProfile'
        }

        It 'Overwrites existing profile with same name' {
            $style1 = [PSColorStyle]::new('TestProfile', 'Red', $null)
            $style1.AddToProfiles()

            $style2 = [PSColorStyle]::new('TestProfile', 'Blue', $null)
            $style2.AddToProfiles()

            [PSColorStyle]::Profiles['TestProfile'].ForegroundColor | Should -Be 'Blue'
        }
    }

    Context 'GetProfile Static Method' {
        BeforeAll {
            $testStyle = [PSColorStyle]::new('GetProfileTest', 'Cyan', $null)
            $testStyle.AddToProfiles()
        }

        It 'Retrieves existing profile by name' {
            $retrieved = [PSColorStyle]::GetProfile('GetProfileTest')

            $retrieved | Should -Not -BeNullOrEmpty
            $retrieved.Name | Should -Be 'GetProfileTest'
            $retrieved.ForegroundColor | Should -Be 'Cyan'
        }

        It 'Returns null for non-existent profile' {
            $retrieved = [PSColorStyle]::GetProfile('NonExistentProfile')

            $retrieved | Should -BeNullOrEmpty
        }

        It 'Retrieves built-in Error profile' {
            $errorProfile = [PSColorStyle]::GetProfile('Error')

            $errorProfile | Should -Not -BeNullOrEmpty
            $errorProfile.Name | Should -Be 'Error'
            $errorProfile.ForegroundColor | Should -Be 'Red'
            $errorProfile.Bold | Should -Be $true
        }
    }

    Context 'ToWriteColorParams Method' {
        It 'Returns hashtable with color properties' {
            $style = [PSColorStyle]::new('TestStyle', 'Red', 'Blue')
            $params = $style.ToWriteColorParams()

            $params | Should -BeOfType [hashtable]
            $params['Color'] | Should -Be 'Red'
            $params['BackGroundColor'] | Should -Be 'Blue'
        }

        It 'Includes Bold when set to true' {
            $style = [PSColorStyle]::new('TestStyle', 'Red', $null)
            $style.Bold = $true
            $params = $style.ToWriteColorParams()

            $params.ContainsKey('Bold') | Should -Be $true
            $params['Bold'] | Should -Be $true
        }

        It 'Excludes Bold when set to false' {
            $style = [PSColorStyle]::new('TestStyle', 'Red', $null)
            $style.Bold = $false
            $params = $style.ToWriteColorParams()

            $params.ContainsKey('Bold') | Should -Be $false
        }

        It 'Includes multiple style properties' {
            $style = [PSColorStyle]::new('TestStyle', 'Red', $null)
            $style.Bold = $true
            $style.Italic = $true
            $style.Underline = $true
            $params = $style.ToWriteColorParams()

            $params['Bold'] | Should -Be $true
            $params['Italic'] | Should -Be $true
            $params['Underline'] | Should -Be $true
        }

        It 'Includes formatting properties when set' {
            $style = [PSColorStyle]::new('TestStyle', 'Red', $null)
            $style.StartTab = 2
            $style.LinesBefore = 1
            $style.LinesAfter = 1
            $params = $style.ToWriteColorParams()

            $params['StartTab'] | Should -Be 2
            $params['LinesBefore'] | Should -Be 1
            $params['LinesAfter'] | Should -Be 1
        }

        It 'Excludes formatting properties when zero' {
            $style = [PSColorStyle]::new('TestStyle', 'Red', $null)
            $params = $style.ToWriteColorParams()

            $params.ContainsKey('StartTab') | Should -Be $false
            $params.ContainsKey('LinesBefore') | Should -Be $false
            $params.ContainsKey('LinesAfter') | Should -Be $false
        }

        It 'Includes Gradient when set' {
            $style = [PSColorStyle]::new('TestStyle', 'Red', $null)
            $style.Gradient = @('Red', 'Blue')
            $params = $style.ToWriteColorParams()

            $params.ContainsKey('Gradient') | Should -Be $true
            $params['Gradient'].Count | Should -Be 2
        }

        It 'Excludes Gradient when less than 2 colors' {
            $style = [PSColorStyle]::new('TestStyle', 'Red', $null)
            $style.Gradient = @('Red')
            $params = $style.ToWriteColorParams()

            $params.ContainsKey('Gradient') | Should -Be $false
        }

        It 'Caches parameters for performance' {
            $style = [PSColorStyle]::new('TestStyle', 'Red', $null)
            $style.Bold = $true

            $params1 = $style.ToWriteColorParams()
            $params2 = $style.ToWriteColorParams()

            # Both should have the same values but be different instances (cloned)
            $params1['Bold'] | Should -Be $params2['Bold']
            $params1 -eq $params2 | Should -Be $false
        }

        It 'Returns clone to prevent external modification' {
            $style = [PSColorStyle]::new('TestStyle', 'Red', $null)
            $params = $style.ToWriteColorParams()

            # Modify returned params
            $params['Color'] = 'Blue'

            # Original should still be Red
            $params2 = $style.ToWriteColorParams()
            $params2['Color'] | Should -Be 'Red'
        }
    }

    Context 'Clone Method' {
        It 'Creates a copy of the style' {
            $original = [PSColorStyle]::new('Original', 'Red', 'Blue')
            $original.Bold = $true
            $original.StartTab = 2

            $clone = $original.Clone()

            $clone.Name | Should -Be 'Original_Copy'
            $clone.ForegroundColor | Should -Be 'Red'
            $clone.BackgroundColor | Should -Be 'Blue'
            $clone.Bold | Should -Be $true
            $clone.StartTab | Should -Be 2
        }

        It 'Creates independent copy that can be modified' {
            $original = [PSColorStyle]::new('Original', 'Red', $null)
            $clone = $original.Clone()

            $clone.ForegroundColor = 'Blue'

            $original.ForegroundColor | Should -Be 'Red'
            $clone.ForegroundColor | Should -Be 'Blue'
        }

        It 'Clone does not share cached parameters' {
            $original = [PSColorStyle]::new('Original', 'Red', $null)
            $original.Bold = $true

            # Warm cache
            $null = $original.ToWriteColorParams()

            $clone = $original.Clone()

            # Clone should have null cache initially
            $clone.Bold = $false
            $params = $clone.ToWriteColorParams()
            $params.ContainsKey('Bold') | Should -Be $false
        }

        It 'Copies all style properties' {
            $original = [PSColorStyle]::new('Original', 'Red', 'Blue')
            $original.Bold = $true
            $original.Italic = $true
            $original.Underline = $true
            $original.Blink = $true
            $original.Faint = $true
            $original.CrossedOut = $true
            $original.DoubleUnderline = $true
            $original.Overline = $true

            $clone = $original.Clone()

            $clone.Bold | Should -Be $true
            $clone.Italic | Should -Be $true
            $clone.Underline | Should -Be $true
            $clone.Blink | Should -Be $true
            $clone.Faint | Should -Be $true
            $clone.CrossedOut | Should -Be $true
            $clone.DoubleUnderline | Should -Be $true
            $clone.Overline | Should -Be $true
        }

        It 'Copies Gradient array' {
            $original = [PSColorStyle]::new('Original', 'Red', $null)
            $original.Gradient = @('Red', 'Blue', 'Green')

            $clone = $original.Clone()

            $clone.Gradient.Count | Should -Be 3
            $clone.Gradient[0] | Should -Be 'Red'
            $clone.Gradient[1] | Should -Be 'Blue'
            $clone.Gradient[2] | Should -Be 'Green'
        }
    }

    Context 'InitializeDefaultProfiles Static Method' {
        It 'Initializes all default profiles' {
            # This is called during module load, so profiles should exist
            [PSColorStyle]::Profiles.Count | Should -BeGreaterOrEqual 7
        }

        It 'Error profile has correct properties' {
            $errorProfile = [PSColorStyle]::GetProfile('Error')

            $errorProfile.Name | Should -Be 'Error'
            $errorProfile.ForegroundColor | Should -Be 'Red'
            $errorProfile.Bold | Should -Be $true
        }

        It 'Warning profile has correct properties' {
            $warningProfile = [PSColorStyle]::GetProfile('Warning')

            $warningProfile.Name | Should -Be 'Warning'
            $warningProfile.ForegroundColor | Should -Be 'Yellow'
        }

        It 'Info profile has correct properties' {
            $infoProfile = [PSColorStyle]::GetProfile('Info')

            $infoProfile.Name | Should -Be 'Info'
            $infoProfile.ForegroundColor | Should -Be 'Cyan'
        }

        It 'Success profile has correct properties' {
            $successProfile = [PSColorStyle]::GetProfile('Success')

            $successProfile.Name | Should -Be 'Success'
            $successProfile.ForegroundColor | Should -Be 'Green'
        }

        It 'Critical profile has correct properties' {
            $criticalProfile = [PSColorStyle]::GetProfile('Critical')

            $criticalProfile.Name | Should -Be 'Critical'
            $criticalProfile.ForegroundColor | Should -Be 'White'
            $criticalProfile.BackgroundColor | Should -Be 'DarkRed'
            $criticalProfile.Bold | Should -Be $true
            $criticalProfile.Blink | Should -Be $true
        }

        It 'Debug profile has correct properties' {
            $debugProfile = [PSColorStyle]::GetProfile('Debug')

            $debugProfile.Name | Should -Be 'Debug'
            $debugProfile.ForegroundColor | Should -Be 'DarkGray'
            $debugProfile.Italic | Should -Be $true
        }

        It 'Default profile answers its parameters' {
            $defaultProfile = [PSColorStyle]::GetProfile('Default')
            $params = $defaultProfile.ToWriteColorParams()

            $params | Should -Not -BeNullOrEmpty
        }
    }

    Context 'Parameters follow property changes' {
        It 'ToWriteColorParams reflects a change to Bold' {
            $style = [PSColorStyle]::new('Test', 'Red', $null)
            $style.ToWriteColorParams().ContainsKey('Bold') | Should -Be $false

            $style.Bold = $true

            $style.ToWriteColorParams().Bold | Should -Be $true
        }

        It 'ToWriteColorParams reflects a change to ForegroundColor' {
            $style = [PSColorStyle]::new('Test', 'Red', $null)
            $style.ToWriteColorParams().Color | Should -Be 'Red'

            $style.ForegroundColor = 'Blue'

            $style.ToWriteColorParams().Color | Should -Be 'Blue'
        }

        It 'ToWriteColorParams reflects a change to BackgroundColor' {
            $style = [PSColorStyle]::new('Test', 'Red', 'Yellow')
            $style.ToWriteColorParams().BackGroundColor | Should -Be 'Yellow'

            $style.BackgroundColor = 'Green'

            $style.ToWriteColorParams().BackGroundColor | Should -Be 'Green'
        }

        It 'ToWriteColorParams reflects changes to several properties' {
            $style = [PSColorStyle]::new('Test', 'Red', $null)
            $null = $style.ToWriteColorParams()

            $style.Bold = $true
            $style.Italic = $true
            $style.Underline = $true
            $style.StartTab = 2

            $params = $style.ToWriteColorParams()
            $params.Bold | Should -Be $true
            $params.Italic | Should -Be $true
            $params.Underline | Should -Be $true
            $params.StartTab | Should -Be 2
        }

        It 'Each call answers a new hashtable' {
            $style = [PSColorStyle]::new('Test', 'Red', $null)
            $first = $style.ToWriteColorParams()
            $first['Color'] = 'Green'

            $style.ToWriteColorParams().Color | Should -Be 'Red'
        }

        It 'InvalidateCache can still be called' {
            $style = [PSColorStyle]::new('Test', 'Red', $null)

            { $style.InvalidateCache() } | Should -Not -Throw
            $style.ToWriteColorParams().Color | Should -Be 'Red'
        }

        It 'A change to a built-in profile reaches its helper' {
            $errorProfile = [PSColorStyle]::GetProfile('Error')
            $saved = $errorProfile.ForegroundColor
            try {
                $errorProfile.ForegroundColor = 'Magenta'

                [PSColorStyle]::GetProfile('Error').ToWriteColorParams().Color | Should -Be 'Magenta'
            } finally {
                $errorProfile.ForegroundColor = $saved
            }
        }

        It 'SetAsDefault keeps the parameters' {
            $originalDefault = [PSColorStyle]::Default
            $style = [PSColorStyle]::new('Test', 'Cyan', $null)
            $style.Bold = $true

            try {
                $style.SetAsDefault()

                $params = $style.ToWriteColorParams()
                $params | Should -Not -BeNullOrEmpty
                $params.Bold | Should -Be $true
            } finally {
                $originalDefault.SetAsDefault()
            }
        }

        It 'AddToProfiles keeps the parameters' {
            $style = [PSColorStyle]::new('ParamsTest', 'Magenta', $null)
            $style.Italic = $true

            $style.AddToProfiles()

            $params = $style.ToWriteColorParams()
            $params | Should -Not -BeNullOrEmpty
            $params.Italic | Should -Be $true

            [PSColorStyle]::Profiles.Remove('ParamsTest')
        }

        It 'A clone changes apart from the original' {
            $original = [PSColorStyle]::new('Original', 'Red', $null)
            $original.Bold = $true

            $clone = $original.Clone()
            $clone.Bold = $false

            $original.ToWriteColorParams().Bold | Should -Be $true
            # Bold = $false leaves the key out
            $clone.ToWriteColorParams().ContainsKey('Bold') | Should -Be $false
        }

        It 'A clone has its own arrays' {
            $original = [PSColorStyle]::new('Original', @(255, 0, 0), $null)
            $original.Gradient = @('Red', 'Blue')
            $original.Style = @('Bold', 'Italic')

            $clone = $original.Clone()
            $clone.Gradient[0] = 'Green'
            $clone.Style[0] = 'Underline'
            $clone.ForegroundColor[0] = 0

            $original.Gradient[0] | Should -Be 'Red'
            $original.Style[0] | Should -Be 'Bold'
            $original.ForegroundColor[0] | Should -Be 255
        }
    }
}