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
|
// SPDX-License-Identifier: GPL-3.0-or-later
package model
import "testing"
// textOf is the stretch a span covers, by character offset.
func textOf(text string, s Span) string {
return string([]rune(text)[s.From:s.To])
}
// find is the first span of a kind whose text matches, for the assertions
// below.
func find(t *testing.T, text string, kind SpanKind, want string) {
t.Helper()
for _, s := range Spans(text) {
if s.Kind == kind && textOf(text, s) == want {
return
}
}
t.Errorf("no %v span %q in %q", kind, want, text)
}
// TestSpansPaintsTheParts: heads, actions, strings and comments each come
// back as their own kind.
func TestSpansPaintsTheParts(t *testing.T) {
text := "(rule \"invoices\" ; the ones from suppliers\n (when (type pdf))\n (move \"Docs\"))\n"
find(t, text, SpanHead, "rule")
find(t, text, SpanHead, "when")
find(t, text, SpanHead, "type")
find(t, text, SpanAction, "move")
find(t, text, SpanString, `"invoices"`)
find(t, text, SpanString, `"Docs"`)
find(t, text, SpanComment, "; the ones from suppliers")
}
// TestSpansKeepsQuotingStraight: a semicolon inside a string is not a
// comment, a quote inside a comment does not start a string, and an escaped
// quote does not end one.
func TestSpansKeepsQuotingStraight(t *testing.T) {
text := `(rule "a;b" (when (name "say \"hi\"")) (move "Out"))` + "\n;; \"not a string\"\n"
find(t, text, SpanString, `"a;b"`)
find(t, text, SpanString, `"say \"hi\""`)
find(t, text, SpanComment, `;; "not a string"`)
for _, s := range Spans(text) {
if s.Kind == SpanComment && textOf(text, s) == ";b\" (when (name \"say \\\"hi\\\"\")) (move \"Out\"))" {
t.Error("a semicolon inside a string started a comment")
}
}
}
// TestSpansOnlyHeadsAreHeads: an argument that happens to be a symbol is
// not painted as a head.
func TestSpansOnlyHeadsAreHeads(t *testing.T) {
text := "(when (type pdf doc))\n"
for _, s := range Spans(text) {
if s.Kind == SpanHead && (textOf(text, s) == "pdf" || textOf(text, s) == "doc") {
t.Errorf("argument %q painted as a head", textOf(text, s))
}
}
find(t, text, SpanHead, "type")
}
// TestSpansCountsCharactersNotBytes: offsets are what a text buffer counts,
// so a file with Polish letters in it still colours the right stretch.
func TestSpansCountsCharactersNotBytes(t *testing.T) {
text := "(rule \"spółka\" (move \"Księgowość\"))\n"
find(t, text, SpanString, `"spółka"`)
find(t, text, SpanString, `"Księgowość"`)
find(t, text, SpanAction, "move")
}
// TestSpansSurvivesUnclosedForms: text being typed is not yet valid, and
// the colouring must not run off the end of it.
func TestSpansSurvivesUnclosedForms(t *testing.T) {
for _, text := range []string{`(rule "half`, "(when (name ", ";; just a comment", `"`} {
for _, s := range Spans(text) {
if s.From < 0 || s.To > len([]rune(text)) || s.From > s.To {
t.Errorf("%q: span %+v is outside the text", text, s)
}
}
}
}
|