okami.mom/albedo

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

dawntui: gallery of every screen state (registerShots) drawn as per-screen contact sheets4db295e

main
3.0 KiB95 linesraw
1// Screenshots: a test that wants to look at a screen calls shot with what
2// the view drew. Without ALBEDO_SHOT_DIR it does nothing, so the gate never
3// writes files; test/manual/tui_shot.py turns the .ans files into PNGs.
4package tui
5
6import (
7	"maps"
8	"os"
9	"path/filepath"
10	"slices"
11	"testing"
12)
13
14// shotTerminal makes the styles what a catppuccin terminal that reported its
15// colors gets, as a user's terminal does, instead of the palette-slot
16// fallback tests otherwise see (reverse video selection, no tints). It
17// restores them after the test, and does nothing without ALBEDO_SHOT_DIR.
18func shotTerminal(t *testing.T) {
19	t.Helper()
20	if os.Getenv("ALBEDO_SHOT_DIR") == "" {
21		return
22	}
23	styles, ramp := DefaultStyles, transcriptInk
24	t.Cleanup(func() { DefaultStyles, transcriptInk = styles, ramp })
25	palette := map[int]rgb{}
26	for slot, hex := range map[int]string{ansiRed: "#f38ba8", ansiGreen: "#a6e3a1", ansiBlue: "#89b4fa", ansiMagenta: "#cba6f7", ansiCyan: "#94e2d5"} {
27		palette[slot] = *parseHex(hex)
28	}
29	colors := termColors{fg: parseHex("#cdd6f4"), bg: parseHex("#1e1e2e"), palette: palette}
30	mixed, ok := mixInk(colors)
31	if !ok {
32		t.Fatal("the mock terminal colors were not trusted")
33	}
34	useInk(mixed)
35}
36
37func shot(t *testing.T, name, view string) {
38	t.Helper()
39	dir := os.Getenv("ALBEDO_SHOT_DIR")
40	if dir == "" {
41		return
42	}
43	if err := os.MkdirAll(dir, 0o755); err != nil {
44		t.Fatal(err)
45	}
46	if err := os.WriteFile(filepath.Join(dir, name+".ans"), []byte(view), 0o644); err != nil {
47		t.Fatal(err)
48	}
49}
50
51// A shot size is a terminal to draw every state in.
52type shotSize struct {
53	name          string
54	width, height int
55}
56
57var shotSizes = []shotSize{{"wide", 120, 30}, {"narrow", 70, 20}}
58
59// A shot state is one situation of a screen: how it draws in a terminal of
60// the given size.
61type shotState struct {
62	name string
63	view func(width, height int) string
64}
65
66var shotScreens = map[string][]shotState{}
67
68// registerShots lists the states a screen can be in, from an init in the
69// screen's shot_<screen>_test.go, so the gallery draws all of them in every
70// size. Cover what a person would want to look at: the list, a filter typed,
71// each confirmation, loading, empty, failure, a notice, a form open.
72func registerShots(screen string, states ...shotState) {
73	shotScreens[screen] = append(shotScreens[screen], states...)
74}
75
76// TestShotGallery draws every registered state at every size to
77// $ALBEDO_SHOT_DIR/<screen>--<state>--<size>.ans. Without the variable it
78// still draws them, which keeps each state buildable. Run one screen with
79// -run TestShotGallery/<screen>.
80func TestShotGallery(t *testing.T) {
81	shotTerminal(t)
82	for _, screen := range slices.Sorted(maps.Keys(shotScreens)) {
83		t.Run(screen, func(t *testing.T) {
84			for _, state := range shotScreens[screen] {
85				for _, size := range shotSizes {
86					view := state.view(size.width, size.height)
87					if view == "" {
88						t.Errorf("%s/%s drew nothing at %s", screen, state.name, size.name)
89					}
90					shot(t, screen+"--"+state.name+"--"+size.name, view)
91				}
92			}
93		})
94	}
95}