aboutsummaryrefslogtreecommitdiff
path: root/gui/internal/model/highlight_test.go
blob: f5c1e28f2e393657de531ae190b184f78f11c5f8 (plain) (blame)
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)
			}
		}
	}
}