okami.mom/albedo

git clone https://git.nkp.pet/okami.mom/albedo

nekomimi.petmake schedule visible7f83187

main
4.4 KiB109 linesraw
1// Transcript re-rendering on width changes, not height changes, prevents
2// expensive UI refreshes. The cost is invisible outside the process: the
3// rendered text is identical either way, so no e2e can catch a regression.
4// The sidebar's stacking, width, row folding and the stale-kernel note are
5// checked here too: a daemon run would need a live job, an active work item and a stale
6// kernel at once to show them.
7package tui
8
9import (
10	"strings"
11	"testing"
12
13	"albedo/cli/internal/daemon"
14	"albedo/cli/internal/daemon/protocol"
15
16	"github.com/charmbracelet/x/ansi"
17)
18
19func TestResizeRendersTranscriptOnlyForNewBodyWidth(t *testing.T) {
20	m := newTestChatModel(t, &daemon.Session{ID: "s"})
21	m.SetSize(140, 24)
22	m.appendSettledEntry(HistoryEntry{Kind: EntryAssistant, Speaker: "albedo", Text: "hello"})
23	const kept = "rendered once"
24	m.settledLines[0] = kept
25
26	m.Glances = []PageGlance{{Title: "pending work", Rows: []PageRow{{ID: "1", Text: "x"}}}}
27	m.SetSize(140, 30)
28	if m.settledLines[0] != kept {
29		t.Fatal("a resize keeping the body width rendered the transcript again")
30	}
31	m.SetSize(80, 30)
32	if m.settledLines[0] == kept {
33		t.Fatal("a narrower body should render the transcript again")
34	}
35}
36
37func TestSidebarStacksActiveWorkAboveBackgroundJobs(t *testing.T) {
38	m := newTestChatModel(t, &daemon.Session{ID: "s"})
39	m.Glances = []PageGlance{{Title: "active work", Rows: []PageRow{{ID: "7", Text: "fix the parser", Tone: ToneActive}}}}
40	m.Status.RunningJobs = []protocol.KernelJob{{ID: "job1", Command: "go test ./..."}, {ID: "job2", Command: "python3 -c '\nimport os\nprint(1)\n'"}}
41	m.SetSize(160, 30)
42	view := ansi.Strip(m.View())
43	work, jobs := strings.Index(view, "active work"), strings.Index(view, "background jobs")
44	if work < 0 || jobs < work || !strings.Contains(view, "fix the parser") || !strings.Contains(view, "go test ./...") {
45		t.Fatalf("the sidebar must show active work, then background jobs:\n%s", view)
46	}
47	if strings.Contains(view, "job1") {
48		t.Fatalf("a background job shows its command, not its id:\n%s", view)
49	}
50	if counts := m.glanceCounts(); len(counts) != 0 {
51		t.Fatalf("the footer repeats sections the sidebar shows: %v", counts)
52	}
53	if !strings.Contains(view, "python3 -c ' import os") {
54		t.Fatalf("a multi-line command must fold onto its row:\n%s", view)
55	}
56	m.Glances, m.Status.RunningJobs = nil, m.Status.RunningJobs[1:]
57	m.Status.KernelJobs = new(int64(1))
58	m.SetSize(60, 20)
59	for line := range strings.SplitSeq(ansi.Strip(m.View()), "\n") {
60		if strings.Contains(line, "import os") && !strings.Contains(line, "background job running") {
61			t.Fatalf("the status line wrapped a job's command:\n%s", ansi.Strip(m.View()))
62		}
63		if ansi.StringWidth(line) > 60 {
64			t.Fatalf("a %d-cell line at width 60: %q", ansi.StringWidth(line), line)
65		}
66	}
67}
68
69func TestSidebarFoldsAMultiLineRowOntoOneLine(t *testing.T) {
70	m := newTestChatModel(t, &daemon.Session{ID: "s"})
71	m.Glances = []PageGlance{{Title: "scheduled", Rows: []PageRow{{ID: "3", Text: "every 1h · ci\nlog"}}}}
72	m.SetSize(160, 30)
73	if view := ansi.Strip(m.View()); !strings.Contains(view, "#3 every 1h · ci log") {
74		t.Fatalf("a scheduled prompt's lines must fold onto its row:\n%s", view)
75	}
76}
77
78func TestSidebarFitsBesideANarrowerTranscript(t *testing.T) {
79	m := newTestChatModel(t, &daemon.Session{ID: "s"})
80	m.Glances = []PageGlance{{Title: "active work", Rows: []PageRow{{ID: "7", Text: "fix the parser", Tone: ToneActive}}}}
81	m.SetSize(110, 30)
82	view := ansi.Strip(m.View())
83	if !strings.Contains(view, "fix the parser") {
84		t.Fatalf("a 110-column terminal must show the sidebar:\n%s", view)
85	}
86	for line := range strings.SplitSeq(view, "\n") {
87		if ansi.StringWidth(line) > 110 {
88			t.Fatalf("a %d-cell line at width 110: %q", ansi.StringWidth(line), line)
89		}
90	}
91	// without glances the transcript takes the width back
92	m.Glances = nil
93	m.SetSize(110, 30)
94	if m.sidebarWidth() != 0 || m.Renderer.BodyWidth != 100 {
95		t.Fatalf("no sidebar must leave the full body: sidebar %d body %d", m.sidebarWidth(), m.Renderer.BodyWidth)
96	}
97}
98
99func TestAStaleKernelIsNamedOnlyWhileAJobKeepsIt(t *testing.T) {
100	m := newTestChatModel(t, &daemon.Session{ID: "s"})
101	m.Status.KernelStale = true
102	if counts := m.glanceCounts(); len(counts) != 0 {
103		t.Fatalf("a stale kernel swaps before the next turn; nothing to say: %v", counts)
104	}
105	m.Status.KernelJobs = new(int64(1))
106	if counts := m.glanceCounts(); len(counts) == 0 || counts[0] != "kernel older until jobs end" {
107		t.Fatalf("a job keeping a stale kernel must be named: %v", counts)
108	}
109}