diffview_test.go

  1package diffview_test
  2
  3import (
  4	_ "embed"
  5	"fmt"
  6	"strings"
  7	"testing"
  8
  9	"github.com/alecthomas/chroma/v2/styles"
 10	"github.com/charmbracelet/crush/internal/exp/diffview"
 11	"github.com/charmbracelet/x/ansi"
 12	"github.com/charmbracelet/x/exp/golden"
 13)
 14
 15//go:embed testdata/TestDefault.before
 16var TestDefaultBefore string
 17
 18//go:embed testdata/TestDefault.after
 19var TestDefaultAfter string
 20
 21//go:embed testdata/TestMultipleHunks.before
 22var TestMultipleHunksBefore string
 23
 24//go:embed testdata/TestMultipleHunks.after
 25var TestMultipleHunksAfter string
 26
 27//go:embed testdata/TestNarrow.before
 28var TestNarrowBefore string
 29
 30//go:embed testdata/TestNarrow.after
 31var TestNarrowAfter string
 32
 33//go:embed testdata/TestTabs.before
 34var TestTabsBefore string
 35
 36//go:embed testdata/TestTabs.after
 37var TestTabsAfter string
 38
 39type (
 40	TestFunc  func(dv *diffview.DiffView) *diffview.DiffView
 41	TestFuncs map[string]TestFunc
 42)
 43
 44var (
 45	UnifiedFunc = func(dv *diffview.DiffView) *diffview.DiffView {
 46		return dv.Unified()
 47	}
 48	SplitFunc = func(dv *diffview.DiffView) *diffview.DiffView {
 49		return dv.Split()
 50	}
 51
 52	DefaultFunc = func(dv *diffview.DiffView) *diffview.DiffView {
 53		return dv.
 54			Before("main.go", TestDefaultBefore).
 55			After("main.go", TestDefaultAfter)
 56	}
 57	NoLineNumbersFunc = func(dv *diffview.DiffView) *diffview.DiffView {
 58		return dv.
 59			Before("main.go", TestDefaultBefore).
 60			After("main.go", TestDefaultAfter).
 61			LineNumbers(false)
 62	}
 63	MultipleHunksFunc = func(dv *diffview.DiffView) *diffview.DiffView {
 64		return dv.
 65			Before("main.go", TestMultipleHunksBefore).
 66			After("main.go", TestMultipleHunksAfter)
 67	}
 68	CustomContextLinesFunc = func(dv *diffview.DiffView) *diffview.DiffView {
 69		return dv.
 70			Before("main.go", TestMultipleHunksBefore).
 71			After("main.go", TestMultipleHunksAfter).
 72			ContextLines(4)
 73	}
 74	NarrowFunc = func(dv *diffview.DiffView) *diffview.DiffView {
 75		return dv.
 76			Before("text.txt", TestNarrowBefore).
 77			After("text.txt", TestNarrowAfter)
 78	}
 79	SmallWidthFunc = func(dv *diffview.DiffView) *diffview.DiffView {
 80		return dv.
 81			Before("main.go", TestMultipleHunksBefore).
 82			After("main.go", TestMultipleHunksAfter).
 83			Width(40)
 84	}
 85	LargeWidthFunc = func(dv *diffview.DiffView) *diffview.DiffView {
 86		return dv.
 87			Before("main.go", TestMultipleHunksBefore).
 88			After("main.go", TestMultipleHunksAfter).
 89			Width(120)
 90	}
 91	NoSyntaxHighlightFunc = func(dv *diffview.DiffView) *diffview.DiffView {
 92		return dv.
 93			Before("main.go", TestMultipleHunksBefore).
 94			After("main.go", TestMultipleHunksAfter).
 95			ChromaStyle(nil)
 96	}
 97
 98	LightModeFunc = func(dv *diffview.DiffView) *diffview.DiffView {
 99		return dv.
100			Style(diffview.DefaultLightStyle()).
101			ChromaStyle(styles.Get("catppuccin-latte"))
102	}
103	DarkModeFunc = func(dv *diffview.DiffView) *diffview.DiffView {
104		return dv.
105			Style(diffview.DefaultDarkStyle()).
106			ChromaStyle(styles.Get("catppuccin-macchiato"))
107	}
108
109	LayoutFuncs = TestFuncs{
110		"Unified": UnifiedFunc,
111		"Split":   SplitFunc,
112	}
113	BehaviorFuncs = TestFuncs{
114		"Default":            DefaultFunc,
115		"NoLineNumbers":      NoLineNumbersFunc,
116		"MultipleHunks":      MultipleHunksFunc,
117		"CustomContextLines": CustomContextLinesFunc,
118		"Narrow":             NarrowFunc,
119		"SmallWidth":         SmallWidthFunc,
120		"LargeWidth":         LargeWidthFunc,
121		"NoSyntaxHighlight":  NoSyntaxHighlightFunc,
122	}
123	ThemeFuncs = TestFuncs{
124		"LightMode": LightModeFunc,
125		"DarkMode":  DarkModeFunc,
126	}
127)
128
129func TestDiffView(t *testing.T) {
130	for layoutName, layoutFunc := range LayoutFuncs {
131		t.Run(layoutName, func(t *testing.T) {
132			for behaviorName, behaviorFunc := range BehaviorFuncs {
133				t.Run(behaviorName, func(t *testing.T) {
134					for themeName, themeFunc := range ThemeFuncs {
135						t.Run(themeName, func(t *testing.T) {
136							t.Parallel()
137
138							dv := diffview.New()
139							dv = layoutFunc(dv)
140							dv = themeFunc(dv)
141							dv = behaviorFunc(dv)
142
143							output := dv.String()
144							golden.RequireEqual(t, []byte(output))
145
146							switch behaviorName {
147							case "SmallWidth":
148								assertLineWidth(t, 40, output)
149							case "LargeWidth":
150								assertLineWidth(t, 120, output)
151							}
152						})
153					}
154				})
155			}
156		})
157	}
158}
159
160func TestDiffViewTabs(t *testing.T) {
161	for layoutName, layoutFunc := range LayoutFuncs {
162		t.Run(layoutName, func(t *testing.T) {
163			t.Parallel()
164
165			dv := diffview.New().
166				Before("main.go", TestTabsBefore).
167				After("main.go", TestTabsAfter).
168				Style(diffview.DefaultLightStyle()).
169				ChromaStyle(styles.Get("catppuccin-latte"))
170			dv = layoutFunc(dv)
171
172			output := dv.String()
173			golden.RequireEqual(t, []byte(output))
174		})
175	}
176}
177
178func TestDiffViewWidth(t *testing.T) {
179	for layoutName, layoutFunc := range LayoutFuncs {
180		t.Run(layoutName, func(t *testing.T) {
181			for width := 1; width <= 110; width++ {
182				if layoutName == "Unified" && width > 60 {
183					continue
184				}
185
186				t.Run(fmt.Sprintf("WidthOf%03d", width), func(t *testing.T) {
187					t.Parallel()
188
189					dv := diffview.New().
190						Before("main.go", TestMultipleHunksBefore).
191						After("main.go", TestMultipleHunksAfter).
192						Width(width).
193						Style(diffview.DefaultLightStyle()).
194						ChromaStyle(styles.Get("catppuccin-latte"))
195					dv = layoutFunc(dv)
196
197					output := dv.String()
198					golden.RequireEqual(t, []byte(output))
199
200					assertLineWidth(t, width, output)
201				})
202			}
203		})
204	}
205}
206
207func TestDiffViewHeight(t *testing.T) {
208	for layoutName, layoutFunc := range LayoutFuncs {
209		t.Run(layoutName, func(t *testing.T) {
210			for height := 1; height <= 20; height++ {
211				t.Run(fmt.Sprintf("HeightOf%03d", height), func(t *testing.T) {
212					t.Parallel()
213
214					dv := diffview.New().
215						Before("main.go", TestMultipleHunksBefore).
216						After("main.go", TestMultipleHunksAfter).
217						Height(height).
218						Style(diffview.DefaultLightStyle()).
219						ChromaStyle(styles.Get("catppuccin-latte"))
220					dv = layoutFunc(dv)
221
222					output := dv.String()
223					golden.RequireEqual(t, []byte(output))
224
225					assertHeight(t, height, output)
226				})
227			}
228		})
229	}
230}
231
232func TestDiffViewXOffset(t *testing.T) {
233	for layoutName, layoutFunc := range LayoutFuncs {
234		t.Run(layoutName, func(t *testing.T) {
235			for xOffset := range 21 {
236				t.Run(fmt.Sprintf("XOffsetOf%02d", xOffset), func(t *testing.T) {
237					t.Parallel()
238
239					dv := diffview.New().
240						Before("main.go", TestDefaultBefore).
241						After("main.go", TestDefaultAfter).
242						Style(diffview.DefaultLightStyle()).
243						ChromaStyle(styles.Get("catppuccin-latte")).
244						Width(60).
245						XOffset(xOffset)
246					dv = layoutFunc(dv)
247
248					output := dv.String()
249					golden.RequireEqual(t, []byte(output))
250
251					assertLineWidth(t, 60, output)
252				})
253			}
254		})
255	}
256}
257
258func TestDiffViewYOffset(t *testing.T) {
259	for layoutName, layoutFunc := range LayoutFuncs {
260		t.Run(layoutName, func(t *testing.T) {
261			for yOffset := range 17 {
262				t.Run(fmt.Sprintf("YOffsetOf%02d", yOffset), func(t *testing.T) {
263					t.Parallel()
264
265					dv := diffview.New().
266						Before("main.go", TestMultipleHunksBefore).
267						After("main.go", TestMultipleHunksAfter).
268						Style(diffview.DefaultLightStyle()).
269						ChromaStyle(styles.Get("catppuccin-latte")).
270						Height(5).
271						YOffset(yOffset)
272					dv = layoutFunc(dv)
273
274					output := dv.String()
275					golden.RequireEqual(t, []byte(output))
276
277					assertHeight(t, 5, output)
278				})
279			}
280		})
281	}
282}
283
284func assertLineWidth(t *testing.T, expected int, output string) {
285	var lineWidth int
286	for line := range strings.SplitSeq(output, "\n") {
287		lineWidth = max(lineWidth, ansi.StringWidth(line))
288	}
289	if lineWidth != expected {
290		t.Errorf("expected output width to be == %d, got %d", expected, lineWidth)
291	}
292}
293
294func assertHeight(t *testing.T, expected int, output string) {
295	output = strings.TrimSuffix(output, "\n")
296	lines := strings.Count(output, "\n") + 1
297	if lines != expected {
298		t.Errorf("expected output height to be == %d, got %d", expected, lines)
299	}
300}