feat(ui): export TruncateText helper
Wrap the existing truncateText logic used by toast rendering so later tab-title work can reuse the same cell-width clipping.
- commit
- b40aa09e134e11bbb40c1fcdff3b09d3709a3648
- author
- brice · · 2026-07-11 07:55 UTC
- parent
- d0ab56b
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +27 -0
M internal/usecase/ui/toast.go +3 -0
| @@ -223,6 +223,9 @@ func drawToast(frame renderer.Frame, toast Toast, styles ToastStyles) { | |||
| 223 | 223 | DrawText(frame, textLeft, textY, textRight, truncateText(toast.Message, textRight-textLeft), styles.Text) | |
| 224 | 224 | } | |
| 225 | 225 | ||
| 226 | + | // TruncateText clips text to maxWidth cells, appending an ellipsis when cut. | |
| 227 | + | func TruncateText(text string, maxWidth int) string { return truncateText(text, maxWidth) } | |
| 228 | + | ||
| 226 | 229 | func truncateText(text string, maxWidth int) string { | |
| 227 | 230 | if maxWidth <= 0 { | |
| 228 | 231 | return "" | |
| @@ -223,6 +223,9 @@ func drawToast(frame renderer.Frame, toast Toast, styles ToastStyles) { | |||||
| 223 | DrawText(frame, textLeft, textY, textRight, truncateText(toast.Message, textRight-textLeft), styles.Text) | 223 | DrawText(frame, textLeft, textY, textRight, truncateText(toast.Message, textRight-textLeft), styles.Text) | ||
| 224 | } | 224 | } | ||
| 225 | 225 | ||||
| 226 | + | // TruncateText clips text to maxWidth cells, appending an ellipsis when cut. | |||
| 227 | + | func TruncateText(text string, maxWidth int) string { return truncateText(text, maxWidth) } | |||
| 228 | + | ||||
| 226 | func truncateText(text string, maxWidth int) string { | 229 | func truncateText(text string, maxWidth int) string { | ||
| 227 | if maxWidth <= 0 { | 230 | if maxWidth <= 0 { | ||
| 228 | return "" | 231 | return "" | ||
M internal/usecase/ui/toast_test.go +24 -0
| @@ -81,6 +81,30 @@ func TestToastManagerDurationDismissAndClear(t *testing.T) { | |||
| 81 | 81 | }) | |
| 82 | 82 | } | |
| 83 | 83 | ||
| 84 | + | func TestTruncateText(t *testing.T) { | |
| 85 | + | tests := []struct { | |
| 86 | + | name string | |
| 87 | + | text string | |
| 88 | + | maxWidth int | |
| 89 | + | want string | |
| 90 | + | }{ | |
| 91 | + | {"no truncation needed", "hello", 10, "hello"}, | |
| 92 | + | {"truncation with ellipsis", "hello world", 8, "hello w…"}, | |
| 93 | + | {"maxWidth zero", "hello", 0, ""}, | |
| 94 | + | // The ellipsis itself is one cell wide, so any non-positive maxWidth is | |
| 95 | + | // also "smaller than the ellipsis" and collapses to the same empty result. | |
| 96 | + | {"maxWidth smaller than ellipsis", "hello world", -1, ""}, | |
| 97 | + | {"wide runes", "日本語のテキスト", 5, "日本…"}, | |
| 98 | + | } | |
| 99 | + | for _, tt := range tests { | |
| 100 | + | t.Run(tt.name, func(t *testing.T) { | |
| 101 | + | if got := TruncateText(tt.text, tt.maxWidth); got != tt.want { | |
| 102 | + | t.Fatalf("TruncateText(%q, %d) = %q, want %q", tt.text, tt.maxWidth, got, tt.want) | |
| 103 | + | } | |
| 104 | + | }) | |
| 105 | + | } | |
| 106 | + | } | |
| 107 | + | ||
| 84 | 108 | func TestToastBoundsAnchorsMarginsAndTinyFrames(t *testing.T) { | |
| 85 | 109 | message := "Reconnection attempts…" | |
| 86 | 110 | width := textWidth(message) + 4 | |
| @@ -81,6 +81,30 @@ func TestToastManagerDurationDismissAndClear(t *testing.T) { | |||||
| 81 | }) | 81 | }) | ||
| 82 | } | 82 | } | ||
| 83 | 83 | ||||
| 84 | + | func TestTruncateText(t *testing.T) { | |||
| 85 | + | tests := []struct { | |||
| 86 | + | name string | |||
| 87 | + | text string | |||
| 88 | + | maxWidth int | |||
| 89 | + | want string | |||
| 90 | + | }{ | |||
| 91 | + | {"no truncation needed", "hello", 10, "hello"}, | |||
| 92 | + | {"truncation with ellipsis", "hello world", 8, "hello w…"}, | |||
| 93 | + | {"maxWidth zero", "hello", 0, ""}, | |||
| 94 | + | // The ellipsis itself is one cell wide, so any non-positive maxWidth is | |||
| 95 | + | // also "smaller than the ellipsis" and collapses to the same empty result. | |||
| 96 | + | {"maxWidth smaller than ellipsis", "hello world", -1, ""}, | |||
| 97 | + | {"wide runes", "日本語のテキスト", 5, "日本…"}, | |||
| 98 | + | } | |||
| 99 | + | for _, tt := range tests { | |||
| 100 | + | t.Run(tt.name, func(t *testing.T) { | |||
| 101 | + | if got := TruncateText(tt.text, tt.maxWidth); got != tt.want { | |||
| 102 | + | t.Fatalf("TruncateText(%q, %d) = %q, want %q", tt.text, tt.maxWidth, got, tt.want) | |||
| 103 | + | } | |||
| 104 | + | }) | |||
| 105 | + | } | |||
| 106 | + | } | |||
| 107 | + | ||||
| 84 | func TestToastBoundsAnchorsMarginsAndTinyFrames(t *testing.T) { | 108 | func TestToastBoundsAnchorsMarginsAndTinyFrames(t *testing.T) { | ||
| 85 | message := "Reconnection attempts…" | 109 | message := "Reconnection attempts…" | ||
| 86 | width := textWidth(message) + 4 | 110 | width := textWidth(message) + 4 | ||