internal/control/table_test.go

3bcdce33fb9a2309312854331359d376171c7368
gitbay/internal/control/table_test.go history · blame · raw

126 lines · 4162 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
 38// A column shrunk below its header's width clips the header too.
 39func TestTableClipsHeaderToColumn(t *testing.T) {
 40	var b bytes.Buffer
 41	tb := (&Ctx{Term: Term{Cols: 16}}).table(&b, "FINGERPRINT", "SCOPE")
 42	tb.row(cFlex("SHA256:abcdefghijklmnopqrstuvwxyz"), cState("full"))
 43	tb.flush()
 44	for _, line := range strings.Split(strings.TrimSuffix(b.String(), "\n"), "\n") {
 45		if cells(line) > 16 {
 46			t.Errorf("line of %d cells at 16 columns: %q", cells(line), line)
 47		}
 48	}
 49	if !strings.HasPrefix(b.String(), "FINGERPR…  SCOPE\n") {
 50		t.Errorf("header:\n%s", b.String())
 51	}
 52}
 53
 54func TestTableColourOnlyAddsSGR(t *testing.T) {
 55	var mono, colour bytes.Buffer
 56	fixtureTable(&Ctx{Term: Term{Cols: 40}}, &mono)
 57	fixtureTable(&Ctx{Term: Term{Cols: 40, Color: true}}, &colour)
 58	if !strings.Contains(colour.String(), sgrGreen+"open"+sgrReset) {
 59		t.Errorf("open not green: %q", colour.String())
 60	}
 61	if !strings.HasPrefix(colour.String(), sgrDim) {
 62		t.Errorf("header not dim: %q", colour.String())
 63	}
 64	if stripSGR(colour.String()) != mono.String() {
 65		t.Errorf("colour changed the layout:\n%s\nvs\n%s", stripSGR(colour.String()), mono.String())
 66	}
 67}
 68
 69func TestTableAgesAndPlainStamps(t *testing.T) {
 70	termNow = func() time.Time { return time.Date(2026, 9, 23, 12, 0, 1, 0, time.UTC) }
 71	t.Cleanup(func() { termNow = time.Now })
 72	var plain, term bytes.Buffer
 73	for _, c := range []struct {
 74		ctx *Ctx
 75		w   *bytes.Buffer
 76	}{{&Ctx{}, &plain}, {&Ctx{Term: Term{Cols: 80}}, &term}} {
 77		tb := c.ctx.table(c.w, "#", "UPDATED")
 78		tb.row(cRef("#1"), cAge("2026-09-23T10:00:00.123Z"))
 79		tb.flush()
 80	}
 81	if plain.String() != "#1\t2026-09-23T10:00:00Z\n" {
 82		t.Errorf("plain = %q", plain.String())
 83	}
 84	if term.String() != "#   UPDATED\n#1  2h ago\n" {
 85		t.Errorf("term = %q", term.String())
 86	}
 87}
 88
 89// A row may carry a cell beyond what the header names — repo list's
 90// trailing [archived] marker, only present on some rows. flush must
 91// still render it, not silently drop it because it falls past
 92// len(header).
 93func TestTableKeepsCellsBeyondTheHeader(t *testing.T) {
 94	var b bytes.Buffer
 95	tb := (&Ctx{Term: Term{Cols: 80}}).table(&b, "PATH", "VISIBILITY", "DESCRIPTION")
 96	tb.row(cRef("a/x"), cState("public"), cFlex("one"))
 97	tb.row(cRef("a/y"), cState("public"), cFlex("two"), cText("[archived]"))
 98	tb.flush()
 99
100	out := b.String()
101	if !strings.Contains(out, "[archived]") {
102		t.Fatalf("archived marker dropped:\n%s", out)
103	}
104	lines := strings.Split(strings.TrimRight(out, "\n"), "\n")
105	if len(lines) != 3 {
106		t.Fatalf("want 3 lines, got %d:\n%s", len(lines), out)
107	}
108	header, row1, row2 := lines[0], lines[1], lines[2]
109	descAt := strings.Index(header, "DESCRIPTION")
110	oneAt := strings.Index(row1, "one")
111	twoAt := strings.Index(row2, "two")
112	if descAt < 0 || oneAt < 0 || twoAt < 0 {
113		t.Fatalf("columns not found:\n%s", out)
114	}
115	if descAt != oneAt || descAt != twoAt {
116		t.Errorf("DESCRIPTION column not aligned: header at %d, row1 at %d, row2 at %d\n%s", descAt, oneAt, twoAt, out)
117	}
118}
119
120func TestTableEmptyPrintsNothing(t *testing.T) {
121	var b bytes.Buffer
122	(&Ctx{Term: Term{Cols: 80}}).table(&b, "#").flush()
123	if b.Len() != 0 {
124		t.Errorf("empty table printed %q", b.String())
125	}
126}