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