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.
105 lines
2.4 KiB
Go
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)
|
|
}
|
|
})
|
|
}
|
|
}
|