Files
zero/internal/tui/transcript_view_test.go
PierrunoYTandClaude Opus 5.5 13e2596b2e chore: move repository references to Twigpine/zero (#1105)
Update the Go module path, imports, install scripts, docs, workflows,
packaging and action defaults from Gitlawb/zero to Twigpine/zero. The npm
scope (already @twigpine/zero on main, with its @gitlawb/zero migration
notes), LICENSE, action author, the OpenGateway provider and historical
CHANGELOG links are unchanged.

The X-AIMLAPI-Integration-Repo header keeps Gitlawb/zero: it is what
AIMLAPI sees for attribution, so changing it needs a maintainer's yes.
docs/INSTALL.md keeps `go install github.com/Gitlawb/zero/...`, because
every tagged release so far declares that module path. The Homebrew
formula comment names the Twigpine/homebrew-tap tap.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-17 07:13:21 +08:00

206 lines
6.6 KiB
Go

package tui
import (
"context"
"fmt"
"strings"
"testing"
tea "charm.land/bubbletea/v2"
"charm.land/lipgloss/v2"
"github.com/Twigpine/zero/internal/tools"
)
func TestTranscriptCommandTogglesDetailedView(t *testing.T) {
m := transcriptViewTestModel()
m.input.SetValue("/transcript")
updated, _ := m.Update(testKey(tea.KeyEnter))
next := updated.(model)
assertContains(t, plainRender(t, next.View()), "Transcript")
next.input.SetValue("/transcript")
updated, _ = next.Update(testKey(tea.KeyEnter))
next = updated.(model)
assertNotContains(t, plainRender(t, next.View()), "Transcript")
}
func TestCtrlOTogglesDetailedTranscriptView(t *testing.T) {
m := transcriptViewTestModel()
updated, _ := m.Update(testKeyCtrl('o'))
next := updated.(model)
assertContains(t, plainRender(t, next.View()), "Transcript")
updated, _ = next.Update(testKeyCtrl('o'))
next = updated.(model)
assertNotContains(t, plainRender(t, next.View()), "Transcript")
}
func TestEscExitsDetailedTranscriptView(t *testing.T) {
m := transcriptViewTestModel()
updated, _ := m.Update(testKeyCtrl('o'))
next := updated.(model)
assertContains(t, plainRender(t, next.View()), "Transcript")
updated, _ = next.Update(testKey(tea.KeyEsc))
next = updated.(model)
assertNotContains(t, plainRender(t, next.View()), "Transcript")
}
func TestDetailedTranscriptIncludesToolOutputBeyondLiveCap(t *testing.T) {
m := transcriptViewTestModel()
output := numberedLines(flushCardBodyMaxLines + 4)
row := transcriptRow{kind: rowToolResult, id: "tool-1", tool: "custom_tool", status: tools.StatusOK, detail: output}
m.transcript = append(m.transcript, row)
m.flushed = len(m.transcript)
// Long generic tool output collapses in the live view (click to expand);
// the body is hidden behind a one-line summary.
compact := plainRender(t, m.renderRow(row, m.width, buildRowContext(m.transcript)))
assertNotContains(t, compact, "line-020")
assertContains(t, compact, "click to expand")
// The detailed transcript view (Ctrl+O) still shows the full, uncapped output.
updated, _ := m.Update(testKeyCtrl('o'))
next := updated.(model)
view := plainRender(t, next.View())
assertContains(t, view, "line-404")
assertNotContains(t, view, "more lines")
assertNotContains(t, view, "click to expand")
}
func TestDetailedTranscriptViewNeverExceedsTerminalWidth(t *testing.T) {
for _, width := range []int{24, 40, 58, 80, 120} {
m := transcriptViewTestModel()
m.width = width
m.transcript = append(m.transcript,
transcriptRow{kind: rowUser, text: "please inspect this very long request and show the transcript without overflowing"},
transcriptRow{kind: rowToolResult, id: "wide", tool: "custom_tool", status: tools.StatusOK, detail: strings.Repeat("wide-output-", 30)},
transcriptRow{kind: rowAssistant, text: "done with a final answer that also needs wrapping", final: true, turnTools: 1},
)
m.flushed = len(m.transcript)
updated, _ := m.Update(testKeyCtrl('o'))
next := updated.(model)
view := viewString(next.View())
assertContains(t, plainRender(t, view), "Transcript")
for index, line := range strings.Split(view, "\n") {
if got := lipgloss.Width(line); got > chatWidth(width) {
t.Fatalf("width %d: detailed transcript line %d is %d cells wide: %q", width, index, got, line)
}
}
}
}
func TestDetailedTranscriptSwallowsNormalChatSubmit(t *testing.T) {
m := transcriptViewTestModel()
updated, _ := m.Update(testKeyCtrl('o'))
next := updated.(model)
next.input.SetValue("this should not launch")
updated, cmd := next.Update(testKey(tea.KeyEnter))
next = updated.(model)
if cmd != nil {
t.Fatal("detailed transcript should not return a run command for Enter")
}
if transcriptContains(next.transcript, "this should not launch") {
t.Fatalf("detailed transcript should not submit composer text, got %#v", next.transcript)
}
assertContains(t, plainRender(t, next.View()), "Transcript")
}
func transcriptViewTestModel() model {
m := newModel(context.Background(), Options{
Cwd: "/work/zero",
ProviderName: "openai",
ModelName: "gpt-test",
})
m.width = 96
m.height = 30
m.headerPrinted = true
return m
}
func TestPgUpDownScrollsDetailedTranscript(t *testing.T) {
m := transcriptViewTestModel()
m.altScreen = true
// Fill the transcript enough to overflow the 30-row viewport.
for i := 0; i < 60; i++ {
m.transcript = append(m.transcript, transcriptRow{kind: rowUser, text: "line content", final: true})
}
m.flushed = len(m.transcript)
// Enter detailed mode.
updated, _ := m.Update(testKeyCtrl('o'))
m = updated.(model)
if !m.transcriptDetailed {
t.Fatal("sanity check failed: Ctrl+O should enter detailed mode")
}
if m.chatScrollOffset != 0 {
t.Fatal("sanity check failed: detailed transcript should start at bottom")
}
// PgUp reveals older content = positive scroll offset.
updated, _ = m.Update(testKey(tea.KeyPgUp))
m = updated.(model)
if m.chatScrollOffset <= 0 {
t.Fatalf("PgUp in detailed mode should scroll toward older content, got chatScrollOffset=%d", m.chatScrollOffset)
}
// PgDown scrolls back toward bottom.
updated, _ = m.Update(testKey(tea.KeyPgDown))
m = updated.(model)
if m.chatScrollOffset != 0 {
t.Fatalf("PgDown after PgUp should return to bottom in detailed mode, got chatScrollOffset=%d", m.chatScrollOffset)
}
}
func TestUpDownArrowsScrollDetailedTranscript(t *testing.T) {
m := transcriptViewTestModel()
m.altScreen = true
for i := 0; i < 60; i++ {
m.transcript = append(m.transcript, transcriptRow{kind: rowUser, text: "line content", final: true})
}
m.flushed = len(m.transcript)
updated, _ := m.Update(testKeyCtrl('o'))
m = updated.(model)
if !m.transcriptDetailed {
t.Fatal("sanity check failed: Ctrl+O should enter detailed mode")
}
// Arrow down scrolls one line toward newer content; at bottom it stays at 0.
updated, _ = m.Update(testKey(tea.KeyDown))
m = updated.(model)
if m.chatScrollOffset != 0 {
t.Fatalf("KeyDown at bottom should stay at 0, got %d", m.chatScrollOffset)
}
// Arrow up scrolls one line toward older content.
updated, _ = m.Update(testKey(tea.KeyUp))
m = updated.(model)
if m.chatScrollOffset != 1 {
t.Fatalf("KeyUp should scroll one line up, got chatScrollOffset=%d", m.chatScrollOffset)
}
// Arrow down scrolls back toward bottom.
updated, _ = m.Update(testKey(tea.KeyDown))
m = updated.(model)
if m.chatScrollOffset != 0 {
t.Fatalf("KeyDown after KeyUp should return to bottom, got chatScrollOffset=%d", m.chatScrollOffset)
}
}
func numberedLines(count int) string {
lines := make([]string, 0, count)
for i := 1; i <= count; i++ {
lines = append(lines, fmt.Sprintf("line-%03d", i))
}
return strings.Join(lines, "\n")
}