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	t.Parallel()
162
163	for layoutName, layoutFunc := range LayoutFuncs {
164		t.Run(layoutName, func(t *testing.T) {
165			t.Parallel()
166
167			dv := diffview.New().
168				Before("main.go", TestTabsBefore).
169				After("main.go", TestTabsAfter).
170				Style(diffview.DefaultLightStyle()).
171				ChromaStyle(styles.Get("catppuccin-latte"))
172			dv = layoutFunc(dv)
173
174			output := dv.String()
175			golden.RequireEqual(t, []byte(output))
176		})
177	}
178}
179
180func TestDiffViewWidth(t *testing.T) {
181	for layoutName, layoutFunc := range LayoutFuncs {
182		t.Run(layoutName, func(t *testing.T) {
183			for width := 1; width <= 110; width++ {
184				if layoutName == "Unified" && width > 60 {
185					continue
186				}
187
188				t.Run(fmt.Sprintf("WidthOf%03d", width), func(t *testing.T) {
189					t.Parallel()
190
191					dv := diffview.New().
192						Before("main.go", TestMultipleHunksBefore).
193						After("main.go", TestMultipleHunksAfter).
194						Width(width).
195						Style(diffview.DefaultLightStyle()).
196						ChromaStyle(styles.Get("catppuccin-latte"))
197					dv = layoutFunc(dv)
198
199					output := dv.String()
200					golden.RequireEqual(t, []byte(output))
201
202					assertLineWidth(t, width, output)
203				})
204			}
205		})
206	}
207}
208
209func TestDiffViewHeight(t *testing.T) {
210	for layoutName, layoutFunc := range LayoutFuncs {
211		t.Run(layoutName, func(t *testing.T) {
212			for height := 1; height <= 20; height++ {
213				t.Run(fmt.Sprintf("HeightOf%03d", height), func(t *testing.T) {
214					t.Parallel()
215
216					dv := diffview.New().
217						Before("main.go", TestMultipleHunksBefore).
218						After("main.go", TestMultipleHunksAfter).
219						Height(height).
220						Style(diffview.DefaultLightStyle()).
221						ChromaStyle(styles.Get("catppuccin-latte"))
222					dv = layoutFunc(dv)
223
224					output := dv.String()
225					golden.RequireEqual(t, []byte(output))
226
227					assertHeight(t, height, output)
228				})
229			}
230		})
231	}
232}
233
234func TestDiffViewXOffset(t *testing.T) {
235	for layoutName, layoutFunc := range LayoutFuncs {
236		t.Run(layoutName, func(t *testing.T) {
237			for xOffset := range 21 {
238				t.Run(fmt.Sprintf("XOffsetOf%02d", xOffset), func(t *testing.T) {
239					t.Parallel()
240
241					dv := diffview.New().
242						Before("main.go", TestDefaultBefore).
243						After("main.go", TestDefaultAfter).
244						Style(diffview.DefaultLightStyle()).
245						ChromaStyle(styles.Get("catppuccin-latte")).
246						Width(60).
247						XOffset(xOffset)
248					dv = layoutFunc(dv)
249
250					output := dv.String()
251					golden.RequireEqual(t, []byte(output))
252
253					assertLineWidth(t, 60, output)
254				})
255			}
256		})
257	}
258}
259
260func TestDiffViewYOffset(t *testing.T) {
261	for layoutName, layoutFunc := range LayoutFuncs {
262		t.Run(layoutName, func(t *testing.T) {
263			for yOffset := range 17 {
264				t.Run(fmt.Sprintf("YOffsetOf%02d", yOffset), func(t *testing.T) {
265					t.Parallel()
266
267					dv := diffview.New().
268						Before("main.go", TestMultipleHunksBefore).
269						After("main.go", TestMultipleHunksAfter).
270						Style(diffview.DefaultLightStyle()).
271						ChromaStyle(styles.Get("catppuccin-latte")).
272						Height(5).
273						YOffset(yOffset)
274					dv = layoutFunc(dv)
275
276					output := dv.String()
277					golden.RequireEqual(t, []byte(output))
278
279					assertHeight(t, 5, output)
280				})
281			}
282		})
283	}
284}
285
286func assertLineWidth(t *testing.T, expected int, output string) {
287	var lineWidth int
288	for line := range strings.SplitSeq(output, "\n") {
289		lineWidth = max(lineWidth, ansi.StringWidth(line))
290	}
291	if lineWidth != expected {
292		t.Errorf("expected output width to be == %d, got %d", expected, lineWidth)
293	}
294}
295
296func assertHeight(t *testing.T, expected int, output string) {
297	output = strings.TrimSuffix(output, "\n")
298	lines := strings.Count(output, "\n") + 1
299	if lines != expected {
300		t.Errorf("expected output height to be == %d, got %d", expected, lines)
301	}
302}