diff --git a/packages/runtime-tests/integration/components/background-color.test.tsx b/packages/runtime-tests/integration/components/background-color.test.tsx
index 989f19e..413cff0 100644
--- a/packages/runtime-tests/integration/components/background-color.test.tsx
+++ b/packages/runtime-tests/integration/components/background-color.test.tsx
@@ -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(
- () => ,
- { columns: 10 },
- );
+ const { frames } = await render(() => , {
+ 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(() => () => (
+
+ Hello World
+
+ )),
+ { columns: 100 },
+ );
+ expect(lastFrame()).toMatchInlineSnapshot(`"[42m[42mHello World[49m[42m[49m"`);
+});
+
+test("Text explicit background color overrides inherited", async () => {
+ const { lastFrame } = await render(
+ defineComponent(() => () => (
+
+ Hello World
+
+ )),
+ { columns: 100 },
+ );
+ expect(lastFrame()).toMatchInlineSnapshot(`"[41m[44mHello World[49m[41m[49m"`);
+});
+
+test("Nested Box background inheritance", async () => {
+ const { lastFrame } = await render(
+ defineComponent(() => () => (
+
+
+ Hello World
+
+
+ )),
+ { columns: 100 },
+ );
+ expect(lastFrame()).toMatchInlineSnapshot(`"[44m[44mHello World[49m[44m[49m"`);
+});
+
+test("Text without parent Box background has no inheritance", async () => {
+ const { lastFrame } = await render(
+ defineComponent(() => () => (
+
+ Hello World
+
+ )),
+ { columns: 100 },
+ );
+ expect(lastFrame()).toBe("Hello World");
+});
+
+test("Multiple Text elements inherit same background", async () => {
+ const { lastFrame } = await render(
+ defineComponent(() => () => (
+
+ Hello
+ World
+
+ )),
+ { columns: 100 },
+ );
+ expect(lastFrame()).toMatchInlineSnapshot(`
+ "[43m[43mHello[49m[43m[49m
+ [43m[43mWorld[49m[43m[49m"
+ `);
+});
+
+test("Mixed text with and without background inheritance", async () => {
+ const { lastFrame } = await render(
+ defineComponent(() => () => (
+
+ Inherited
+ No BG
+ Red BG
+
+ )),
+ { columns: 100 },
+ );
+ expect(lastFrame()).toMatchInlineSnapshot(`
+ "[42m[42mInherited[49m[42m[49m
+ [42mNo BG [49m
+ [42m[41mRed BG[49m[42m [49m"
+ `);
+});
+
+test("Complex nested structure with background inheritance", async () => {
+ const { lastFrame } = await render(
+ defineComponent(() => () => (
+
+
+ Outer:
+
+ Inner:
+ Explicit
+
+
+
+ )),
+ { columns: 100 },
+ );
+ expect(lastFrame()).toMatchInlineSnapshot(`
+ "[43m[43mOuter:[49m[43m [49m
+ [44m[44mInner:[49m[44m [49m
+ [44m[41mExplicit[49m[44m[49m"
+ `);
+});
+
+test("Box background with standard color", async () => {
+ const { lastFrame } = await render(
+ defineComponent(() => () => (
+
+ Hello
+
+ )),
+ { columns: 100 },
+ );
+ expect(lastFrame()).toMatchInlineSnapshot(`"[41m[41mHello[49m[41m[49m"`);
+});
+
+test("Box background with hex color", async () => {
+ const { lastFrame } = await render(
+ defineComponent(() => () => (
+
+ Hello
+
+ )),
+ { columns: 100 },
+ );
+ expect(lastFrame()).toMatchInlineSnapshot(
+ `"[48;2;255;0;0m[48;2;255;0;0mHello[49m[48;2;255;0;0m[49m"`,
+ );
+});
+
+test("Box background with rgb color", async () => {
+ const { lastFrame } = await render(
+ defineComponent(() => () => (
+
+ Hello
+
+ )),
+ { columns: 100 },
+ );
+ expect(lastFrame()).toMatchInlineSnapshot(
+ `"[48;2;255;0;0m[48;2;255;0;0mHello[49m[48;2;255;0;0m[49m"`,
+ );
+});
+
+test("Box background with ansi256 color", async () => {
+ const { lastFrame } = await render(
+ defineComponent(() => () => (
+
+ Hello
+
+ )),
+ { columns: 100 },
+ );
+ expect(lastFrame()).toMatchInlineSnapshot(`"[48;5;9m[48;5;9mHello[49m[48;5;9m[49m"`);
+});
+
+test("Box background with wide characters", async () => {
+ const { lastFrame } = await render(
+ defineComponent(() => () => (
+
+ こんにちは
+
+ )),
+ { columns: 100 },
+ );
+ expect(lastFrame()).toMatchInlineSnapshot(`"[43m[43mこんにちは[49m[43m[49m"`);
+});
+
+test("Box background with emojis", async () => {
+ const { lastFrame } = await render(
+ defineComponent(() => () => (
+
+ 🎉🎊
+
+ )),
+ { columns: 100 },
+ );
+ expect(lastFrame()).toMatchInlineSnapshot(`"[41m[41m🎉🎊[49m[41m[49m"`);
+});
+
+test("Box background fills entire area with standard color", async () => {
+ const { lastFrame } = await render(
+ defineComponent(() => () => (
+
+ Hello
+
+ )),
+ { columns: 100 },
+ );
+ expect(lastFrame()).toMatchInlineSnapshot(`
+ "[41m[41mHello[49m[41m [49m
+ [41m [49m
+ [41m [49m"
+ `);
+});
+
+test("Box background fills with hex color", async () => {
+ const bgHexRed = "[48;2;255;0;0m";
+ const bgReset = "[49m";
+
+ const { lastFrame } = await render(
+ defineComponent(() => () => (
+
+ Hello
+
+ )),
+ { 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(() => () => (
+
+ Hello
+
+ )),
+ { 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(() => () => (
+
+ Hello
+
+ )),
+ { 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(() => () => (
+
+ Hi
+
+ )),
+ { columns: 100 },
+ );
+ expect(lastFrame()).toMatchInlineSnapshot(`
+ "[46m╭────────╮[49m
+ [46m│[49m[46m[46mHi[49m[46m [49m[46m│[49m
+ [46m│[49m[46m [49m[46m│[49m
+ [46m│[49m[46m [49m[46m│[49m
+ [46m╰────────╯[49m"
+ `);
+});
+
+test("Box background with padding fills entire padded area", async () => {
+ const { lastFrame } = await render(
+ defineComponent(() => () => (
+
+ Hi
+
+ )),
+ { columns: 100 },
+ );
+ expect(lastFrame()).toMatchInlineSnapshot(`
+ "[45m [49m
+ [45m [45m[45mHi[49m[45m [49m [49m
+ [45m [49m
+ [45m [49m
+ [45m [49m"
+ `);
+});
+
+test("Box background with center alignment fills entire area", async () => {
+ const { lastFrame } = await render(
+ defineComponent(() => () => (
+
+ Hi
+
+ )),
+ { columns: 100 },
+ );
+ expect(lastFrame()).toMatchInlineSnapshot(`
+ "[44m [49m
+ [44m[44mHi[49m[44m [49m
+ [44m [49m"
+ `);
+});
+
+test("Box background with column layout fills entire area", async () => {
+ const { lastFrame } = await render(
+ defineComponent(() => () => (
+
+ Line 1
+ Line 2
+
+ )),
+ { columns: 100 },
+ );
+ expect(lastFrame()).toMatchInlineSnapshot(`
+ "[42m[42mLine 1[49m[42m [49m
+ [42m[42mLine 2[49m[42m [49m
+ [42m [49m
+ [42m [49m
+ [42m [49m"
+ `);
+});
+
+test("Box background updates on rerender", async () => {
+ const bgColor = shallowRef(undefined);
+
+ const { lastFrame } = await render(
+ defineComponent(() => () => (
+
+ Hello
+
+ )),
+ { columns: 100 },
+ );
+
+ expect(lastFrame()).toBe("Hello");
+
+ bgColor.value = "green";
+ await nextTick();
+ expect(lastFrame()).toMatchInlineSnapshot(`"[42m[42mHello[49m[42m[49m"`);
+
+ 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(() => () => (
+
+ Hello World!!
+
+ )),
+ { columns: 100 },
+ );
+ expect(lastFrame()).toMatchInlineSnapshot(`
+ "[41m[41mHello[49m[41m [49m
+ [41m[41mWorld!![49m[41m [49m"
+ `);
+});
+
+test("Text-only backgroundColor colors text content but does not fill Box width", async () => {
+ const { lastFrame } = await render(
+ defineComponent(() => () => (
+
+ Hello World!!
+
+ )),
+ { columns: 100 },
+ );
+ expect(lastFrame()).toMatchInlineSnapshot(`
+ "[41mHello[49m
+ [41mWorld!![49m"
+ `);
+});
+
+// --- Ink border-backgrounds tests ---
+
+test("border with background color", async () => {
+ const { lastFrame } = await render(
+ defineComponent(() => () => (
+
+
+ Test
+
+
+ )),
+ { 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(() => () => (
+
+
+ Test
+
+
+ )),
+ { 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(() => () => (
+
+
+ Test
+
+
+ )),
+ { 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(() => () => (
+
+ Text longer than the Box width, so will definitely wrap.
+
+ )),
+ { 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(() => () => (
+
+ Hi
+
+ )),
+ { 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");
+});