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}