Skip to content

Commit 22fffff

Browse files
committed
test(tui): cover ZWSP, ZWJ, ZWNJ, and BOM in label rendering
1 parent aeb4b89 commit 22fffff

1 file changed

Lines changed: 110 additions & 0 deletions

File tree

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { describe, it, expect } from "bun:test"
2+
import { truncate, truncateMiddle } from "./locale"
3+
4+
describe("truncate", () => {
5+
it("returns the string unchanged when its rendered width fits", () => {
6+
const input = "hello"
7+
expect(truncate(input, 10)).toBe(input)
8+
expect(truncate(input, 5)).toBe(input)
9+
})
10+
11+
it("does not truncate a U+200B-prefixed string whose rendered width equals the limit", () => {
12+
const input = "\u200BSisyphus"
13+
expect(Bun.stringWidth(input)).toBe(8)
14+
expect(truncate(input, 8)).toBe(input)
15+
})
16+
17+
it("truncates a U+200B-prefixed string whose rendered width exceeds the limit at the visible boundary", () => {
18+
const input = "\u200BSisyphus: Ultraworker"
19+
const result = truncate(input, 10)
20+
expect(result.endsWith("…")).toBe(true)
21+
const body = result.slice(0, -1)
22+
expect(Bun.stringWidth(body) + 1).toBeLessThanOrEqual(10)
23+
expect(Bun.stringWidth(result)).toBeLessThanOrEqual(10)
24+
expect(result).toBe("\u200BSisyphus:…")
25+
})
26+
27+
it("truncates plain ASCII to display width ending in ellipsis", () => {
28+
const result = truncate("Sisyphus: Ultraworker", 10)
29+
expect(result.endsWith("…")).toBe(true)
30+
expect(Bun.stringWidth(result)).toBe(10)
31+
expect(result).toBe("Sisyphus:…")
32+
})
33+
34+
it("handles all zero-width codepoint variants (U+200C, U+200D, U+FEFF)", () => {
35+
const zwnj = "\u200CSisyphus: Ultraworker"
36+
const zwj = "\u200DSisyphus: Ultraworker"
37+
const bom = "\uFEFFSisyphus: Ultraworker"
38+
const resultZwnj = truncate(zwnj, 10)
39+
const resultZwj = truncate(zwj, 10)
40+
const resultBom = truncate(bom, 10)
41+
expect(resultZwnj.endsWith("…")).toBe(true)
42+
expect(resultZwj.endsWith("…")).toBe(true)
43+
expect(resultBom.endsWith("…")).toBe(true)
44+
expect(Bun.stringWidth(resultZwnj)).toBeLessThanOrEqual(10)
45+
expect(Bun.stringWidth(resultZwj)).toBeLessThanOrEqual(10)
46+
expect(Bun.stringWidth(resultBom)).toBeLessThanOrEqual(10)
47+
expect(resultZwnj).toBe("\u200CSisyphus:…")
48+
expect(resultZwj).toBe("\u200DSisyphus:…")
49+
expect(resultBom).toBe("\uFEFFSisyphus:…")
50+
})
51+
52+
it("truncates mixed strings containing emoji, ZWSP, and ASCII correctly", () => {
53+
const input = "🔥\u200BSisyphus: Ultraworker"
54+
expect(Bun.stringWidth("🔥")).toBe(2)
55+
const result = truncate(input, 10)
56+
expect(result.endsWith("…")).toBe(true)
57+
expect(Bun.stringWidth(result)).toBeLessThanOrEqual(10)
58+
expect(result).toBe("🔥\u200BSisyphu…")
59+
})
60+
})
61+
62+
describe("truncateMiddle", () => {
63+
it("returns the string unchanged when its rendered width is <= maxLength", () => {
64+
const input = "hello world"
65+
expect(truncateMiddle(input, 20)).toBe(input)
66+
expect(truncateMiddle(input, 11)).toBe(input)
67+
})
68+
69+
it("does not truncate a U+200B-prefixed string at exact width", () => {
70+
const input = "\u200BSisyphus"
71+
expect(Bun.stringWidth(input)).toBe(8)
72+
expect(truncateMiddle(input, 8)).toBe(input)
73+
})
74+
75+
it("truncates a long ZWSP-bearing string in the middle with correct widths", () => {
76+
const input = "\u200BSisyphus: Ultraworker helps you"
77+
const maxLength = 15
78+
const result = truncateMiddle(input, maxLength)
79+
expect(result.includes("…")).toBe(true)
80+
expect(Bun.stringWidth(result)).toBeLessThanOrEqual(maxLength)
81+
const ellipsisIndex = result.indexOf("…")
82+
const startOut = result.slice(0, ellipsisIndex)
83+
const endOut = result.slice(ellipsisIndex + 1)
84+
const budget = maxLength - 1
85+
const keepStart = Math.ceil(budget / 2)
86+
const keepEnd = Math.floor(budget / 2)
87+
expect(Bun.stringWidth(startOut)).toBeLessThanOrEqual(keepStart)
88+
expect(Bun.stringWidth(endOut)).toBeLessThanOrEqual(keepEnd)
89+
expect(result.startsWith("\u200B")).toBe(true)
90+
expect(result.endsWith("u")).toBe(true)
91+
expect(result).toBe("\u200BSisyphu…lps you")
92+
})
93+
94+
it("uses a default maxLength of 35", () => {
95+
const short = "a".repeat(35)
96+
expect(truncateMiddle(short)).toBe(short)
97+
const long = "a".repeat(50)
98+
const result = truncateMiddle(long)
99+
expect(result.includes("…")).toBe(true)
100+
expect(Bun.stringWidth(result)).toBeLessThanOrEqual(35)
101+
})
102+
103+
it("truncates plain ASCII with unchanged behavior", () => {
104+
const input = "abcdefghijklmnopqrstuvwxyz"
105+
const result = truncateMiddle(input, 10)
106+
expect(result.includes("…")).toBe(true)
107+
expect(Bun.stringWidth(result)).toBeLessThanOrEqual(10)
108+
expect(result).toBe("abcde…wxyz")
109+
})
110+
})

0 commit comments

Comments
 (0)