turbo-editors/turbo-corepublic Fork 0
28d59854361aeda8541d853093e732126f3d7bff
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-core.git
git clone ssh://git@rickub.com/turbo-editors/turbo-core.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

markdown_test.go · 196 lines · 6.3 KBGo Blame HistoryRaw
🛟 Updated. 28d5985 k33g 17h ago1package syntax
2
3import (
4 "strings"
5 "testing"
6)
7
8// spansOf renders one line's spans as "text:class" strings, which reads like
9// the screen does and makes a failure legible.
10func spansOf(t *testing.T, language Language, src string, line int) []string {
11 t.Helper()
12
13 all := Highlight(language, src)
14 if line >= len(all) {
15 t.Fatalf("line %d is past the end of a %d-line document", line, len(all))
16 }
17
18 text := []rune(strings.Split(src, "\n")[line])
19 out := make([]string, 0, len(all[line]))
20 for _, s := range all[line] {
21 out = append(out, string(text[s.Start:s.End])+":"+s.Class.String())
22 }
23 return out
24}
25
26// wantLine compares one line's spans against what they should be.
27func wantLine(t *testing.T, language Language, src string, line int, want ...string) {
28 t.Helper()
29
30 got := spansOf(t, language, src, line)
31 if len(got) != len(want) {
32 t.Fatalf("line %d of %q gave\n %v\nwant\n %v", line, src, got, want)
33 }
34 for i := range want {
35 if got[i] != want[i] {
36 t.Errorf("line %d span %d = %q, want %q (whole line: %v)", line, i, got[i], want[i], got)
37 }
38 }
39}
40
41// wantMarkdown is wantLine for Markdown, which most of this file needs.
42func wantMarkdown(t *testing.T, src string, line int, want ...string) {
43 t.Helper()
44 wantLine(t, LanguageMarkdown, src, line, want...)
45}
46
47func TestMarkdownColoursAHeadingWholeLine(t *testing.T) {
48 wantMarkdown(t, "# A title", 0, "# A title:heading")
49 wantMarkdown(t, "### Deeper", 0, "### Deeper:heading")
50}
51
52func TestMarkdownColoursBoldAndItalic(t *testing.T) {
53 wantMarkdown(t, "some *italic* here", 0, "*italic*:emphasis")
54 wantMarkdown(t, "some **bold** here", 0, "**bold**:emphasis")
55 wantMarkdown(t, "some _italic_ here", 0, "_italic_:emphasis")
56 wantMarkdown(t, "some __bold__ here", 0, "__bold__:emphasis")
57}
58
59func TestBoldIsOneSpanNotTwoItalics(t *testing.T) {
60 // The run of markers at the start has to be matched by the same run at the
61 // end, or ** opens an italic that closes immediately.
62 got := spansOf(t, LanguageMarkdown, "**bold**", 0)
63 if len(got) != 1 {
64 t.Errorf("**bold** gave %v, want one span", got)
65 }
66}
67
68func TestMarkdownColoursACodeSpan(t *testing.T) {
69 wantMarkdown(t, "call `go build` first", 0, "`go build`:string")
70}
71
72func TestMarkdownColoursALinkAndAnImage(t *testing.T) {
73 wantMarkdown(t, "see [the docs](docs/README.md) now", 0, "[the docs](docs/README.md):link")
74 wantMarkdown(t, "![a picture](x.png)", 0, "![a picture](x.png):link")
75}
76
77func TestAnUnclosedLinkIsLeftAlone(t *testing.T) {
78 // Half a link is typed most of the time a link is being typed.
79 if got := spansOf(t, LanguageMarkdown, "see [the docs", 0); len(got) != 0 {
80 t.Errorf("an unfinished link gave %v, want nothing coloured", got)
81 }
82 if got := spansOf(t, LanguageMarkdown, "see [text] but no target", 0); len(got) != 0 {
83 t.Errorf("a bracket with no target gave %v", got)
84 }
85}
86
87func TestMarkdownColoursListMarkers(t *testing.T) {
88 wantMarkdown(t, "- an item", 0, "-:punctuation")
89 wantMarkdown(t, "* an item", 0, "*:punctuation")
90 wantMarkdown(t, "+ an item", 0, "+:punctuation")
91 wantMarkdown(t, "1. an item", 0, "1.:punctuation")
92 wantMarkdown(t, "12) an item", 0, "12):punctuation")
93}
94
95func TestAListMarkerNeedsASpaceAfterIt(t *testing.T) {
96 // "*emphasis*" starts the same way a bullet does.
97 wantMarkdown(t, "*italic* text", 0, "*italic*:emphasis")
98}
99
100func TestMarkdownColoursABlockQuote(t *testing.T) {
101 wantMarkdown(t, "> quoted **text**", 0, ">:punctuation", "**text**:emphasis")
102}
103
104func TestMarkdownColoursAHorizontalRule(t *testing.T) {
105 for _, rule := range []string{"---", "***", "___", "- - -"} {
106 t.Run(rule, func(t *testing.T) {
107 wantMarkdown(t, rule, 0, rule+":punctuation")
108 })
109 }
110}
111
112func TestAHorizontalRuleIsNotAListItem(t *testing.T) {
113 // "---" and "- item" both start with a dash; the rule is checked first.
114 got := spansOf(t, LanguageMarkdown, "---", 0)
115 if len(got) != 1 || !strings.HasSuffix(got[0], ":punctuation") {
116 t.Errorf("--- gave %v", got)
117 }
118}
119
120func TestMarkdownColoursAFencedBlockAsOneThing(t *testing.T) {
121 src := "before\n```go\nfunc main() {}\n```\nafter"
122
123 wantMarkdown(t, src, 1, "```go:string")
124 wantMarkdown(t, src, 2, "func main() {}:string")
125 wantMarkdown(t, src, 3, "```:string")
126 // The line after the fence is prose again.
127 if got := spansOf(t, LanguageMarkdown, src, 4); len(got) != 0 {
128 t.Errorf("the line after a fence gave %v, want plain prose", got)
129 }
130}
131
132func TestATildeFenceWorksToo(t *testing.T) {
133 src := "~~~\nx = 1\n~~~\n# after"
134
135 wantMarkdown(t, src, 1, "x = 1:string")
136 wantMarkdown(t, src, 3, "# after:heading")
137}
138
139func TestABacktickDoesNotCloseATildeFence(t *testing.T) {
140 src := "~~~\n```\nstill inside\n~~~"
141
142 wantMarkdown(t, src, 2, "still inside:string")
143}
144
145func TestMarkupInsideAFenceIsNotColoured(t *testing.T) {
146 src := "```\n# not a heading\n**not bold**\n```"
147
148 wantMarkdown(t, src, 1, "# not a heading:string")
149 wantMarkdown(t, src, 2, "**not bold**:string")
150}
151
152func TestAnUnclosedFenceColoursToTheEndOfTheFile(t *testing.T) {
153 src := "```\nstill going\nand going"
154
155 wantMarkdown(t, src, 1, "still going:string")
156 wantMarkdown(t, src, 2, "and going:string")
157}
158
159func TestALineOfProseWithBackticksDoesNotOpenAFence(t *testing.T) {
160 src := "use `a` and `b`\n# a heading"
161
162 wantMarkdown(t, src, 1, "# a heading:heading")
163}
164
165func TestMarkdownGivesOneEntryPerLine(t *testing.T) {
166 if got := len(Highlight(LanguageMarkdown, "a\nb\n\nc\n")); got != 5 {
167 t.Errorf("Highlight() returned %d lines for a 5-line document", got)
168 }
169}
170
171func TestMarkdownSpansStayInsideTheirLine(t *testing.T) {
172 src := "# Title\n\nSome **bold** and `code` and [a link](x).\n\n```go\nfunc f() {}\n```\n"
173 assertSpansFit(t, LanguageMarkdown, src)
174}
175
176// assertSpansFit checks that every span sits inside its own line and is not
177// empty, which is what the editor indexes without a bounds check.
178func assertSpansFit(t *testing.T, language Language, src string) {
179 t.Helper()
180
181 lines := strings.Split(src, "\n")
182 for i, spans := range Highlight(language, src) {
183 width := len([]rune(lines[i]))
184 for _, s := range spans {
185 if s.Start < 0 || s.End > width || s.End <= s.Start {
186 t.Errorf("line %d (%d runes) has span %d-%d %v", i, width, s.Start, s.End, s.Class)
187 }
188 }
189 }
190}
191
192func TestMarkdownHandlesAccentsInRuneColumns(t *testing.T) {
193 // A byte offset here would put the spans of this line out of step with
194 // what is drawn.
195 wantMarkdown(t, "évidemment **gras** ici", 0, "**gras**:emphasis")
196}