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/tui/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
 39//go:embed testdata/TestLineBreakIssue.before
 40var TestLineBreakIssueBefore string
 41
 42//go:embed testdata/TestLineBreakIssue.after
 43var TestLineBreakIssueAfter string
 44
 45type (
 46	TestFunc  func(dv *diffview.DiffView) *diffview.DiffView
 47	TestFuncs map[string]TestFunc
 48)
 49
 50var (
 51	UnifiedFunc = func(dv *diffview.DiffView) *diffview.DiffView {
 52		return dv.Unified()
 53	}
 54	SplitFunc = func(dv *diffview.DiffView) *diffview.DiffView {
 55		return dv.Split()
 56	}
 57
 58	DefaultFunc = func(dv *diffview.DiffView) *diffview.DiffView {
 59		return dv.
 60			Before("main.go", TestDefaultBefore).
 61			After("main.go", TestDefaultAfter)
 62	}
 63	NoLineNumbersFunc = func(dv *diffview.DiffView) *diffview.DiffView {
 64		return dv.
 65			Before("main.go", TestDefaultBefore).
 66			After("main.go", TestDefaultAfter).
 67			LineNumbers(false)
 68	}
 69	MultipleHunksFunc = func(dv *diffview.DiffView) *diffview.DiffView {
 70		return dv.
 71			Before("main.go", TestMultipleHunksBefore).
 72			After("main.go", TestMultipleHunksAfter)
 73	}
 74	CustomContextLinesFunc = func(dv *diffview.DiffView) *diffview.DiffView {
 75		return dv.
 76			Before("main.go", TestMultipleHunksBefore).
 77			After("main.go", TestMultipleHunksAfter).
 78			ContextLines(4)
 79	}
 80	NarrowFunc = func(dv *diffview.DiffView) *diffview.DiffView {
 81		return dv.
 82			Before("text.txt", TestNarrowBefore).
 83			After("text.txt", TestNarrowAfter)
 84	}
 85	SmallWidthFunc = func(dv *diffview.DiffView) *diffview.DiffView {
 86		return dv.
 87			Before("main.go", TestMultipleHunksBefore).
 88			After("main.go", TestMultipleHunksAfter).
 89			Width(40)
 90	}
 91	LargeWidthFunc = func(dv *diffview.DiffView) *diffview.DiffView {
 92		return dv.
 93			Before("main.go", TestMultipleHunksBefore).
 94			After("main.go", TestMultipleHunksAfter).
 95			Width(120)
 96	}
 97	NoSyntaxHighlightFunc = func(dv *diffview.DiffView) *diffview.DiffView {
 98		return dv.
 99			Before("main.go", TestMultipleHunksBefore).
100			After("main.go", TestMultipleHunksAfter).
101			ChromaStyle(nil)
102	}
103
104	LightModeFunc = func(dv *diffview.DiffView) *diffview.DiffView {
105		return dv.
106			Style(diffview.DefaultLightStyle()).
107			ChromaStyle(styles.Get("catppuccin-latte"))
108	}
109	DarkModeFunc = func(dv *diffview.DiffView) *diffview.DiffView {
110		return dv.
111			Style(diffview.DefaultDarkStyle()).
112			ChromaStyle(styles.Get("catppuccin-macchiato"))
113	}
114
115	LayoutFuncs = TestFuncs{
116		"Unified": UnifiedFunc,
117		"Split":   SplitFunc,
118	}
119	BehaviorFuncs = TestFuncs{
120		"Default":            DefaultFunc,
121		"NoLineNumbers":      NoLineNumbersFunc,
122		"MultipleHunks":      MultipleHunksFunc,
123		"CustomContextLines": CustomContextLinesFunc,
124		"Narrow":             NarrowFunc,
125		"SmallWidth":         SmallWidthFunc,
126		"LargeWidth":         LargeWidthFunc,
127		"NoSyntaxHighlight":  NoSyntaxHighlightFunc,
128	}
129	ThemeFuncs = TestFuncs{
130		"LightMode": LightModeFunc,
131		"DarkMode":  DarkModeFunc,
132	}
133)
134
135func TestDiffView(t *testing.T) {
136	for layoutName, layoutFunc := range LayoutFuncs {
137		t.Run(layoutName, func(t *testing.T) {
138			for behaviorName, behaviorFunc := range BehaviorFuncs {
139				t.Run(behaviorName, func(t *testing.T) {
140					for themeName, themeFunc := range ThemeFuncs {
141						t.Run(themeName, func(t *testing.T) {
142							t.Parallel()
143
144							dv := diffview.New()
145							dv = layoutFunc(dv)
146							dv = themeFunc(dv)
147							dv = behaviorFunc(dv)
148
149							output := dv.String()
150							golden.RequireEqual(t, []byte(output))
151
152							switch behaviorName {
153							case "SmallWidth":
154								assertLineWidth(t, 40, output)
155							case "LargeWidth":
156								assertLineWidth(t, 120, output)
157							}
158						})
159					}
160				})
161			}
162		})
163	}
164}
165
166func TestDiffViewTabs(t *testing.T) {
167	t.Parallel()
168
169	for layoutName, layoutFunc := range LayoutFuncs {
170		t.Run(layoutName, func(t *testing.T) {
171			t.Parallel()
172
173			dv := diffview.New().
174				Before("main.go", TestTabsBefore).
175				After("main.go", TestTabsAfter).
176				Style(diffview.DefaultLightStyle()).
177				ChromaStyle(styles.Get("catppuccin-latte"))
178			dv = layoutFunc(dv)
179
180			output := dv.String()
181			golden.RequireEqual(t, []byte(output))
182		})
183	}
184}
185
186func TestDiffViewLineBreakIssue(t *testing.T) {
187	t.Parallel()
188
189	for layoutName, layoutFunc := range LayoutFuncs {
190		t.Run(layoutName, func(t *testing.T) {
191			t.Parallel()
192
193			dv := diffview.New().
194				Before("index.js", TestLineBreakIssueBefore).
195				After("index.js", TestLineBreakIssueAfter).
196				Style(diffview.DefaultLightStyle()).
197				ChromaStyle(styles.Get("catppuccin-latte"))
198			dv = layoutFunc(dv)
199
200			output := dv.String()
201			golden.RequireEqual(t, []byte(output))
202		})
203	}
204}
205
206func TestDiffViewWidth(t *testing.T) {
207	for layoutName, layoutFunc := range LayoutFuncs {
208		t.Run(layoutName, func(t *testing.T) {
209			for width := 1; width <= 110; width++ {
210				if layoutName == "Unified" && width > 60 {
211					continue
212				}
213
214				t.Run(fmt.Sprintf("WidthOf%03d", width), func(t *testing.T) {
215					t.Parallel()
216
217					dv := diffview.New().
218						Before("main.go", TestMultipleHunksBefore).
219						After("main.go", TestMultipleHunksAfter).
220						Width(width).
221						Style(diffview.DefaultLightStyle()).
222						ChromaStyle(styles.Get("catppuccin-latte"))
223					dv = layoutFunc(dv)
224
225					output := dv.String()
226					golden.RequireEqual(t, []byte(output))
227
228					assertLineWidth(t, width, output)
229				})
230			}
231		})
232	}
233}
234
235func TestDiffViewHeight(t *testing.T) {
236	for layoutName, layoutFunc := range LayoutFuncs {
237		t.Run(layoutName, func(t *testing.T) {
238			for height := 1; height <= 20; height++ {
239				t.Run(fmt.Sprintf("HeightOf%03d", height), func(t *testing.T) {
240					t.Parallel()
241
242					dv := diffview.New().
243						Before("main.go", TestMultipleHunksBefore).
244						After("main.go", TestMultipleHunksAfter).
245						Height(height).
246						Style(diffview.DefaultLightStyle()).
247						ChromaStyle(styles.Get("catppuccin-latte"))
248					dv = layoutFunc(dv)
249
250					output := dv.String()
251					golden.RequireEqual(t, []byte(output))
252				})
253			}
254		})
255	}
256}
257
258func TestDiffViewXOffset(t *testing.T) {
259	for layoutName, layoutFunc := range LayoutFuncs {
260		t.Run(layoutName, func(t *testing.T) {
261			for xOffset := range 21 {
262				t.Run(fmt.Sprintf("XOffsetOf%02d", xOffset), func(t *testing.T) {
263					t.Parallel()
264
265					dv := diffview.New().
266						Before("main.go", TestDefaultBefore).
267						After("main.go", TestDefaultAfter).
268						Style(diffview.DefaultLightStyle()).
269						ChromaStyle(styles.Get("catppuccin-latte")).
270						Width(60).
271						XOffset(xOffset)
272					dv = layoutFunc(dv)
273
274					output := dv.String()
275					golden.RequireEqual(t, []byte(output))
276
277					assertLineWidth(t, 60, output)
278				})
279			}
280		})
281	}
282}
283
284func TestDiffViewYOffset(t *testing.T) {
285	for layoutName, layoutFunc := range LayoutFuncs {
286		t.Run(layoutName, func(t *testing.T) {
287			for yOffset := range 17 {
288				t.Run(fmt.Sprintf("YOffsetOf%02d", yOffset), func(t *testing.T) {
289					t.Parallel()
290
291					dv := diffview.New().
292						Before("main.go", TestMultipleHunksBefore).
293						After("main.go", TestMultipleHunksAfter).
294						Style(diffview.DefaultLightStyle()).
295						ChromaStyle(styles.Get("catppuccin-latte")).
296						Height(5).
297						YOffset(yOffset)
298					dv = layoutFunc(dv)
299
300					output := dv.String()
301					golden.RequireEqual(t, []byte(output))
302				})
303			}
304		})
305	}
306}
307
308func TestDiffViewYOffsetInfinite(t *testing.T) {
309	for layoutName, layoutFunc := range LayoutFuncs {
310		t.Run(layoutName, func(t *testing.T) {
311			for yOffset := range 17 {
312				t.Run(fmt.Sprintf("YOffsetOf%02d", yOffset), func(t *testing.T) {
313					t.Parallel()
314
315					dv := diffview.New().
316						Before("main.go", TestMultipleHunksBefore).
317						After("main.go", TestMultipleHunksAfter).
318						Style(diffview.DefaultLightStyle()).
319						ChromaStyle(styles.Get("catppuccin-latte")).
320						Height(5).
321						YOffset(yOffset).
322						InfiniteYScroll(true)
323					dv = layoutFunc(dv)
324
325					output := dv.String()
326					golden.RequireEqual(t, []byte(output))
327				})
328			}
329		})
330	}
331}
332
333func assertLineWidth(t *testing.T, expected int, output string) {
334	var lineWidth int
335	for line := range strings.SplitSeq(output, "\n") {
336		lineWidth = max(lineWidth, ansi.StringWidth(line))
337	}
338	if lineWidth != expected {
339		t.Errorf("expected output width to be == %d, got %d", expected, lineWidth)
340	}
341}
342
343func assertHeight(t *testing.T, expected int, output string) {
344	output = strings.TrimSuffix(output, "\n")
345	lines := strings.Count(output, "\n") + 1
346	if lines != expected {
347		t.Errorf("expected output height to be == %d, got %d", expected, lines)
348	}
349}