import { defineComponent } from "vue";
import { test, expect } from "vite-plus/test";
import stripAnsi from "strip-ansi";
import stringWidth from "string-width";
import { renderToString, Box, Text } from "@vue-tui/runtime";
test("wide characters do not add extra space inside fixed-width Box", () => {
const output = renderToString(
defineComponent(() => () => (
๐
|
โณ
|
)),
{ columns: 100 },
);
const lines = output.split("\n");
expect(lines.length).toBe(2);
expect(lines[0]).toBe("๐|");
expect(lines[1]).toBe("โณ|");
});
test("CJK characters occupy correct width in fixed-width Box", () => {
const output = renderToString(
defineComponent(() => () => (
ไฝ ๅฅฝ
|
)),
{ columns: 100 },
);
expect(output).toBe("ไฝ ๅฅฝ|");
});
test("mixed ASCII and wide characters align correctly", () => {
const output = renderToString(
defineComponent(() => () => (
ab๐cd
|
abcdef
|
)),
{ columns: 100 },
);
const lines = output.split("\n");
expect(lines.length).toBe(2);
expect(lines[0]).toBe("ab๐cd|");
expect(lines[1]).toBe("abcdef|");
});
test("ANSI styled text does not affect layout width", () => {
const output = renderToString(
defineComponent(() => () => (
hello
|
)),
{ columns: 100 },
);
expect(stripAnsi(output)).toBe("hello|");
});
test("empty Text does not affect sibling layout", () => {
const output = renderToString(
defineComponent(() => () => (
hello
)),
{ columns: 100 },
);
expect(output).toBe("hello");
});
test("truncate CJK text at end", () => {
const output = renderToString(
defineComponent(() => () => (
ใใใใใใใใใใ|end
)),
{ columns: 100 },
);
expect(stringWidth(stripAnsi(output))).toBeLessThanOrEqual(20);
});
test("truncate CJK text in the middle", () => {
const output = renderToString(
defineComponent(() => () => (
ใใใใใใใใใใ|end
)),
{ columns: 100 },
);
expect(stringWidth(stripAnsi(output))).toBeLessThanOrEqual(20);
});
test("truncate CJK text at start", () => {
const output = renderToString(
defineComponent(() => () => (
ใใใใใใใใใใ|end
)),
{ columns: 100 },
);
expect(stringWidth(stripAnsi(output))).toBeLessThanOrEqual(20);
});
test("truncate CJK text does not exceed Box width", () => {
const output = renderToString(
defineComponent(() => () => (
ใใใใใใใใใใ|end
|
)),
{ columns: 100 },
);
const lines = output.split("\n");
expect(lines.length).toBe(1);
expect(stripAnsi(lines[0]!).endsWith("|")).toBe(true);
});
test("overlay on 2nd cell of CJK character clears the full character", () => {
const output = renderToString(
defineComponent(() => () => (
ใใใใใใใใใใ
XYZ
)),
{ columns: 20 },
);
const lines = output.split("\n");
expect(stringWidth(lines[0]!)).toBe(20);
expect(stripAnsi(lines[0]!)).toBe("ใใใใ XYZใใใใ");
});
test("overlay on 1st cell of CJK character clears trailing placeholder", () => {
const output = renderToString(
defineComponent(() => () => (
ใใใใใใใใใใ
X
)),
{ columns: 20 },
);
const lines = output.split("\n");
expect(stringWidth(lines[0]!)).toBe(20);
expect(stripAnsi(lines[0]!)).toBe("ใใใใใX ใใใใ");
});
test("CJK overlay on 2nd cell of CJK clears both sides", () => {
const output = renderToString(
defineComponent(() => () => (
ใใใใใใใใใใ
ๆผขๅญใในใ
)),
{ columns: 20 },
);
const lines = output.split("\n");
expect(stringWidth(lines[0]!)).toBe(20);
expect(stripAnsi(lines[0]!)).toBe("ใใ ๆผขๅญใในใ ใใ");
});
test("clipped empty write does not corrupt existing wide characters", () => {
const output = renderToString(
defineComponent(() => () => (
ใใ
Z
)),
{ columns: 4 },
);
expect(stripAnsi(output)).toBe("ใใ");
});
// A wide char whose LEADING cell is in-bounds but TRAILING cell exceeds the
// terminal/box width must still render its leading cell and OVERFLOW the row โ
// matching Ink, which has no x-bounds check in its Output write loop. The
// past-width trailing placeholder is dropped later by line.filter + trimEnd.
// Box width 4 (== terminal); ไฝ (width 2) overlaid at left=3 lands on cols 3,4 โ
// trailing col 4 is past width. Ink renders "aa ไฝ " (visible width 5); vue used
// to drop ไฝ wholesale (its leading col-3 cell included) via the now-removed
// `offsetX + characterWidth > this.width` guard. Verified against the built Ink
// reference (/tmp/ink @ v7.0.4 renderToString of this exact tree โ "aa ไฝ ").
test("wide char with in-bounds leading cell but out-of-bounds trailing cell still renders (overflows row, Ink parity)", () => {
const output = renderToString(
defineComponent(() => () => (
aa
ไฝ
)),
{ columns: 4 },
);
expect(stripAnsi(output)).toBe("aa ไฝ ");
expect(stringWidth(stripAnsi(output))).toBe(5);
});