Files
mathias 23fa5427b7 feat(web): previewText truncation helper for summary cards
One-line lede for list cards: collapses whitespace, prefers the first
sentence within budget, else word-boundary truncation with an ellipsis.
Pure and rune-based (multibyte-safe). Table-driven tests cover empty,
short, first-sentence, word-boundary, and multibyte cases.
2026-06-03 15:10:50 +02:00

105 lines
2.4 KiB
Go

package web
import "testing"
func TestPreviewText(t *testing.T) {
t.Parallel()
tests := []struct {
name string
in string
max int
want string
}{
{
name: "empty input",
in: "",
max: 160,
want: "",
},
{
name: "whitespace-only input",
in: " \n\t ",
max: 160,
want: "",
},
{
name: "short string unchanged",
in: "A tidy little summary",
max: 160,
want: "A tidy little summary",
},
{
name: "short single sentence unchanged",
in: "Hello there.",
max: 160,
want: "Hello there.",
},
{
name: "first sentence taken when more follows",
in: "First sentence. Second sentence that we drop.",
max: 160,
want: "First sentence.",
},
{
name: "first sentence with question mark",
in: "What is this? It is a preview.",
max: 160,
want: "What is this?",
},
{
name: "long string truncated on word boundary with ellipsis",
// 5 ten-char words past the limit; max cuts mid "ones".
in: "alpha bravo charlie delta echo foxtrot golf hotel india juliet",
max: 30,
// runes[:30] = "alpha bravo charlie delta echo"; ends exactly on a
// word so the next char would be a space — backs off to last space.
want: "alpha bravo charlie delta…",
},
{
name: "no mid-word cut",
in: "internationalization frameworks everywhere today",
max: 25,
// runes[:25] = "internationalization fram" — back off to the space
// after the first word; never emit a partial word.
want: "internationalization…",
},
{
name: "multibyte safe truncation",
// Accented + emoji runes; cutting on rune indices must not split a
// multibyte sequence.
in: "café déjà vû señor naïve résumé piñata fiancé",
max: 20,
want: "café déjà vû señor…",
},
{
name: "multibyte short unchanged",
in: "café señor",
max: 160,
want: "café señor",
},
{
name: "collapses internal whitespace",
in: "line one\n\n line two\tline three",
max: 160,
want: "line one line two line three",
},
{
name: "sentence beyond max falls back to char truncation",
in: "alpha bravo charlie delta echo foxtrot golf. short.",
max: 20,
want: "alpha bravo charlie…",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := previewText(tt.in, tt.max)
if got != tt.want {
t.Errorf("previewText(%q, %d) = %q, want %q", tt.in, tt.max, got, tt.want)
}
})
}
}