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}