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