repos / pico

pico services mono repo
git clone https://github.com/picosh/pico.git

pico / pkg / shared
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}