1package diffview_test
2
3import (
4 _ "embed"
5 "fmt"
6 "strings"
7 "testing"
8
9 "github.com/alecthomas/chroma/v2/styles"
10 "github.com/charmbracelet/crush/internal/tui/exp/diffview"
11 "github.com/charmbracelet/x/ansi"
12 "github.com/charmbracelet/x/exp/golden"
13)
14
15//go:embed testdata/TestDefault.before
16var TestDefaultBefore string
17
18//go:embed testdata/TestDefault.after
19var TestDefaultAfter string
20
21//go:embed testdata/TestMultipleHunks.before
22var TestMultipleHunksBefore string
23
24//go:embed testdata/TestMultipleHunks.after
25var TestMultipleHunksAfter string
26
27//go:embed testdata/TestNarrow.before
28var TestNarrowBefore string
29
30//go:embed testdata/TestNarrow.after
31var TestNarrowAfter string
32
33//go:embed testdata/TestTabs.before
34var TestTabsBefore string
35
36//go:embed testdata/TestTabs.after
37var TestTabsAfter string
38
39//go:embed testdata/TestLineBreakIssue.before
40var TestLineBreakIssueBefore string
41
42//go:embed testdata/TestLineBreakIssue.after
43var TestLineBreakIssueAfter string
44
45type (
46 TestFunc func(dv *diffview.DiffView) *diffview.DiffView
47 TestFuncs map[string]TestFunc
48)
49
50var (
51 UnifiedFunc = func(dv *diffview.DiffView) *diffview.DiffView {
52 return dv.Unified()
53 }
54 SplitFunc = func(dv *diffview.DiffView) *diffview.DiffView {
55 return dv.Split()
56 }
57
58 DefaultFunc = func(dv *diffview.DiffView) *diffview.DiffView {
59 return dv.
60 Before("main.go", TestDefaultBefore).
61 After("main.go", TestDefaultAfter)
62 }
63 NoLineNumbersFunc = func(dv *diffview.DiffView) *diffview.DiffView {
64 return dv.
65 Before("main.go", TestDefaultBefore).
66 After("main.go", TestDefaultAfter).
67 LineNumbers(false)
68 }
69 MultipleHunksFunc = func(dv *diffview.DiffView) *diffview.DiffView {
70 return dv.
71 Before("main.go", TestMultipleHunksBefore).
72 After("main.go", TestMultipleHunksAfter)
73 }
74 CustomContextLinesFunc = func(dv *diffview.DiffView) *diffview.DiffView {
75 return dv.
76 Before("main.go", TestMultipleHunksBefore).
77 After("main.go", TestMultipleHunksAfter).
78 ContextLines(4)
79 }
80 NarrowFunc = func(dv *diffview.DiffView) *diffview.DiffView {
81 return dv.
82 Before("text.txt", TestNarrowBefore).
83 After("text.txt", TestNarrowAfter)
84 }
85 SmallWidthFunc = func(dv *diffview.DiffView) *diffview.DiffView {
86 return dv.
87 Before("main.go", TestMultipleHunksBefore).
88 After("main.go", TestMultipleHunksAfter).
89 Width(40)
90 }
91 LargeWidthFunc = func(dv *diffview.DiffView) *diffview.DiffView {
92 return dv.
93 Before("main.go", TestMultipleHunksBefore).
94 After("main.go", TestMultipleHunksAfter).
95 Width(120)
96 }
97 NoSyntaxHighlightFunc = func(dv *diffview.DiffView) *diffview.DiffView {
98 return dv.
99 Before("main.go", TestMultipleHunksBefore).
100 After("main.go", TestMultipleHunksAfter).
101 ChromaStyle(nil)
102 }
103
104 LightModeFunc = func(dv *diffview.DiffView) *diffview.DiffView {
105 return dv.
106 Style(diffview.DefaultLightStyle()).
107 ChromaStyle(styles.Get("catppuccin-latte"))
108 }
109 DarkModeFunc = func(dv *diffview.DiffView) *diffview.DiffView {
110 return dv.
111 Style(diffview.DefaultDarkStyle()).
112 ChromaStyle(styles.Get("catppuccin-macchiato"))
113 }
114
115 LayoutFuncs = TestFuncs{
116 "Unified": UnifiedFunc,
117 "Split": SplitFunc,
118 }
119 BehaviorFuncs = TestFuncs{
120 "Default": DefaultFunc,
121 "NoLineNumbers": NoLineNumbersFunc,
122 "MultipleHunks": MultipleHunksFunc,
123 "CustomContextLines": CustomContextLinesFunc,
124 "Narrow": NarrowFunc,
125 "SmallWidth": SmallWidthFunc,
126 "LargeWidth": LargeWidthFunc,
127 "NoSyntaxHighlight": NoSyntaxHighlightFunc,
128 }
129 ThemeFuncs = TestFuncs{
130 "LightMode": LightModeFunc,
131 "DarkMode": DarkModeFunc,
132 }
133)
134
135func TestDiffView(t *testing.T) {
136 for layoutName, layoutFunc := range LayoutFuncs {
137 t.Run(layoutName, func(t *testing.T) {
138 for behaviorName, behaviorFunc := range BehaviorFuncs {
139 t.Run(behaviorName, func(t *testing.T) {
140 for themeName, themeFunc := range ThemeFuncs {
141 t.Run(themeName, func(t *testing.T) {
142 t.Parallel()
143
144 dv := diffview.New()
145 dv = layoutFunc(dv)
146 dv = themeFunc(dv)
147 dv = behaviorFunc(dv)
148
149 output := dv.String()
150 golden.RequireEqual(t, []byte(output))
151
152 switch behaviorName {
153 case "SmallWidth":
154 assertLineWidth(t, 40, output)
155 case "LargeWidth":
156 assertLineWidth(t, 120, output)
157 }
158 })
159 }
160 })
161 }
162 })
163 }
164}
165
166func TestDiffViewTabs(t *testing.T) {
167 t.Parallel()
168
169 for layoutName, layoutFunc := range LayoutFuncs {
170 t.Run(layoutName, func(t *testing.T) {
171 t.Parallel()
172
173 dv := diffview.New().
174 Before("main.go", TestTabsBefore).
175 After("main.go", TestTabsAfter).
176 Style(diffview.DefaultLightStyle()).
177 ChromaStyle(styles.Get("catppuccin-latte"))
178 dv = layoutFunc(dv)
179
180 output := dv.String()
181 golden.RequireEqual(t, []byte(output))
182 })
183 }
184}
185
186func TestDiffViewLineBreakIssue(t *testing.T) {
187 t.Parallel()
188
189 for layoutName, layoutFunc := range LayoutFuncs {
190 t.Run(layoutName, func(t *testing.T) {
191 t.Parallel()
192
193 dv := diffview.New().
194 Before("index.js", TestLineBreakIssueBefore).
195 After("index.js", TestLineBreakIssueAfter).
196 Style(diffview.DefaultLightStyle()).
197 ChromaStyle(styles.Get("catppuccin-latte"))
198 dv = layoutFunc(dv)
199
200 output := dv.String()
201 golden.RequireEqual(t, []byte(output))
202 })
203 }
204}
205
206func TestDiffViewWidth(t *testing.T) {
207 for layoutName, layoutFunc := range LayoutFuncs {
208 t.Run(layoutName, func(t *testing.T) {
209 for width := 1; width <= 110; width++ {
210 if layoutName == "Unified" && width > 60 {
211 continue
212 }
213
214 t.Run(fmt.Sprintf("WidthOf%03d", width), func(t *testing.T) {
215 t.Parallel()
216
217 dv := diffview.New().
218 Before("main.go", TestMultipleHunksBefore).
219 After("main.go", TestMultipleHunksAfter).
220 Width(width).
221 Style(diffview.DefaultLightStyle()).
222 ChromaStyle(styles.Get("catppuccin-latte"))
223 dv = layoutFunc(dv)
224
225 output := dv.String()
226 golden.RequireEqual(t, []byte(output))
227
228 assertLineWidth(t, width, output)
229 })
230 }
231 })
232 }
233}
234
235func TestDiffViewHeight(t *testing.T) {
236 for layoutName, layoutFunc := range LayoutFuncs {
237 t.Run(layoutName, func(t *testing.T) {
238 for height := 1; height <= 20; height++ {
239 t.Run(fmt.Sprintf("HeightOf%03d", height), func(t *testing.T) {
240 t.Parallel()
241
242 dv := diffview.New().
243 Before("main.go", TestMultipleHunksBefore).
244 After("main.go", TestMultipleHunksAfter).
245 Height(height).
246 Style(diffview.DefaultLightStyle()).
247 ChromaStyle(styles.Get("catppuccin-latte"))
248 dv = layoutFunc(dv)
249
250 output := dv.String()
251 golden.RequireEqual(t, []byte(output))
252
253 assertHeight(t, height, output)
254 })
255 }
256 })
257 }
258}
259
260func TestDiffViewXOffset(t *testing.T) {
261 for layoutName, layoutFunc := range LayoutFuncs {
262 t.Run(layoutName, func(t *testing.T) {
263 for xOffset := range 21 {
264 t.Run(fmt.Sprintf("XOffsetOf%02d", xOffset), func(t *testing.T) {
265 t.Parallel()
266
267 dv := diffview.New().
268 Before("main.go", TestDefaultBefore).
269 After("main.go", TestDefaultAfter).
270 Style(diffview.DefaultLightStyle()).
271 ChromaStyle(styles.Get("catppuccin-latte")).
272 Width(60).
273 XOffset(xOffset)
274 dv = layoutFunc(dv)
275
276 output := dv.String()
277 golden.RequireEqual(t, []byte(output))
278
279 assertLineWidth(t, 60, output)
280 })
281 }
282 })
283 }
284}
285
286func TestDiffViewYOffset(t *testing.T) {
287 for layoutName, layoutFunc := range LayoutFuncs {
288 t.Run(layoutName, func(t *testing.T) {
289 for yOffset := range 17 {
290 t.Run(fmt.Sprintf("YOffsetOf%02d", yOffset), func(t *testing.T) {
291 t.Parallel()
292
293 dv := diffview.New().
294 Before("main.go", TestMultipleHunksBefore).
295 After("main.go", TestMultipleHunksAfter).
296 Style(diffview.DefaultLightStyle()).
297 ChromaStyle(styles.Get("catppuccin-latte")).
298 Height(5).
299 YOffset(yOffset)
300 dv = layoutFunc(dv)
301
302 output := dv.String()
303 golden.RequireEqual(t, []byte(output))
304
305 assertHeight(t, 5, output)
306 })
307 }
308 })
309 }
310}
311
312func TestDiffViewYOffsetInfinite(t *testing.T) {
313 for layoutName, layoutFunc := range LayoutFuncs {
314 t.Run(layoutName, func(t *testing.T) {
315 for yOffset := range 17 {
316 t.Run(fmt.Sprintf("YOffsetOf%02d", yOffset), func(t *testing.T) {
317 t.Parallel()
318
319 dv := diffview.New().
320 Before("main.go", TestMultipleHunksBefore).
321 After("main.go", TestMultipleHunksAfter).
322 Style(diffview.DefaultLightStyle()).
323 ChromaStyle(styles.Get("catppuccin-latte")).
324 Height(5).
325 YOffset(yOffset).
326 InfiniteYScroll(true)
327 dv = layoutFunc(dv)
328
329 output := dv.String()
330 golden.RequireEqual(t, []byte(output))
331
332 assertHeight(t, 5, output)
333 })
334 }
335 })
336 }
337}
338
339func assertLineWidth(t *testing.T, expected int, output string) {
340 var lineWidth int
341 for line := range strings.SplitSeq(output, "\n") {
342 lineWidth = max(lineWidth, ansi.StringWidth(line))
343 }
344 if lineWidth != expected {
345 t.Errorf("expected output width to be == %d, got %d", expected, lineWidth)
346 }
347}
348
349func assertHeight(t *testing.T, expected int, output string) {
350 output = strings.TrimSuffix(output, "\n")
351 lines := strings.Count(output, "\n") + 1
352 if lines != expected {
353 t.Errorf("expected output height to be == %d, got %d", expected, lines)
354 }
355}