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