package main
import (
"reflect"
"strings"
"testing"
)
func scopesOf(name, code string) []scope {
return findScopes(splitLines(tokenise(codeLexer(name), code)), langName(codeLexer(name)) == "python")
}
// A block that spans lines is a scope, and brackets or a string on one
// line around something are a leaf; brackets inside a string or comment,
// or around nothing, are neither. A box reaches past each box inside it, and a call
// whose argument is a function draws one box.
func TestScopesGo(t *testing.T) {
src := "func f() {\n" + // 0
"\tif x { y() }\n" + // 1
"\tfor {\n" + // 2
"\t\ts := \"{\" // }\n" + // 3
"\t\tgo(func() {\n" + // 4
"\t\t}())\n" + // 5
"\t}\n" + // 6
"}\n" // 7
got := scopesOf("a.go", src)
want := []scope{
{first: 0, last: 7, left: 0, right: 30, depth: 1},
{first: 1, last: 1, left: 13, right: 20, depth: 2, leaf: 1},
{first: 2, last: 6, left: 8, right: 29, depth: 2},
{first: 3, last: 3, left: 21, right: 24, depth: 3, leaf: 1},
{first: 4, last: 5, left: 16, right: 27, depth: 3},
}
if !reflect.DeepEqual(got, want) {
t.Errorf("scopes\n%+v, want\n%+v", got, want)
}
}
// In Python a line ending with a colon opens the lines indented under it,
// blank lines included, up to the last of them.
func TestScopesPython(t *testing.T) {
src := "class A:\n" + // 0
" def f(self):\n" + // 1
" x = {'a': 1}\n" + // 2
"\n" + // 3
" return x\n" + // 4
"y = 1\n" // 5
var got []scope
for _, s := range scopesOf("a.py", src) {
if s.leaf == 0 {
got = append(got, s)
}
}
if len(got) != 2 || got[0].first != 0 || got[0].last != 4 || got[1].first != 1 || got[1].last != 4 || got[1].left != 4 {
t.Errorf("scopes %+v, want class 0-4 and def 1-4 at 4", got)
}
}
// Leaves nest within a line, each a level below the one around it, and
// sit below the blocks around their row.
func TestScopeLeaves(t *testing.T) {
got := scopesOf("a.go", "x := f(g(a), [2]int{1, 2})\n")
want := []scope{
{first: 0, last: 0, left: 6, right: 26, depth: 1, leaf: 1},
{first: 0, last: 0, left: 8, right: 11, depth: 2, leaf: 2},
{first: 0, last: 0, left: 13, right: 16, depth: 2, leaf: 2},
{first: 0, last: 0, left: 19, right: 25, depth: 2, leaf: 2},
}
if !reflect.DeepEqual(got, want) {
t.Errorf("leaves\n%+v, want\n%+v", got, want)
}
}
// A string is a leaf with its quotes and escapes, inside the brackets
// around it; an empty one is not, and one that spans lines is a block.
func TestScopeStrings(t *testing.T) {
got := scopesOf("a.go", "f(\"a\\n\", \"\")\nx := `one\ntwo`\n")
want := []scope{
{first: 0, last: 0, left: 1, right: 12, depth: 1, leaf: 1},
{first: 0, last: 0, left: 2, right: 7, depth: 2, leaf: 2},
{first: 1, last: 2, left: 0, right: 9, depth: 1},
}
if !reflect.DeepEqual(got, want) {
t.Errorf("strings\n%+v, want\n%+v", got, want)
}
}
// A box starts at the least indent among its rows, so a raw string's
// lines at the margin stay inside it and the boxes around it.
func TestScopeLeftmostRow(t *testing.T) {
src := "func f() {\n" + // 0
"\tx := `a\n" + // 1
"b`\n" + // 2
"}\n" // 3
got := scopesOf("a.go", src)
want := []scope{
{first: 0, last: 3, left: 0, right: 16, depth: 1},
{first: 1, last: 2, left: 0, right: 15, depth: 2},
}
if !reflect.DeepEqual(got, want) {
t.Errorf("scopes\n%+v, want\n%+v", got, want)
}
}
// A code page draws each scope as an empty box in its first line, after
// the line number; a flat page and fences draw none.
func TestScopeBoxes(t *testing.T) {
src := "func f() {\n\treturn\n}\n"
var b strings.Builder
highlight(&b, codeLexer("a.go"), src, true, true)
out := b.String()
if !strings.Contains(out, `<pre class="sourceCode numberSource go numberLines scopes">`) ||
!strings.Contains(out, `<span id="L1"><a href="#L1"></a><i class="scope shade" style="--c:0;--w:14;--r:3;--d:1"></i><span class="kw">func</span>`) {
t.Errorf("no box in the first line: %s", out)
}
b.Reset()
highlight(&b, codeLexer("a.go"), src, true, false)
if strings.Contains(b.String(), "scope") {
t.Errorf("flat page has scopes: %s", b.String())
}
}