okami.mom/albedo

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

Aly Raffaufperf(cli): reduce struct padding3922552

main
4.9 KiB108 linesraw
1// Markdown table bounds and transcript copy selection live in the terminal
2// rendering path: the daemon e2e sees only committed text, never rendered
3// rows, and no harness can drive a mouse selection, so these regressions are
4// invisible outside the TUI.
5package tui
6
7import (
8	"strings"
9	"testing"
10
11	"albedo/cli/internal/daemon"
12	"github.com/charmbracelet/x/ansi"
13)
14
15const tableTranscript = "| Tool | Logged median | Warm in-kernel benchmark |\n" +
16	"|:---|---:|---:|\n" +
17	"| `files.read` | 3 ms | 0.07 ms |\n" +
18	"| `files.paths` | too few isolated calls | 12.7 ms |"
19
20// Tables render inside the transcript's width: every cell that fits survives,
21// a narrow table truncates its columns rather than its rows or its syntax,
22// and escaped pipes and fenced literal tables are never parsed as tables.
23func TestMarkdownTablesRenderWithinTheirBounds(t *testing.T) {
24	for _, tc := range []struct {
25		name   string
26		text   string
27		absent string
28		want   []string
29		width  int
30	}{
31		{name: "keeps every column it fits", text: tableTranscript, width: 80,
32			want: []string{"Tool", "files.read", "files.paths", "12.7 ms"}, absent: "|:---"},
33		{name: "narrow truncates columns, not rows or the syntax", text: tableTranscript, width: 38,
34			want: []string{"Tool", "files.read", "files.path", "12.7 ms"}, absent: "|:---"},
35		{name: "escaped pipes and fenced rows stay literal",
36			text: "Intro\n\n| Name | Value |\n| --- | --- |\n| a\\|b | `x\\|y` |\n\n" +
37				"```text\n| literal | row |\n| --- | --- |\n```\nOutro",
38			width: 72,
39			want:  []string{"Intro", "a|b", "x|y", "| literal | row |", "| --- | --- |", "Outro"}, absent: ""},
40	} {
41		got := ansi.Strip(RenderMarkdownAnsi(tc.text, tc.width))
42		for _, value := range tc.want {
43			if !strings.Contains(got, value) {
44				t.Fatalf("%s: width %d lost %q:\n%s", tc.name, tc.width, value, got)
45			}
46		}
47		if tc.absent != "" && strings.Contains(got, tc.absent) {
48			t.Fatalf("%s: width %d exposed %q:\n%s", tc.name, tc.width, tc.absent, got)
49		}
50		for row := range strings.SplitSeq(got, "\n") {
51			if w := ansi.StringWidth(row); w > tc.width {
52				t.Fatalf("%s: width %d produced a %d-cell row: %q", tc.name, tc.width, w, row)
53			}
54		}
55	}
56}
57
58func TestSelectionSkipsTheRail(t *testing.T) {
59	lines := []string{"│ you", "│ can you list the tools", "", "│ albedo"}
60	sel := Selection{Anchor: Point{Row: 0, Col: 0}, Head: Point{Row: 3, Col: 8}, Gutter: railWidth}
61	if got, want := SelectedText(lines, sel), "you\ncan you list the tools\n\nalbedo"; got != want {
62		t.Fatalf("copied %q, want %q", got, want)
63	}
64	for _, row := range HighlightSelection(lines, sel) {
65		if strings.HasPrefix(row, "\x1b[7m") {
66			t.Fatalf("highlight covers the rail: %q", row)
67		}
68	}
69}
70
71// A copy is the text: no padding out to the viewport's width or height, no
72// chrome, no code block frame or quote bars, and wrapped rows joined back
73// into their lines.
74func TestSelectionCopiesOnlyText(t *testing.T) {
75	m := newTestChatModel(t, &daemon.Session{ID: "s"})
76	m.SetSize(60, 70)
77	m.appendSettledEntry(HistoryEntry{Kind: EntryUser, Text: "why does the thing break when i do the other thing, it is really annoying"})
78	m.appendSettledEntry(HistoryEntry{Kind: EntryTool, ToolName: "bash"})
79	m.appendSettledEntry(HistoryEntry{Kind: EntryAssistant, Text: "a paragraph that is long enough that it will **definitely** wrap around the width.\n" +
80		"hard break\n\nsee https://example.com/a/really/long/path/without/any/spaces/in/it/at/all\n\n" +
81		"- an item that also goes on for a while so it wraps onto a second row\n\n" +
82		"```go\nfmt.Println(\"a line of code that is longer than the width of the chat\")\n```\n\n" +
83		"> quoted text that goes on long enough to wrap around the width\n> > twice\n\n" +
84		"done."})
85	m.appendSettledEntry(HistoryEntry{Kind: EntryTurnEnd, Mood: moodDone, ElapsedMs: 5000, Tools: 1})
86	m.refreshViewportContent()
87	lines := strings.Split(m.Viewport.View(), "\n")
88	all := Selection{Anchor: Point{Row: 0, Col: 0}, Head: Point{Row: len(lines) - 1, Col: 60}, Gutter: railWidth}
89	want := "why does the thing break when i do the other thing, it is really annoying\n\n" +
90		"a paragraph that is long enough that it will definitely wrap around the width.\n" +
91		"hard break\n\nsee https://example.com/a/really/long/path/without/any/spaces/in/it/at/all\n\n" +
92		"• an item that also goes on for a while so it wraps onto a second row\n\n" +
93		"fmt.Println(\"a line of code that is longer than the width of the chat\")\n\n" +
94		"quoted text that goes on long enough to wrap around the width\n\ntwice\n\n" +
95		"done."
96	if got := SelectedText(lines, all); got != want {
97		t.Fatalf("copied %q, want %q", got, want)
98	}
99	// chrome copies when it is all you selected
100	for i, line := range lines {
101		if strings.Contains(ansi.Strip(line), "bash") {
102			row := Selection{Anchor: Point{Row: i, Col: 0}, Head: Point{Row: i, Col: 60}, Gutter: railWidth}
103			if got := SelectedText(lines, row); got != "ran bash" {
104				t.Fatalf("copied the tool row as %q", got)
105			}
106		}
107	}
108}