Files
patterm/internal/app/summarizer_test.go
Harry Bayliss d2342f99cf Show every agent tab's summary, not just the focused one
The tab bar's row-2 summary was painted only for the active tab. Add a
per-child summaryTextFor/summaryRawFor helper (active variants now
delegate to it), carry each tab's childID on its tabRect, and loop over
all visible tabs so each renders its own summary under its column.
Layout is unchanged (still 3 rows); narrow tabs clip as before.

Resolves the per-tab summary TODO item.
2026-05-25 13:06:53 +01:00

126 lines
4.0 KiB
Go

package app
import (
"strings"
"testing"
"github.com/hjbdev/patterm/internal/preset"
)
func TestParseSummarizerResponseAllowsWrappedJSON(t *testing.T) {
resp, err := parseSummarizerResponse("log\n{\"summary\":\"Waiting for tests\",\"state\":\"WORKING\"}\n")
if err != nil {
t.Fatalf("parseSummarizerResponse: %v", err)
}
if resp.Summary != "Waiting for tests" || summaryIdleState(resp.State) != StateWorking {
t.Fatalf("response = %+v", resp)
}
}
func TestCompactSummaryTextDropsControlAndRedundantWhitespace(t *testing.T) {
got := compactSummaryText("hello\x00 world \n\n\n\x1b[31mred\x1b[0m\n")
if strings.ContainsRune(got, '\x00') {
t.Fatalf("control byte survived: %q", got)
}
if strings.Contains(got, "\n\n\n") {
t.Fatalf("redundant blanks survived: %q", got)
}
if strings.Contains(got, "\x1b") {
t.Fatalf("ansi survived: %q", got)
}
}
func TestWrapSidebarSummaryKeepsWordBoundaries(t *testing.T) {
got := wrapSidebarSummary("alpha beta gamma delta", 12)
want := []string{"alpha beta", "gamma delta"}
if len(got) != len(want) {
t.Fatalf("lines = %#v", got)
}
for i := range want {
if got[i] != want[i] {
t.Fatalf("line %d = %q want %q", i, got[i], want[i])
}
}
long := wrapSidebarSummary("supercalifragilistic short", 8)
if len(long) == 0 || strings.Contains(strings.Join(long, ""), "…") {
t.Fatalf("long word should wrap without ellipsis: %#v", long)
}
for _, line := range long {
if visibleLen(line) > 8 {
t.Fatalf("line %q exceeds width", line)
}
}
}
func TestSummaryTextForSelectsChildAndClips(t *testing.T) {
sess := NewSession(t.TempDir(), "test")
cfg := defaultSettings()
st := &uiState{
sess: sess,
settings: cfg,
summaries: newSummaryManager(sess, t.TempDir(), preset.Set{}, func() autoSummarySettings {
return cfg.AutoSummary.clone()
}, nil, nil),
}
st.summaries.mu.Lock()
st.summaries.entries["a1"] = &summaryEntry{state: summaryState{Text: " alpha summary "}}
st.summaries.entries["a2"] = &summaryEntry{state: summaryState{Text: "beta summary"}}
st.summaries.entries["empty"] = &summaryEntry{state: summaryState{Text: " "}}
st.summaries.entries["long"] = &summaryEntry{state: summaryState{Text: "abcdefghijklmnopqrstuvwxyz"}}
st.summaries.mu.Unlock()
if got := st.summaryTextFor("a2", 20); got != "beta summary" {
t.Fatalf("summaryTextFor(a2) = %q, want beta summary", got)
}
if got := st.summaryTextFor("empty", 20); got != "" {
t.Fatalf("summaryTextFor(empty) = %q, want empty", got)
}
if got := st.summaryTextFor("long", 8); got != "abcdefg…" {
t.Fatalf("summaryTextFor(long) = %q, want abcdefg…", got)
}
st.settingsMu.Lock()
st.settings.AutoSummary.Enabled = false
st.settingsMu.Unlock()
if got := st.summaryTextFor("a1", 20); got != "" {
t.Fatalf("summaryTextFor disabled = %q, want empty", got)
}
}
func TestSummaryManagerArmsOnlyTrackedTopLevelAgents(t *testing.T) {
sess := NewSession(t.TempDir(), "test")
c := newChildEntry("a1", "agent", KindAgent, []string{"fake"}, nil, "", "", "")
running := StatusRunning
c.status.Store(&running)
sess.children[c.ID] = c
sess.order = append(sess.order, c.ID)
cfg := defaultSettings().AutoSummary
m := newSummaryManager(sess, t.TempDir(), preset.Set{}, func() autoSummarySettings {
return cfg.clone()
}, nil, nil)
m.ObserveHumanInput(c.ID, []byte("please summarize"))
if got := m.Summary(c.ID); got.Text != "" {
t.Fatalf("untracked agent should not update summary state: %+v", got)
}
m.RegisterChild(c)
m.ObserveHumanInput(c.ID, []byte("please summarize"))
m.ObserveOutput(c.ID)
m.mu.Lock()
e := m.entries[c.ID]
m.mu.Unlock()
if e == nil || !e.armed || !e.dirty {
t.Fatalf("tracked top-level agent not armed/dirty: %+v", e)
}
sub := newChildEntry("a2", "sub", KindAgent, []string{"fake"}, nil, c.ID, "", "")
sub.status.Store(&running)
m.RegisterChild(sub)
m.ObserveHumanInput(sub.ID, []byte("please summarize"))
m.mu.Lock()
_, ok := m.entries[sub.ID]
m.mu.Unlock()
if ok {
t.Fatal("sub-agent should not get a summary entry")
}
}