mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-24 20:14:44 -07:00
fix(ui): stop truncating output from the model rendered in <static> (#9972)
This commit is contained in:
@@ -6,17 +6,30 @@
|
|||||||
|
|
||||||
import { render } from 'ink-testing-library';
|
import { render } from 'ink-testing-library';
|
||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
|
import { LoadedSettings } from '../config/settings.js';
|
||||||
import { KeypressProvider } from '../ui/contexts/KeypressContext.js';
|
import { KeypressProvider } from '../ui/contexts/KeypressContext.js';
|
||||||
|
import { SettingsContext } from '../ui/contexts/SettingsContext.js';
|
||||||
import { ShellFocusContext } from '../ui/contexts/ShellFocusContext.js';
|
import { ShellFocusContext } from '../ui/contexts/ShellFocusContext.js';
|
||||||
|
|
||||||
|
const mockSettings = new LoadedSettings(
|
||||||
|
{ path: '', settings: {}, originalSettings: {} },
|
||||||
|
{ path: '', settings: {}, originalSettings: {} },
|
||||||
|
{ path: '', settings: {}, originalSettings: {} },
|
||||||
|
{ path: '', settings: {}, originalSettings: {} },
|
||||||
|
true,
|
||||||
|
new Set(),
|
||||||
|
);
|
||||||
|
|
||||||
export const renderWithProviders = (
|
export const renderWithProviders = (
|
||||||
component: React.ReactElement,
|
component: React.ReactElement,
|
||||||
{ shellFocus = true } = {},
|
{ shellFocus = true, settings = mockSettings } = {},
|
||||||
): ReturnType<typeof render> =>
|
): ReturnType<typeof render> =>
|
||||||
render(
|
render(
|
||||||
<ShellFocusContext.Provider value={shellFocus}>
|
<SettingsContext.Provider value={settings}>
|
||||||
<KeypressProvider kittyProtocolEnabled={true}>
|
<ShellFocusContext.Provider value={shellFocus}>
|
||||||
{component}
|
<KeypressProvider kittyProtocolEnabled={true}>
|
||||||
</KeypressProvider>
|
{component}
|
||||||
</ShellFocusContext.Provider>,
|
</KeypressProvider>
|
||||||
|
</ShellFocusContext.Provider>
|
||||||
|
</SettingsContext.Provider>,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { render } from 'ink-testing-library';
|
|
||||||
import { describe, it, expect, vi } from 'vitest';
|
import { describe, it, expect, vi } from 'vitest';
|
||||||
import { HistoryItemDisplay } from './HistoryItemDisplay.js';
|
import { HistoryItemDisplay } from './HistoryItemDisplay.js';
|
||||||
import { type HistoryItem, ToolCallStatus } from '../types.js';
|
import { type HistoryItem, ToolCallStatus } from '../types.js';
|
||||||
@@ -15,6 +14,7 @@ import type {
|
|||||||
ToolExecuteConfirmationDetails,
|
ToolExecuteConfirmationDetails,
|
||||||
} from '@google/gemini-cli-core';
|
} from '@google/gemini-cli-core';
|
||||||
import { ToolGroupMessage } from './messages/ToolGroupMessage.js';
|
import { ToolGroupMessage } from './messages/ToolGroupMessage.js';
|
||||||
|
import { renderWithProviders } from '../../test-utils/render.js';
|
||||||
|
|
||||||
// Mock child components
|
// Mock child components
|
||||||
vi.mock('./messages/ToolGroupMessage.js', () => ({
|
vi.mock('./messages/ToolGroupMessage.js', () => ({
|
||||||
@@ -37,7 +37,7 @@ describe('<HistoryItemDisplay />', () => {
|
|||||||
type: MessageType.USER,
|
type: MessageType.USER,
|
||||||
text: 'Hello',
|
text: 'Hello',
|
||||||
};
|
};
|
||||||
const { lastFrame } = render(
|
const { lastFrame } = renderWithProviders(
|
||||||
<HistoryItemDisplay {...baseItem} item={item} />,
|
<HistoryItemDisplay {...baseItem} item={item} />,
|
||||||
);
|
);
|
||||||
expect(lastFrame()).toContain('Hello');
|
expect(lastFrame()).toContain('Hello');
|
||||||
@@ -49,7 +49,7 @@ describe('<HistoryItemDisplay />', () => {
|
|||||||
type: MessageType.USER,
|
type: MessageType.USER,
|
||||||
text: '/theme',
|
text: '/theme',
|
||||||
};
|
};
|
||||||
const { lastFrame } = render(
|
const { lastFrame } = renderWithProviders(
|
||||||
<HistoryItemDisplay {...baseItem} item={item} />,
|
<HistoryItemDisplay {...baseItem} item={item} />,
|
||||||
);
|
);
|
||||||
expect(lastFrame()).toContain('/theme');
|
expect(lastFrame()).toContain('/theme');
|
||||||
@@ -61,7 +61,7 @@ describe('<HistoryItemDisplay />', () => {
|
|||||||
type: MessageType.STATS,
|
type: MessageType.STATS,
|
||||||
duration: '1s',
|
duration: '1s',
|
||||||
};
|
};
|
||||||
const { lastFrame } = render(
|
const { lastFrame } = renderWithProviders(
|
||||||
<SessionStatsProvider>
|
<SessionStatsProvider>
|
||||||
<HistoryItemDisplay {...baseItem} item={item} />
|
<HistoryItemDisplay {...baseItem} item={item} />
|
||||||
</SessionStatsProvider>,
|
</SessionStatsProvider>,
|
||||||
@@ -81,7 +81,7 @@ describe('<HistoryItemDisplay />', () => {
|
|||||||
gcpProject: 'test-project',
|
gcpProject: 'test-project',
|
||||||
ideClient: 'test-ide',
|
ideClient: 'test-ide',
|
||||||
};
|
};
|
||||||
const { lastFrame } = render(
|
const { lastFrame } = renderWithProviders(
|
||||||
<HistoryItemDisplay {...baseItem} item={item} />,
|
<HistoryItemDisplay {...baseItem} item={item} />,
|
||||||
);
|
);
|
||||||
expect(lastFrame()).toContain('About Gemini CLI');
|
expect(lastFrame()).toContain('About Gemini CLI');
|
||||||
@@ -92,7 +92,7 @@ describe('<HistoryItemDisplay />', () => {
|
|||||||
...baseItem,
|
...baseItem,
|
||||||
type: 'model_stats',
|
type: 'model_stats',
|
||||||
};
|
};
|
||||||
const { lastFrame } = render(
|
const { lastFrame } = renderWithProviders(
|
||||||
<SessionStatsProvider>
|
<SessionStatsProvider>
|
||||||
<HistoryItemDisplay {...baseItem} item={item} />
|
<HistoryItemDisplay {...baseItem} item={item} />
|
||||||
</SessionStatsProvider>,
|
</SessionStatsProvider>,
|
||||||
@@ -107,7 +107,7 @@ describe('<HistoryItemDisplay />', () => {
|
|||||||
...baseItem,
|
...baseItem,
|
||||||
type: 'tool_stats',
|
type: 'tool_stats',
|
||||||
};
|
};
|
||||||
const { lastFrame } = render(
|
const { lastFrame } = renderWithProviders(
|
||||||
<SessionStatsProvider>
|
<SessionStatsProvider>
|
||||||
<HistoryItemDisplay {...baseItem} item={item} />
|
<HistoryItemDisplay {...baseItem} item={item} />
|
||||||
</SessionStatsProvider>,
|
</SessionStatsProvider>,
|
||||||
@@ -123,7 +123,7 @@ describe('<HistoryItemDisplay />', () => {
|
|||||||
type: 'quit',
|
type: 'quit',
|
||||||
duration: '1s',
|
duration: '1s',
|
||||||
};
|
};
|
||||||
const { lastFrame } = render(
|
const { lastFrame } = renderWithProviders(
|
||||||
<SessionStatsProvider>
|
<SessionStatsProvider>
|
||||||
<HistoryItemDisplay {...baseItem} item={item} />
|
<HistoryItemDisplay {...baseItem} item={item} />
|
||||||
</SessionStatsProvider>,
|
</SessionStatsProvider>,
|
||||||
@@ -138,7 +138,7 @@ describe('<HistoryItemDisplay />', () => {
|
|||||||
text: 'Hello, \u001b[31mred\u001b[0m world!',
|
text: 'Hello, \u001b[31mred\u001b[0m world!',
|
||||||
};
|
};
|
||||||
|
|
||||||
const { lastFrame } = render(
|
const { lastFrame } = renderWithProviders(
|
||||||
<HistoryItemDisplay
|
<HistoryItemDisplay
|
||||||
item={historyItem}
|
item={historyItem}
|
||||||
terminalWidth={80}
|
terminalWidth={80}
|
||||||
@@ -174,7 +174,7 @@ describe('<HistoryItemDisplay />', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
render(
|
renderWithProviders(
|
||||||
<HistoryItemDisplay
|
<HistoryItemDisplay
|
||||||
item={historyItem}
|
item={historyItem}
|
||||||
terminalWidth={80}
|
terminalWidth={80}
|
||||||
@@ -190,4 +190,84 @@ describe('<HistoryItemDisplay />', () => {
|
|||||||
'echo "\\u001b[31mhello\\u001b[0m"',
|
'echo "\\u001b[31mhello\\u001b[0m"',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const longCode =
|
||||||
|
'# Example code block:\n' +
|
||||||
|
'```python\n' +
|
||||||
|
Array.from({ length: 50 }, (_, i) => `Line ${i + 1}`).join('\n') +
|
||||||
|
'\n```';
|
||||||
|
|
||||||
|
it('should render a truncated gemini item', () => {
|
||||||
|
const item: HistoryItem = {
|
||||||
|
id: 1,
|
||||||
|
type: 'gemini',
|
||||||
|
text: longCode,
|
||||||
|
};
|
||||||
|
const { lastFrame } = renderWithProviders(
|
||||||
|
<HistoryItemDisplay
|
||||||
|
item={item}
|
||||||
|
isPending={false}
|
||||||
|
terminalWidth={80}
|
||||||
|
availableTerminalHeight={10}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(lastFrame()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render a full gemini item when using availableTerminalHeightGemini', () => {
|
||||||
|
const item: HistoryItem = {
|
||||||
|
id: 1,
|
||||||
|
type: 'gemini',
|
||||||
|
text: longCode,
|
||||||
|
};
|
||||||
|
const { lastFrame } = renderWithProviders(
|
||||||
|
<HistoryItemDisplay
|
||||||
|
item={item}
|
||||||
|
isPending={false}
|
||||||
|
terminalWidth={80}
|
||||||
|
availableTerminalHeight={10}
|
||||||
|
availableTerminalHeightGemini={Number.MAX_SAFE_INTEGER}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(lastFrame()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render a truncated gemini_content item', () => {
|
||||||
|
const item: HistoryItem = {
|
||||||
|
id: 1,
|
||||||
|
type: 'gemini_content',
|
||||||
|
text: longCode,
|
||||||
|
};
|
||||||
|
const { lastFrame } = renderWithProviders(
|
||||||
|
<HistoryItemDisplay
|
||||||
|
item={item}
|
||||||
|
isPending={false}
|
||||||
|
terminalWidth={80}
|
||||||
|
availableTerminalHeight={10}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(lastFrame()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render a full gemini_content item when using availableTerminalHeightGemini', () => {
|
||||||
|
const item: HistoryItem = {
|
||||||
|
id: 1,
|
||||||
|
type: 'gemini_content',
|
||||||
|
text: longCode,
|
||||||
|
};
|
||||||
|
const { lastFrame } = renderWithProviders(
|
||||||
|
<HistoryItemDisplay
|
||||||
|
item={item}
|
||||||
|
isPending={false}
|
||||||
|
terminalWidth={80}
|
||||||
|
availableTerminalHeight={10}
|
||||||
|
availableTerminalHeightGemini={Number.MAX_SAFE_INTEGER}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(lastFrame()).toMatchSnapshot();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ interface HistoryItemDisplayProps {
|
|||||||
commands?: readonly SlashCommand[];
|
commands?: readonly SlashCommand[];
|
||||||
activeShellPtyId?: number | null;
|
activeShellPtyId?: number | null;
|
||||||
embeddedShellFocused?: boolean;
|
embeddedShellFocused?: boolean;
|
||||||
|
availableTerminalHeightGemini?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HistoryItemDisplay: React.FC<HistoryItemDisplayProps> = ({
|
export const HistoryItemDisplay: React.FC<HistoryItemDisplayProps> = ({
|
||||||
@@ -47,6 +48,7 @@ export const HistoryItemDisplay: React.FC<HistoryItemDisplayProps> = ({
|
|||||||
isFocused = true,
|
isFocused = true,
|
||||||
activeShellPtyId,
|
activeShellPtyId,
|
||||||
embeddedShellFocused,
|
embeddedShellFocused,
|
||||||
|
availableTerminalHeightGemini,
|
||||||
}) => {
|
}) => {
|
||||||
const itemForDisplay = useMemo(() => escapeAnsiCtrlCodes(item), [item]);
|
const itemForDisplay = useMemo(() => escapeAnsiCtrlCodes(item), [item]);
|
||||||
|
|
||||||
@@ -63,7 +65,9 @@ export const HistoryItemDisplay: React.FC<HistoryItemDisplayProps> = ({
|
|||||||
<GeminiMessage
|
<GeminiMessage
|
||||||
text={itemForDisplay.text}
|
text={itemForDisplay.text}
|
||||||
isPending={isPending}
|
isPending={isPending}
|
||||||
availableTerminalHeight={availableTerminalHeight}
|
availableTerminalHeight={
|
||||||
|
availableTerminalHeightGemini ?? availableTerminalHeight
|
||||||
|
}
|
||||||
terminalWidth={terminalWidth}
|
terminalWidth={terminalWidth}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -71,7 +75,9 @@ export const HistoryItemDisplay: React.FC<HistoryItemDisplayProps> = ({
|
|||||||
<GeminiMessageContent
|
<GeminiMessageContent
|
||||||
text={itemForDisplay.text}
|
text={itemForDisplay.text}
|
||||||
isPending={isPending}
|
isPending={isPending}
|
||||||
availableTerminalHeight={availableTerminalHeight}
|
availableTerminalHeight={
|
||||||
|
availableTerminalHeightGemini ?? availableTerminalHeight
|
||||||
|
}
|
||||||
terminalWidth={terminalWidth}
|
terminalWidth={terminalWidth}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -12,6 +12,12 @@ import { useUIState } from '../contexts/UIStateContext.js';
|
|||||||
import { useAppContext } from '../contexts/AppContext.js';
|
import { useAppContext } from '../contexts/AppContext.js';
|
||||||
import { AppHeader } from './AppHeader.js';
|
import { AppHeader } from './AppHeader.js';
|
||||||
|
|
||||||
|
// Limit Gemini messages to a very high number of lines to mitigate performance
|
||||||
|
// issues in the worst case if we somehow get an enormous response from Gemini.
|
||||||
|
// This threshold is arbitrary but should be high enough to never impact normal
|
||||||
|
// usage.
|
||||||
|
const MAX_GEMINI_MESSAGE_LINES = 65536;
|
||||||
|
|
||||||
export const MainContent = () => {
|
export const MainContent = () => {
|
||||||
const { version } = useAppContext();
|
const { version } = useAppContext();
|
||||||
const uiState = useUIState();
|
const uiState = useUIState();
|
||||||
@@ -32,6 +38,7 @@ export const MainContent = () => {
|
|||||||
<HistoryItemDisplay
|
<HistoryItemDisplay
|
||||||
terminalWidth={mainAreaWidth}
|
terminalWidth={mainAreaWidth}
|
||||||
availableTerminalHeight={staticAreaMaxItemHeight}
|
availableTerminalHeight={staticAreaMaxItemHeight}
|
||||||
|
availableTerminalHeightGemini={MAX_GEMINI_MESSAGE_LINES}
|
||||||
key={h.id}
|
key={h.id}
|
||||||
item={h}
|
item={h}
|
||||||
isPending={false}
|
isPending={false}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import { usePermissionsModifyTrust } from '../hooks/usePermissionsModifyTrust.js
|
|||||||
const mockedCwd = vi.hoisted(() => vi.fn());
|
const mockedCwd = vi.hoisted(() => vi.fn());
|
||||||
const mockedLoadTrustedFolders = vi.hoisted(() => vi.fn());
|
const mockedLoadTrustedFolders = vi.hoisted(() => vi.fn());
|
||||||
const mockedIsWorkspaceTrusted = vi.hoisted(() => vi.fn());
|
const mockedIsWorkspaceTrusted = vi.hoisted(() => vi.fn());
|
||||||
const mockedUseSettings = vi.hoisted(() => vi.fn());
|
|
||||||
|
|
||||||
// Mock the modules themselves
|
// Mock the modules themselves
|
||||||
vi.mock('node:process', async (importOriginal) => {
|
vi.mock('node:process', async (importOriginal) => {
|
||||||
@@ -40,10 +39,6 @@ vi.mock('../../config/trustedFolders.js', () => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../contexts/SettingsContext.js', () => ({
|
|
||||||
useSettings: mockedUseSettings,
|
|
||||||
}));
|
|
||||||
|
|
||||||
vi.mock('../hooks/usePermissionsModifyTrust.js');
|
vi.mock('../hooks/usePermissionsModifyTrust.js');
|
||||||
|
|
||||||
describe('PermissionsModifyTrustDialog', () => {
|
describe('PermissionsModifyTrustDialog', () => {
|
||||||
|
|||||||
@@ -0,0 +1,137 @@
|
|||||||
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
|
exports[`<HistoryItemDisplay /> > should render a full gemini item when using availableTerminalHeightGemini 1`] = `
|
||||||
|
"✦ Example code block:
|
||||||
|
1 Line 1
|
||||||
|
2 Line 2
|
||||||
|
3 Line 3
|
||||||
|
4 Line 4
|
||||||
|
5 Line 5
|
||||||
|
6 Line 6
|
||||||
|
7 Line 7
|
||||||
|
8 Line 8
|
||||||
|
9 Line 9
|
||||||
|
10 Line 10
|
||||||
|
11 Line 11
|
||||||
|
12 Line 12
|
||||||
|
13 Line 13
|
||||||
|
14 Line 14
|
||||||
|
15 Line 15
|
||||||
|
16 Line 16
|
||||||
|
17 Line 17
|
||||||
|
18 Line 18
|
||||||
|
19 Line 19
|
||||||
|
20 Line 20
|
||||||
|
21 Line 21
|
||||||
|
22 Line 22
|
||||||
|
23 Line 23
|
||||||
|
24 Line 24
|
||||||
|
25 Line 25
|
||||||
|
26 Line 26
|
||||||
|
27 Line 27
|
||||||
|
28 Line 28
|
||||||
|
29 Line 29
|
||||||
|
30 Line 30
|
||||||
|
31 Line 31
|
||||||
|
32 Line 32
|
||||||
|
33 Line 33
|
||||||
|
34 Line 34
|
||||||
|
35 Line 35
|
||||||
|
36 Line 36
|
||||||
|
37 Line 37
|
||||||
|
38 Line 38
|
||||||
|
39 Line 39
|
||||||
|
40 Line 40
|
||||||
|
41 Line 41
|
||||||
|
42 Line 42
|
||||||
|
43 Line 43
|
||||||
|
44 Line 44
|
||||||
|
45 Line 45
|
||||||
|
46 Line 46
|
||||||
|
47 Line 47
|
||||||
|
48 Line 48
|
||||||
|
49 Line 49
|
||||||
|
50 Line 50"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<HistoryItemDisplay /> > should render a full gemini_content item when using availableTerminalHeightGemini 1`] = `
|
||||||
|
" Example code block:
|
||||||
|
1 Line 1
|
||||||
|
2 Line 2
|
||||||
|
3 Line 3
|
||||||
|
4 Line 4
|
||||||
|
5 Line 5
|
||||||
|
6 Line 6
|
||||||
|
7 Line 7
|
||||||
|
8 Line 8
|
||||||
|
9 Line 9
|
||||||
|
10 Line 10
|
||||||
|
11 Line 11
|
||||||
|
12 Line 12
|
||||||
|
13 Line 13
|
||||||
|
14 Line 14
|
||||||
|
15 Line 15
|
||||||
|
16 Line 16
|
||||||
|
17 Line 17
|
||||||
|
18 Line 18
|
||||||
|
19 Line 19
|
||||||
|
20 Line 20
|
||||||
|
21 Line 21
|
||||||
|
22 Line 22
|
||||||
|
23 Line 23
|
||||||
|
24 Line 24
|
||||||
|
25 Line 25
|
||||||
|
26 Line 26
|
||||||
|
27 Line 27
|
||||||
|
28 Line 28
|
||||||
|
29 Line 29
|
||||||
|
30 Line 30
|
||||||
|
31 Line 31
|
||||||
|
32 Line 32
|
||||||
|
33 Line 33
|
||||||
|
34 Line 34
|
||||||
|
35 Line 35
|
||||||
|
36 Line 36
|
||||||
|
37 Line 37
|
||||||
|
38 Line 38
|
||||||
|
39 Line 39
|
||||||
|
40 Line 40
|
||||||
|
41 Line 41
|
||||||
|
42 Line 42
|
||||||
|
43 Line 43
|
||||||
|
44 Line 44
|
||||||
|
45 Line 45
|
||||||
|
46 Line 46
|
||||||
|
47 Line 47
|
||||||
|
48 Line 48
|
||||||
|
49 Line 49
|
||||||
|
50 Line 50"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<HistoryItemDisplay /> > should render a truncated gemini item 1`] = `
|
||||||
|
"✦ Example code block:
|
||||||
|
... first 41 lines hidden ...
|
||||||
|
42 Line 42
|
||||||
|
43 Line 43
|
||||||
|
44 Line 44
|
||||||
|
45 Line 45
|
||||||
|
46 Line 46
|
||||||
|
47 Line 47
|
||||||
|
48 Line 48
|
||||||
|
49 Line 49
|
||||||
|
50 Line 50"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<HistoryItemDisplay /> > should render a truncated gemini_content item 1`] = `
|
||||||
|
" Example code block:
|
||||||
|
... first 41 lines hidden ...
|
||||||
|
42 Line 42
|
||||||
|
43 Line 43
|
||||||
|
44 Line 44
|
||||||
|
45 Line 45
|
||||||
|
46 Line 46
|
||||||
|
47 Line 47
|
||||||
|
48 Line 48
|
||||||
|
49 Line 49
|
||||||
|
50 Line 50"
|
||||||
|
`;
|
||||||
@@ -4,12 +4,11 @@
|
|||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { render } from 'ink-testing-library';
|
|
||||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
import { MarkdownDisplay } from './MarkdownDisplay.js';
|
import { MarkdownDisplay } from './MarkdownDisplay.js';
|
||||||
import { LoadedSettings } from '../../config/settings.js';
|
import { LoadedSettings } from '../../config/settings.js';
|
||||||
import { SettingsContext } from '../contexts/SettingsContext.js';
|
|
||||||
import { EOL } from 'node:os';
|
import { EOL } from 'node:os';
|
||||||
|
import { renderWithProviders } from '../../test-utils/render.js';
|
||||||
|
|
||||||
describe('<MarkdownDisplay />', () => {
|
describe('<MarkdownDisplay />', () => {
|
||||||
const baseProps = {
|
const baseProps = {
|
||||||
@@ -18,34 +17,21 @@ describe('<MarkdownDisplay />', () => {
|
|||||||
availableTerminalHeight: 40,
|
availableTerminalHeight: 40,
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockSettings = new LoadedSettings(
|
|
||||||
{ path: '', settings: {}, originalSettings: {} },
|
|
||||||
{ path: '', settings: {}, originalSettings: {} },
|
|
||||||
{ path: '', settings: {}, originalSettings: {} },
|
|
||||||
{ path: '', settings: {}, originalSettings: {} },
|
|
||||||
true,
|
|
||||||
new Set(),
|
|
||||||
);
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders nothing for empty text', () => {
|
it('renders nothing for empty text', () => {
|
||||||
const { lastFrame } = render(
|
const { lastFrame } = renderWithProviders(
|
||||||
<SettingsContext.Provider value={mockSettings}>
|
<MarkdownDisplay {...baseProps} text="" />,
|
||||||
<MarkdownDisplay {...baseProps} text="" />
|
|
||||||
</SettingsContext.Provider>,
|
|
||||||
);
|
);
|
||||||
expect(lastFrame()).toMatchSnapshot();
|
expect(lastFrame()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders a simple paragraph', () => {
|
it('renders a simple paragraph', () => {
|
||||||
const text = 'Hello, world.';
|
const text = 'Hello, world.';
|
||||||
const { lastFrame } = render(
|
const { lastFrame } = renderWithProviders(
|
||||||
<SettingsContext.Provider value={mockSettings}>
|
<MarkdownDisplay {...baseProps} text={text} />,
|
||||||
<MarkdownDisplay {...baseProps} text={text} />
|
|
||||||
</SettingsContext.Provider>,
|
|
||||||
);
|
);
|
||||||
expect(lastFrame()).toMatchSnapshot();
|
expect(lastFrame()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
@@ -57,10 +43,8 @@ describe('<MarkdownDisplay />', () => {
|
|||||||
### Header 3
|
### Header 3
|
||||||
#### Header 4
|
#### Header 4
|
||||||
`.replace(/\n/g, EOL);
|
`.replace(/\n/g, EOL);
|
||||||
const { lastFrame } = render(
|
const { lastFrame } = renderWithProviders(
|
||||||
<SettingsContext.Provider value={mockSettings}>
|
<MarkdownDisplay {...baseProps} text={text} />,
|
||||||
<MarkdownDisplay {...baseProps} text={text} />
|
|
||||||
</SettingsContext.Provider>,
|
|
||||||
);
|
);
|
||||||
expect(lastFrame()).toMatchSnapshot();
|
expect(lastFrame()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
@@ -70,30 +54,24 @@ describe('<MarkdownDisplay />', () => {
|
|||||||
/\n/g,
|
/\n/g,
|
||||||
EOL,
|
EOL,
|
||||||
);
|
);
|
||||||
const { lastFrame } = render(
|
const { lastFrame } = renderWithProviders(
|
||||||
<SettingsContext.Provider value={mockSettings}>
|
<MarkdownDisplay {...baseProps} text={text} />,
|
||||||
<MarkdownDisplay {...baseProps} text={text} />
|
|
||||||
</SettingsContext.Provider>,
|
|
||||||
);
|
);
|
||||||
expect(lastFrame()).toMatchSnapshot();
|
expect(lastFrame()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders a fenced code block without a language', () => {
|
it('renders a fenced code block without a language', () => {
|
||||||
const text = '```\nplain text\n```'.replace(/\n/g, EOL);
|
const text = '```\nplain text\n```'.replace(/\n/g, EOL);
|
||||||
const { lastFrame } = render(
|
const { lastFrame } = renderWithProviders(
|
||||||
<SettingsContext.Provider value={mockSettings}>
|
<MarkdownDisplay {...baseProps} text={text} />,
|
||||||
<MarkdownDisplay {...baseProps} text={text} />
|
|
||||||
</SettingsContext.Provider>,
|
|
||||||
);
|
);
|
||||||
expect(lastFrame()).toMatchSnapshot();
|
expect(lastFrame()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles unclosed (pending) code blocks', () => {
|
it('handles unclosed (pending) code blocks', () => {
|
||||||
const text = '```typescript\nlet y = 2;'.replace(/\n/g, EOL);
|
const text = '```typescript\nlet y = 2;'.replace(/\n/g, EOL);
|
||||||
const { lastFrame } = render(
|
const { lastFrame } = renderWithProviders(
|
||||||
<SettingsContext.Provider value={mockSettings}>
|
<MarkdownDisplay {...baseProps} text={text} isPending={true} />,
|
||||||
<MarkdownDisplay {...baseProps} text={text} isPending={true} />
|
|
||||||
</SettingsContext.Provider>,
|
|
||||||
);
|
);
|
||||||
expect(lastFrame()).toMatchSnapshot();
|
expect(lastFrame()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
@@ -104,10 +82,8 @@ describe('<MarkdownDisplay />', () => {
|
|||||||
* item B
|
* item B
|
||||||
+ item C
|
+ item C
|
||||||
`.replace(/\n/g, EOL);
|
`.replace(/\n/g, EOL);
|
||||||
const { lastFrame } = render(
|
const { lastFrame } = renderWithProviders(
|
||||||
<SettingsContext.Provider value={mockSettings}>
|
<MarkdownDisplay {...baseProps} text={text} />,
|
||||||
<MarkdownDisplay {...baseProps} text={text} />
|
|
||||||
</SettingsContext.Provider>,
|
|
||||||
);
|
);
|
||||||
expect(lastFrame()).toMatchSnapshot();
|
expect(lastFrame()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
@@ -118,10 +94,8 @@ describe('<MarkdownDisplay />', () => {
|
|||||||
* Level 2
|
* Level 2
|
||||||
* Level 3
|
* Level 3
|
||||||
`.replace(/\n/g, EOL);
|
`.replace(/\n/g, EOL);
|
||||||
const { lastFrame } = render(
|
const { lastFrame } = renderWithProviders(
|
||||||
<SettingsContext.Provider value={mockSettings}>
|
<MarkdownDisplay {...baseProps} text={text} />,
|
||||||
<MarkdownDisplay {...baseProps} text={text} />
|
|
||||||
</SettingsContext.Provider>,
|
|
||||||
);
|
);
|
||||||
expect(lastFrame()).toMatchSnapshot();
|
expect(lastFrame()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
@@ -131,10 +105,8 @@ describe('<MarkdownDisplay />', () => {
|
|||||||
1. First item
|
1. First item
|
||||||
2. Second item
|
2. Second item
|
||||||
`.replace(/\n/g, EOL);
|
`.replace(/\n/g, EOL);
|
||||||
const { lastFrame } = render(
|
const { lastFrame } = renderWithProviders(
|
||||||
<SettingsContext.Provider value={mockSettings}>
|
<MarkdownDisplay {...baseProps} text={text} />,
|
||||||
<MarkdownDisplay {...baseProps} text={text} />
|
|
||||||
</SettingsContext.Provider>,
|
|
||||||
);
|
);
|
||||||
expect(lastFrame()).toMatchSnapshot();
|
expect(lastFrame()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
@@ -147,10 +119,8 @@ World
|
|||||||
***
|
***
|
||||||
Test
|
Test
|
||||||
`.replace(/\n/g, EOL);
|
`.replace(/\n/g, EOL);
|
||||||
const { lastFrame } = render(
|
const { lastFrame } = renderWithProviders(
|
||||||
<SettingsContext.Provider value={mockSettings}>
|
<MarkdownDisplay {...baseProps} text={text} />,
|
||||||
<MarkdownDisplay {...baseProps} text={text} />
|
|
||||||
</SettingsContext.Provider>,
|
|
||||||
);
|
);
|
||||||
expect(lastFrame()).toMatchSnapshot();
|
expect(lastFrame()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
@@ -162,10 +132,8 @@ Test
|
|||||||
| Cell 1 | Cell 2 |
|
| Cell 1 | Cell 2 |
|
||||||
| Cell 3 | Cell 4 |
|
| Cell 3 | Cell 4 |
|
||||||
`.replace(/\n/g, EOL);
|
`.replace(/\n/g, EOL);
|
||||||
const { lastFrame } = render(
|
const { lastFrame } = renderWithProviders(
|
||||||
<SettingsContext.Provider value={mockSettings}>
|
<MarkdownDisplay {...baseProps} text={text} />,
|
||||||
<MarkdownDisplay {...baseProps} text={text} />
|
|
||||||
</SettingsContext.Provider>,
|
|
||||||
);
|
);
|
||||||
expect(lastFrame()).toMatchSnapshot();
|
expect(lastFrame()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
@@ -176,10 +144,8 @@ Some text before.
|
|||||||
| A | B |
|
| A | B |
|
||||||
|---|
|
|---|
|
||||||
| 1 | 2 |`.replace(/\n/g, EOL);
|
| 1 | 2 |`.replace(/\n/g, EOL);
|
||||||
const { lastFrame } = render(
|
const { lastFrame } = renderWithProviders(
|
||||||
<SettingsContext.Provider value={mockSettings}>
|
<MarkdownDisplay {...baseProps} text={text} />,
|
||||||
<MarkdownDisplay {...baseProps} text={text} />
|
|
||||||
</SettingsContext.Provider>,
|
|
||||||
);
|
);
|
||||||
expect(lastFrame()).toMatchSnapshot();
|
expect(lastFrame()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
@@ -188,10 +154,8 @@ Some text before.
|
|||||||
const text = `Paragraph 1.
|
const text = `Paragraph 1.
|
||||||
|
|
||||||
Paragraph 2.`.replace(/\n/g, EOL);
|
Paragraph 2.`.replace(/\n/g, EOL);
|
||||||
const { lastFrame } = render(
|
const { lastFrame } = renderWithProviders(
|
||||||
<SettingsContext.Provider value={mockSettings}>
|
<MarkdownDisplay {...baseProps} text={text} />,
|
||||||
<MarkdownDisplay {...baseProps} text={text} />
|
|
||||||
</SettingsContext.Provider>,
|
|
||||||
);
|
);
|
||||||
expect(lastFrame()).toMatchSnapshot();
|
expect(lastFrame()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
@@ -211,10 +175,8 @@ some code
|
|||||||
|
|
||||||
Another paragraph.
|
Another paragraph.
|
||||||
`.replace(/\n/g, EOL);
|
`.replace(/\n/g, EOL);
|
||||||
const { lastFrame } = render(
|
const { lastFrame } = renderWithProviders(
|
||||||
<SettingsContext.Provider value={mockSettings}>
|
<MarkdownDisplay {...baseProps} text={text} />,
|
||||||
<MarkdownDisplay {...baseProps} text={text} />
|
|
||||||
</SettingsContext.Provider>,
|
|
||||||
);
|
);
|
||||||
expect(lastFrame()).toMatchSnapshot();
|
expect(lastFrame()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
@@ -234,10 +196,9 @@ Another paragraph.
|
|||||||
new Set(),
|
new Set(),
|
||||||
);
|
);
|
||||||
|
|
||||||
const { lastFrame } = render(
|
const { lastFrame } = renderWithProviders(
|
||||||
<SettingsContext.Provider value={settings}>
|
<MarkdownDisplay {...baseProps} text={text} />,
|
||||||
<MarkdownDisplay {...baseProps} text={text} />
|
{ settings },
|
||||||
</SettingsContext.Provider>,
|
|
||||||
);
|
);
|
||||||
expect(lastFrame()).toMatchSnapshot();
|
expect(lastFrame()).toMatchSnapshot();
|
||||||
expect(lastFrame()).not.toContain(' 1 ');
|
expect(lastFrame()).not.toContain(' 1 ');
|
||||||
@@ -245,10 +206,8 @@ Another paragraph.
|
|||||||
|
|
||||||
it('shows line numbers in code blocks by default', () => {
|
it('shows line numbers in code blocks by default', () => {
|
||||||
const text = '```javascript\nconst x = 1;\n```'.replace(/\n/g, EOL);
|
const text = '```javascript\nconst x = 1;\n```'.replace(/\n/g, EOL);
|
||||||
const { lastFrame } = render(
|
const { lastFrame } = renderWithProviders(
|
||||||
<SettingsContext.Provider value={mockSettings}>
|
<MarkdownDisplay {...baseProps} text={text} />,
|
||||||
<MarkdownDisplay {...baseProps} text={text} />
|
|
||||||
</SettingsContext.Provider>,
|
|
||||||
);
|
);
|
||||||
expect(lastFrame()).toMatchSnapshot();
|
expect(lastFrame()).toMatchSnapshot();
|
||||||
expect(lastFrame()).toContain(' 1 ');
|
expect(lastFrame()).toContain(' 1 ');
|
||||||
|
|||||||
Reference in New Issue
Block a user