Files
vue-tui/packages/runtime-tests/integration/layout/position.test.tsx
T
Yunfei He 5a1f2eba2d fix: trim trailing whitespace per line in paint output to match Ink behavior
Ink's Output.get() calls .trimEnd() on each line of the rendered grid.
Without this, vue-tui's paint grid always fills the full root width with
spaces, causing raw frame output to contain 100-char-wide padding. This
broke tests using { raw: true } and made meaningful trailing-space
assertions impossible with { trimLines: true }.

Also corrects test assertions for 'set width in percent' and 'relative
position' that expected trailing spaces which Ink never produces.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 22:36:48 +08:00

199 lines
5.5 KiB
TypeScript

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";
test("absolute position with top and left offsets", async () => {
const { lastFrame } = await render(
defineComponent(() => () => (
<Box flexDirection="row" width={5} height={3}>
<Box position="absolute" top={1} left={2}>
<Text>X</Text>
</Box>
</Box>
)),
{ columns: 100 },
);
expect(lastFrame({ trimLines: true })).toBe("\n X\n");
});
test("absolute position with bottom and right offsets", async () => {
const { lastFrame } = await render(
defineComponent(() => () => (
<Box flexDirection="row" width={6} height={4}>
<Box position="absolute" bottom={1} right={1}>
<Text>X</Text>
</Box>
</Box>
)),
{ columns: 100 },
);
expect(lastFrame({ trimLines: true })).toBe("\n\n X\n");
});
test("absolute position with percentage offsets", async () => {
const { lastFrame } = await render(
defineComponent(() => () => (
<Box flexDirection="row" width={6} height={4}>
<Box position="absolute" top="50%" left="50%">
<Text>X</Text>
</Box>
</Box>
)),
{ columns: 100 },
);
expect(lastFrame({ trimLines: true })).toBe("\n\n X\n");
});
test("absolute position with percentage bottom and right offsets", async () => {
const { lastFrame } = await render(
defineComponent(() => () => (
<Box flexDirection="row" width={6} height={4}>
<Box position="absolute" bottom="50%" right="50%">
<Text>X</Text>
</Box>
</Box>
)),
{ columns: 100 },
);
expect(lastFrame({ trimLines: true })).toBe("\n X\n\n");
});
test("relative position offsets visual position while keeping flow", async () => {
const { lastFrame } = await render(
defineComponent(() => () => (
<Box flexDirection="row" width={5}>
<Box position="relative" left={2}>
<Text>A</Text>
</Box>
<Text>B</Text>
</Box>
)),
{ columns: 100 },
);
expect(lastFrame({ trimLines: true })).toBe(" BA");
});
test("static position ignores offsets", async () => {
const { lastFrame } = await render(
defineComponent(() => () => (
<Box flexDirection="row" width={5}>
<Box position="static" left={2}>
<Text>A</Text>
</Box>
<Text>B</Text>
</Box>
)),
{ columns: 100 },
);
expect(lastFrame({ trimLines: true })).toBe("AB");
});
test("static position ignores percentage offsets", async () => {
const { lastFrame } = await render(
defineComponent(() => () => (
<Box flexDirection="row" width={5}>
<Box position="static" left="50%">
<Text>A</Text>
</Box>
<Text>B</Text>
</Box>
)),
{ columns: 100 },
);
expect(lastFrame({ trimLines: true })).toBe("AB");
});
test("clears top offset on rerender", async () => {
const top = shallowRef<number | undefined>(1);
const { lastFrame } = await render(
defineComponent(() => () => (
<Box flexDirection="row" width={5} height={3}>
<Box position="absolute" top={top.value} left={2}>
<Text>X</Text>
</Box>
</Box>
)),
{ columns: 100 },
);
expect(lastFrame({ trimLines: true })).toBe("\n X\n");
top.value = undefined;
await nextTick();
expect(lastFrame({ trimLines: true })).toBe(" X\n\n");
});
test("clears percentage top and left offsets on rerender", async () => {
const top = shallowRef<string | undefined>("50%");
const left = shallowRef<string | undefined>("50%");
const { lastFrame } = await render(
defineComponent(() => () => (
<Box flexDirection="row" width={6} height={4}>
<Box position="absolute" top={top.value} left={left.value}>
<Text>X</Text>
</Box>
</Box>
)),
{ columns: 100 },
);
expect(lastFrame({ trimLines: true })).toBe("\n\n X\n");
top.value = undefined;
left.value = undefined;
await nextTick();
expect(lastFrame({ trimLines: true })).toBe("X\n\n\n");
});
test("clears percentage top and left offsets when props are omitted on rerender", async () => {
const showOffsets = shallowRef(true);
const { lastFrame } = await render(
defineComponent(() => () => (
<Box flexDirection="row" width={6} height={4}>
<Box
position="absolute"
{...(showOffsets.value ? { top: "50%" as const, left: "50%" as const } : {})}
>
<Text>X</Text>
</Box>
</Box>
)),
{ columns: 100 },
);
expect(lastFrame({ trimLines: true })).toBe("\n\n X\n");
showOffsets.value = false;
await nextTick();
expect(lastFrame({ trimLines: true })).toBe("X\n\n\n");
});
test("clears bottom and right offsets on rerender", async () => {
const bottom = shallowRef<number | undefined>(1);
const right = shallowRef<number | undefined>(1);
const { lastFrame } = await render(
defineComponent(() => () => (
<Box flexDirection="row" width={6} height={4}>
<Box position="absolute" bottom={bottom.value} right={right.value}>
<Text>X</Text>
</Box>
</Box>
)),
{ columns: 100 },
);
expect(lastFrame({ trimLines: true })).toBe("\n\n X\n");
bottom.value = undefined;
right.value = undefined;
await nextTick();
expect(lastFrame({ trimLines: true })).toBe("X\n\n\n");
});
// Skipped: absolute position with top and left offsets - concurrent