resident_subagent_indicator_test.go RawHistoryFolder
1package modes
2
3import (
4 "strings"
5 "testing"
6 "time"
7
8 "github.com/bnema/zut/packages/agent/subagents"
9 "github.com/bnema/zut/packages/provider"
10 "github.com/bnema/zut/packages/tui"
11)
12
13func TestRenderResidentSubagentActivityLinesShowsOnlyActiveChildren(t *testing.T) {
14 now := time.Date(2026, time.August, 13, 12, 0, 0, 0, time.UTC)
15 lines := renderResidentSubagentActivityLines(tui.Dark, "/", []subagents.ResidentSnapshot{
16 {ID: "running-id", Profile: "reviewer", State: subagents.ResidentRunning, TurnStartedAt: now.Add(-90 * time.Second), ActivityUpdatedAt: now, WaitingForModel: true},
17 {ID: "queued-id", Profile: "planner", State: subagents.ResidentQueued, TurnStartedAt: now.Add(-10 * time.Second), ActivityUpdatedAt: now.Add(-5 * time.Second)},
18 {ID: "foreign-id", Profile: "another process", State: subagents.ResidentRunning, OwnedElsewhere: true, TurnStartedAt: now.Add(-10 * time.Second)},
19 {ID: "idle-id", Profile: "finished", State: subagents.ResidentIdle},
20 }, 80, now)
21 got := strings.Join(plainResidentIndicatorLines(lines), "\n")
22 if !strings.Contains(got, "/ reviewer · Zzzz · activity 0s ago · 1m30s") || !strings.Contains(got, "… planner · queued · activity 5s ago · 10s") {
23 t.Fatalf("activity lines = %q", got)
24 }
25 if strings.Contains(got, "finished") || strings.Contains(got, "another process") {
26 t.Fatalf("activity lines include inactive or foreign child: %q", got)
27 }
28}
29
30func TestRenderResidentSubagentActivityLinesShowsUsageMetadata(t *testing.T) {
31 now := time.Date(2026, time.August, 13, 12, 0, 0, 0, time.UTC)
32 lines := renderResidentSubagentActivityLines(tui.Dark, "/", []subagents.ResidentSnapshot{{
33 ID: "running-id", Profile: "reviewer", State: subagents.ResidentRunning,
34 Usage: provider.Usage{InputTokens: 84_000, OutputTokens: 1_500, CacheReadTokens: 123_000, CacheMeasuredPromptTokens: 207_000, CacheMeasuredReadTokens: 123_000, CostUSD: 0.525},
35 ContextUsed: 45_152, ContextMax: 272_000, Subscription: true,
36 }}, 100, now)
37 got := strings.Join(plainResidentIndicatorLines(lines), "\n")
38 if !strings.Contains(got, "\n ↑84k ↓1.5k R123k/ C59.4% $0.525 (sub) 16.6%/272k") {
39 t.Fatalf("activity lines = %q, want usage metadata line", got)
40 }
41}
42
43func TestRenderResidentSubagentActivityLinesAnimatesModelWait(t *testing.T) {
44 started := time.Date(2026, time.August, 13, 12, 0, 0, 0, time.UTC)
45 now := started.Add(2 * time.Second)
46 lines := renderResidentSubagentActivityLines(tui.Dark, "/", []subagents.ResidentSnapshot{{
47 ID: "running-id", Profile: "reviewer", State: subagents.ResidentRunning,
48 TurnStartedAt: started, ActivityUpdatedAt: started, WaitingForModel: true,
49 }}, 80, now)
50 got := strings.Join(plainResidentIndicatorLines(lines), "\n")
51 if !strings.Contains(got, "zzZz") {
52 t.Fatalf("model-wait indicator = %q, want animated waiting frame", got)
53 }
54}
55
56func TestRenderResidentSubagentActivityLinesShowsRunningDuringToolWork(t *testing.T) {
57 now := time.Date(2026, time.August, 13, 12, 0, 2, 0, time.UTC)
58 lines := renderResidentSubagentActivityLines(tui.Dark, "/", []subagents.ResidentSnapshot{{
59 ID: "running-id", Profile: "reviewer", State: subagents.ResidentRunning,
60 TurnStartedAt: now.Add(-time.Minute), ActivityUpdatedAt: now.Add(-time.Second),
61 }}, 80, now)
62 got := strings.Join(plainResidentIndicatorLines(lines), "\n")
63 if !strings.Contains(got, "/ reviewer · running · activity 1s ago · 1m00s") || strings.Contains(got, "Zzzz") {
64 t.Fatalf("tool-work indicator = %q", got)
65 }
66}
67
68func TestLimitResidentSubagentActivityLinesSummarizesOverflow(t *testing.T) {
69 got := plainResidentIndicatorLines(limitResidentSubagentActivityLines(tui.Dark, []string{"one", "two", "three"}, 0, 2, 80))
70 want := []string{"one", " … 2 more active subagents"}
71 if strings.Join(got, "\n") != strings.Join(want, "\n") {
72 t.Fatalf("limited lines = %#v, want %#v", got, want)
73 }
74}
75
76func TestLimitResidentSubagentActivityLinesKeepsMetadataWithItsAgent(t *testing.T) {
77 lines := []string{" agent one", " usage one", " agent two", " usage two"}
78 got := plainResidentIndicatorLines(limitResidentSubagentActivityLines(tui.Dark, lines, 0, 3, 80))
79 want := []string{" agent one", " usage one", " … 1 more active subagents"}
80 if strings.Join(got, "\n") != strings.Join(want, "\n") {
81 t.Fatalf("limited lines = %#v, want %#v", got, want)
82 }
83}
84
85func TestLimitResidentSubagentActivityLinesIncludesAlreadyHiddenChildren(t *testing.T) {
86 got := plainResidentIndicatorLines(limitResidentSubagentActivityLines(tui.Dark, []string{"one", "two", "three"}, 4, 2, 80))
87 want := []string{"one", " … 6 more active subagents"}
88 if strings.Join(got, "\n") != strings.Join(want, "\n") {
89 t.Fatalf("limited lines = %#v, want %#v", got, want)
90 }
91}
92
93func plainResidentIndicatorLines(lines []string) []string {
94 plain := make([]string, len(lines))
95 for index, line := range lines {
96 plain[index] = stripANSIBytes(line)
97 }
98 return plain
99}