Eric Bower
·
2026-01-10
listparser_test.go
1package shared
2
3import (
4 "reflect"
5 "testing"
6)
7
8func TestListParseText(t *testing.T) {
9 t.Run("TestEmptyList", func(t *testing.T) {
10 text := ""
11 parsed := ListParseText(text)
12 if parsed == nil {
13 t.Fatal("parsed should not be nil")
14 }
15 if len(parsed.Items) != 0 {
16 t.Fatalf("expected 0 items, got %d", len(parsed.Items))
17 }
18 if parsed.ListMetaData == nil {
19 t.Fatal("ListMetaData should not be nil")
20 }
21 })
22
23 t.Run("TestSimpleListItems", func(t *testing.T) {
24 text := "First item\nSecond item"
25 parsed := ListParseText(text)
26 if parsed == nil {
27 t.Fatal("parsed should not be nil")
28 }
29 if len(parsed.Items) != 2 {
30 t.Fatalf("expected 2 items, got %d", len(parsed.Items))
31 }
32 if parsed.Items[0].Value != "First item" {
33 t.Errorf("expected 'First item', got '%s'", parsed.Items[0].Value)
34 }
35 if !parsed.Items[0].IsText {
36 t.Error("expected IsText to be true")
37 }
38 if parsed.Items[1].Value != "Second item" {
39 t.Errorf("expected 'Second item', got '%s'", parsed.Items[1].Value)
40 }
41 if !parsed.Items[1].IsText {
42 t.Error("expected IsText to be true")
43 }
44 })
45
46 t.Run("TestListWithEmptyLines", func(t *testing.T) {
47 text := "First item\n\nSecond item"
48 parsed := ListParseText(text)
49 if parsed == nil {
50 t.Fatal("parsed should not be nil")
51 }
52 if len(parsed.Items) != 2 {
53 t.Fatalf("expected 2 items, got %d", len(parsed.Items))
54 }
55 if parsed.Items[0].Value != "First item" {
56 t.Errorf("expected 'First item', got '%s'", parsed.Items[0].Value)
57 }
58 if parsed.Items[1].Value != "Second item" {
59 t.Errorf("expected 'Second item', got '%s'", parsed.Items[1].Value)
60 }
61 })
62
63 t.Run("TestHyperlinks", func(t *testing.T) {
64 text := "=> https://pico.sh\n=> https://prose.sh blog platform"
65 parsed := ListParseText(text)
66 if parsed == nil {
67 t.Fatal("parsed should not be nil")
68 }
69 if len(parsed.Items) != 2 {
70 t.Fatalf("expected 2 items, got %d", len(parsed.Items))
71 }
72
73 if !parsed.Items[0].IsURL {
74 t.Error("expected IsURL to be true")
75 }
76 if string(parsed.Items[0].URL) != "https://pico.sh" {
77 t.Errorf("expected URL 'https://pico.sh', got '%s'", parsed.Items[0].URL)
78 }
79 if parsed.Items[0].Value != "https://pico.sh" {
80 t.Errorf("expected Value 'https://pico.sh', got '%s'", parsed.Items[0].Value)
81 }
82
83 if !parsed.Items[1].IsURL {
84 t.Error("expected IsURL to be true")
85 }
86 if string(parsed.Items[1].URL) != "https://prose.sh" {
87 t.Errorf("expected URL 'https://prose.sh', got '%s'", parsed.Items[1].URL)
88 }
89 if parsed.Items[1].Value != "blog platform" {
90 t.Errorf("expected Value 'blog platform', got '%s'", parsed.Items[1].Value)
91 }
92 })
93
94 t.Run("TestImages", func(t *testing.T) {
95 text := "=< https://i.imgur.com/iXMNUN5.jpg\n=< https://i.imgur.com/iXMNUN5.jpg I use arch, btw"
96 parsed := ListParseText(text)
97 if parsed == nil {
98 t.Fatal("parsed should not be nil")
99 }
100 if len(parsed.Items) != 2 {
101 t.Fatalf("expected 2 items, got %d", len(parsed.Items))
102 }
103
104 if !parsed.Items[0].IsImg {
105 t.Error("expected IsImg to be true")
106 }
107 if string(parsed.Items[0].URL) != "https://i.imgur.com/iXMNUN5.jpg" {
108 t.Errorf("expected URL 'https://i.imgur.com/iXMNUN5.jpg', got '%s'", parsed.Items[0].URL)
109 }
110 if parsed.Items[0].Value != "https://i.imgur.com/iXMNUN5.jpg" {
111 t.Errorf("expected Value 'https://i.imgur.com/iXMNUN5.jpg', got '%s'", parsed.Items[0].Value)
112 }
113
114 if !parsed.Items[1].IsImg {
115 t.Error("expected IsImg to be true")
116 }
117 if string(parsed.Items[1].URL) != "https://i.imgur.com/iXMNUN5.jpg" {
118 t.Errorf("expected URL 'https://i.imgur.com/iXMNUN5.jpg', got '%s'", parsed.Items[1].URL)
119 }
120 if parsed.Items[1].Value != "I use arch, btw" {
121 t.Errorf("expected Value 'I use arch, btw', got '%s'", parsed.Items[1].Value)
122 }
123 })
124
125 t.Run("TestHeaders", func(t *testing.T) {
126 text := "# Header One\n## Header Two"
127 parsed := ListParseText(text)
128 if parsed == nil {
129 t.Fatal("parsed should not be nil")
130 }
131 if len(parsed.Items) != 2 {
132 t.Fatalf("expected 2 items, got %d", len(parsed.Items))
133 }
134
135 if !parsed.Items[0].IsHeaderOne {
136 t.Error("expected IsHeaderOne to be true")
137 }
138 if parsed.Items[0].Value != " Header One" {
139 t.Errorf("expected ' Header One', got '%s'", parsed.Items[0].Value)
140 }
141
142 if !parsed.Items[1].IsHeaderTwo {
143 t.Error("expected IsHeaderTwo to be true")
144 }
145 if parsed.Items[1].Value != " Header Two" {
146 t.Errorf("expected ' Header Two', got '%s'", parsed.Items[1].Value)
147 }
148 })
149
150 t.Run("TestBlockquotes", func(t *testing.T) {
151 text := "> This is a blockquote."
152 parsed := ListParseText(text)
153 if parsed == nil {
154 t.Fatal("parsed should not be nil")
155 }
156 if len(parsed.Items) != 1 {
157 t.Fatalf("expected 1 item, got %d", len(parsed.Items))
158 }
159
160 if !parsed.Items[0].IsBlock {
161 t.Error("expected IsBlock to be true")
162 }
163 if parsed.Items[0].Value != " This is a blockquote." {
164 t.Errorf("expected ' This is a blockquote.', got '%s'", parsed.Items[0].Value)
165 }
166 })
167
168 t.Run("TestPreformattedText", func(t *testing.T) {
169 text := "```\nsimple preformatted\nline 2\n```"
170 parsed := ListParseText(text)
171 if parsed == nil {
172 t.Fatal("parsed should not be nil")
173 }
174 if len(parsed.Items) != 1 {
175 t.Fatalf("expected 1 item, got %d", len(parsed.Items))
176 }
177
178 if !parsed.Items[0].IsPre {
179 t.Error("expected IsPre to be true")
180 }
181 expected := "\nsimple preformatted\nline 2"
182 if expected != parsed.Items[0].Value {
183 t.Errorf("expected '%s', got '%s'", expected, parsed.Items[0].Value)
184 }
185 })
186
187 t.Run("TestUnclosedPreformattedText", func(t *testing.T) {
188 text := "```\nsimple unclosed"
189 parsed := ListParseText(text)
190 if parsed == nil {
191 t.Fatal("parsed should not be nil")
192 }
193 if len(parsed.Items) != 1 {
194 t.Fatalf("expected 1 item, got %d", len(parsed.Items))
195 }
196 if !parsed.Items[0].IsPre {
197 t.Error("expected IsPre to be true")
198 }
199 expected := "\nsimple unclosed"
200 if expected != parsed.Items[0].Value {
201 t.Errorf("expected '%s', got '%s'", expected, parsed.Items[0].Value)
202 }
203 })
204
205 t.Run("TestVariableParsing", func(t *testing.T) {
206 text := "=: title Hello World\n=: tags one, two, three\nAn actual list item"
207 parsed := ListParseText(text)
208 if parsed == nil {
209 t.Fatal("parsed should not be nil")
210 }
211 if len(parsed.Items) != 3 {
212 t.Fatalf("expected 3 items, got %d", len(parsed.Items))
213 }
214 if parsed.Items[0].Value != "=: title Hello World" {
215 t.Errorf("expected '=: title Hello World', got '%s'", parsed.Items[0].Value)
216 }
217 if parsed.Items[1].Value != "=: tags one, two, three" {
218 t.Errorf("expected '=: tags one, two, three', got '%s'", parsed.Items[1].Value)
219 }
220 if parsed.Items[2].Value != "An actual list item" {
221 t.Errorf("expected 'An actual list item', got '%s'", parsed.Items[2].Value)
222 }
223
224 if parsed.Title != "Hello World" {
225 t.Errorf("expected Title 'Hello World', got '%s'", parsed.Title)
226 }
227 expectedTags := []string{"one", "two", "three"}
228 if !reflect.DeepEqual(expectedTags, parsed.Tags) {
229 t.Errorf("expected Tags '%v', got '%v'", expectedTags, parsed.Tags)
230 }
231 })
232
233 t.Run("TestNestedListWithSpaces", func(t *testing.T) {
234 text := "first item\n second item\n third item\nlast item"
235 parsed := ListParseText(text)
236 if parsed == nil {
237 t.Fatal("parsed should not be nil")
238 }
239 if len(parsed.Items) != 4 {
240 t.Fatalf("expected 4 items, got %d", len(parsed.Items))
241 }
242
243 if parsed.Items[0].Indent != 0 {
244 t.Errorf("expected Indent 0, got %d", parsed.Items[0].Indent)
245 }
246 if parsed.Items[1].Indent != 1 {
247 t.Errorf("expected Indent 1, got %d", parsed.Items[1].Indent)
248 }
249 if parsed.Items[2].Indent != 2 {
250 t.Errorf("expected Indent 2, got %d", parsed.Items[2].Indent)
251 }
252 if parsed.Items[3].Indent != 0 {
253 t.Errorf("expected Indent 0, got %d", parsed.Items[3].Indent)
254 }
255 })
256
257 t.Run("TestNestedListWithTabs", func(t *testing.T) {
258 text := "first item\n\tsecond item\n\t\tthird item\nlast item"
259 parsed := ListParseText(text)
260 if parsed == nil {
261 t.Fatal("parsed should not be nil")
262 }
263 if len(parsed.Items) != 4 {
264 t.Fatalf("expected 4 items, got %d", len(parsed.Items))
265 }
266
267 if parsed.Items[0].Indent != 0 {
268 t.Errorf("expected Indent 0, got %d", parsed.Items[0].Indent)
269 }
270 if parsed.Items[1].Indent != 1 {
271 t.Errorf("expected Indent 1, got %d", parsed.Items[1].Indent)
272 }
273 if parsed.Items[2].Indent != 2 {
274 t.Errorf("expected Indent 2, got %d", parsed.Items[2].Indent)
275 }
276 if parsed.Items[3].Indent != 0 {
277 t.Errorf("expected Indent 0, got %d", parsed.Items[3].Indent)
278 }
279 })
280
281 t.Run("TestDeeplyNestedList", func(t *testing.T) {
282 text := "1\n 2\n 3\n 4\n 5"
283 parsed := ListParseText(text)
284 if parsed == nil {
285 t.Fatal("parsed should not be nil")
286 }
287 if len(parsed.Items) != 5 {
288 t.Fatalf("expected 5 items, got %d", len(parsed.Items))
289 }
290
291 if parsed.Items[0].Indent != 0 {
292 t.Errorf("expected Indent 0, got %d", parsed.Items[0].Indent)
293 }
294 if parsed.Items[1].Indent != 1 {
295 t.Errorf("expected Indent 1, got %d", parsed.Items[1].Indent)
296 }
297 if parsed.Items[2].Indent != 2 {
298 t.Errorf("expected Indent 2, got %d", parsed.Items[2].Indent)
299 }
300 if parsed.Items[3].Indent != 3 {
301 t.Errorf("expected Indent 3, got %d", parsed.Items[3].Indent)
302 }
303 if parsed.Items[4].Indent != 4 {
304 t.Errorf("expected Indent 4, got %d", parsed.Items[4].Indent)
305 }
306 })
307
308 t.Run("TestMixedSpaceAndTabIndentation", func(t *testing.T) {
309 // spec advises against this, but we test the behavior
310 text := "first\n\tsecond\n third"
311 parsed := ListParseText(text)
312
313 if len(parsed.Items) != 3 {
314 t.Fatalf("expected 3 items, got %d", len(parsed.Items))
315 }
316 if parsed.Items[0].Indent != 0 {
317 t.Errorf("expected Indent 0, got %d", parsed.Items[0].Indent)
318 }
319 if parsed.Items[1].Indent != 1 {
320 t.Errorf("expected Indent 1, got %d", parsed.Items[1].Indent)
321 }
322 if parsed.Items[2].Indent != 2 {
323 t.Errorf("expected Indent 2, got %d", parsed.Items[2].Indent)
324 }
325 })
326
327 t.Run("TestComprehensiveList", func(t *testing.T) {
328 text := `=: title My Awesome List
329# Big Header
330Here is a normal item.
331=> https://pico.sh
332 * nested item 1
333> A wise man once said...
334`
335 parsed := ListParseText(text)
336 if parsed == nil {
337 t.Fatal("parsed should not be nil")
338 }
339 if parsed.Title != "My Awesome List" {
340 t.Errorf("expected Title 'My Awesome List', got '%s'", parsed.Title)
341 }
342
343 if len(parsed.Items) != 6 {
344 t.Fatalf("expected 6 items, got %d", len(parsed.Items))
345 }
346
347 if parsed.Items[0].Value != "=: title My Awesome List" {
348 t.Errorf("expected '=: title My Awesome List', got '%s'", parsed.Items[0].Value)
349 }
350 if !parsed.Items[1].IsHeaderOne {
351 t.Error("expected IsHeaderOne to be true")
352 }
353 if parsed.Items[1].Value != " Big Header" {
354 t.Errorf("expected ' Big Header', got '%s'", parsed.Items[1].Value)
355 }
356 if !parsed.Items[2].IsText {
357 t.Error("expected IsText to be true")
358 }
359 if parsed.Items[2].Value != "Here is a normal item." {
360 t.Errorf("expected 'Here is a normal item.', got '%s'", parsed.Items[2].Value)
361 }
362 if !parsed.Items[3].IsURL {
363 t.Error("expected IsURL to be true")
364 }
365 if parsed.Items[4].Value != "* nested item 1" {
366 t.Errorf("expected '* nested item 1', got '%s'", parsed.Items[4].Value)
367 }
368 if !parsed.Items[5].IsBlock {
369 t.Error("expected IsBlock to be true")
370 }
371 })
372
373 t.Run("TestMalformedLines", func(t *testing.T) {
374 text := "= > malformed url\n= < malformed image"
375 parsed := ListParseText(text)
376 if len(parsed.Items) != 2 {
377 t.Fatalf("expected 2 items, got %d", len(parsed.Items))
378 }
379
380 if !parsed.Items[0].IsText {
381 t.Error("expected IsText to be true")
382 }
383 if parsed.Items[0].Value != "= > malformed url" {
384 t.Errorf("expected '= > malformed url', got '%s'", parsed.Items[0].Value)
385 }
386
387 if !parsed.Items[1].IsText {
388 t.Error("expected IsText to be true")
389 }
390 if parsed.Items[1].Value != "= < malformed image" {
391 t.Errorf("expected '= < malformed image', got '%s'", parsed.Items[1].Value)
392 }
393 })
394}