Download
122 lines 3.4 KiBGoModified 2026-10-20 raw
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
package render

import (
	"strings"
	"testing"

	"code.bruce-hill.com/repo-renderer/internal/highlight"
)

func TestCountWords(t *testing.T) {
	for in, want := range map[string]int{
		"":                         0,
		"one two\nthree":           3,
		"# Title\n\n- item | x |":  3,
		"```go\nx := 1\n```":       3,
		"café naïve — 2026":        3,
		"hard\nwrapped\nparagraph": 3,
	} {
		if got := countWords([]byte(in)); got != want {
			t.Errorf("countWords(%q) = %d, want %d", in, got, want)
		}
	}
}

func TestPagesText(t *testing.T) {
	for words, want := range map[int]string{
		0:      "0 words ≈ 1 page",
		300:    "300 words ≈ 1 page",
		500:    "500 words ≈ 2 pages",
		2500:   "2.5K words ≈ 8 pages",
		300000: "300K words ≈ 1K pages",
	} {
		if got := pagesText(words); got != want {
			t.Errorf("pagesText(%d) = %q, want %q", words, got, want)
		}
	}
}

func TestBinaryFormat(t *testing.T) {
	for in, want := range map[string]string{
		"img/logo.png": "PNG",
		"a/b.JPG":      "JPEG",
		"photo.jpeg":   "JPEG",
		"scan.tif":     "TIFF",
		"Makefile":     "",
		"dir.d/blob":   "",
	} {
		if got := binaryFormat(in); got != want {
			t.Errorf("binaryFormat(%q) = %q, want %q", in, got, want)
		}
	}
}

func TestTypeSlug(t *testing.T) {
	for in, want := range map[string]string{
		"C":           "c",
		"C++":         "cpp",
		"C#":          "csharp",
		"Objective-C": "objective-c",
		"Org Mode":    "org-mode",
		"Other":       "other",
		"--":          "other",
	} {
		if got := typeSlug(in); got != want {
			t.Errorf("typeSlug(%q) = %q, want %q", in, got, want)
		}
	}
}

// TestFoldStatsOneOther: unnamed types and the folded tail share a single
// "Other", last, however large the unnamed share is.
func TestFoldStatsOneOther(t *testing.T) {
	got := foldStats(map[string]int{"": 50, "A": 10, "B": 5, "C": 1}, 2)
	want := []LangStat{{Name: "A", Count: 10}, {Name: "B", Count: 5}, {Name: "Other", Count: 51}}
	if len(got) != len(want) {
		t.Fatalf("foldStats = %+v, want %+v", got, want)
	}
	for i := range want {
		if got[i].Name != want[i].Name || got[i].Count != want[i].Count {
			t.Fatalf("foldStats = %+v, want %+v", got, want)
		}
	}
	if got := foldStats(map[string]int{"": 5, "A": 10}, 8); len(got) != 2 || got[1].Name != "Other" {
		t.Errorf("unfolded: %+v", got)
	}
}

// TestCountLines: listings count lines as wc -l does, by newlines, and
// binary data not at all.
func TestCountLines(t *testing.T) {
	for in, want := range map[string]int{
		"":           0,
		"one":        0,
		"one\n":      1,
		"one\ntwo":   1,
		"a\n\nb\n":   3,
		"\x00\x01\n": -1,
	} {
		if got := countLines([]byte(in)); got != want {
			t.Errorf("countLines(%q) = %d, want %d", in, got, want)
		}
	}
}

// TestLineCounter: streaming a blob through lineCounter, in any chunks,
// counts as countLines does, a NUL past the binary probe included.
func TestLineCounter(t *testing.T) {
	late := strings.Repeat("x\n", highlight.BinaryProbe) + "\x00\n"
	for _, in := range []string{"", "one", "a\nb\n", "\x00\x01\n", strings.Repeat("x", highlight.BinaryProbe-1) + "\x00", late} {
		for _, chunk := range []int{1, 7, 4096, len(in) + 1} {
			var lc lineCounter
			for i := 0; i < len(in); i += chunk {
				if _, err := lc.Write([]byte(in[i:min(i+chunk, len(in))])); err != nil {
					t.Fatal(err)
				}
			}
			if got, want := lc.count(), countLines([]byte(in)); got != want {
				t.Errorf("%d bytes in chunks of %d: count %d, want %d", len(in), chunk, got, want)
			}
		}
	}
}