mirror of
https://github.com/oven-sh/bun
synced 2026-02-10 10:58:56 +00:00
* improve Bun.stringWidth's algorithm * add a bunch more tests from string-width package * make typescript happy * undo typescript changes * use better #define check for debug mode * properly handle latin1 width tests * support grapheme clusters * fix trailing newline * visibleUTF16WidthFn- add fast path for leading ascii * add firstNonASCII16IgnoreMin * fix firstNonASCII16CheckMin * vectorize visibleUTF16WidthFn * support emoji variation selector * expose stringWidth in release mode too * vectorize visibleLatin1Width * support ambiguousIsNarrow option * add typescript definition for stringWidth
154 lines
6.7 KiB
TypeScript
154 lines
6.7 KiB
TypeScript
import { test, expect, describe } from "bun:test";
|
|
import npmStringWidth from "string-width";
|
|
|
|
const bun_has_stringwidth = "stringWidth" in Bun;
|
|
|
|
expect.extend({
|
|
toMatchNPMStringWidth(received: string) {
|
|
const width = npmStringWidth(received, { countAnsiEscapeCodes: true });
|
|
const bunWidth = Bun.stringWidth(received, { countAnsiEscapeCodes: true });
|
|
const pass = width === bunWidth;
|
|
const message = () => `expected ${received} to have npm string width ${width} but got ${bunWidth}`;
|
|
return { pass, message };
|
|
},
|
|
toMatchNPMStringWidthExcludeANSI(received: string) {
|
|
const width = npmStringWidth(received, { countAnsiEscapeCodes: false });
|
|
const bunWidth = Bun.stringWidth(received, { countAnsiEscapeCodes: false });
|
|
const pass = width === bunWidth;
|
|
const message = () => `expected ${received} to have npm string width ${width} but got ${bunWidth}`;
|
|
return { pass, message };
|
|
},
|
|
});
|
|
|
|
test.skipIf(!bun_has_stringwidth)("stringWidth", () => {
|
|
expect(undefined).toMatchNPMStringWidth();
|
|
expect("").toMatchNPMStringWidth();
|
|
expect("a").toMatchNPMStringWidth();
|
|
expect("ab").toMatchNPMStringWidth();
|
|
expect("abc").toMatchNPMStringWidth();
|
|
expect("😀").toMatchNPMStringWidth();
|
|
expect("😀😀").toMatchNPMStringWidth();
|
|
expect("😀😀😀").toMatchNPMStringWidth();
|
|
expect("😀😀😀😀").toMatchNPMStringWidth();
|
|
expect("😀😀😀😀😀").toMatchNPMStringWidth();
|
|
expect("😀😀😀😀😀😀").toMatchNPMStringWidth();
|
|
expect("😀😀😀😀😀😀😀").toMatchNPMStringWidth();
|
|
expect("😀😀😀😀😀😀😀😀").toMatchNPMStringWidth();
|
|
expect("😀😀😀😀😀😀😀😀😀").toMatchNPMStringWidth();
|
|
expect("😀😀😀😀😀😀😀😀😀😀").toMatchNPMStringWidth();
|
|
});
|
|
|
|
for (let matcher of ["toMatchNPMStringWidth", "toMatchNPMStringWidthExcludeANSI"]) {
|
|
describe(matcher, () => {
|
|
test.skipIf(!bun_has_stringwidth)("ansi colors", () => {
|
|
expect("\u001b[31m")[matcher]();
|
|
expect("\u001b[31ma")[matcher]();
|
|
expect("\u001b[31mab")[matcher]();
|
|
expect("\u001b[31mabc")[matcher]();
|
|
expect("\u001b[31m😀")[matcher]();
|
|
expect("\u001b[31m😀😀")[matcher]();
|
|
expect("\u001b[31m😀😀😀")[matcher]();
|
|
expect("\u001b[31m😀😀😀😀")[matcher]();
|
|
expect("\u001b[31m😀😀😀😀😀")[matcher]();
|
|
expect("\u001b[31m😀😀😀😀😀😀")[matcher]();
|
|
expect("\u001b[31m😀😀😀😀😀😀😀")[matcher]();
|
|
expect("\u001b[31m😀😀😀😀😀😀😀😀")[matcher]();
|
|
expect("\u001b[31m😀😀😀😀😀😀😀😀😀")[matcher]();
|
|
expect("\u001b[31m😀😀😀😀😀😀😀😀😀😀")[matcher]();
|
|
|
|
expect("a\u001b[31m")[matcher]();
|
|
expect("ab\u001b[31m")[matcher]();
|
|
expect("abc\u001b[31m")[matcher]();
|
|
expect("😀\u001b[31m")[matcher]();
|
|
expect("😀😀\u001b[31m")[matcher]();
|
|
expect("😀😀😀\u001b[31m")[matcher]();
|
|
expect("😀😀😀😀\u001b[31m")[matcher]();
|
|
expect("😀😀😀😀😀\u001b[31m")[matcher]();
|
|
expect("😀😀😀😀😀😀\u001b[31m")[matcher]();
|
|
expect("😀😀😀😀😀😀😀\u001b[31m")[matcher]();
|
|
expect("😀😀😀😀😀😀😀😀\u001b[31m")[matcher]();
|
|
expect("😀😀😀😀😀😀😀😀😀\u001b[31m")[matcher]();
|
|
|
|
expect("a\u001b[31mb")[matcher]();
|
|
expect("ab\u001b[31mc")[matcher]();
|
|
expect("abc\u001b[31m😀")[matcher]();
|
|
expect("😀\u001b[31m😀😀")[matcher]();
|
|
expect("😀😀\u001b[31m😀😀😀")[matcher]();
|
|
expect("😀😀😀\u001b[31m😀😀😀😀")[matcher]();
|
|
expect("😀😀😀😀\u001b[31m😀😀😀😀😀")[matcher]();
|
|
expect("😀😀😀😀😀\u001b[31m😀😀😀😀😀😀")[matcher]();
|
|
expect("😀😀😀😀😀😀\u001b[31m😀😀😀😀😀😀😀")[matcher]();
|
|
expect("😀😀😀😀😀😀😀\u001b[31m😀😀😀😀😀😀😀😀")[matcher]();
|
|
expect("😀😀😀😀😀😀😀😀\u001b[31m😀😀😀😀😀😀😀😀😀")[matcher]();
|
|
});
|
|
});
|
|
}
|
|
|
|
for (let matcher of ["toMatchNPMStringWidth", "toMatchNPMStringWidthExcludeANSI"]) {
|
|
test.skipIf(!bun_has_stringwidth)("leading non-ansi characters in UTF-16 string seems to fail", () => {
|
|
expect("\x1b[31mhshh🌎")[matcher]();
|
|
expect("a\x1b[31mhshh🌎")[matcher]();
|
|
expect("a\x1b[31mhshh🌎a")[matcher]();
|
|
});
|
|
}
|
|
|
|
for (let matcher of ["toMatchNPMStringWidth", "toMatchNPMStringWidthExcludeANSI"]) {
|
|
test.skipIf(!bun_has_stringwidth)("upstream", () => {
|
|
expect("abcde")[matcher]();
|
|
expect("古池や")[matcher]();
|
|
expect("あいうabc")[matcher]();
|
|
expect("あいう★")[matcher]();
|
|
expect("±")[matcher]();
|
|
expect("ノード.js")[matcher]();
|
|
expect("你好")[matcher]();
|
|
expect("안녕하세요")[matcher]();
|
|
expect("A\uD83C\uDE00BC")[matcher]();
|
|
expect("\u001B[31m\u001B[39m")[matcher]();
|
|
// expect("\u001B]8;;https://github.com\u0007Click\u001B]8;;\u0007")[matcher]();
|
|
expect("\u{231A}")[matcher]();
|
|
expect("\u{2194}\u{FE0F}")[matcher]();
|
|
expect("\u{1F469}")[matcher]();
|
|
expect("\u{1F469}\u{1F3FF}")[matcher]();
|
|
expect("\u{845B}\u{E0100}")[matcher]();
|
|
expect("ปฏัก")[matcher]();
|
|
expect("_\u0E34")[matcher]();
|
|
expect("\u001B[31m\u001B[39m")[matcher]();
|
|
});
|
|
}
|
|
|
|
test("ambiguousIsNarrow=false", () => {
|
|
for (let countAnsiEscapeCodes of [false, true]) {
|
|
for (let string of ["⛣", "あいう★", "“"]) {
|
|
const actual = Bun.stringWidth(string, { countAnsiEscapeCodes, ambiguousIsNarrow: false });
|
|
expect(actual).toBe(npmStringWidth(string, { countAnsiEscapeCodes, ambiguousIsNarrow: false }));
|
|
}
|
|
}
|
|
});
|
|
|
|
for (let matcher of ["toMatchNPMStringWidth", "toMatchNPMStringWidthExcludeANSI"]) {
|
|
test.skipIf(!bun_has_stringwidth)("ignores control characters", () => {
|
|
expect(String.fromCodePoint(0))[matcher]();
|
|
expect(String.fromCodePoint(31))[matcher]();
|
|
expect(String.fromCodePoint(127))[matcher]();
|
|
expect(String.fromCodePoint(134))[matcher]();
|
|
expect(String.fromCodePoint(159))[matcher]();
|
|
expect("\u001B")[matcher]();
|
|
});
|
|
}
|
|
|
|
for (let matcher of ["toMatchNPMStringWidth", "toMatchNPMStringWidthExcludeANSI"]) {
|
|
test.skipIf(!bun_has_stringwidth)("handles combining characters", () => {
|
|
expect("x\u0300")[matcher]();
|
|
});
|
|
}
|
|
|
|
for (let matcher of ["toMatchNPMStringWidth", "toMatchNPMStringWidthExcludeANSI"]) {
|
|
test.skipIf(!bun_has_stringwidth)("handles ZWJ characters", () => {
|
|
expect("👶")[matcher]();
|
|
expect("👶🏽")[matcher]();
|
|
expect("aa👶🏽aa")[matcher]();
|
|
expect("👩👩👦👦")[matcher]();
|
|
expect("👨❤️💋👨")[matcher]();
|
|
});
|
|
}
|