package telegram import ( "strings" "testing" ) func TestDisplayWidth(t *testing.T) { tests := []struct { input string want int }{ {"hello", 5}, {"Khởi động", 9}, // Vietnamese diacritics = single-width {"Hardware tối thiểu", 18}, // Vietnamese diacritics = single-width {"Ngôn ngữ", 8}, {"đ", 1}, // Vietnamese d-stroke = single-width {"中文", 4}, // CJK = double-width {"日本語", 6}, // CJK = double-width } for _, tt := range tests { got := displayWidth(tt.input) if got != tt.want { t.Errorf("displayWidth(%q) = %d, want %d", tt.input, got, tt.want) } } } func TestRenderTableAsCode_Vietnamese(t *testing.T) { lines := []string{ "| Metric | OpenClaw | ZeroClaw |", "|--------|----------|----------|", "| Ngôn ngữ | TypeScript/Node.js | Rust |", "| Khởi động | > 500s | < 10ms |", "| Hardware tối thiểu | Mac mini $599 | $10 (bao gồm cả Raspberry Pi) |", } result := renderTableAsCode(lines) // Every non-separator line should have the same number of pipes resultLines := strings.Split(result, "\n") if len(resultLines) < 3 { t.Fatalf("expected at least 3 lines, got %d", len(resultLines)) } // Check separator line width matches header line width headerWidth := displayWidth(resultLines[0]) sepWidth := displayWidth(resultLines[1]) if headerWidth != sepWidth { t.Errorf("header width (%d) != separator width (%d)\nheader: %s\nsep: %s", headerWidth, sepWidth, resultLines[0], resultLines[1]) } // Check all data rows match header width for i := 2; i < len(resultLines); i++ { rowWidth := displayWidth(resultLines[i]) if rowWidth != headerWidth { t.Errorf("row %d width (%d) != header width (%d)\nrow: %s\nheader: %s", i, rowWidth, headerWidth, resultLines[i], resultLines[0]) } } } func TestMarkdownToTelegramHTML_Mentions(t *testing.T) { tests := []struct { name string input string want string // substring that MUST appear deny string // substring that must NOT appear }{ { name: "mention stays plain text", input: "Hello @viettran how are you?", want: "@viettran", deny: `href="https://t.me/`, }, { name: "mention not wrapped in link tag", input: "cc @john please review", want: "@john", deny: "link world", maxLen: 12, want: []string{"hello", "link", "world"}, }, { name: "avoid mid-tag split safe", input: "hello link world", maxLen: 25, // "hello " (6) -> remaining "link world" (28) // maxLen 25. remaining[:25] is "link wo" // lastOpen is at "" (21). // lastOpen < lastClose (18 < 21). No cut change from safety. // lastSpace is at index 22 (" world"). cutAt=23. want: []string{"hello", "link", "world"}, }, { name: "avoid mid-entity split", input: "hello & world", maxLen: 9, // "hello " (6) -> then "& world" // At second chunk: remaining="& world", maxLen=9 // remaining[:9] is "& wor" // lastSpace is at index 5. cutAt=6. want: []string{"hello", "&", "world"}, }, { name: "monolithic fallback", input: "monolithicblock", maxLen: 5, want: []string{"monol", "ithic", "block"}, }, { name: "paragraph preferred", input: "para1\n\npara2\nline3", maxLen: 10, want: []string{"para1", "para2", "line3"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := chunkHTML(tt.input, tt.maxLen) if len(got) != len(tt.want) { t.Fatalf("chunkHTML() returned %d chunks, want %d\ngot: %q\nwant: %q", len(got), len(tt.want), got, tt.want) } for i := range got { if got[i] != tt.want[i] { t.Errorf("chunk[%d] = %q, want %q", i, got[i], tt.want[i]) } } }) } } func TestMarkdownToTelegramHTML_URLsWithUnderscores(t *testing.T) { tests := []struct { name string input string want string deny string }{ { name: "bare URL with underscores not broken by italic", input: "Check https://pre.glomotra.dev/uk/syngas_dailymail_2026_ai/?fname=James here", want: "https://pre.glomotra.dev/uk/syngas_dailymail_2026_ai/?fname=James", deny: "", }, { name: "URL without underscores unchanged", input: "Visit https://example.com/path today", want: "https://example.com/path", }, { name: "markdown link with underscored URL preserved", input: "[Click](https://example.com/a_b_c)", want: `href="https://example.com/a_b_c"`, deny: "", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := markdownToTelegramHTML(tt.input) if !strings.Contains(got, tt.want) { t.Errorf("expected %q in output, got: %s", tt.want, got) } if tt.deny != "" && strings.Contains(got, tt.deny) { t.Errorf("unexpected %q in output, got: %s", tt.deny, got) } }) } }