Search Apps Documentation Source Content File Folder Download Copy Actions Download

md_test.gno

8.07 Kb · 374 lines
  1package md_test
  2
  3import (
  4	"testing"
  5
  6	"gno.land/p/moul/md"
  7)
  8
  9func TestHelpers(t *testing.T) {
 10	tests := []struct {
 11		name     string
 12		function func() string
 13		expected string
 14	}{
 15		{"Bold", func() string { return md.Bold("foo") }, "**foo**"},
 16		{"Italic", func() string { return md.Italic("foo") }, "*foo*"},
 17		{"Strikethrough", func() string { return md.Strikethrough("foo") }, "~~foo~~"},
 18		{"H1", func() string { return md.H1("foo") }, "# foo\n"},
 19		{"HorizontalRule", md.HorizontalRule, "---\n"},
 20		{"InlineCode", func() string { return md.InlineCode("foo") }, "`foo`"},
 21		{"CodeBlock", func() string { return md.CodeBlock("foo") }, "```\nfoo\n```\n"},
 22		{"LanguageCodeBlock", func() string { return md.LanguageCodeBlock("go", "foo") }, "```go\nfoo\n```\n"},
 23		{"Link", func() string { return md.Link("foo", "http://example.com") }, "[foo](http://example.com)"},
 24		{"UserLink", func() string { return md.UserLink("moul") }, "[@moul](/u/moul)"},
 25		{"Image", func() string { return md.Image("foo", "http://example.com") }, "![foo](http://example.com)"},
 26		{"InlineImageWithLink", func() string {
 27			return md.InlineImageWithLink("alt", "http://img.example.com/x.png", "http://link.example.com")
 28		}, "[![alt](http://img.example.com/x.png)](http://link.example.com)"},
 29		{"FootnoteDefinition", func() string { return md.FootnoteDefinition("foo", "bar") }, "[^foo]:\n    bar\n"},
 30		{"LinkReferenceDefinition", func() string {
 31			return md.LinkReferenceDefinition("foo", "http://example.com", "")
 32		}, "\n\n[foo]: http://example.com\n\n"},
 33		{"Paragraph", func() string { return md.Paragraph("foo") }, "foo\n\n"},
 34	}
 35
 36	for _, tt := range tests {
 37		t.Run(tt.name, func(t *testing.T) {
 38			result := tt.function()
 39			if result != tt.expected {
 40				t.Errorf("%s() = %q, want %q", tt.name, result, tt.expected)
 41			}
 42		})
 43	}
 44}
 45
 46func TestLists(t *testing.T) {
 47	t.Run("BulletList", func(t *testing.T) {
 48		items := []string{"foo", "bar"}
 49		expected := "- foo\n- bar\n"
 50		result := md.BulletList(items)
 51		if result != expected {
 52			t.Errorf("BulletList(%q) = %q, want %q", items, result, expected)
 53		}
 54	})
 55
 56	t.Run("OrderedList", func(t *testing.T) {
 57		items := []string{"foo", "bar"}
 58		expected := "1. foo\n2. bar\n"
 59		result := md.OrderedList(items)
 60		if result != expected {
 61			t.Errorf("OrderedList(%q) = %q, want %q", items, result, expected)
 62		}
 63	})
 64
 65	t.Run("TodoList", func(t *testing.T) {
 66		items := []string{"foo", "bar\nmore bar"}
 67		done := []bool{true, false}
 68		expected := "- [x] foo\n- [ ] bar\n  more bar\n"
 69		result := md.TodoList(items, done)
 70		if result != expected {
 71			t.Errorf("TodoList(%q, %q) = %q, want %q", items, done, result, expected)
 72		}
 73	})
 74}
 75
 76func TestUserLink(t *testing.T) {
 77	tests := []struct {
 78		name     string
 79		input    string
 80		expected string
 81	}{
 82		{"username", "moul", "[@moul](/u/moul)"},
 83		// "g1blah" data part is too short for a bech32 address (min 6
 84		// chars after "1"); UserLink validates as a username instead.
 85		{"short g1-prefixed string treated as username", "g1blah", "[@g1blah](/u/g1blah)"},
 86		// "user_name" is a valid username (charset includes _ and -);
 87		// the validator returns it verbatim, so no markdown escape of _.
 88		{"username with underscore", "user_name", "[@user_name](/u/user_name)"},
 89		// "g1abc123" has a 6-char data part — valid bech32 shape.
 90		{"address with 6-char data", "g1abc123", "[g1abc123](/u/g1abc123)"},
 91		// invalid identifier (spaces, uppercase, etc.) → empty.
 92		{"rejected: spaces", "hello world", ""},
 93		{"rejected: uppercase first char", "Alice", ""},
 94	}
 95
 96	for _, tt := range tests {
 97		t.Run(tt.name, func(t *testing.T) {
 98			result := md.UserLink(tt.input)
 99			if result != tt.expected {
100				t.Errorf("UserLink(%q) = %q, want %q", tt.input, result, tt.expected)
101			}
102		})
103	}
104}
105
106func TestNested(t *testing.T) {
107	t.Run("Nested Single Level", func(t *testing.T) {
108		content := "- foo\n- bar"
109		expected := "  - foo\n  - bar"
110		result := md.Nested(content, "  ")
111		if result != expected {
112			t.Errorf("Nested(%q) = %q, want %q", content, result, expected)
113		}
114	})
115
116	t.Run("Nested Double Level", func(t *testing.T) {
117		content := "  - foo\n  - bar"
118		expected := "    - foo\n    - bar"
119		result := md.Nested(content, "  ")
120		if result != expected {
121			t.Errorf("Nested(%q) = %q, want %q", content, result, expected)
122		}
123	})
124}
125
126func TestColumns(t *testing.T) {
127	tests := []struct {
128		name     string
129		input    []string
130		padded   bool
131		expected string
132	}{
133		{
134			name:     "no columns",
135			input:    []string{},
136			padded:   false,
137			expected: "",
138		},
139		{
140			name:     "no columns padded",
141			input:    []string{},
142			padded:   true,
143			expected: "",
144		},
145		{
146			name:   "one column",
147			input:  []string{"Column 1"},
148			padded: false,
149			expected: `<gno-columns>
150Column 1
151</gno-columns>
152`,
153		},
154		{
155			name:   "one column padded",
156			input:  []string{"Column 1"},
157			padded: true,
158			expected: `<gno-columns>
159Column 1
160<gno-columns-sep>
161
162<gno-columns-sep>
163
164<gno-columns-sep>
165
166</gno-columns>
167`,
168		},
169		{
170			name:   "two columns",
171			input:  []string{"Column 1", "Column 2"},
172			padded: false,
173			expected: `<gno-columns>
174Column 1
175<gno-columns-sep>
176Column 2
177</gno-columns>
178`,
179		},
180		{
181			name:   "two columns padded",
182			input:  []string{"Column 1", "Column 2"},
183			padded: true,
184			expected: `<gno-columns>
185Column 1
186<gno-columns-sep>
187Column 2
188<gno-columns-sep>
189
190<gno-columns-sep>
191
192</gno-columns>
193`,
194		},
195		{
196			name:   "four columns",
197			input:  []string{"A", "B", "C", "D"},
198			padded: false,
199			expected: `<gno-columns>
200A
201<gno-columns-sep>
202B
203<gno-columns-sep>
204C
205<gno-columns-sep>
206D
207</gno-columns>
208`,
209		},
210		{
211			name:   "four columns padded",
212			input:  []string{"A", "B", "C", "D"},
213			padded: true,
214			expected: `<gno-columns>
215A
216<gno-columns-sep>
217B
218<gno-columns-sep>
219C
220<gno-columns-sep>
221D
222</gno-columns>
223`,
224		},
225		{
226			name:   "more than four columns",
227			input:  []string{"1", "2", "3", "4", "5"},
228			padded: false,
229			expected: `<gno-columns>
2301
231<gno-columns-sep>
2322
233<gno-columns-sep>
2343
235<gno-columns-sep>
2364
237<gno-columns-sep>
2385
239</gno-columns>
240`,
241		},
242		{
243			name:   "more than four columns padded",
244			input:  []string{"1", "2", "3", "4", "5"},
245			padded: true,
246			expected: `<gno-columns>
2471
248<gno-columns-sep>
2492
250<gno-columns-sep>
2513
252<gno-columns-sep>
2534
254<gno-columns-sep>
2555
256<gno-columns-sep>
257
258<gno-columns-sep>
259
260<gno-columns-sep>
261
262</gno-columns>
263`,
264		},
265	}
266
267	for _, tt := range tests {
268		t.Run(tt.name, func(t *testing.T) {
269			result := md.Columns(tt.input, tt.padded)
270			if result != tt.expected {
271				t.Errorf("Columns(%v, %v) =\n%q\nwant:\n%q", tt.input, tt.padded, result, tt.expected)
272			}
273		})
274	}
275}
276
277func TestColumnsN(t *testing.T) {
278	tests := []struct {
279		name       string
280		content    []string
281		colsPerRow int
282		padded     bool
283		expected   string
284	}{
285		{
286			name:       "empty input",
287			content:    []string{},
288			colsPerRow: 2,
289			padded:     false,
290			expected:   "",
291		},
292		{
293			name:       "colsPerRow <= 0",
294			content:    []string{"A", "B", "C"},
295			colsPerRow: 0,
296			padded:     false,
297			expected: `<gno-columns>
298A
299<gno-columns-sep>
300B
301<gno-columns-sep>
302C
303</gno-columns>
304`,
305		},
306		{
307			name:       "exact full row, no padding",
308			content:    []string{"A", "B"},
309			colsPerRow: 2,
310			padded:     false,
311			expected: `<gno-columns>
312A
313<gno-columns-sep>
314B
315</gno-columns>
316`,
317		},
318		{
319			name:       "partial last row, no padding",
320			content:    []string{"A", "B", "C"},
321			colsPerRow: 2,
322			padded:     false,
323			expected: `<gno-columns>
324A
325<gno-columns-sep>
326B
327</gno-columns>
328<gno-columns>
329C
330</gno-columns>
331`,
332		},
333		{
334			name:       "partial last row, with padding",
335			content:    []string{"A", "B", "C"},
336			colsPerRow: 2,
337			padded:     true,
338			expected: `<gno-columns>
339A
340<gno-columns-sep>
341B
342</gno-columns>
343<gno-columns>
344C
345<gno-columns-sep>
346
347</gno-columns>
348`,
349		},
350		{
351			name:       "padded with more empty cells",
352			content:    []string{"X"},
353			colsPerRow: 3,
354			padded:     true,
355			expected: `<gno-columns>
356X
357<gno-columns-sep>
358
359<gno-columns-sep>
360
361</gno-columns>
362`,
363		},
364	}
365
366	for _, tt := range tests {
367		t.Run(tt.name, func(t *testing.T) {
368			result := md.ColumnsN(tt.content, tt.colsPerRow, tt.padded)
369			if result != tt.expected {
370				t.Errorf("ColumnsN(%v, %d, %v) =\n%q\nwant:\n%q", tt.content, tt.colsPerRow, tt.padded, result, tt.expected)
371			}
372		})
373	}
374}