package highlight import ( "bytes" "strings" "testing" ) func TestDetectLang(t *testing.T) { cases := map[string]string{ "foo.ts": "TypeScript", "foo.tsx": "TypeScript", "foo.js": "JavaScript", "foo.py": "Python", "foo.rs": "Rust", "foo.go": "Go", "foo.c": "C", "foo.cpp": "C++", "foo.h": "C", "foo.yml": "YAML", "foo.toml": "TOML", "foo.json": "JSON", "foo.md": "markdown", "foo.sh": "Bash", "foo.patch": "Diff", "Dockerfile": "Docker", "Makefile": "Makefile", "src/foo/bar.ts": "TypeScript", "infra/Dockerfile": "Docker", "foo.xyz": "", "LICENSE": "", } for name, want := range cases { if got := DetectLang(name); got != want { t.Errorf("DetectLang(%q) = %q, want %q", name, got, want) } } } func TestHasBinaryContent(t *testing.T) { nulAt := func(n, size int) []byte { b := bytes.Repeat([]byte{'A'}, size) b[n] = 0 return b } cases := []struct { name string in []byte want bool }{ {"NUL byte", []byte{0x68, 0x65, 0x00, 0x6c, 0x6f}, true}, {"all NUL", make([]byte, 10), true}, {"plain text", []byte("hello world\n"), false}, {"CRLF text", []byte("line1\r\nline2\r\n"), false}, // Git treats BEL and ESC as text. {"other control chars", []byte{0x01, 0x07, 0x1b, 0x41}, false}, {"NUL past the sample window", nulAt(8000, 8001), false}, {"NUL inside the sample window", nulAt(7999, 8001), true}, } for _, c := range cases { if got := HasBinaryContent(c.in); got != c.want { t.Errorf("%s: got %v, want %v", c.name, got, c.want) } } } // makeDiff builds a minimal unified diff that adds the given lines. func makeDiff(lines ...string) string { out := []string{ "diff --git a/test.txt b/test.txt", "index 0000000..1111111 100644", "--- a/test.txt", "+++ b/test.txt", "@@ -0,0 +1 @@", } for _, l := range lines { out = append(out, "+"+l) } return strings.Join(out, "\n") } func firstRowHTML(t *testing.T, h *Highlighter, diff string) string { t.Helper() files := parseDiff(diff, nil) if len(files) != 1 { t.Fatalf("parsed %d files, want 1", len(files)) } return h.highlightFile(files[0]).Hunks[0].Rows[0].HTML } func TestDiffControlCharacters(t *testing.T) { h := New(512 << 10) cases := []struct { name string line string want string absent bool }{ {name: "CR renders as ^M", line: "hello\rworld", want: `^M`}, {name: "BEL renders as ^G", line: "ring\x07bell", want: `^G`}, {name: "ESC renders as ^[", line: "\x1b[31mred\x1b[0m", want: `^[`}, {name: "TAB is not a control span", line: "\thello", want: "diff-ctrl", absent: true}, {name: "normal text has no control spans", line: "hello world", want: "diff-ctrl", absent: true}, } for _, c := range cases { got := firstRowHTML(t, h, makeDiff(c.line)) if strings.Contains(got, c.want) == c.absent { t.Errorf("%s: html %q, contains %q = %v", c.name, got, c.want, !c.absent) } } } func TestDiffTrailingCROnEveryLine(t *testing.T) { h := New(512 << 10) files := parseDiff(makeDiff("alpha\r", "beta\r", "gamma\r"), nil) rows := h.highlightFile(files[0]).Hunks[0].Rows if len(rows) != 3 { t.Fatalf("got %d rows, want 3", len(rows)) } for i, row := range rows { if !strings.Contains(row.HTML, `^M`) { t.Errorf("row %d has no ^M: %q", i, row.HTML) } } } func TestDiffHighlightsKnownLanguage(t *testing.T) { h := New(512 << 10) diff := strings.Join([]string{ "diff --git a/x.go b/x.go", "index 111..222 100644", "--- a/x.go", "+++ b/x.go", "@@ -1,2 +1,2 @@", " package main", "-const a = 1", "+const a = 2", }, "\n") files := parseDiff(diff, nil) f := h.highlightFile(files[0]) if f.Added != 1 || f.Removed != 1 { t.Errorf("added=%d removed=%d, want 1/1", f.Added, f.Removed) } var all strings.Builder for _, hunk := range f.Hunks { for _, row := range hunk.Rows { all.WriteString(row.HTML) } } if !strings.Contains(all.String(), `2`) { t.Errorf("missing line-number markup: %q", view.HTML) } view = h.ServeFile([]byte("just some words\n"), "notes.xyz", "") if view.Type != "inline" || !strings.Contains(view.HTML, "just some words") { t.Errorf("unknown extension: %+v", view) } if strings.Contains(view.HTML, "ch-") { t.Errorf("unknown extension should not be highlighted: %q", view.HTML) } if view := h.ServeFile([]byte("a\x00b"), "blob.bin", ""); view.Type != "binary" { t.Errorf("type = %q, want binary", view.Type) } big := bytes.Repeat([]byte("x"), 100) if view := h.ServeFile(big, "big.txt", ""); view.Type != "download" || view.Size != 100 { t.Errorf("got %+v, want download/100", view) } } func TestServeFileEscapesHTML(t *testing.T) { h := New(512 << 10) view := h.ServeFile([]byte("\n"), "notes.xyz", "") if strings.Contains(view.HTML, "