nvm.tests.ps1

Remove-Module nvm -Force -ErrorAction SilentlyContinue
Import-Module ./nvm.psd1

Describe "Get-NodeVersions" {
    InModuleScope nvm {
        Context "Local versions" {
            It "Gets known versions" {
                $tmpDir = [system.io.path]::GetTempPath()
                Mock Get-NodeInstallLocation { Join-Path $tmpDir '.nvm\settings.json' }
                Mock Test-Path { return $true }
                Mock Get-ChildItem {
                    [PSCustomObject]@{
                        Name = 'v20.13.0'
                        Path = "$Path\v20.13.0"
                    }
                    [PSCustomObject]@{
                        Name = 'v22.1.0'
                        Path = "$Path\v22.1.0"
                    }
                }
                Mock Get-ChildItem -ParameterFilter { $Filter -match 'node' } {
                    [PSCustomObject]@{
                        Name        = 'node.exe'
                        VersionInfo = [PSCustomObject]@{
                            ProductVersion = ( Split-Path -Path $Path -Leaf ).Replace('v', '')
                        }
                    }
                }

                $versions = Get-NodeVersions
                $versions.Count | Should -Be 2
                $versions | Should -Be @('v22.1.0'; 'v20.13.0')
            }

            It "Gets known versions with filter" {
                $tmpDir = [system.io.path]::GetTempPath()
                Mock Get-NodeInstallLocation { Join-Path $tmpDir '.nvm\settings.json' }
                Mock Test-Path { return $true }
                Mock Get-ChildItem {
                    [PSCustomObject]@{
                        Name = 'v20.13.0'
                        Path = "$Path\v20.13.0"
                    }
                    [PSCustomObject]@{
                        Name = 'v22.1.0'
                        Path = "$Path\v22.1.0"
                    }
                }
                Mock Get-ChildItem -ParameterFilter { $Filter -match 'node' } {
                    [PSCustomObject]@{
                        Name        = 'node.exe'
                        VersionInfo = [PSCustomObject]@{
                            ProductVersion = ( Split-Path -Path $Path -Leaf ).Replace('v', '')
                        }
                    }
                }

                $versions = Get-NodeVersions -Filter 'v20.13.0'
                $versions | Should -Be 'v20.13.0'

            }

            It "Returns nothing when no versions are installed" {
                $tmpDir = [system.io.path]::GetTempPath()
                Mock Get-NodeInstallLocation { Join-Path $tmpDir '.nvm\settings.json' }
                Mock Test-Path { return $false }
                Get-NodeVersions -Filter 'v20.13.0' | Should -BeNullOrEmpty
            }
        }

        Context "Remote versions" {
            It "Will list remote versions" {
                $mockJson = "[
                    {""version"":""v22.1.0"",""date"":""2017-10-31"",""files"":[""aix-ppc64"",""headers"",""linux-arm64"",""linux-armv6l"",""linux-armv7l"",""linux-ppc64le"",""linux-x64"",""linux-x86"",""osx-x64-pkg"",""osx-x64-tar"",""src"",""sunos-x64"",""sunos-x86"",""win-x64-7z"",""win-x64-exe"",""win-x64-msi"",""win-x64-zip"",""win-x86-7z"",""win-x86-exe"",""win-x86-msi"",""win-x86-zip""],""npm"":""5.5.1"",""v8"":""6.2.414.32"",""uv"":""1.15.0"",""zlib"":""1.2.11"",""openssl"":""1.0.2l"",""modules"":""59"",""lts"":false},
                    {""version"":""v20.13.0"",""date"":""2017-10-31"",""files"":[""aix-ppc64"",""headers"",""linux-arm64"",""linux-armv6l"",""linux-armv7l"",""linux-ppc64le"",""linux-x64"",""linux-x86"",""osx-x64-pkg"",""osx-x64-tar"",""src"",""sunos-x64"",""sunos-x86"",""win-x64-7z"",""win-x64-exe"",""win-x64-msi"",""win-x64-zip"",""win-x86-7z"",""win-x86-exe"",""win-x86-msi"",""win-x86-zip""],""npm"":""5.5.1"",""v8"":""6.1.534.46"",""uv"":""1.15.0"",""zlib"":""1.2.11"",""openssl"":""1.0.2l"",""modules"":""57"",""lts"":""Carbon""},
                    {""version"":""v20.0.0"",""date"":""2017-10-25"",""files"":[""aix-ppc64"",""headers"",""linux-arm64"",""linux-armv6l"",""linux-armv7l"",""linux-ppc64le"",""linux-x64"",""linux-x86"",""osx-x64-pkg"",""osx-x64-tar"",""src"",""sunos-x64"",""sunos-x86"",""win-x64-7z"",""win-x64-exe"",""win-x64-msi"",""win-x64-zip"",""win-x86-7z"",""win-x86-exe"",""win-x86-msi"",""win-x86-zip""],""npm"":""5.4.2"",""v8"":""6.1.534.42"",""uv"":""1.15.0"",""zlib"":""1.2.11"",""openssl"":""1.0.2l"",""modules"":""57"",""lts"":false}
                ]"


                Mock Invoke-WebRequest { return $mockJson }

                $versions = Get-NodeVersions -Remote
                $versions.Count | Should -Be 3
            }

            It "Will list remote versions with filter" {
                $mockJson = "[
                    {""version"":""v22.1.0"",""date"":""2017-10-31"",""files"":[""aix-ppc64"",""headers"",""linux-arm64"",""linux-armv6l"",""linux-armv7l"",""linux-ppc64le"",""linux-x64"",""linux-x86"",""osx-x64-pkg"",""osx-x64-tar"",""src"",""sunos-x64"",""sunos-x86"",""win-x64-7z"",""win-x64-exe"",""win-x64-msi"",""win-x64-zip"",""win-x86-7z"",""win-x86-exe"",""win-x86-msi"",""win-x86-zip""],""npm"":""5.5.1"",""v8"":""6.2.414.32"",""uv"":""1.15.0"",""zlib"":""1.2.11"",""openssl"":""1.0.2l"",""modules"":""59"",""lts"":false},
                    {""version"":""v20.13.0"",""date"":""2017-10-31"",""files"":[""aix-ppc64"",""headers"",""linux-arm64"",""linux-armv6l"",""linux-armv7l"",""linux-ppc64le"",""linux-x64"",""linux-x86"",""osx-x64-pkg"",""osx-x64-tar"",""src"",""sunos-x64"",""sunos-x86"",""win-x64-7z"",""win-x64-exe"",""win-x64-msi"",""win-x64-zip"",""win-x86-7z"",""win-x86-exe"",""win-x86-msi"",""win-x86-zip""],""npm"":""5.5.1"",""v8"":""6.1.534.46"",""uv"":""1.15.0"",""zlib"":""1.2.11"",""openssl"":""1.0.2l"",""modules"":""57"",""lts"":""Carbon""},
                    {""version"":""v20.0.0"",""date"":""2017-10-25"",""files"":[""aix-ppc64"",""headers"",""linux-arm64"",""linux-armv6l"",""linux-armv7l"",""linux-ppc64le"",""linux-x64"",""linux-x86"",""osx-x64-pkg"",""osx-x64-tar"",""src"",""sunos-x64"",""sunos-x86"",""win-x64-7z"",""win-x64-exe"",""win-x64-msi"",""win-x64-zip"",""win-x86-7z"",""win-x86-exe"",""win-x86-msi"",""win-x86-zip""],""npm"":""5.4.2"",""v8"":""6.1.534.42"",""uv"":""1.15.0"",""zlib"":""1.2.11"",""openssl"":""1.0.2l"",""modules"":""57"",""lts"":false}
                ]"


                Mock Invoke-WebRequest { return $mockJson }

                $versions = Get-NodeVersions -Remote -Filter "v20"
                $versions.Count | Should -Be 2
            }
        }
    }
}

Describe "Get-NodeInstallLocation" {
    InModuleScope nvm {
        It "Should return the location when it exists" {
            $tmpDir = [system.io.path]::GetTempPath()
            $installPath = Join-Path $tmpDir '.nvm'
            Mock Test-Path { return $true }
            Mock Get-Content { return @{ InstallPath = $installPath } | ConvertTo-Json }

            $location = Get-NodeInstallLocation
            $location | Should -Be $installPath
        }
    }
}

Describe "Install-NodeVersion" {
    InModuleScope nvm {
        Context "auto-discovery" {
            BeforeEach {
                $nodeVersion = 'v22.1.0'
            }

            It "Install version from the .nvmrc file" -Skip:($env:include_integration_tests -ne $true) {
                Mock Test-Path -ParameterFilter { $Path -match '.nvmrc$' } { return $true }
                Mock Get-Content -ParameterFilter { $Path -match '.nvmrc$' } { return $nodeVersion }

                Install-NodeVersion

                $versions = Get-NodeVersions -Filter 'v22.1.0'
                $versions | Should -Be 'v22.1.0'
            }

            It "Install version from the package.json field" -Skip:($env:include_integration_tests -ne $true) {
                Mock Test-Path -ParameterFilter { $Path -match '.nvmrc$' } { return $false }
                Mock Test-Path -ParameterFilter { $Path -match 'package.json$' } { return $true }
                Mock Get-Content -ParameterFilter { $Path -match 'package.json$' } {
                    return @{
                        engines = @{
                            node = '^22.1.0'
                        }
                    } | ConvertTo-Json
                }

                Install-NodeVersion

                $versions = Get-NodeVersions -Filter 'v22.*'
                $versions | Should -BeLike 'v22.*'
            }

            It "Will error if no version in the package.json field" -Skip:($env:include_integration_tests -ne $true) {
                Mock Test-Path -ParameterFilter { $Path -match '.nvmrc$' } { return $false }
                Mock Test-Path -ParameterFilter { $Path -match 'package.json$' } { return $true }
                Mock Get-Content -ParameterFilter { $Path -match 'package.json$' } {
                    return @{
                        engines = @{
                        }
                    } | ConvertTo-Json
                }

                { Install-NodeVersion } | Should -Throw
            }

            It "Will error if no version, no .nvmrc and no package.json, no default" -Skip:($env:include_integration_tests -ne $true) {
                Mock Get-NodeInstallLocation { return "/" }
                Mock Test-Path -ParameterFilter { $Path -eq '/default' } { return $false }
                Mock Test-Path -ParameterFilter { $Path -match '.nvmrc$' } { return $false }
                Mock Test-Path -ParameterFilter { $Path -match 'package.json$' } { return $false }

                { Install-NodeVersion } | Should -Throw "Version not given, no .nvmrc found in folder, and package.json missing or does not contain node engines field"
            }
        }

        Context "Installing with a specific version" {
            It "Install a requested version" -Skip:($env:include_integration_tests -ne $true) {
                Install-NodeVersion -Version 'v22.1.0'

                $versions = Get-NodeVersions -Filter 'v22.1.0'
                $versions | Should -Be 'v22.1.0'
            }

            It "Throws when version already exists" -Skip:($env:include_integration_tests -ne $true) {
                Install-NodeVersion -Version 'v22.1.0'
                { Install-NodeVersion -Version 'v22.1.0' } | Should -Throw
            }

            It "Won't throw when version already exists if you use the -Force flag" -Skip:($env:include_integration_tests -ne $true) {
                { Install-NodeVersion -Version 'v22.1.0' -Force } | Should -Not -Throw
            }

            It "Can install without a 'v' prefix" -Skip:($env:include_integration_tests -ne $true) {
                { Install-NodeVersion -Version '22.1.0' -Force } | Should -Not -Throw
            }

            It "Can install multiple versions" -Skip:($env:include_integration_tests -ne $true) {
                { Install-NodeVersion -Version '22.1.0', '20.13.0' } | Should -Not -Throw
            }
        }

        Context "Major version installing" {
            It "Can install from just a major version" -Skip:($env:include_integration_tests -ne $true) {
                Install-NodeVersion -Version '22'

                $versions = Get-NodeVersions -Filter 'v22'
                $versions | Should -Match 'v22'
            }
        }

        Context "Major and minor version installing" {
            It "Can install from just a major and minor version" -Skip:($env:include_integration_tests -ne $true) {
                Install-NodeVersion -Version '22.0'

                $versions = Get-NodeVersions -Filter 'v22.0'
                $versions | Should -Match 'v22.0'
            }
        }

        Context "Installing with a keyword" {
            It "Installs under the `latest` flag" -Skip:($env:include_integration_tests -ne $true) {
                Install-NodeVersion -Version 'latest'

                $versions = Get-NodeVersions
                $versions.GetType() | Should -Be 'SemVer.Version'
            }
        }

        Context "Incomplete installation" {
            BeforeEach {
                Mock Get-Command -ParameterFilter { $Name -match 'node' -or $Name -match 'npm' } {
                    throw (
                        "The term '$Name' is not recognized as the name of a cmdlet, function, script file, or " +
                        "operable program. Check the spelling of the name, or if a path was included, verify that " +
                        "the path is correct and try again."
                    )
                }
            }

            It "Will error if node or npm can't be called" -Skip:($env:include_integration_tests -ne $true) {
                { Install-NodeVersion latest } | Should -Throw
            }
        }
    }

    BeforeEach {
        $basePath = if ($IsWindows) { $env:SystemDrive } else { [system.io.path]::GetTempPath() }
        $installLocation = Join-Path $basePath '.nvm'
        Set-NodeInstallLocation -Path $installLocation
    }

    AfterEach {
        if (Test-Path $installLocation) {
            Remove-Item -Recurse -Force $installLocation
        }

        $settingsFile = Join-Path $PSScriptRoot 'settings.json'

        if ((Test-Path $settingsFile) -eq $true) {
            Remove-Item -Force $settingsFile
        }
    }
}

Describe "Set-NodeVersion" {
    InModuleScope nvm {
        BeforeEach {
            $nodeVersion = 'v22.1.0'
        }
        Context "auto-discovery" {

            It "Will set from the .nvmrc file" {
                $tmpDir = [system.io.path]::GetTempPath()
                $nvmDir = Join-Path $tmpDir '.nvm'
                Mock Test-Path { return $true } -ParameterFilter { $Path.StartsWith('variable') -eq $false }
                Mock Get-Content -ParameterFilter { $Path -match '\.nvmrc$' } { return $nodeVersion }
                Mock Get-NodeInstallLocation { return $nvmDir }

                Set-NodeVersion -InformationVariable infos
                $infos | Should -Be "Switched to node version $nodeVersion"
            }

            It "Will set from the engines package.json field" {
                $tmpDir = [system.io.path]::GetTempPath()
                Mock Test-Path -ParameterFilter { $Path.StartsWith('variable') -eq $false } {
                    return (-not ($Path -match '\.nvmrc$'))
                }
                Mock Get-Content -ParameterFilter { $Path -match 'package.json$' } {
                    return @{
                        engines = @{
                            node = '^22.1.0'
                        }
                    } | ConvertTo-Json
                }
                Mock Get-NodeVersions { return 'v9.1.0' }
                Mock Get-NodeInstallLocation { return Join-Path $tmpDir '.nvm' }

                Set-NodeVersion -InformationVariable infos
                $infos | Should -Be "Switched to node version v9.1.0"
            }

            It "Will set from the default file" {
                $tmpDir = [system.io.path]::GetTempPath()
                $nvmDir = Join-Path $tmpDir '.nvm'
                Mock Test-Path { return $false } -ParameterFilter { $Path.Contains('.nvmrc') }
                Mock Test-Path { return $false } -ParameterFilter { $Path.Contains('./package.json') }
                Mock Test-Path { return $true } -ParameterFilter { $Path.Contains((Join-Path $nvmDir 'default')) }
                Mock Get-Content -ParameterFilter { $Path -match 'default$' } { return $nodeVersion }
                Mock Get-NodeInstallLocation { return $nvmDir }

                Set-NodeVersion -InformationVariable infos
                $infos | Should -Be "Switched to node version $nodeVersion"
            }

            It "Will error if no version in the package.json field" {
                Mock Test-Path -ParameterFilter { $Path.StartsWith('variable') -eq $false } {
                    return (-not ($Path -match '\.nvmrc$'))
                }
                Mock Get-Content -ParameterFilter { $Path -match 'package.json$' } {
                    return @{
                        engines = @{
                        }
                    } | ConvertTo-Json
                }

                { Set-NodeVersion } | Should -Throw
            }

            It "Will error if no version, no .nvmrc and no package.json, no default" {
                Mock Get-NodeInstallLocation { return "/" }
                Mock Test-Path { return $false } -ParameterFilter { $Path -eq (Join-Path (Get-NodeInstallLocation) 'default') }
                Mock Test-Path { return $false } -ParameterFilter { $Path.Contains('.nvmrc') }
                Mock Test-Path { return $false } -ParameterFilter { $Path.Contains('./package.json') }

                { Set-NodeVersion } | Should -Throw "Version not given, no .nvmrc found in folder, and package.json missing or does not contain node engines field"
            }
        }

        Context "Set from version string" {
            It "Will set from the supplied version" {
                Set-NodeVersion $nodeVersion -InformationVariable infos
                $infos | Should -Be "Switched to node version $nodeVersion"
            }

            It "Will set from a version range" {
                Mock Get-NodeVersions { return @('v22.1.0'; 'v20.13.0') }

                Set-NodeVersion 'v9' -InformationVariable infos
                $infos | Should -Be "Switched to node version $nodeVersion"
            }

            It "Will set from a version range with caret" {
                Mock Get-NodeVersions { return @('v22.1.0'; 'v20.13.0') }

                Set-NodeVersion '^22.1.0' -InformationVariable infos
                $infos | Should -Be "Switched to node version $nodeVersion"
            }

            It "Will throw error on unmatched version range" {
                {
                    Mock Get-NodeVersions { return @() }

                    Set-NodeVersion 'v7'
                } | Should -Throw "No version found that matches v7"
            }

            It "Will set npm config path" {
                Mock Get-NodeVersions { return @('v22.1.0') }

                Set-NodeVersion 'v9' -InformationVariable infos
                $env:NPM_CONFIG_GLOBALCONFIG | Should -Not -Be $null
            }

            It "Will update environment path" {
                $explicitVersion = "v9.32.99"
                $nvmPath = Get-NodeInstallLocation
                Set-NodeVersion -Version 'v22.1.0' -InformationVariable infos
                Set-NodeVersion -Version $explicitVersion -InformationVariable infos
                $separator = [System.IO.Path]::PathSeparator
                [System.String[]]$nvmPaths = ($env:PATH -split $separator) | Where-Object { $_.StartsWith($nvmPath) }
                $nvmPaths.Count | Should -Be 1
                $nvmPaths | Should -Match $explicitVersion
            }

            BeforeEach {
                $tmpDir = [system.io.path]::GetTempPath()
                Mock Get-NodeInstallLocation { return Join-Path $tmpDir '.nvm' }
                Mock Test-Path { return $true } -ParameterFilter { $Path.StartsWith((Join-Path $tmpDir '.nvm')) -eq $true }
            }
        }

        Context "pipeline" {
            BeforeEach {
                $nodeVersion = "v22.1.0"
                Mock Test-Path -ParameterFilter { $Path -match 'vs' } { return $true }
                Mock Get-ChildItem {
                    [PSCustomObject]@{
                        Name = 'v22.1.0'
                        Path = "$Path\v22.1.0"
                    }
                }
                Mock Get-ChildItem -ParameterFilter { $Filter -match 'node' } {
                    [PSCustomObject]@{
                        Name        = 'node.exe'
                        VersionInfo = [PSCustomObject]@{
                            ProductVersion = ( Split-Path -Path $Path -Leaf ).Replace('v', '')
                        }
                    }
                }
            }
            It "Will set from the supplied version via Install-NodeVersion pipeline output" {
                [PSCustomObject]@{
                    Name    = 'node.exe'
                    Version = '22.1.0'
                } | Set-NodeVersion -InformationVariable infos
                $infos | Should -Be "Switched to node version $nodeVersion"
            }

            It "Will set from the supplied version via Get-NodeVersion pipeline output" {
                [SemVer.Version]::new($nodeVersion, $true) | Set-NodeVersion -InformationVariable infos
                $infos | Should -Be "Switched to node version $nodeVersion"
            }
        }
    }
    AfterEach {
        $settingsFile = Join-Path $PSScriptRoot 'settings.json'

        if ((Test-Path $settingsFile) -eq $true) {
            Remove-Item -Force $settingsFile
        }
    }
}

Describe "Remove-NodeVersion" {
    InModuleScope nvm {
        It "Should remove a version" {
            $tmpDir = [system.io.path]::GetTempPath()
            Mock Get-NodeInstallLocation { return $tmpDir }
            Mock Test-Path { return $true }
            Mock Remove-Item { }

            Remove-NodeVersion 'v22.1.0'

            Assert-MockCalled -CommandName Remove-Item -Times 1 -ParameterFilter { $Path -eq (Join-Path $tmpDir 'v22.1.0') }
        }

        It "Should remove multiple versions" {
            $tmpDir = [system.io.path]::GetTempPath()
            Mock Get-NodeInstallLocation { return $tmpDir }
            Mock Test-Path { return $true }
            Mock Remove-Item { }

            Remove-NodeVersion 'v22.1.0', 'v10.0.0'

            Assert-MockCalled -CommandName Remove-Item -Times 1 -ParameterFilter { $Path -eq (Join-Path $tmpDir 'v22.1.0') }
            Assert-MockCalled -CommandName Remove-Item -Times 1 -ParameterFilter { $Path -eq (Join-Path $tmpDir 'v10.0.0') }
        }

        It "Should remove versions passed from the pipeline" {
            $tmpDir = [system.io.path]::GetTempPath()
            Mock Get-NodeInstallLocation { return $tmpDir }
            Mock Test-Path { return $true }
            Mock Remove-Item { }
            Mock Get-NodeVersions {
                'v22.1.0'
                'v10.0.0'
            }

            Get-NodeVersions | Remove-NodeVersion

            Assert-MockCalled -CommandName Remove-Item -Times 1 -ParameterFilter { $Path -eq (Join-Path $tmpDir 'v22.1.0') }
            Assert-MockCalled -CommandName Remove-Item -Times 1 -ParameterFilter { $Path -eq (Join-Path $tmpDir 'v10.0.0') }
        }

        It "Should throw when version doesn't exist" {
            $tmpDir = [system.io.path]::GetTempPath()
            Mock Get-NodeInstallLocation { return $tmpDir }
            Mock Test-Path { return $false }
            Mock Remove-Item { }

            $version = 'v22.1.0'
            { Remove-NodeVersion $version } | Should -Throw "Could not find node version $version"
        }
    }
}