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/x/ansi"
 11	"github.com/charmbracelet/x/exp/golden"
 12	"github.com/opencode-ai/opencode/internal/exp/diffview"
 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							dv := diffview.New()
137							dv = layoutFunc(dv)
138							dv = themeFunc(dv)
139							dv = behaviorFunc(dv)
140
141							output := dv.String()
142							golden.RequireEqual(t, []byte(output))
143
144							switch behaviorName {
145							case "SmallWidth":
146								assertLineWidth(t, 40, output)
147							case "LargeWidth":
148								assertLineWidth(t, 120, output)
149							}
150						})
151					}
152				})
153			}
154		})
155	}
156}
157
158func TestDiffViewTabs(t *testing.T) {
159	for layoutName, layoutFunc := range LayoutFuncs {
160		t.Run(layoutName, func(t *testing.T) {
161			dv := diffview.New().
162				Before("main.go", TestTabsBefore).
163				After("main.go", TestTabsAfter).
164				Style(diffview.DefaultLightStyle).
165				ChromaStyle(styles.Get("catppuccin-latte"))
166			dv = layoutFunc(dv)
167
168			output := dv.String()
169			golden.RequireEqual(t, []byte(output))
170		})
171	}
172}
173
174func TestDiffViewWidth(t *testing.T) {
175	for layoutName, layoutFunc := range LayoutFuncs {
176		t.Run(layoutName, func(t *testing.T) {
177			for width := 1; width <= 110; width++ {
178				if layoutName == "Unified" && width > 60 {
179					continue
180				}
181
182				t.Run(fmt.Sprintf("WidthOf%03d", width), func(t *testing.T) {
183					dv := diffview.New().
184						Before("main.go", TestMultipleHunksBefore).
185						After("main.go", TestMultipleHunksAfter).
186						Width(width).
187						Style(diffview.DefaultLightStyle).
188						ChromaStyle(styles.Get("catppuccin-latte"))
189					dv = layoutFunc(dv)
190
191					output := dv.String()
192					golden.RequireEqual(t, []byte(output))
193
194					assertLineWidth(t, width, output)
195				})
196			}
197		})
198	}
199}
200
201func TestDiffViewHeight(t *testing.T) {
202	for layoutName, layoutFunc := range LayoutFuncs {
203		t.Run(layoutName, func(t *testing.T) {
204			for height := 1; height <= 20; height++ {
205				t.Run(fmt.Sprintf("HeightOf%03d", height), func(t *testing.T) {
206					dv := diffview.New().
207						Before("main.go", TestMultipleHunksBefore).
208						After("main.go", TestMultipleHunksAfter).
209						Height(height).
210						Style(diffview.DefaultLightStyle).
211						ChromaStyle(styles.Get("catppuccin-latte"))
212					dv = layoutFunc(dv)
213
214					output := dv.String()
215					golden.RequireEqual(t, []byte(output))
216
217					assertHeight(t, height, output)
218				})
219			}
220		})
221	}
222}
223
224func TestDiffViewXOffset(t *testing.T) {
225	for layoutName, layoutFunc := range LayoutFuncs {
226		t.Run(layoutName, func(t *testing.T) {
227			for xOffset := range 21 {
228				t.Run(fmt.Sprintf("XOffsetOf%02d", xOffset), func(t *testing.T) {
229					dv := diffview.New().
230						Before("main.go", TestDefaultBefore).
231						After("main.go", TestDefaultAfter).
232						Style(diffview.DefaultLightStyle).
233						ChromaStyle(styles.Get("catppuccin-latte")).
234						Width(60).
235						XOffset(xOffset)
236					dv = layoutFunc(dv)
237
238					output := dv.String()
239					golden.RequireEqual(t, []byte(output))
240
241					assertLineWidth(t, 60, output)
242				})
243			}
244		})
245	}
246}
247
248func TestDiffViewYOffset(t *testing.T) {
249	for layoutName, layoutFunc := range LayoutFuncs {
250		t.Run(layoutName, func(t *testing.T) {
251			for yOffset := range 17 {
252				t.Run(fmt.Sprintf("YOffsetOf%02d", yOffset), func(t *testing.T) {
253					dv := diffview.New().
254						Before("main.go", TestMultipleHunksBefore).
255						After("main.go", TestMultipleHunksAfter).
256						Style(diffview.DefaultLightStyle).
257						ChromaStyle(styles.Get("catppuccin-latte")).
258						Height(5).
259						YOffset(yOffset)
260					dv = layoutFunc(dv)
261
262					output := dv.String()
263					golden.RequireEqual(t, []byte(output))
264
265					assertHeight(t, 5, output)
266				})
267			}
268		})
269	}
270}
271
272func assertLineWidth(t *testing.T, expected int, output string) {
273	var lineWidth int
274	for line := range strings.SplitSeq(output, "\n") {
275		lineWidth = max(lineWidth, ansi.StringWidth(line))
276	}
277	if lineWidth != expected {
278		t.Errorf("expected output width to be == %d, got %d", expected, lineWidth)
279	}
280}
281
282func assertHeight(t *testing.T, expected int, output string) {
283	output = strings.TrimSuffix(output, "\n")
284	lines := strings.Count(output, "\n") + 1
285	if lines != expected {
286		t.Errorf("expected output height to be == %d, got %d", expected, lines)
287	}
288}