/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { render, renderWithProviders } from '../../../test-utils/render.js';
import { OverflowProvider } from '../../contexts/OverflowContext.js';
import { MaxSizedBox } from './MaxSizedBox.js';
import { MarkdownDisplay } from '../../utils/MarkdownDisplay.js';
import { Box, Text } from 'ink';
import { describe, it, expect } from 'vitest';
describe('', () => {
it('renders children without truncation when they fit', async () => {
const { lastFrame, waitUntilReady, unmount } = render(
Hello, World!
,
);
await waitUntilReady();
expect(lastFrame()).toContain('Hello, World!');
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('hides lines when content exceeds maxHeight', async () => {
const { lastFrame, waitUntilReady, unmount } = render(
Line 1
Line 2
Line 3
,
);
await waitUntilReady();
expect(lastFrame()).toContain(
'... first 2 lines hidden (Ctrl+O to show) ...',
);
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('hides lines at the end when content exceeds maxHeight and overflowDirection is bottom', async () => {
const { lastFrame, waitUntilReady, unmount } = render(
Line 1
Line 2
Line 3
,
);
await waitUntilReady();
expect(lastFrame()).toContain(
'... last 2 lines hidden (Ctrl+O to show) ...',
);
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('shows plural "lines" when more than one line is hidden', async () => {
const { lastFrame, waitUntilReady, unmount } = render(
Line 1
Line 2
Line 3
,
);
await waitUntilReady();
expect(lastFrame()).toContain(
'... first 2 lines hidden (Ctrl+O to show) ...',
);
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('shows singular "line" when exactly one line is hidden', async () => {
const { lastFrame, waitUntilReady, unmount } = render(
Line 1
,
);
await waitUntilReady();
expect(lastFrame()).toContain(
'... first 1 line hidden (Ctrl+O to show) ...',
);
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('accounts for additionalHiddenLinesCount', async () => {
const { lastFrame, waitUntilReady, unmount } = render(
Line 1
Line 2
Line 3
,
);
await waitUntilReady();
expect(lastFrame()).toContain(
'... first 7 lines hidden (Ctrl+O to show) ...',
);
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('wraps text that exceeds maxWidth', async () => {
const { lastFrame, waitUntilReady, unmount } = render(
This is a long line of text
,
);
await waitUntilReady();
expect(lastFrame()).toContain('This is a');
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('does not truncate when maxHeight is undefined', async () => {
const { lastFrame, waitUntilReady, unmount } = render(
Line 1
Line 2
,
);
await waitUntilReady();
expect(lastFrame()).toContain('Line 1');
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('renders an empty box for empty children', async () => {
const { lastFrame, waitUntilReady, unmount } = render(
,
);
await waitUntilReady();
expect(lastFrame({ allowEmpty: true })?.trim()).equals('');
unmount();
});
it('handles React.Fragment as a child', async () => {
const { lastFrame, waitUntilReady, unmount } = render(
<>
Line 1 from Fragment
Line 2 from Fragment
>
Line 3 direct child
,
);
await waitUntilReady();
expect(lastFrame()).toContain('Line 1 from Fragment');
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('clips a long single text child from the top', async () => {
const THIRTY_LINES = Array.from(
{ length: 30 },
(_, i) => `Line ${i + 1}`,
).join('\n');
const { lastFrame, waitUntilReady, unmount } = render(
{THIRTY_LINES}
,
);
await waitUntilReady();
expect(lastFrame()).toContain(
'... first 21 lines hidden (Ctrl+O to show) ...',
);
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('clips a long single text child from the bottom', async () => {
const THIRTY_LINES = Array.from(
{ length: 30 },
(_, i) => `Line ${i + 1}`,
).join('\n');
const { lastFrame, waitUntilReady, unmount } = render(
{THIRTY_LINES}
,
);
await waitUntilReady();
expect(lastFrame()).toContain(
'... last 21 lines hidden (Ctrl+O to show) ...',
);
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('does not leak content after hidden indicator with bottom overflow', async () => {
const markdownContent = Array.from(
{ length: 20 },
(_, i) => `- Step ${i + 1}: Do something important`,
).join('\n');
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
,
{ width: 80 },
);
await waitUntilReady();
expect(lastFrame()).toContain('... last');
const frame = lastFrame();
const lines = frame.trim().split('\n');
const lastLine = lines[lines.length - 1];
// The last line should only contain the hidden indicator, no leaked content
expect(lastLine).toMatch(
/^\.\.\. last \d+ lines? hidden \(Ctrl\+O to show\) \.\.\.$/,
);
expect(lastFrame()).toMatchSnapshot();
unmount();
});
});