gitstack

grindlemire/go-tui code browser

4.5 KB Go 143 lines 2026-05-29 · d1d652d raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package markdown

import "testing"

func TestParse_Headings(t *testing.T) {
	blocks := Parse("# H1\n## H2\n### H3")
	if len(blocks) != 3 {
		t.Fatalf("want 3 blocks, got %d", len(blocks))
	}
	for i, want := range []int{1, 2, 3} {
		if blocks[i].Kind != KindHeading || blocks[i].Level != want {
			t.Errorf("block %d = kind %d level %d, want heading level %d", i, blocks[i].Kind, blocks[i].Level, want)
		}
	}
	if blocks[0].Inline[0].Text != "H1" {
		t.Errorf("h1 text = %q, want H1", blocks[0].Inline[0].Text)
	}
}

func TestParse_Setext(t *testing.T) {
	blocks := Parse("Title\n=====\n\nSub\n---")
	if len(blocks) != 2 {
		t.Fatalf("want 2 blocks, got %d: %+v", len(blocks), blocks)
	}
	if blocks[0].Kind != KindHeading || blocks[0].Level != 1 || blocks[0].Inline[0].Text != "Title" {
		t.Errorf("block 0 = %+v, want setext h1 Title", blocks[0])
	}
	if blocks[1].Kind != KindHeading || blocks[1].Level != 2 || blocks[1].Inline[0].Text != "Sub" {
		t.Errorf("block 1 = %+v, want setext h2 Sub", blocks[1])
	}
}

func TestParse_Paragraph(t *testing.T) {
	blocks := Parse("one line\ntwo line")
	if len(blocks) != 1 || blocks[0].Kind != KindParagraph {
		t.Fatalf("want 1 paragraph, got %+v", blocks)
	}
	if blocks[0].Inline[0].Text != "one line two line" {
		t.Errorf("joined text = %q", blocks[0].Inline[0].Text)
	}
}

func TestParse_CodeFence(t *testing.T) {
	blocks := Parse("```go\nfunc main() {}\nx := 1\n```")
	if len(blocks) != 1 || blocks[0].Kind != KindCodeFence {
		t.Fatalf("want 1 code fence, got %+v", blocks)
	}
	if blocks[0].Lang != "go" {
		t.Errorf("lang = %q, want go", blocks[0].Lang)
	}
	if len(blocks[0].Lines) != 2 || blocks[0].Lines[0] != "func main() {}" || blocks[0].Lines[1] != "x := 1" {
		t.Errorf("lines = %+v", blocks[0].Lines)
	}
}

func TestParse_Table(t *testing.T) {
	blocks := Parse("| A | B |\n| - | - |\n| 1 | 2 |\n| 3 | 4 |")
	if len(blocks) != 1 || blocks[0].Kind != KindTable {
		t.Fatalf("want 1 table, got %+v", blocks)
	}
	rows := blocks[0].Rows
	if len(rows) != 3 {
		t.Fatalf("want 3 rows (header + 2), got %d", len(rows))
	}
	if len(rows[0]) != 2 || rows[0][0].Inline[0].Text != "A" || rows[0][1].Inline[0].Text != "B" {
		t.Errorf("header = %+v", rows[0])
	}
	if rows[2][1].Inline[0].Text != "4" {
		t.Errorf("cell (2,1) = %+v, want 4", rows[2][1])
	}
}

func TestParse_FullDocument(t *testing.T) {
	src := "# Title\n" +
		"\n" +
		"A para with **bold** and a [link](https://go.dev).\n" +
		"\n" +
		"```go\nx := 1\n```\n" +
		"\n" +
		"| A | B |\n| - | - |\n| 1 | 2 |\n" +
		"\n" +
		"- one\n- two\n  - nested\n" +
		"\n" +
		"> quoted para\n"
	blocks := Parse(src)

	wantKinds := []BlockKind{KindHeading, KindParagraph, KindCodeFence, KindTable, KindList, KindBlockquote}
	if len(blocks) != len(wantKinds) {
		t.Fatalf("want %d blocks, got %d: %+v", len(wantKinds), len(blocks), blocks)
	}
	for i, want := range wantKinds {
		if blocks[i].Kind != want {
			t.Errorf("block %d kind = %d, want %d", i, blocks[i].Kind, want)
		}
	}

	// Link URL inside the paragraph.
	para := blocks[1]
	var foundLink bool
	for _, in := range para.Inline {
		if in.Link == "https://go.dev" && in.Text == "link" {
			foundLink = true
		}
	}
	if !foundLink {
		t.Errorf("paragraph missing link: %+v", para.Inline)
	}

	// Nested list item under the second top-level item.
	list := blocks[4]
	if len(list.Children) != 2 {
		t.Fatalf("want 2 list items, got %d", len(list.Children))
	}
	second := list.Children[1]
	if len(second.Children) != 1 || second.Children[0].Kind != KindList {
		t.Fatalf("second item should have a nested list, got %+v", second.Children)
	}
	nestedItem := second.Children[0].Children[0]
	if nestedItem.Inline[0].Text != "nested" {
		t.Errorf("nested item text = %q, want nested", nestedItem.Inline[0].Text)
	}

	// Blockquote contains a paragraph.
	bq := blocks[5]
	if len(bq.Children) != 1 || bq.Children[0].Kind != KindParagraph {
		t.Errorf("blockquote children = %+v", bq.Children)
	}
}

func TestParse_TableDirectlyAfterParagraph(t *testing.T) {
	// A table immediately following a paragraph line (no blank line between)
	// must parse as a paragraph + a table, not one swallowed paragraph.
	blocks := Parse("intro text\n| A | B |\n| - | - |\n| 1 | 2 |")
	if len(blocks) != 2 {
		t.Fatalf("want 2 blocks (paragraph + table), got %d: %+v", len(blocks), blocks)
	}
	if blocks[0].Kind != KindParagraph || blocks[0].Inline[0].Text != "intro text" {
		t.Errorf("block 0 = %+v, want paragraph \"intro text\"", blocks[0])
	}
	if blocks[1].Kind != KindTable || len(blocks[1].Rows) != 2 {
		t.Errorf("block 1 = %+v, want table with header + 1 row", blocks[1])
	}
}