// 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) } } } }