// ingestion/internal/textutil/truncate_test.go package textutil_test import ( "strings" "testing" "unicode/utf8" "github.com/mathiasbq/hyperguild/ingestion/internal/textutil" ) func TestTruncate_ShortStringsAreUntouched(t *testing.T) { for _, s := range []string{"", "kort", "räv", "a — b"} { if got := textutil.Truncate(s, 100); got != s { t.Errorf("Truncate(%q, 100) = %q, want it unchanged", s, got) } } } func TestTruncate_NeverCutsInsideACharacter(t *testing.T) { // The property, not an example: whatever the limit, the result must be // valid UTF-8. Byte-slicing Swedish text (å ä ö) or an em dash produces // an orphaned lead byte, which is what corrupted wiki/index.md (#93). s := strings.Repeat("räv — ål — ökäd ", 20) for limit := 0; limit <= len(s)+5; limit++ { got := textutil.Truncate(s, limit) if !utf8.ValidString(got) { t.Fatalf("Truncate(s, %d) produced invalid UTF-8: %q", limit, got) } } } func TestTruncate_CountsCharactersNotBytes(t *testing.T) { // "räv" is 4 bytes, 3 runes. A byte-based limit of 3 would cut inside the // "ä"; a rune-based one returns it whole. if got := textutil.Truncate("räv", 3); got != "räv" { t.Errorf("Truncate(\"räv\", 3) = %q, want the whole word", got) } got := textutil.Truncate("räven springer", 5) if utf8.RuneCountInString(got) != 6 { // 5 runes + the ellipsis t.Errorf("Truncate(...) = %q, want 5 runes plus the marker", got) } if !strings.HasSuffix(got, "…") { t.Errorf("Truncate(...) = %q, want it marked as truncated", got) } } func TestTruncate_MarksOnlyWhenSomethingWasDropped(t *testing.T) { // An ellipsis on text that was not cut is a lie about the content. if got := textutil.Truncate("räv", 10); strings.HasSuffix(got, "…") { t.Errorf("Truncate(%q, 10) = %q, want no truncation marker", "räv", got) } if got := textutil.Truncate("räv", 3); strings.HasSuffix(got, "…") { t.Errorf("exact-length input must not be marked: %q", got) } } func TestTruncate_NonPositiveLimitYieldsNothingRatherThanPanicking(t *testing.T) { for _, limit := range []int{0, -1, -100} { if got := textutil.Truncate("räven", limit); got != "" { t.Errorf("Truncate(_, %d) = %q, want empty", limit, got) } } }