turbo-editors/turbo-corepublic Fork 0
v1.0.0
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.

xml_test.go · 222 lines · 6.8 KBGo Blame HistoryRaw
🛟 Updated. 28d5985 k33g 18h ago1package syntax
2
3import (
4 "strings"
5 "testing"
6)
7
8// xmlClassOf returns the class covering the first occurrence of a piece of text
9// in an XML document.
10func xmlClassOf(t *testing.T, src, want string) Class {
11 t.Helper()
12
13 index := strings.Index(src, want)
14 if index < 0 {
15 t.Fatalf("%q does not appear in the source", want)
16 }
17 line := strings.Count(src[:index], "\n")
18 col := index - (strings.LastIndex(src[:index], "\n") + 1)
19
20 class, ok := classAt(Highlight(LanguageXML, src), line, col)
21 if !ok {
22 t.Fatalf("no span covers %q at line %d column %d", want, line, col)
23 }
24 return class
25}
26
27func TestXMLColoursItsParts(t *testing.T) {
28 const src = `<?xml version="1.0" encoding="UTF-8"?>
29<!DOCTYPE note SYSTEM "note.dtd">
30<!-- a comment -->
31<xsl:template match="/" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
32 <to>Tove</to>
33 &amp;
34</xsl:template>
35`
36
37 tests := map[string]Class{
38 "<?xml": ClassKeyword,
39 "version": ClassAttribute,
40 `"1.0"`: ClassString,
41 "<!DOCTYPE": ClassKeyword,
42 "<!-- a comment -->": ClassComment,
43 "<xsl:template": ClassTag,
44 "match": ClassAttribute,
45 "&amp;": ClassConstant,
46 }
47
48 for text, want := range tests {
49 t.Run(text, func(t *testing.T) {
50 if got := xmlClassOf(t, src, text); got != want {
51 t.Errorf("%q is %v, want %v", text, got, want)
52 }
53 })
54 }
55}
56
57func TestANamespacedNameIsOneSpan(t *testing.T) {
58 // Splitting the prefix from the local name would be two colours for one
59 // name, in the documents where namespaces are used most.
60 const name = "<xsl:template"
61 src := name + ">\n</xsl:template>\n"
62 spans := Highlight(LanguageXML, src)
63
64 for col := 0; col < len(name); col++ {
65 if class, ok := classAt(spans, 0, col); !ok || class != ClassTag {
66 t.Fatalf("column %d of %q is %v (covered: %v)", col, name, class, ok)
67 }
68 }
69}
70
71func TestTheEqualsBetweenAnAttributeAndItsValueIsAnOperator(t *testing.T) {
72 const src = `<a href="x"/>`
73
74 if got := xmlClassOf(t, src, "="); got != ClassOperator {
75 t.Errorf("the = is %v, want operator", got)
76 }
77}
78
79func TestASelfClosingTagIsClosed(t *testing.T) {
80 const src = `<br/>`
81 spans := Highlight(LanguageXML, src)
82
83 if class, ok := classAt(spans, 0, 3); !ok || class != ClassTag {
84 t.Errorf("the /> is %v (covered: %v), want tag", class, ok)
85 }
86}
87
88func TestCDATAContentsAreNotColouredAsMarkup(t *testing.T) {
89 // The reason XML has a scanner of its own. Everything between the
90 // delimiters is text, and a `<not>` inside one is not a tag.
91 const src = `<a><![CDATA[ <not> & markup ]]></a>`
92 spans := Highlight(LanguageXML, src)
93
94 start := strings.Index(src, "<![CDATA[")
95 end := strings.Index(src, "]]>") + len("]]>")
96 for col := start; col < end; col++ {
97 class, ok := classAt(spans, 0, col)
98 if !ok || class != ClassString {
99 t.Fatalf("column %d of the CDATA section is %v (covered: %v), want string", col-start, class, ok)
100 }
101 }
102 // And the document carries on as markup afterwards.
103 if class, ok := classAt(spans, 0, end); !ok || class != ClassTag {
104 t.Errorf("the tag after the CDATA is %v (covered: %v), want tag", class, ok)
105 }
106}
107
108func TestCDATACarriesAcrossLines(t *testing.T) {
109 const src = "<a><![CDATA[\n <not> markup\n]]></a>\n"
110 spans := Highlight(LanguageXML, src)
111
112 if class, ok := classAt(spans, 1, 2); !ok || class != ClassString {
113 t.Errorf("the second line of the CDATA is %v (covered: %v), want string", class, ok)
114 }
115 if class, ok := classAt(spans, 2, 3); !ok || class != ClassTag {
116 t.Errorf("the tag after the CDATA is %v (covered: %v), want tag", class, ok)
117 }
118}
119
120func TestACommentCarriesAcrossLines(t *testing.T) {
121 const src = "<!-- one\ntwo\n--><a/>\n"
122 spans := Highlight(LanguageXML, src)
123
124 if class, ok := classAt(spans, 1, 0); !ok || class != ClassComment {
125 t.Errorf("the second line of the comment is %v (covered: %v), want comment", class, ok)
126 }
127 if class, ok := classAt(spans, 2, 3); !ok || class != ClassTag {
128 t.Errorf("the tag after the comment is %v (covered: %v), want tag", class, ok)
129 }
130}
131
132func TestACommentAndACDATAAreClosedByDifferentDelimiters(t *testing.T) {
133 // A single carried flag would close whichever came first on either, so a
134 // `-->` inside a CDATA section would end it.
135 const src = "<a><![CDATA[\n a --> b\n]]></a>\n"
136 spans := Highlight(LanguageXML, src)
137
138 if class, ok := classAt(spans, 1, 2); !ok || class != ClassString {
139 t.Errorf("a --> inside CDATA ended it: line 1 is %v (covered: %v)", class, ok)
140 }
141}
142
143func TestABareAmpersandIsLeftAloneInXML(t *testing.T) {
144 // It is legal text in plenty of documents, and swallowing the rest of the
145 // line looking for a semicolon would be worse than missing an entity.
146 const src = `<a>Tom & Jerry</a>`
147 spans := Highlight(LanguageXML, src)
148
149 if class, ok := classAt(spans, 0, strings.Index(src, "&")); ok && class == ClassConstant {
150 t.Error("a bare & was coloured as an entity")
151 }
152 if class, ok := classAt(spans, 0, strings.Index(src, "</a>")); !ok || class != ClassTag {
153 t.Errorf("the closing tag is %v (covered: %v); the & swallowed it", class, ok)
154 }
155}
156
157func TestANumericEntityIsAConstant(t *testing.T) {
158 if got := xmlClassOf(t, "<a>&#169;</a>", "&#169;"); got != ClassConstant {
159 t.Errorf("a numeric entity is %v, want constant", got)
160 }
161}
162
163func TestXMLIsRecognisedByItsManyExtensions(t *testing.T) {
164 for _, name := range []string{
165 "pom.xml", "icon.svg", "schema.xsd", "sheet.xsl", "sheet.xslt",
166 "Info.plist", "app.csproj",
167 } {
168 t.Run(name, func(t *testing.T) {
169 if got := LanguageOf(name, ""); got != LanguageXML {
170 t.Errorf("LanguageOf(%q) = %q, want xml", name, got)
171 }
172 })
173 }
174}
175
176func TestHTMLIsStillHTML(t *testing.T) {
177 // Adding XML must not take the .html extension with it.
178 for _, name := range []string{"index.html", "page.htm"} {
179 if got := LanguageOf(name, ""); got != LanguageHTML {
180 t.Errorf("LanguageOf(%q) = %q, want html", name, got)
181 }
182 }
183}
184
185func TestXMLReturnsOneEntryPerLine(t *testing.T) {
186 tests := map[string]int{
187 "": 1,
188 "<a/>": 1,
189 "<a/>\n": 2,
190 "<a>\n <b/>\n</a>\n": 4,
191 }
192
193 for src, want := range tests {
194 if got := len(Highlight(LanguageXML, src)); got != want {
195 t.Errorf("Highlight(%q) returned %d lines, want %d", src, got, want)
196 }
197 }
198}
199
200func TestBrokenXMLIsStillColoured(t *testing.T) {
201 for _, src := range []string{"<a", "<!--", "<![CDATA[", "<?xml", "&", "</"} {
202 t.Run(src, func(t *testing.T) {
203 if got := len(Highlight(LanguageXML, src)); got != 1 {
204 t.Errorf("Highlight(%q) returned %d lines, want 1", src, got)
205 }
206 })
207 }
208}
209
210func TestXMLSpansAreOrderedAndDoNotOverlap(t *testing.T) {
211 const src = "<?xml version=\"1.0\"?>\n<!-- c -->\n<ns:a b=\"1\">&amp;<![CDATA[x]]></ns:a>\n"
212
213 for line, onLine := range Highlight(LanguageXML, src) {
214 previousEnd := 0
215 for _, span := range onLine {
216 if span.Start < previousEnd {
217 t.Errorf("line %d: span %+v starts before the previous one ended at %d", line, span, previousEnd)
218 }
219 previousEnd = span.End
220 }
221 }
222}