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}