diffview_test.go

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