internal/control/table_test.go
110 lines · 3589 bytes
1package control
2
3import (
4 "bytes"
5 "strings"
6 "testing"
7 "time"
8)
9
10func fixtureTable(c *Ctx, w *bytes.Buffer) {
11 tb := c.table(w, "#", "STATE", "TITLE", "AUTHOR")
12 tb.row(cRef("#252"), cState("open"), cFlex("Dependency updates available for every module"), cText("gitbay-bot"))
13 tb.row(cRef("#12"), cState("closed"), cFlex("Android app"), cText("cmc"))
14 tb.flush()
15}
16
17func TestTablePlainIsTabs(t *testing.T) {
18 var b bytes.Buffer
19 fixtureTable(&Ctx{}, &b)
20 want := "#252\topen\tDependency updates available for every module\tgitbay-bot\n" +
21 "#12\tclosed\tAndroid app\tcmc\n"
22 if b.String() != want {
23 t.Errorf("plain:\n%q\nwant\n%q", b.String(), want)
24 }
25}
26
27func TestTableTerminalFits(t *testing.T) {
28 var b bytes.Buffer
29 fixtureTable(&Ctx{Term: Term{Cols: 40}}, &b)
30 want := "# STATE TITLE AUTHOR\n" +
31 "#252 open Dependency up… gitbay-bot\n" +
32 "#12 closed Android app cmc\n"
33 if b.String() != want {
34 t.Errorf("terminal:\n%s\nwant\n%s", b.String(), want)
35 }
36}
37
38func TestTableColourOnlyAddsSGR(t *testing.T) {
39 var mono, colour bytes.Buffer
40 fixtureTable(&Ctx{Term: Term{Cols: 40}}, &mono)
41 fixtureTable(&Ctx{Term: Term{Cols: 40, Color: true}}, &colour)
42 if !strings.Contains(colour.String(), sgrGreen+"open"+sgrReset) {
43 t.Errorf("open not green: %q", colour.String())
44 }
45 if !strings.HasPrefix(colour.String(), sgrDim) {
46 t.Errorf("header not dim: %q", colour.String())
47 }
48 if stripSGR(colour.String()) != mono.String() {
49 t.Errorf("colour changed the layout:\n%s\nvs\n%s", stripSGR(colour.String()), mono.String())
50 }
51}
52
53func TestTableAgesAndPlainStamps(t *testing.T) {
54 termNow = func() time.Time { return time.Date(2026, 9, 23, 12, 0, 1, 0, time.UTC) }
55 t.Cleanup(func() { termNow = time.Now })
56 var plain, term bytes.Buffer
57 for _, c := range []struct {
58 ctx *Ctx
59 w *bytes.Buffer
60 }{{&Ctx{}, &plain}, {&Ctx{Term: Term{Cols: 80}}, &term}} {
61 tb := c.ctx.table(c.w, "#", "UPDATED")
62 tb.row(cRef("#1"), cAge("2026-09-23T10:00:00.123Z"))
63 tb.flush()
64 }
65 if plain.String() != "#1\t2026-09-23T10:00:00Z\n" {
66 t.Errorf("plain = %q", plain.String())
67 }
68 if term.String() != "# UPDATED\n#1 2h ago\n" {
69 t.Errorf("term = %q", term.String())
70 }
71}
72
73// A row may carry a cell beyond what the header names — repo list's
74// trailing [archived] marker, only present on some rows. flush must
75// still render it, not silently drop it because it falls past
76// len(header).
77func TestTableKeepsCellsBeyondTheHeader(t *testing.T) {
78 var b bytes.Buffer
79 tb := (&Ctx{Term: Term{Cols: 80}}).table(&b, "PATH", "VISIBILITY", "DESCRIPTION")
80 tb.row(cRef("a/x"), cState("public"), cFlex("one"))
81 tb.row(cRef("a/y"), cState("public"), cFlex("two"), cText("[archived]"))
82 tb.flush()
83
84 out := b.String()
85 if !strings.Contains(out, "[archived]") {
86 t.Fatalf("archived marker dropped:\n%s", out)
87 }
88 lines := strings.Split(strings.TrimRight(out, "\n"), "\n")
89 if len(lines) != 3 {
90 t.Fatalf("want 3 lines, got %d:\n%s", len(lines), out)
91 }
92 header, row1, row2 := lines[0], lines[1], lines[2]
93 descAt := strings.Index(header, "DESCRIPTION")
94 oneAt := strings.Index(row1, "one")
95 twoAt := strings.Index(row2, "two")
96 if descAt < 0 || oneAt < 0 || twoAt < 0 {
97 t.Fatalf("columns not found:\n%s", out)
98 }
99 if descAt != oneAt || descAt != twoAt {
100 t.Errorf("DESCRIPTION column not aligned: header at %d, row1 at %d, row2 at %d\n%s", descAt, oneAt, twoAt, out)
101 }
102}
103
104func TestTableEmptyPrintsNothing(t *testing.T) {
105 var b bytes.Buffer
106 (&Ctx{Term: Term{Cols: 80}}).table(&b, "#").flush()
107 if b.Len() != 0 {
108 t.Errorf("empty table printed %q", b.String())
109 }
110}