# Issue #257: Preview support + draggable popup for choose-tree/choose-session
#
# This test verifies:
# 1. capture-pane with cross-window targeting (-t :@WID) works
# (the underlying mechanism the preview pane relies on)
# 2. capture-pane with cross-pane targeting (-t :@WID.%PID) works
# 3. The TUI popup (choose-tree) opens visible without crashing
# 4. The injector can drive prefix+w / prefix+s to open the pickers
#
# This test does NOT screen-scrape the popup — visual verification is via
# CLI plus by confirming the underlying capture mechanism returns content.
$ErrorActionPreference = "Continue"
$PSMUX = (Get-Command psmux -EA Stop).Source
$SESSION = "issue257_preview"
$psmuxDir = "$env:USERPROFILE\.psmux"
$script:TestsPassed = 0
$script:TestsFailed = 0
function Write-Pass($msg) { Write-Host " [PASS] $msg" -ForegroundColor Green; $script:TestsPassed++ }
function Write-Fail($msg) { Write-Host " [FAIL] $msg" -ForegroundColor Red; $script:TestsFailed++ }
function Cleanup {
& $PSMUX kill-session -t $SESSION 2>&1 | Out-Null
& $PSMUX kill-session -t "${SESSION}_b" 2>&1 | Out-Null
Start-Sleep -Milliseconds 500
Remove-Item "$psmuxDir\$SESSION.*" -Force -EA SilentlyContinue
Remove-Item "$psmuxDir\${SESSION}_b.*" -Force -EA SilentlyContinue
}
Cleanup
Write-Host "`n=== Issue #257: Preview support + draggable popup ===" -ForegroundColor Cyan
# Create a session with two windows so the tree has multiple entries.
& $PSMUX new-session -d -s $SESSION
Start-Sleep -Seconds 3
& $PSMUX has-session -t $SESSION 2>$null
if ($LASTEXITCODE -ne 0) {
Write-Fail "Session creation failed"
Cleanup; exit 1
}
# Create a 2nd window and put a marker in it.
& $PSMUX new-window -t $SESSION -n "preview_target"
Start-Sleep -Seconds 2
& $PSMUX send-keys -t $SESSION "echo PREVIEW_MARKER_FROM_W1" Enter
Start-Sleep -Seconds 1
# Get window IDs
$winsJson = & $PSMUX list-windows -t $SESSION -F '#{window_index}:#{window_id}:#{window_name}' 2>&1
Write-Host " Windows: $($winsJson -join '; ')" -ForegroundColor DarkGray
# === TEST 1: capture-pane with cross-window target -t :@WID works ===
Write-Host "`n[Test 1] capture-pane -t :@WID returns active pane content" -ForegroundColor Yellow
# Find the @ id of the second window
$secondWid = $null
foreach ($line in $winsJson) {
if ($line -match '^\s*1:@(\d+):') { $secondWid = $matches[1]; break }
}
if (-not $secondWid) {
# Fallback: try without index parsing
$idLine = (& $PSMUX display-message -t "${SESSION}:1" -p '#{window_id}' 2>&1).Trim()
if ($idLine -match '@?(\d+)') { $secondWid = $matches[1] }
}
Write-Host " Second window ID: @$secondWid" -ForegroundColor DarkGray
if ($secondWid) {
$cap = & $PSMUX capture-pane -p -t ":@$secondWid" 2>&1 | Out-String
if ($cap -match "PREVIEW_MARKER_FROM_W1") {
Write-Pass "Cross-window capture-pane returned target window content"
} else {
Write-Fail "Cross-window capture-pane did not return marker. Got: $($cap.Substring(0, [Math]::Min(200, $cap.Length)))"
}
} else {
Write-Fail "Could not parse second window id"
}
# === TEST 2: capture-pane with explicit pane id -t :@WID.%PID ===
Write-Host "`n[Test 2] capture-pane -t :@WID.%PID returns specific pane" -ForegroundColor Yellow
if ($secondWid) {
$panesJson = & $PSMUX list-panes -t "${SESSION}:1" -F '#{pane_id}' 2>&1
$firstPane = ($panesJson | Select-Object -First 1).Trim().TrimStart('%')
if ($firstPane -match '^\d+$') {
$cap2 = & $PSMUX capture-pane -p -t ":@$secondWid.%$firstPane" 2>&1 | Out-String
if ($cap2.Length -gt 0) {
Write-Pass "Pane-targeted capture-pane returned content (length=$($cap2.Length))"
} else {
Write-Fail "Pane-targeted capture-pane returned empty"
}
} else {
Write-Fail "Could not parse pane id from: $panesJson"
}
}
# === TEST 3: Visible TUI popup opens without crash ===
Write-Host "`n[Test 3] Win32 TUI: prefix+w opens choose-tree popup" -ForegroundColor Yellow
$injectorExe = "$env:TEMP\psmux_injector.exe"
if (-not (Test-Path $injectorExe)) {
$csc = "C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe"
if (Test-Path $csc) {
& $csc /nologo /optimize /out:$injectorExe tests\injector.cs 2>&1 | Out-Null
}
}
$SESSION_TUI = "issue257_tui"
& $PSMUX kill-session -t $SESSION_TUI 2>&1 | Out-Null
Start-Sleep -Milliseconds 500
Remove-Item "$psmuxDir\$SESSION_TUI.*" -Force -EA SilentlyContinue
$proc = Start-Process -FilePath $PSMUX -ArgumentList "new-session","-s",$SESSION_TUI -PassThru
Start-Sleep -Seconds 4
# Add another window so the tree has content
& $PSMUX new-window -t $SESSION_TUI 2>&1 | Out-Null
Start-Sleep -Seconds 1
$beforeWin = (& $PSMUX display-message -t $SESSION_TUI -p '#{window_index}' 2>&1).Trim()
Write-Host " Active window before: $beforeWin" -ForegroundColor DarkGray
if (Test-Path $injectorExe) {
# Open choose-tree (prefix+w), navigate down, press Enter
& $injectorExe $proc.Id "^b{SLEEP:300}w{SLEEP:600}{ENTER}"
Start-Sleep -Seconds 2
# The session is still alive (didn't crash)
& $PSMUX has-session -t $SESSION_TUI 2>$null
if ($LASTEXITCODE -eq 0) {
Write-Pass "Session survived prefix+w + Enter (no crash from preview rendering)"
} else {
Write-Fail "Session died after prefix+w (popup or preview crashed)"
}
} else {
Write-Fail "Injector not available (skipping TUI keystroke test)"
}
# === TEST 4: choose-session opens without crash ===
Write-Host "`n[Test 4] Win32 TUI: prefix+s opens choose-session popup" -ForegroundColor Yellow
if (Test-Path $injectorExe) {
& $injectorExe $proc.Id "^b{SLEEP:300}s{SLEEP:600}{ESC}"
Start-Sleep -Seconds 1
& $PSMUX has-session -t $SESSION_TUI 2>$null
if ($LASTEXITCODE -eq 0) {
Write-Pass "Session survived prefix+s + Esc"
} else {
Write-Fail "Session died after prefix+s"
}
}
# === Cleanup ===
& $PSMUX kill-session -t $SESSION_TUI 2>&1 | Out-Null
try { Stop-Process -Id $proc.Id -Force -EA SilentlyContinue } catch {}
Cleanup
# === TEST 5 (follow-up): window-layout endpoint reflects real splits ===
Write-Host "`n[Test 5] window-layout returns full split layout JSON" -ForegroundColor Yellow
$SESSION_LAY = "issue257_layout"
& $PSMUX kill-session -t $SESSION_LAY 2>&1 | Out-Null
Start-Sleep -Milliseconds 500
Remove-Item "$psmuxDir\$SESSION_LAY.*" -Force -EA SilentlyContinue
& $PSMUX new-session -d -s $SESSION_LAY
Start-Sleep -Seconds 2
& $PSMUX split-window -h -t $SESSION_LAY 2>&1 | Out-Null
Start-Sleep -Milliseconds 600
& $PSMUX split-window -v -t $SESSION_LAY 2>&1 | Out-Null
Start-Sleep -Milliseconds 600
$wid = (& $PSMUX display-message -t $SESSION_LAY -p '#{window_id}' 2>&1).Trim().TrimStart('@')
$panes = & $PSMUX list-panes -t $SESSION_LAY -F '#{pane_id}' 2>&1
$paneCount = ($panes | Where-Object { $_ -match '%\d+' }).Count
Write-Host " Window @$wid has $paneCount panes" -ForegroundColor DarkGray
if ($paneCount -ne 3) {
Write-Fail "Expected 3 panes, got $paneCount"
} else {
$portPath = "$psmuxDir\$SESSION_LAY.port"
$keyPath = "$psmuxDir\$SESSION_LAY.key"
if (-not (Test-Path $portPath) -or -not (Test-Path $keyPath)) {
Write-Fail "Port or key file missing for $SESSION_LAY"
} else {
$port = [int](Get-Content $portPath -Raw).Trim()
$key = (Get-Content $keyPath -Raw).Trim()
try {
$client = [System.Net.Sockets.TcpClient]::new('127.0.0.1', $port)
$stream = $client.GetStream()
$stream.ReadTimeout = 1500
$writer = [System.IO.StreamWriter]::new($stream)
$writer.NewLine = "`n"
$writer.AutoFlush = $true
$reader = [System.IO.StreamReader]::new($stream)
$writer.WriteLine("AUTH $key")
# Consume auth ack ("OK")
$null = $reader.ReadLine()
$writer.WriteLine("window-layout $wid")
Start-Sleep -Milliseconds 400
$resp = ""
while ($stream.DataAvailable) {
$resp += [char]$stream.ReadByte()
}
$client.Close()
Write-Host " Layout JSON: $resp" -ForegroundColor DarkGray
$leafCount = ([regex]::Matches($resp, '"type":"leaf"')).Count
$hasSplit = $resp -match '"type":"split"'
$hasH = $resp -match '"kind":"Horizontal"'
$hasV = $resp -match '"kind":"Vertical"'
if ($hasSplit -and $leafCount -ge 3 -and $hasH -and $hasV) {
Write-Pass "window-layout returned 3 leaves with both Horizontal+Vertical splits"
} else {
Write-Fail "Layout JSON missing structure (split=$hasSplit, leaves=$leafCount, H=$hasH, V=$hasV)"
}
} catch {
Write-Fail "TCP request to window-layout failed: $_"
}
}
}
& $PSMUX kill-session -t $SESSION_LAY 2>&1 | Out-Null
Remove-Item "$psmuxDir\$SESSION_LAY.*" -Force -EA SilentlyContinue
# === TEST 6: capture-pane -e -p preserves ANSI SGR escape sequences ===
Write-Host "`n[Test 6] capture-pane -e -p emits SGR escape sequences" -ForegroundColor Yellow
$SESSION_E = "issue257_esc"
& $PSMUX kill-session -t $SESSION_E 2>&1 | Out-Null
Start-Sleep -Milliseconds 300
Remove-Item "$psmuxDir\$SESSION_E.*" -Force -EA SilentlyContinue
& $PSMUX new-session -d -s $SESSION_E
Start-Sleep -Seconds 2
# Print a colored marker (red on default)
& $PSMUX send-keys -t $SESSION_E "Write-Host 'ESCMARKER' -ForegroundColor Red" Enter
Start-Sleep -Seconds 2
$capE = & $PSMUX capture-pane -e -p -t $SESSION_E 2>&1 | Out-String
# Look for ESC[ ... m sequences (SGR) and the marker
$ESC = [char]27
$hasSgr = $capE -match "$ESC\["
$hasMarker = $capE -match 'ESCMARKER'
if ($hasSgr -and $hasMarker) {
Write-Pass "capture-pane -e returned SGR-escaped output containing the marker"
} else {
Write-Fail "Expected SGR + ESCMARKER. hasSgr=$hasSgr hasMarker=$hasMarker"
}
& $PSMUX kill-session -t $SESSION_E 2>&1 | Out-Null
Remove-Item "$psmuxDir\$SESSION_E.*" -Force -EA SilentlyContinue
# === TEST 7: Win32 TUI: pressing 'p' inside choose-session does not crash ===
Write-Host "`n[Test 7] Win32 TUI: 'p' toggles preview without crashing" -ForegroundColor Yellow
if (Test-Path $injectorExe) {
$SESSION_TG = "issue257_toggle"
& $PSMUX kill-session -t $SESSION_TG 2>&1 | Out-Null
Start-Sleep -Milliseconds 300
Remove-Item "$psmuxDir\$SESSION_TG.*" -Force -EA SilentlyContinue
$procT = Start-Process -FilePath $PSMUX -ArgumentList "new-session","-s",$SESSION_TG -PassThru
Start-Sleep -Seconds 4
# Open choose-session, press p twice (toggle off, toggle on), then Esc.
& $injectorExe $procT.Id "^b{SLEEP:300}s{SLEEP:600}p{SLEEP:200}p{SLEEP:200}{ESC}"
Start-Sleep -Seconds 1
& $PSMUX has-session -t $SESSION_TG 2>$null
if ($LASTEXITCODE -eq 0) {
Write-Pass "Session survived 'p' toggle in choose-session"
} else {
Write-Fail "Session died after pressing 'p'"
}
& $PSMUX kill-session -t $SESSION_TG 2>&1 | Out-Null
try { Stop-Process -Id $procT.Id -Force -EA SilentlyContinue } catch {}
Remove-Item "$psmuxDir\$SESSION_TG.*" -Force -EA SilentlyContinue
} else {
Write-Fail "Injector not available"
}
# === TEST 8 (follow-up): window-dump returns DIFFERENT content per pane ===
# This is the regression test for the "preview shows pstop everywhere" bug:
# capture-pane -t :@WID.%PID was misrouting through transient -t focus and
# returning the active pane's content for every queried pane. The new
# `window-dump` endpoint sidesteps that path entirely by walking the window
# tree on the server and emitting each pane's own rows_v2, so previews are
# guaranteed to be per-pane correct.
Write-Host "`n[Test 8] window-dump returns distinct content per pane" -ForegroundColor Yellow
$SESSION_DUMP = "issue257_dump"
& $PSMUX kill-session -t $SESSION_DUMP 2>&1 | Out-Null
Start-Sleep -Milliseconds 500
Remove-Item "$psmuxDir\$SESSION_DUMP.*" -Force -EA SilentlyContinue
& $PSMUX new-session -d -s $SESSION_DUMP
Start-Sleep -Seconds 2
& $PSMUX split-window -h -t $SESSION_DUMP 2>&1 | Out-Null
Start-Sleep -Milliseconds 500
& $PSMUX split-window -v -t $SESSION_DUMP 2>&1 | Out-Null
Start-Sleep -Milliseconds 500
# Get the 3 pane ids in order.
$paneIds = (& $PSMUX list-panes -t $SESSION_DUMP -F '#{pane_id}' 2>&1 | Where-Object { $_ -match '^%\d+$' }) -split "`r`n"
$paneIds = @($paneIds | Where-Object { $_ -match '^%\d+$' })
if ($paneIds.Count -ne 3) {
Write-Fail "Expected 3 panes in dump session, got $($paneIds.Count)"
} else {
# Write a unique marker into each pane's stdout.
& $PSMUX send-keys -t "${SESSION_DUMP}:0.$($paneIds[0])" "Write-Host 'DUMPMARK_AAA'" Enter
& $PSMUX send-keys -t "${SESSION_DUMP}:0.$($paneIds[1])" "Write-Host 'DUMPMARK_BBB'" Enter
& $PSMUX send-keys -t "${SESSION_DUMP}:0.$($paneIds[2])" "Write-Host 'DUMPMARK_CCC'" Enter
Start-Sleep -Seconds 3
$wid = (& $PSMUX display-message -t $SESSION_DUMP -p '#{window_id}' 2>&1).Trim().TrimStart('@')
$portPath = "$psmuxDir\$SESSION_DUMP.port"
$keyPath = "$psmuxDir\$SESSION_DUMP.key"
$port = [int](Get-Content $portPath -Raw).Trim()
$key = (Get-Content $keyPath -Raw).Trim()
try {
$client = [System.Net.Sockets.TcpClient]::new('127.0.0.1', $port)
$stream = $client.GetStream()
$stream.ReadTimeout = 3000
$writer = [System.IO.StreamWriter]::new($stream)
$writer.NewLine = "`n"
$writer.AutoFlush = $true
$reader = [System.IO.StreamReader]::new($stream)
$writer.WriteLine("AUTH $key")
$null = $reader.ReadLine()
$writer.WriteLine("window-dump $wid")
Start-Sleep -Milliseconds 800
$resp = ""
while ($stream.DataAvailable) { $resp += [char]$stream.ReadByte() }
$client.Close()
# Assertions: response should contain ALL THREE markers, and each
# marker should appear in a different leaf section. We check the
# distinct-presence side first (the duplication bug would have
# returned the same active-pane buffer in every leaf, so only the
# most-recently-active pane's marker would show up).
$hasA = $resp -match 'DUMPMARK_AAA'
$hasB = $resp -match 'DUMPMARK_BBB'
$hasC = $resp -match 'DUMPMARK_CCC'
if ($hasA -and $hasB -and $hasC) {
Write-Pass "window-dump JSON contains all three distinct pane markers"
} else {
Write-Fail "Missing markers in window-dump (A=$hasA B=$hasB C=$hasC). Resp length=$($resp.Length)"
}
# Stronger assertion: count occurrences of each marker. With the
# bug each marker would either appear 0 times or 3 times (active
# pane echoed everywhere). With the fix each appears exactly once.
$countA = ([regex]::Matches($resp, 'DUMPMARK_AAA')).Count
$countB = ([regex]::Matches($resp, 'DUMPMARK_BBB')).Count
$countC = ([regex]::Matches($resp, 'DUMPMARK_CCC')).Count
Write-Host " Marker counts: AAA=$countA BBB=$countB CCC=$countC" -ForegroundColor DarkGray
if ($countA -eq 1 -and $countB -eq 1 -and $countC -eq 1) {
Write-Pass "Each marker appears exactly once (per-pane targeting works)"
} else {
Write-Fail "Expected each marker exactly once, got A=$countA B=$countB C=$countC"
}
} catch {
Write-Fail "TCP request to window-dump failed: $_"
}
}
& $PSMUX kill-session -t $SESSION_DUMP 2>&1 | Out-Null
Remove-Item "$psmuxDir\$SESSION_DUMP.*" -Force -EA SilentlyContinue
Write-Host "`n=== Issue #257 results: $script:TestsPassed passed, $script:TestsFailed failed ===" -ForegroundColor Cyan
if ($script:TestsFailed -gt 0) { exit 1 }