test: port Ink background and border-background tests (29 tests)

Covers inheritance, override, hex/rgb/ansi256 colors, wide chars,
emojis, fill area, fill with border/padding, border backgrounds.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Yunfei He
2026-05-25 21:48:50 +08:00
parent ce9ed4c399
commit 8c4a3b2c9d
@@ -1,3 +1,4 @@
import { defineComponent, shallowRef, nextTick } from "vue";
import { expect, test } from "vite-plus/test";
import { render } from "@vue-tui/testing";
import { Box, Text } from "@vue-tui/runtime";
@@ -5,10 +6,9 @@ import { Box, Text } from "@vue-tui/runtime";
const BG_BLUE = "\x1b[44m";
test("Box backgroundColor produces ANSI background codes", async () => {
const { frames } = await render(
() => <Box backgroundColor="blue" width={5} height={1} />,
{ columns: 10 },
);
const { frames } = await render(() => <Box backgroundColor="blue" width={5} height={1} />, {
columns: 10,
});
expect(frames.at(-1)).toContain(BG_BLUE);
});
@@ -51,3 +51,513 @@ test("wrapped text preserves backgroundColor on every line", async () => {
expect(line).toContain(BG_BLUE);
}
});
// --- Ink background tests ---
test("Text inherits parent Box background color", async () => {
const { lastFrame } = await render(
defineComponent(() => () => (
<Box backgroundColor="green" alignSelf="flex-start">
<Text>Hello World</Text>
</Box>
)),
{ columns: 100 },
);
expect(lastFrame()).toMatchInlineSnapshot(`"Hello World"`);
});
test("Text explicit background color overrides inherited", async () => {
const { lastFrame } = await render(
defineComponent(() => () => (
<Box backgroundColor="red" alignSelf="flex-start">
<Text backgroundColor="blue">Hello World</Text>
</Box>
)),
{ columns: 100 },
);
expect(lastFrame()).toMatchInlineSnapshot(`"Hello World"`);
});
test("Nested Box background inheritance", async () => {
const { lastFrame } = await render(
defineComponent(() => () => (
<Box backgroundColor="red" alignSelf="flex-start">
<Box backgroundColor="blue">
<Text>Hello World</Text>
</Box>
</Box>
)),
{ columns: 100 },
);
expect(lastFrame()).toMatchInlineSnapshot(`"Hello World"`);
});
test("Text without parent Box background has no inheritance", async () => {
const { lastFrame } = await render(
defineComponent(() => () => (
<Box alignSelf="flex-start">
<Text>Hello World</Text>
</Box>
)),
{ columns: 100 },
);
expect(lastFrame()).toBe("Hello World");
});
test("Multiple Text elements inherit same background", async () => {
const { lastFrame } = await render(
defineComponent(() => () => (
<Box backgroundColor="yellow" alignSelf="flex-start">
<Text>Hello </Text>
<Text>World</Text>
</Box>
)),
{ columns: 100 },
);
expect(lastFrame()).toMatchInlineSnapshot(`
"Hello
World"
`);
});
test("Mixed text with and without background inheritance", async () => {
const { lastFrame } = await render(
defineComponent(() => () => (
<Box backgroundColor="green" alignSelf="flex-start">
<Text>Inherited </Text>
<Text backgroundColor="">No BG </Text>
<Text backgroundColor="red">Red BG</Text>
</Box>
)),
{ columns: 100 },
);
expect(lastFrame()).toMatchInlineSnapshot(`
"Inherited
No BG 
Red BG "
`);
});
test("Complex nested structure with background inheritance", async () => {
const { lastFrame } = await render(
defineComponent(() => () => (
<Box backgroundColor="yellow" alignSelf="flex-start">
<Box>
<Text>Outer: </Text>
<Box backgroundColor="blue">
<Text>Inner: </Text>
<Text backgroundColor="red">Explicit</Text>
</Box>
</Box>
</Box>
)),
{ columns: 100 },
);
expect(lastFrame()).toMatchInlineSnapshot(`
"Outer: 
Inner: 
Explicit"
`);
});
test("Box background with standard color", async () => {
const { lastFrame } = await render(
defineComponent(() => () => (
<Box backgroundColor="red" alignSelf="flex-start">
<Text>Hello</Text>
</Box>
)),
{ columns: 100 },
);
expect(lastFrame()).toMatchInlineSnapshot(`"Hello"`);
});
test("Box background with hex color", async () => {
const { lastFrame } = await render(
defineComponent(() => () => (
<Box backgroundColor="#FF0000" alignSelf="flex-start">
<Text>Hello</Text>
</Box>
)),
{ columns: 100 },
);
expect(lastFrame()).toMatchInlineSnapshot(
`"Hello"`,
);
});
test("Box background with rgb color", async () => {
const { lastFrame } = await render(
defineComponent(() => () => (
<Box backgroundColor="rgb(255, 0, 0)" alignSelf="flex-start">
<Text>Hello</Text>
</Box>
)),
{ columns: 100 },
);
expect(lastFrame()).toMatchInlineSnapshot(
`"Hello"`,
);
});
test("Box background with ansi256 color", async () => {
const { lastFrame } = await render(
defineComponent(() => () => (
<Box backgroundColor="ansi256(9)" alignSelf="flex-start">
<Text>Hello</Text>
</Box>
)),
{ columns: 100 },
);
expect(lastFrame()).toMatchInlineSnapshot(`"Hello"`);
});
test("Box background with wide characters", async () => {
const { lastFrame } = await render(
defineComponent(() => () => (
<Box backgroundColor="yellow" alignSelf="flex-start">
<Text>こんにちは</Text>
</Box>
)),
{ columns: 100 },
);
expect(lastFrame()).toMatchInlineSnapshot(`"こんにちは"`);
});
test("Box background with emojis", async () => {
const { lastFrame } = await render(
defineComponent(() => () => (
<Box backgroundColor="red" alignSelf="flex-start">
<Text>🎉🎊</Text>
</Box>
)),
{ columns: 100 },
);
expect(lastFrame()).toMatchInlineSnapshot(`"🎉🎊"`);
});
test("Box background fills entire area with standard color", async () => {
const { lastFrame } = await render(
defineComponent(() => () => (
<Box backgroundColor="red" width={10} height={3} alignSelf="flex-start">
<Text>Hello</Text>
</Box>
)),
{ columns: 100 },
);
expect(lastFrame()).toMatchInlineSnapshot(`
"Hello 
 
 "
`);
});
test("Box background fills with hex color", async () => {
const bgHexRed = "[48;2;255;0;0m";
const bgReset = "[49m";
const { lastFrame } = await render(
defineComponent(() => () => (
<Box backgroundColor="#FF0000" width={10} height={3} alignSelf="flex-start">
<Text>Hello</Text>
</Box>
)),
{ columns: 100 },
);
const output = lastFrame()!;
expect(output).toContain("Hello");
expect(output).toContain(bgHexRed);
expect(output).toContain(bgReset);
});
test("Box background fills with rgb color", async () => {
const bgHexRed = "[48;2;255;0;0m";
const bgReset = "[49m";
const { lastFrame } = await render(
defineComponent(() => () => (
<Box backgroundColor="rgb(255, 0, 0)" width={10} height={3} alignSelf="flex-start">
<Text>Hello</Text>
</Box>
)),
{ columns: 100 },
);
const output = lastFrame()!;
expect(output).toContain("Hello");
expect(output).toContain(bgHexRed);
expect(output).toContain(bgReset);
});
test("Box background fills with ansi256 color", async () => {
const bgAnsi256Nine = "[48;5;9m";
const bgReset = "[49m";
const { lastFrame } = await render(
defineComponent(() => () => (
<Box backgroundColor="ansi256(9)" width={10} height={3} alignSelf="flex-start">
<Text>Hello</Text>
</Box>
)),
{ columns: 100 },
);
const output = lastFrame()!;
expect(output).toContain("Hello");
expect(output).toContain(bgAnsi256Nine);
expect(output).toContain(bgReset);
});
test("Box background with border fills content area", async () => {
const { lastFrame } = await render(
defineComponent(() => () => (
<Box backgroundColor="cyan" borderStyle="round" width={10} height={5} alignSelf="flex-start">
<Text>Hi</Text>
</Box>
)),
{ columns: 100 },
);
expect(lastFrame()).toMatchInlineSnapshot(`
"╭────────╮
│Hi │
│ │
│ │
╰────────╯"
`);
});
test("Box background with padding fills entire padded area", async () => {
const { lastFrame } = await render(
defineComponent(() => () => (
<Box backgroundColor="magenta" padding={1} width={10} height={5} alignSelf="flex-start">
<Text>Hi</Text>
</Box>
)),
{ columns: 100 },
);
expect(lastFrame()).toMatchInlineSnapshot(`
" 
 Hi  
 
 
 "
`);
});
test("Box background with center alignment fills entire area", async () => {
const { lastFrame } = await render(
defineComponent(() => () => (
<Box
backgroundColor="blue"
width={10}
height={3}
justifyContent="center"
alignSelf="flex-start"
>
<Text>Hi</Text>
</Box>
)),
{ columns: 100 },
);
expect(lastFrame()).toMatchInlineSnapshot(`
" 
Hi 
 "
`);
});
test("Box background with column layout fills entire area", async () => {
const { lastFrame } = await render(
defineComponent(() => () => (
<Box
backgroundColor="green"
flexDirection="column"
width={10}
height={5}
alignSelf="flex-start"
>
<Text>Line 1</Text>
<Text>Line 2</Text>
</Box>
)),
{ columns: 100 },
);
expect(lastFrame()).toMatchInlineSnapshot(`
"Line 1 
Line 2 
 
 
 "
`);
});
test("Box background updates on rerender", async () => {
const bgColor = shallowRef<string | undefined>(undefined);
const { lastFrame } = await render(
defineComponent(() => () => (
<Box backgroundColor={bgColor.value} alignSelf="flex-start">
<Text>Hello</Text>
</Box>
)),
{ columns: 100 },
);
expect(lastFrame()).toBe("Hello");
bgColor.value = "green";
await nextTick();
expect(lastFrame()).toMatchInlineSnapshot(`"Hello"`);
bgColor.value = undefined;
await nextTick();
expect(lastFrame()).toBe("Hello");
});
test("Box backgroundColor fills full width on every line when text wraps", async () => {
const { lastFrame } = await render(
defineComponent(() => () => (
<Box backgroundColor="red" width={10} alignSelf="flex-start">
<Text>Hello World!!</Text>
</Box>
)),
{ columns: 100 },
);
expect(lastFrame()).toMatchInlineSnapshot(`
"Hello 
World!! "
`);
});
test("Text-only backgroundColor colors text content but does not fill Box width", async () => {
const { lastFrame } = await render(
defineComponent(() => () => (
<Box width={10} alignSelf="flex-start">
<Text backgroundColor="red">Hello World!!</Text>
</Box>
)),
{ columns: 100 },
);
expect(lastFrame()).toMatchInlineSnapshot(`
"Hello
World!!"
`);
});
// --- Ink border-backgrounds tests ---
test("border with background color", async () => {
const { lastFrame } = await render(
defineComponent(() => () => (
<Box borderStyle="single" borderColor="white" borderBackgroundColor="blue">
<Box width={4}>
<Text>Test</Text>
</Box>
</Box>
)),
{ columns: 100 },
);
const output = lastFrame()!;
expect(output).toContain("┌");
expect(output).toContain("┐");
expect(output).toContain("└");
expect(output).toContain("┘");
expect(output).toContain("Test");
// Named blue background => ESC[44m
expect(output).toContain("[44m");
});
test("border with different background colors per side", async () => {
const { lastFrame } = await render(
defineComponent(() => () => (
<Box
borderStyle="single"
borderTopBackgroundColor="red"
borderBottomBackgroundColor="blue"
borderLeftBackgroundColor="green"
borderRightBackgroundColor="yellow"
>
<Box width={4}>
<Text>Test</Text>
</Box>
</Box>
)),
{ columns: 100 },
);
const output = lastFrame()!;
expect(output).toContain("┌");
expect(output).toContain("┐");
expect(output).toContain("└");
expect(output).toContain("┘");
expect(output).toContain("Test");
// red => 41, green => 42, yellow => 43, blue => 44
expect(output).toContain("[41m");
expect(output).toContain("[42m");
expect(output).toContain("[43m");
expect(output).toContain("[44m");
});
test("border background color fallback to general borderBackgroundColor", async () => {
const { lastFrame } = await render(
defineComponent(() => () => (
<Box borderStyle="single" borderBackgroundColor="magenta" borderTopBackgroundColor="cyan">
<Box width={4}>
<Text>Test</Text>
</Box>
</Box>
)),
{ columns: 100 },
);
const output = lastFrame()!;
expect(output).toContain("┌");
expect(output).toContain("┐");
expect(output).toContain("└");
expect(output).toContain("┘");
expect(output).toContain("Test");
// cyan => 46, magenta => 45
expect(output).toContain("[46m");
expect(output).toContain("[45m");
});
test("vertical border background does not bleed into content rows", async () => {
const { lastFrame } = await render(
defineComponent(() => () => (
<Box borderStyle="classic" borderBackgroundColor="cyan" alignSelf="flex-start" width={12}>
<Text>Text longer than the Box width, so will definitely wrap.</Text>
</Box>
)),
{ columns: 100 },
);
const output = lastFrame()!;
const bgCyanPattern = "\\[46m";
const bgResetPattern = "\\[49m";
const tableBorderChar = "|";
const tableBorderPattern = bgCyanPattern + tableBorderChar + bgResetPattern;
const contentRowPattern = new RegExp(`^${tableBorderPattern}.*${tableBorderPattern}$`);
const tableRows = output.split("\n");
const contentRows = tableRows.slice(1, -1);
for (const contentRow of contentRows) {
expect(contentRow).toMatch(contentRowPattern);
}
});
test("foreground, background and dim combine correctly", async () => {
const { lastFrame } = await render(
defineComponent(() => () => (
<Box
borderTopDimColor
borderStyle="single"
borderTopColor="red"
borderTopBackgroundColor="cyan"
alignSelf="flex-start"
>
<Text>Hi</Text>
</Box>
)),
{ columns: 100 },
);
const output = lastFrame()!;
// red FG (31), cyan BG (46), dim (2)
expect(output).toContain("[31m");
expect(output).toContain("[46m");
expect(output).toContain("[2m");
});