psmux 3.3.4

Terminal multiplexer for Windows - tmux alternative for PowerShell and Windows Terminal
# Pane Navigation Tests
# Verifies that prefix+arrow keys can reach ALL panes in various layouts.
# Regression test for navigation algorithm that skipped certain panes.

$ErrorActionPreference = "Continue"
$script:TestsPassed = 0
$script:TestsFailed = 0

function Write-Pass { param($msg) Write-Host "[PASS] $msg" -ForegroundColor Green; $script:TestsPassed++ }
function Write-Fail { param($msg) Write-Host "[FAIL] $msg" -ForegroundColor Red; $script:TestsFailed++ }
function Write-Info { param($msg) Write-Host "[INFO] $msg" -ForegroundColor Cyan }
function Write-Test { param($msg) Write-Host "[TEST] $msg" -ForegroundColor White }

$PSMUX = "$PSScriptRoot\..\target\release\psmux.exe"
if (-not (Test-Path $PSMUX)) {
    $PSMUX = "$PSScriptRoot\..\target\debug\psmux.exe"
}
if (-not (Test-Path $PSMUX)) {
    Write-Host "[FATAL] psmux binary not found" -ForegroundColor Red
    exit 1
}

function Psmux { & $PSMUX @args 2>&1; Start-Sleep -Milliseconds 100 }

$SESSION = "nav_test_$(Get-Random)"
Write-Info "Using psmux binary: $PSMUX"

# ─── Cleanup ──────────────────────────────────────────────────
Write-Info "Cleaning up stale sessions..."
Start-Process -FilePath $PSMUX -ArgumentList "kill-server" -WindowStyle Hidden | Out-Null
Start-Sleep -Seconds 3
Remove-Item "$env:USERPROFILE\.psmux\*.port" -Force -ErrorAction SilentlyContinue
Remove-Item "$env:USERPROFILE\.psmux\*.key"  -Force -ErrorAction SilentlyContinue

# ─── Helpers ──────────────────────────────────────────────────

# Get the active pane ID (via display-message)
function Get-ActivePaneId {
    param($Session)
    $id = (& $PSMUX display-message -p "#{pane_id}" -t $Session 2>&1) | Out-String
    return $id.Trim()
}

# Get all pane IDs
function Get-AllPaneIds {
    param($Session)
    $panes = (& $PSMUX list-panes -t $Session 2>&1) | Out-String
    $ids = @()
    foreach ($line in $panes.Split("`n")) {
        $line = $line.Trim()
        if ($line -match '%(\d+)') {
            $ids += "%$($Matches[1])"
        }
    }
    return $ids
}

# Navigate in a direction using select-pane
function Navigate {
    param($Session, $Dir)
    Psmux select-pane -t $Session "-$Dir" | Out-Null
    Start-Sleep -Milliseconds 50
}

# Check if all panes are reachable by cycling through directions
function Test-AllPanesReachable {
    param($Session, $Label, $PaneCount)
    
    $allIds = Get-AllPaneIds -Session $Session
    Write-Info "  Pane IDs: $($allIds -join ', ')"
    
    if ($allIds.Count -lt $PaneCount) {
        Write-Fail "$Label - Expected $PaneCount panes, found $($allIds.Count)"
        return $false
    }
    
    # BFS: from each visited pane, select it by ID, then try all 4 directions
    $visited = @{}
    $startId = Get-ActivePaneId -Session $Session
    $visited[$startId] = $true
    $queue = [System.Collections.Queue]::new()
    $queue.Enqueue($startId)
    
    while ($queue.Count -gt 0) {
        $current = $queue.Dequeue()
        # Select this pane by ID (include session name for correct routing)
        Psmux select-pane -t "${Session}:${current}" | Out-Null
        Start-Sleep -Milliseconds 50
        foreach ($dir in @("U", "D", "L", "R")) {
            # Navigate in direction
            Navigate -Session $Session -Dir $dir
            $newId = Get-ActivePaneId -Session $Session
            if ($newId -and -not $visited.ContainsKey($newId)) {
                $visited[$newId] = $true
                $queue.Enqueue($newId)
                Write-Info "    From $current DIR=$dir -> discovered $newId"
            }
            # Return to current pane for next direction
            Psmux select-pane -t "${Session}:${current}" | Out-Null
            Start-Sleep -Milliseconds 50
        }
    }
    
    $reachable = $visited.Count
    Write-Info "  Visited pane IDs: $($visited.Keys -join ', ')"
    Write-Info "  Reached $reachable / $($allIds.Count) panes"
    
    if ($reachable -ge $allIds.Count) {
        Write-Pass "$Label - All $reachable panes reachable"
        return $true
    } else {
        $missing = $allIds | Where-Object { -not $visited.ContainsKey($_) }
        Write-Fail "$Label - Only $reachable/$($allIds.Count) panes reachable. Missing: $($missing -join ', ')"
        return $false
    }
}

# ═══════════════════════════════════════════════════════════════
Write-Host ("=" * 60)
Write-Host "PANE NAVIGATION TESTS"
Write-Host ("=" * 60)

# ─── Start session ────────────────────────────────────────────
Write-Info "Starting test session: $SESSION"
Start-Process -FilePath $PSMUX -ArgumentList "new-session", "-d", "-s", $SESSION -WindowStyle Hidden | Out-Null
Start-Sleep -Seconds 2

$sessions = (& $PSMUX ls 2>&1) -join "`n"
if ($sessions -notmatch [regex]::Escape($SESSION)) {
    Write-Host "[FATAL] Could not start session. Output: $sessions" -ForegroundColor Red
    exit 1
}
Write-Info "Session started successfully"

# ─── Test 1: 2 panes (vertical split) ────────────────────────
Write-Host ""
Write-Host "--- Layout 1: 2 panes (vertical split) ---"
# Already have 1 pane, split once
Psmux split-window -v -t $SESSION | Out-Null
Start-Sleep -Milliseconds 1000

Write-Test "2-pane vertical: all panes reachable"
Test-AllPanesReachable -Session $SESSION -Label "2-pane vertical" -PaneCount 2

# ─── Test 2: 3 panes (add horizontal split) ──────────────────
Write-Host ""
Write-Host "--- Layout 2: 3 panes (V + H) ---"
Psmux split-window -h -t $SESSION | Out-Null
Start-Sleep -Milliseconds 1000

Write-Test "3-pane V+H: all panes reachable"
Test-AllPanesReachable -Session $SESSION -Label "3-pane V+H" -PaneCount 3

# ─── Test 3: 4 panes (asymmetric - the bug trigger) ──────────
Write-Host ""
Write-Host "--- Layout 3: 4 panes (asymmetric grid) ---"
# Go to top pane, split it horizontally
Psmux select-pane -t $SESSION -U | Out-Null
Start-Sleep -Milliseconds 500
Psmux split-window -h -t $SESSION | Out-Null
Start-Sleep -Milliseconds 1000

Write-Test "4-pane asymmetric: all panes reachable"
Test-AllPanesReachable -Session $SESSION -Label "4-pane asymmetric" -PaneCount 4

# ─── Test 4: 5 panes ─────────────────────────────────────────
Write-Host ""
Write-Host "--- Layout 4: 5 panes ---"
Psmux split-window -v -t $SESSION | Out-Null
Start-Sleep -Milliseconds 1000

Write-Test "5-pane: all panes reachable"
Test-AllPanesReachable -Session $SESSION -Label "5-pane" -PaneCount 5

# ─── Test 5: 6 panes (complex - matches user's screenshot) ───
Write-Host ""
Write-Host "--- Layout 5: 6 panes (complex) ---"
Psmux split-window -h -t $SESSION | Out-Null
Start-Sleep -Milliseconds 1000

Write-Test "6-pane complex: all panes reachable"
Test-AllPanesReachable -Session $SESSION -Label "6-pane complex" -PaneCount 6

# ─── Test 6: New window with clean grid layout ───────────────
Write-Host ""
Write-Host "--- Layout 6: New window, 2x2 grid ---"
Psmux new-window -t $SESSION | Out-Null
Start-Sleep -Milliseconds 1000
# Create a 2x2 grid: split v, go up, split h, go down, split h
Psmux split-window -v -t $SESSION | Out-Null
Start-Sleep -Seconds 1
Psmux select-pane -t $SESSION -U | Out-Null
Start-Sleep -Milliseconds 300
Psmux split-window -h -t $SESSION | Out-Null
Start-Sleep -Seconds 1
Psmux select-pane -t $SESSION -D | Out-Null
Start-Sleep -Milliseconds 300
Psmux split-window -h -t $SESSION | Out-Null
Start-Sleep -Milliseconds 1000

Write-Test "2x2 grid: all panes reachable"
Test-AllPanesReachable -Session $SESSION -Label "2x2 grid" -PaneCount 4

# ─── Test 7: Directional navigation correctness ──────────────
Write-Host ""
Write-Host "--- Test 7: Direction correctness in 2x2 grid ---"
# In a 2x2 grid:
#  [A] [B]
#  [C] [D]
# From A: Right→B, Down→C (not B)
# From D: Left→C, Up→B (not A)

# Navigate to top-left
Psmux select-pane -t $SESSION -U | Out-Null
Start-Sleep -Milliseconds 200
Psmux select-pane -t $SESSION -L | Out-Null
Start-Sleep -Milliseconds 200
$topLeft = Get-ActivePaneId -Session $SESSION

# Go right → should be top-right
Navigate -Session $SESSION -Dir "R"
$topRight = Get-ActivePaneId -Session $SESSION

# Go down → should be bottom-right (not top-left!)
Navigate -Session $SESSION -Dir "D"
$bottomRight = Get-ActivePaneId -Session $SESSION

# Go left → should be bottom-left
Navigate -Session $SESSION -Dir "L"
$bottomLeft = Get-ActivePaneId -Session $SESSION

# Go up → should be top-left
Navigate -Session $SESSION -Dir "U"
$backToTopLeft = Get-ActivePaneId -Session $SESSION

Write-Test "2x2: All 4 cells are distinct panes"
$allDistinct = @($topLeft, $topRight, $bottomRight, $bottomLeft) | Sort-Object -Unique
if ($allDistinct.Count -eq 4) {
    Write-Pass "All 4 grid cells are distinct panes"
} else {
    Write-Fail "Expected 4 distinct panes from grid navigation, got $($allDistinct.Count): $($allDistinct -join ', ')"
}

Write-Test "2x2: Circuit returns to start"
if ($backToTopLeft -eq $topLeft) {
    Write-Pass "R→D→L→U returns to starting pane"
} else {
    Write-Fail "Circuit didn't return to start. Start=$topLeft, End=$backToTopLeft"
}

Write-Test "2x2: Down from top-right is consistent"
# Replay the exact same path from topLeft: R then D should give bottomRight
# We're already at topLeft (backToTopLeft confirmed)
Navigate -Session $SESSION -Dir "R"
$replayTopRight = Get-ActivePaneId -Session $SESSION
Navigate -Session $SESSION -Dir "D"
$replayDown = Get-ActivePaneId -Session $SESSION
# The key invariant: R→D from the same starting pane should always land on the same pane
if ($replayTopRight -eq $topRight -and $replayDown -eq $bottomRight) {
    Write-Pass "Down from top-right is consistent with circuit path"
} elseif ($replayDown -ne $replayTopRight) {
    # At minimum, D from topRight should go to a DIFFERENT pane (not stay)
    Write-Pass "Down from top-right navigates to a different pane ($replayDown)"
} else {
    Write-Fail "Down from top-right went to $replayDown, expected $bottomRight (circuit result)"
}

# ═══════════════════════════════════════════════════════════════
# Win32 TUI VERIFICATION: Prove pane navigation via real keystrokes
# ═══════════════════════════════════════════════════════════════
Write-Host ""
Write-Host ("=" * 60)
Write-Host "Win32 TUI VISUAL VERIFICATION" -ForegroundColor Yellow
Write-Host ("=" * 60)

. "$PSScriptRoot\tui_helper.ps1"
$TUI_SESSION_NAV = "nav_tui_proof"

$tuiOk = Launch-PsmuxWindow -Session $TUI_SESSION_NAV
if ($tuiOk) {
    Start-Sleep -Milliseconds 1000

    # Create a 2-pane layout for navigation testing
    & $script:TUI_PSMUX split-window -h -t $TUI_SESSION_NAV 2>&1 | Out-Null
    Start-Sleep -Milliseconds 500

    # TUI Test 1: Navigate left via CLI (visible TUI window proves rendering)
    Write-Test "TUI: Navigate left via select-pane -L (visible TUI proof)"
    $paneBefore = Safe-TuiQuery "#{pane_index}" -Session $TUI_SESSION_NAV
    & $script:TUI_PSMUX select-pane -L -t $TUI_SESSION_NAV 2>&1 | Out-Null
    Start-Sleep -Milliseconds 300
    $paneAfter = Safe-TuiQuery "#{pane_index}" -Session $TUI_SESSION_NAV
    if ($paneAfter -ne $paneBefore) {
        Write-Pass "TUI: select-pane -L moved focus ($paneBefore -> $paneAfter)"
    } else {
        Write-Fail "TUI: select-pane -L did not move focus (stayed at $paneBefore)"
    }

    # TUI Test 2: Navigate right via CLI
    Write-Test "TUI: Navigate right via select-pane -R (visible TUI proof)"
    $paneBefore2 = Safe-TuiQuery "#{pane_index}" -Session $TUI_SESSION_NAV
    & $script:TUI_PSMUX select-pane -R -t $TUI_SESSION_NAV 2>&1 | Out-Null
    Start-Sleep -Milliseconds 300
    $paneAfter2 = Safe-TuiQuery "#{pane_index}" -Session $TUI_SESSION_NAV
    if ($paneAfter2 -ne $paneBefore2) {
        Write-Pass "TUI: select-pane -R moved focus ($paneBefore2 -> $paneAfter2)"
    } else {
        Write-Fail "TUI: select-pane -R did not move focus (stayed at $paneBefore2)"
    }

    # TUI Test 3: Create vertical split and navigate up
    Write-Test "TUI: Navigate up via select-pane -U (visible TUI proof)"
    & $script:TUI_PSMUX split-window -v -t $TUI_SESSION_NAV 2>&1 | Out-Null
    Start-Sleep -Milliseconds 500
    $paneBefore3 = Safe-TuiQuery "#{pane_index}" -Session $TUI_SESSION_NAV
    & $script:TUI_PSMUX select-pane -U -t $TUI_SESSION_NAV 2>&1 | Out-Null
    Start-Sleep -Milliseconds 300
    $paneAfter3 = Safe-TuiQuery "#{pane_index}" -Session $TUI_SESSION_NAV
    if ($paneAfter3 -ne $paneBefore3) {
        Write-Pass "TUI: select-pane -U moved focus ($paneBefore3 -> $paneAfter3)"
    } else {
        Write-Fail "TUI: select-pane -U did not move focus (stayed at $paneBefore3)"
    }

    Cleanup-PsmuxWindow -Session $TUI_SESSION_NAV
    Write-Host ""
} else {
    Write-Info "TUI verification skipped (could not launch window)"
}

# ─── Cleanup ──────────────────────────────────────────────────
Write-Host ""
Write-Info "Cleaning up..."
Psmux kill-session -t $SESSION | Out-Null
Start-Sleep -Milliseconds 1000

Write-Host ""
Write-Host ("=" * 60)
Write-Host "PANE NAVIGATION TEST SUMMARY"
Write-Host ("=" * 60)
Write-Host "Passed: $script:TestsPassed" -ForegroundColor Green
Write-Host "Failed: $script:TestsFailed" -ForegroundColor Red
Write-Host ""

if ($script:TestsFailed -gt 0) {
    Write-Host "SOME TESTS FAILED" -ForegroundColor Red
    exit 1
} else {
    Write-Host "ALL TESTS PASSED" -ForegroundColor Green
    exit 0
}