package editor import ( "testing" "glint/internal/theme" "github.com/charmbracelet/lipgloss" ) // styleOf returns the foreground color of the span covering the first occurrence // of sub within the line's spans, or "" if not found. func styleOf(spans []Span, sub string) lipgloss.Color { for _, sp := range spans { if sp.Text == sub { return lipgloss.Color(sp.Style.GetForeground().(lipgloss.Color)) } } return lipgloss.Color("") } func TestCodeScannerColorsStrings(t *testing.T) { th := theme.FlexokiDark() out := ScanCode([]string{`x := "hello"`}, "main.go", th) if got := styleOf(out[0], `"hello"`); got != th.Code { t.Errorf("string color = %q, want Code %q (spans: %+v)", got, th.Code, out[0]) } } func TestCodeScannerColorsComments(t *testing.T) { th := theme.FlexokiDark() out := ScanCode([]string{"// a note"}, "main.go", th) // The whole comment line should be Comment-colored. for _, sp := range out[0] { if sp.Style.GetForeground() != th.Comment { t.Errorf("comment span %q color = %v, want Comment %v", sp.Text, sp.Style.GetForeground(), th.Comment) } } } func TestCodeScannerColorsNumbers(t *testing.T) { th := theme.FlexokiDark() out := ScanCode([]string{"x := 42"}, "main.go", th) if got := styleOf(out[0], "42"); got != th.Accent { t.Errorf("number color = %q, want Accent %q (spans: %+v)", got, th.Accent, out[0]) } } func TestCodeScannerIdentifiersStayBase(t *testing.T) { th := theme.FlexokiDark() out := ScanCode([]string{"foo := bar"}, "main.go", th) if got := styleOf(out[0], "foo"); got != th.Text { t.Errorf("identifier color = %q, want base Text %q", got, th.Text) } } func TestCodeScannerInvariant(t *testing.T) { th := theme.FlexokiDark() lines := []string{ `package main`, ``, `func main() {`, ` x := "hi" // greet`, ` _ = x + 1`, `}`, } out := ScanCode(lines, "main.go", th) if len(out) != len(lines) { t.Fatalf("got %d lines of spans, want %d", len(out), len(lines)) } for i, raw := range lines { if got := spanText(out[i]); got != raw { t.Errorf("line %d: span text %q != raw %q", i, got, raw) } } } func TestCodeScannerMultilineComment(t *testing.T) { th := theme.FlexokiDark() lines := []string{"/* first", "second */"} out := ScanCode(lines, "main.go", th) for i := range lines { if len(out[i]) == 0 { t.Fatalf("line %d empty", i) } for _, sp := range out[i] { if sp.Style.GetForeground() != th.Comment { t.Errorf("line %d span %q color = %v, want Comment", i, sp.Text, sp.Style.GetForeground()) } } } } func TestBuildVisualUsesCodeScannerForCode(t *testing.T) { e := New() e.SetContent([]byte(`x := "hi"`)) e.SetLanguage("main.go") var found bool for _, vr := range e.buildVisual() { if c := styleOf(vr.spans, `"hi"`); c == e.theme.Code { found = true } } if !found { t.Error("code file: string not colored via buildVisual") } } func TestBuildVisualKeepsMarkdownForMd(t *testing.T) { e := New() e.SetContent([]byte("# Title")) e.SetLanguage("notes.md") var found bool for _, vr := range e.buildVisual() { if c := styleOf(vr.spans, "Title"); c == e.theme.Heading { found = true } } if !found { t.Error("markdown file: heading not colored via buildVisual (wrong scanner?)") } } func TestSetLanguageRoutesScanner(t *testing.T) { e := New() e.SetLanguage("main.go") if e.codeFile == "" { t.Error("SetLanguage(main.go) should enable the code scanner") } e.SetLanguage("notes.md") if e.codeFile != "" { t.Errorf("SetLanguage(notes.md) should keep markdown scanner, got codeFile=%q", e.codeFile) } e.SetLanguage("plain.txt") if e.codeFile != "" { t.Errorf("SetLanguage(plain.txt) should keep markdown scanner, got codeFile=%q", e.codeFile) } e.SetLanguage("README") if e.codeFile != "" { t.Errorf("SetLanguage(README) (no ext) should keep markdown scanner, got codeFile=%q", e.codeFile) } }