Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .github/workflows/pr-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ on:
- '**.go'
- 'go.mod'
- 'go.sum'
- 'cmd/clippy/testdata/**'
- 'scripts/picker_snapshot_tmux.sh'
- '.github/workflows/pr-test.yml'

permissions:
Expand All @@ -32,6 +34,21 @@ jobs:
# Run tests sequentially to avoid clipboard conflicts between packages.
go test -v -p 1 ./...

- name: Ensure tmux is available
run: |
if ! command -v tmux >/dev/null 2>&1; then
brew install tmux
fi
tmux -V

- name: Run TUI snapshot regression
run: |
if ! command -v tmux >/dev/null 2>&1; then
echo "tmux is required for TUI snapshot tests"
exit 1
fi
./scripts/picker_snapshot_tmux.sh

- name: Install golangci-lint
run: |
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.5.0
Expand Down
113 changes: 113 additions & 0 deletions cmd/clippy/picker_snapshot_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package main

import (
"fmt"
"os"
"regexp"
"strings"
"testing"
"time"

"github.com/neilberkman/clippy/pkg/recent"
)

const (
pickerSnapshotPath = "testdata/picker_snapshot.txt"
beginMarker = "===PICKER_SNAPSHOT_BEGIN==="
endMarker = "===PICKER_SNAPSHOT_END==="
)

var ansiRegex = regexp.MustCompile(`\x1b\[[0-9;]*[A-Za-z]`)

func TestPickerSnapshotGolden(t *testing.T) {
snapshot := renderPickerSnapshot()

if os.Getenv("UPDATE_SNAPSHOTS") == "1" {
if err := os.WriteFile(pickerSnapshotPath, []byte(snapshot), 0644); err != nil {
t.Fatalf("failed writing snapshot: %v", err)
}
}

wantBytes, err := os.ReadFile(pickerSnapshotPath)
if err != nil {
t.Fatalf("failed reading snapshot %s: %v", pickerSnapshotPath, err)
}

want := strings.TrimSpace(string(wantBytes))
got := strings.TrimSpace(snapshot)
if got != want {
t.Fatalf(
"picker snapshot mismatch\nre-run with UPDATE_SNAPSHOTS=1 if change is intentional\n--- got ---\n%s\n--- want ---\n%s",
got,
want,
)
}
}

func TestPickerSnapshotPrint(t *testing.T) {
if os.Getenv("CLIPPY_SNAPSHOT_PRINT") != "1" {
t.Skip("snapshot print disabled")
}

fmt.Println(beginMarker)
fmt.Println(renderPickerSnapshot())
fmt.Println(endMarker)
}

func renderPickerSnapshot() string {
baseTime := time.Date(2026, 2, 13, 9, 30, 0, 0, time.UTC)
files := []recent.FileInfo{
{
Name: "workflow-run-logs-2026-02-13.txt",
Path: "/Users/tester/Downloads/workflow-run-logs-2026-02-13.txt",
Size: 1536,
Modified: baseTime,
MimeType: "text/plain",
},
{
Name: "incident-response-playbook-v3.pdf",
Path: "/Users/tester/Documents/incident-response-playbook-v3.pdf",
Size: 987654,
Modified: baseTime.Add(-15 * time.Minute),
MimeType: "application/pdf",
},
{
Name: "database-backup-2026-02-13-0915.sql.gz",
Path: "/Users/tester/Downloads/database-backup-2026-02-13-0915.sql.gz",
Size: 1843200,
Modified: baseTime.Add(-45 * time.Minute),
MimeType: "application/gzip",
},
{
Name: "screenshot-prod-error.png",
Path: "/Users/tester/Desktop/screenshot-prod-error.png",
Size: 245760,
Modified: baseTime.Add(-2 * time.Hour),
MimeType: "image/png",
},
}

model := pickerModel{
files: files,
cursor: 1,
selected: make(map[int]bool),
absoluteTime: true,
terminalWidth: 100,
terminalHeight: 24,
newFiles: map[string]time.Time{files[3].Path: baseTime},
}

return normalizeSnapshotOutput(model.View())
}

func normalizeSnapshotOutput(view string) string {
s := strings.ReplaceAll(view, "\r\n", "\n")
s = ansiRegex.ReplaceAllString(s, "")

lines := strings.Split(s, "\n")
for i := range lines {
lines[i] = strings.TrimRight(lines[i], " \t")
}

return strings.TrimSpace(strings.Join(lines, "\n"))
}
15 changes: 15 additions & 0 deletions cmd/clippy/testdata/picker_snapshot.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Select files (Enter: current item, Space: multi-select, p: copy & paste)

] workflow-run-logs-2026-02-13.txt [Plain text document] (Feb 13 09:30)
▶ ] incident-response-playbook-v3.pdf [PDF document] (Feb 13 09:15)
] database-backup-2026-02-13-0915.sql.gz [Gzip archive] (Feb 13 08:45)
] screenshot-prod-error.png [PNG image] (Feb 13 07:30)

╭─────────────────────────────────────────────────────────────────╮
│ Name: incident-response-playbook-v3.pdf │
│ Type: PDF document │
│ Size: 964.5 KB │
│ Modified: Feb 13 09:15:00 │
│ Path: /Users/tester/Documents/incident-response-playbook-v3.pdf │
╰─────────────────────────────────────────────────────────────────╯
↑/↓ navigate • Enter: copy current • Space: toggle select • p: copy&paste • Esc: cancel
58 changes: 58 additions & 0 deletions scripts/picker_snapshot_tmux.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/usr/bin/env bash
set -euo pipefail

ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
SOCKET_NAME="clippy-snapshot-$RANDOM-$RANDOM"
SESSION_NAME="picker-snapshot"
BEGIN_MARKER="===PICKER_SNAPSHOT_BEGIN==="
END_MARKER="===PICKER_SNAPSHOT_END==="
EXPECTED_FILE="$ROOT_DIR/cmd/clippy/testdata/picker_snapshot.txt"

cleanup() {
tmux -L "$SOCKET_NAME" kill-server >/dev/null 2>&1 || true
}
trap cleanup EXIT

tmux -L "$SOCKET_NAME" -f /dev/null new-session -d -s "$SESSION_NAME" -x 120 -y 40 \
"cd '$ROOT_DIR' && CLIPPY_SNAPSHOT_PRINT=1 go test ./cmd/clippy -run TestPickerSnapshotPrint -count=1 -v; echo __CLIPPY_SNAPSHOT_DONE__; sleep 2"

for _ in $(seq 1 120); do
pane="$(tmux -L "$SOCKET_NAME" capture-pane -p -t "$SESSION_NAME" || true)"
if grep -q "__CLIPPY_SNAPSHOT_DONE__" <<<"$pane"; then
break
fi
sleep 0.2
done

pane="$(tmux -L "$SOCKET_NAME" capture-pane -p -t "$SESSION_NAME" || true)"
captured="$(awk -v begin="$BEGIN_MARKER" -v end="$END_MARKER" '
$0==begin {flag=1; next}
$0==end {flag=0; exit}
flag {print}
' <<<"$pane")"

if [[ -z "$captured" ]]; then
echo "Failed to capture picker snapshot from tmux pane."
echo "Captured pane:"
echo "$pane"
exit 1
fi

if [[ ! -f "$EXPECTED_FILE" ]]; then
echo "Expected snapshot file not found: $EXPECTED_FILE"
exit 1
fi

tmpfile="$(mktemp)"
printf '%s' "$captured" >"$tmpfile"

if ! diff -u "$EXPECTED_FILE" "$tmpfile"; then
echo
echo "Picker snapshot differs from golden file."
echo "If expected, run: UPDATE_SNAPSHOTS=1 go test ./cmd/clippy -run TestPickerSnapshotGolden"
rm -f "$tmpfile"
exit 1
fi

rm -f "$tmpfile"
echo "Picker tmux snapshot matches golden output."