Migrate core render util to use xterm.js as part of the rendering loop. (#19044)

This commit is contained in:
Jacob Richman
2026-02-18 16:46:50 -08:00
committed by GitHub
parent 04c52513e7
commit 04f65f3d55
213 changed files with 7065 additions and 3852 deletions
+103 -100
View File
@@ -245,13 +245,13 @@ const createMockConfig = (overrides = {}): Config =>
...overrides,
}) as unknown as Config;
const renderComposer = (
const renderComposer = async (
uiState: UIState,
settings = createMockSettings(),
config = createMockConfig(),
uiActions = createMockUIActions(),
) =>
render(
) => {
const result = render(
<ConfigContext.Provider value={config as unknown as Config}>
<SettingsContext.Provider value={settings as unknown as LoadedSettings}>
<UIStateContext.Provider value={uiState}>
@@ -262,6 +262,9 @@ const renderComposer = (
</SettingsContext.Provider>
</ConfigContext.Provider>,
);
await result.waitUntilReady();
return result;
};
describe('Composer', () => {
beforeEach(() => {
@@ -274,20 +277,20 @@ describe('Composer', () => {
});
describe('Footer Display Settings', () => {
it('renders Footer by default when hideFooter is false', () => {
it('renders Footer by default when hideFooter is false', async () => {
const uiState = createMockUIState();
const settings = createMockSettings({ ui: { hideFooter: false } });
const { lastFrame } = renderComposer(uiState, settings);
const { lastFrame } = await renderComposer(uiState, settings);
expect(lastFrame()).toContain('Footer');
});
it('does NOT render Footer when hideFooter is true', () => {
it('does NOT render Footer when hideFooter is true', async () => {
const uiState = createMockUIState();
const settings = createMockSettings({ ui: { hideFooter: true } });
const { lastFrame } = renderComposer(uiState, settings);
const { lastFrame } = await renderComposer(uiState, settings);
// Check for content that only appears IN the Footer component itself
expect(lastFrame()).not.toContain('[NORMAL]'); // Vim mode indicator
@@ -331,7 +334,7 @@ describe('Composer', () => {
setVimMode: vi.fn(),
} as unknown as ReturnType<typeof useVimMode>);
const { lastFrame } = renderComposer(uiState, settings, config);
const { lastFrame } = await renderComposer(uiState, settings, config);
expect(lastFrame()).toContain('Footer');
// Footer should be rendered with all the state passed through
@@ -339,7 +342,7 @@ describe('Composer', () => {
});
describe('Loading Indicator', () => {
it('renders LoadingIndicator with thought when streaming', () => {
it('renders LoadingIndicator with thought when streaming', async () => {
const uiState = createMockUIState({
streamingState: StreamingState.Responding,
thought: {
@@ -350,13 +353,13 @@ describe('Composer', () => {
elapsedTime: 1500,
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
const output = lastFrame();
expect(output).toContain('LoadingIndicator: Processing');
});
it('renders generic thinking text in loading indicator when full inline thinking is enabled', () => {
it('renders generic thinking text in loading indicator when full inline thinking is enabled', async () => {
const uiState = createMockUIState({
streamingState: StreamingState.Responding,
thought: {
@@ -368,27 +371,27 @@ describe('Composer', () => {
ui: { inlineThinkingMode: 'full' },
});
const { lastFrame } = renderComposer(uiState, settings);
const { lastFrame } = await renderComposer(uiState, settings);
const output = lastFrame();
expect(output).toContain('LoadingIndicator: Thinking ...');
});
it('hides shortcuts hint while loading', () => {
it('hides shortcuts hint while loading', async () => {
const uiState = createMockUIState({
streamingState: StreamingState.Responding,
elapsedTime: 1,
cleanUiDetailsVisible: false,
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
const output = lastFrame();
expect(output).toContain('LoadingIndicator');
expect(output).not.toContain('ShortcutsHint');
});
it('renders LoadingIndicator without thought when accessibility disables loading phrases', () => {
it('renders LoadingIndicator without thought when accessibility disables loading phrases', async () => {
const uiState = createMockUIState({
streamingState: StreamingState.Responding,
thought: { subject: 'Hidden', description: 'Should not show' },
@@ -397,14 +400,14 @@ describe('Composer', () => {
getAccessibility: vi.fn(() => ({ enableLoadingPhrases: false })),
});
const { lastFrame } = renderComposer(uiState, undefined, config);
const { lastFrame } = await renderComposer(uiState, undefined, config);
const output = lastFrame();
expect(output).toContain('LoadingIndicator');
expect(output).not.toContain('Should not show');
});
it('does not render LoadingIndicator when waiting for confirmation', () => {
it('does not render LoadingIndicator when waiting for confirmation', async () => {
const uiState = createMockUIState({
streamingState: StreamingState.WaitingForConfirmation,
thought: {
@@ -413,13 +416,13 @@ describe('Composer', () => {
},
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
const output = lastFrame();
expect(output).not.toContain('LoadingIndicator');
});
it('does not render LoadingIndicator when a tool confirmation is pending', () => {
it('does not render LoadingIndicator when a tool confirmation is pending', async () => {
const uiState = createMockUIState({
streamingState: StreamingState.Responding,
pendingHistoryItems: [
@@ -439,27 +442,27 @@ describe('Composer', () => {
],
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
const output = lastFrame();
expect(output).not.toContain('LoadingIndicator');
expect(output).not.toContain('esc to cancel');
});
it('renders LoadingIndicator when embedded shell is focused but background shell is visible', () => {
it('renders LoadingIndicator when embedded shell is focused but background shell is visible', async () => {
const uiState = createMockUIState({
streamingState: StreamingState.Responding,
embeddedShellFocused: true,
isBackgroundShellVisible: true,
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
const output = lastFrame();
expect(output).toContain('LoadingIndicator');
});
it('renders both LoadingIndicator and ApprovalModeIndicator when streaming in full UI mode', () => {
it('renders both LoadingIndicator and ApprovalModeIndicator when streaming in full UI mode', async () => {
const uiState = createMockUIState({
streamingState: StreamingState.Responding,
thought: {
@@ -469,21 +472,21 @@ describe('Composer', () => {
showApprovalModeIndicator: ApprovalMode.PLAN,
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
const output = lastFrame();
expect(output).toContain('LoadingIndicator: Thinking');
expect(output).toContain('ApprovalModeIndicator');
});
it('does NOT render LoadingIndicator when embedded shell is focused and background shell is NOT visible', () => {
it('does NOT render LoadingIndicator when embedded shell is focused and background shell is NOT visible', async () => {
const uiState = createMockUIState({
streamingState: StreamingState.Responding,
embeddedShellFocused: true,
isBackgroundShellVisible: false,
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
const output = lastFrame();
expect(output).not.toContain('LoadingIndicator');
@@ -491,7 +494,7 @@ describe('Composer', () => {
});
describe('Message Queue Display', () => {
it('displays queued messages when present', () => {
it('displays queued messages when present', async () => {
const uiState = createMockUIState({
messageQueue: [
'First queued message',
@@ -500,7 +503,7 @@ describe('Composer', () => {
],
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
const output = lastFrame();
expect(output).toContain('First queued message');
@@ -508,12 +511,12 @@ describe('Composer', () => {
expect(output).toContain('Third queued message');
});
it('renders QueuedMessageDisplay with empty message queue', () => {
it('renders QueuedMessageDisplay with empty message queue', async () => {
const uiState = createMockUIState({
messageQueue: [],
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
// The component should render but return null for empty queue
// This test verifies that the component receives the correct prop
@@ -523,14 +526,14 @@ describe('Composer', () => {
});
describe('Context and Status Display', () => {
it('shows StatusDisplay and ApprovalModeIndicator in normal state', () => {
it('shows StatusDisplay and ApprovalModeIndicator in normal state', async () => {
const uiState = createMockUIState({
ctrlCPressedOnce: false,
ctrlDPressedOnce: false,
showEscapePrompt: false,
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
const output = lastFrame();
expect(output).toContain('StatusDisplay');
@@ -538,12 +541,12 @@ describe('Composer', () => {
expect(output).not.toContain('ToastDisplay');
});
it('shows ToastDisplay and hides ApprovalModeIndicator when a toast is present', () => {
it('shows ToastDisplay and hides ApprovalModeIndicator when a toast is present', async () => {
const uiState = createMockUIState({
ctrlCPressedOnce: true,
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
const output = lastFrame();
expect(output).toContain('ToastDisplay');
@@ -551,7 +554,7 @@ describe('Composer', () => {
expect(output).toContain('StatusDisplay');
});
it('shows ToastDisplay for other toast types', () => {
it('shows ToastDisplay for other toast types', async () => {
const uiState = createMockUIState({
transientMessage: {
text: 'Warning',
@@ -559,7 +562,7 @@ describe('Composer', () => {
},
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
const output = lastFrame();
expect(output).toContain('ToastDisplay');
@@ -568,12 +571,12 @@ describe('Composer', () => {
});
describe('Input and Indicators', () => {
it('hides non-essential UI details in clean mode', () => {
it('hides non-essential UI details in clean mode', async () => {
const uiState = createMockUIState({
cleanUiDetailsVisible: false,
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
const output = lastFrame();
expect(output).toContain('ShortcutsHint');
@@ -583,22 +586,22 @@ describe('Composer', () => {
expect(output).not.toContain('ContextSummaryDisplay');
});
it('renders InputPrompt when input is active', () => {
it('renders InputPrompt when input is active', async () => {
const uiState = createMockUIState({
isInputActive: true,
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
expect(lastFrame()).toContain('InputPrompt');
});
it('does not render InputPrompt when input is inactive', () => {
it('does not render InputPrompt when input is inactive', async () => {
const uiState = createMockUIState({
isInputActive: false,
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
expect(lastFrame()).not.toContain('InputPrompt');
});
@@ -610,44 +613,44 @@ describe('Composer', () => {
[ApprovalMode.YOLO],
])(
'shows ApprovalModeIndicator when approval mode is %s and shell mode is inactive',
(mode) => {
async (mode) => {
const uiState = createMockUIState({
showApprovalModeIndicator: mode,
shellModeActive: false,
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
expect(lastFrame()).toMatch(/ApprovalModeIndic[\s\S]*ator/);
},
);
it('shows ShellModeIndicator when shell mode is active', () => {
it('shows ShellModeIndicator when shell mode is active', async () => {
const uiState = createMockUIState({
shellModeActive: true,
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
expect(lastFrame()).toMatch(/ShellModeIndic[\s\S]*tor/);
});
it('shows RawMarkdownIndicator when renderMarkdown is false', () => {
it('shows RawMarkdownIndicator when renderMarkdown is false', async () => {
const uiState = createMockUIState({
renderMarkdown: false,
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
expect(lastFrame()).toContain('raw markdown mode');
});
it('does not show RawMarkdownIndicator when renderMarkdown is true', () => {
it('does not show RawMarkdownIndicator when renderMarkdown is true', async () => {
const uiState = createMockUIState({
renderMarkdown: true,
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
expect(lastFrame()).not.toContain('raw markdown mode');
});
@@ -658,18 +661,18 @@ describe('Composer', () => {
[ApprovalMode.AUTO_EDIT, 'auto edit'],
])(
'shows minimal mode badge "%s" when clean UI details are hidden',
(mode, label) => {
async (mode, label) => {
const uiState = createMockUIState({
cleanUiDetailsVisible: false,
showApprovalModeIndicator: mode,
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
expect(lastFrame()).toContain(label);
},
);
it('hides minimal mode badge while loading in clean mode', () => {
it('hides minimal mode badge while loading in clean mode', async () => {
const uiState = createMockUIState({
cleanUiDetailsVisible: false,
streamingState: StreamingState.Responding,
@@ -677,14 +680,14 @@ describe('Composer', () => {
showApprovalModeIndicator: ApprovalMode.PLAN,
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
const output = lastFrame();
expect(output).toContain('LoadingIndicator');
expect(output).not.toContain('plan');
expect(output).not.toContain('ShortcutsHint');
});
it('hides minimal mode badge while action-required state is active', () => {
it('hides minimal mode badge while action-required state is active', async () => {
const uiState = createMockUIState({
cleanUiDetailsVisible: false,
showApprovalModeIndicator: ApprovalMode.PLAN,
@@ -695,26 +698,26 @@ describe('Composer', () => {
),
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
const output = lastFrame();
expect(output).not.toContain('plan');
expect(output).not.toContain('ShortcutsHint');
});
it('shows Esc rewind prompt in minimal mode without showing full UI', () => {
it('shows Esc rewind prompt in minimal mode without showing full UI', async () => {
const uiState = createMockUIState({
cleanUiDetailsVisible: false,
showEscapePrompt: true,
history: [{ id: 1, type: 'user', text: 'msg' }],
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
const output = lastFrame();
expect(output).toContain('ToastDisplay');
expect(output).not.toContain('ContextSummaryDisplay');
});
it('shows context usage bleed-through when over 60%', () => {
it('shows context usage bleed-through when over 60%', async () => {
const model = 'gemini-2.5-pro';
const uiState = createMockUIState({
cleanUiDetailsVisible: false,
@@ -734,13 +737,13 @@ describe('Composer', () => {
},
});
const { lastFrame } = renderComposer(uiState, settings);
const { lastFrame } = await renderComposer(uiState, settings);
expect(lastFrame()).toContain('%');
});
});
describe('Error Details Display', () => {
it('shows DetailedMessagesDisplay when showErrorDetails is true', () => {
it('shows DetailedMessagesDisplay when showErrorDetails is true', async () => {
const uiState = createMockUIState({
showErrorDetails: true,
filteredConsoleMessages: [
@@ -752,18 +755,18 @@ describe('Composer', () => {
],
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
expect(lastFrame()).toContain('DetailedMessagesDisplay');
expect(lastFrame()).toContain('ShowMoreLines');
});
it('does not show error details when showErrorDetails is false', () => {
it('does not show error details when showErrorDetails is false', async () => {
const uiState = createMockUIState({
showErrorDetails: false,
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
expect(lastFrame()).not.toContain('DetailedMessagesDisplay');
});
@@ -780,7 +783,7 @@ describe('Composer', () => {
setVimMode: vi.fn(),
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
expect(lastFrame()).toContain(
"InputPrompt: Press 'Esc' for NORMAL mode.",
@@ -797,7 +800,7 @@ describe('Composer', () => {
setVimMode: vi.fn(),
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
expect(lastFrame()).toContain(
"InputPrompt: Press 'i' for INSERT mode.",
@@ -806,7 +809,7 @@ describe('Composer', () => {
});
describe('Shortcuts Hint', () => {
it('hides shortcuts hint when showShortcutsHint setting is false', () => {
it('hides shortcuts hint when showShortcutsHint setting is false', async () => {
const uiState = createMockUIState();
const settings = createMockSettings({
ui: {
@@ -814,12 +817,12 @@ describe('Composer', () => {
},
});
const { lastFrame } = renderComposer(uiState, settings);
const { lastFrame } = await renderComposer(uiState, settings);
expect(lastFrame()).not.toContain('ShortcutsHint');
});
it('hides shortcuts hint when a action is required (e.g. dialog is open)', () => {
it('hides shortcuts hint when a action is required (e.g. dialog is open)', async () => {
const uiState = createMockUIState({
customDialog: (
<Box>
@@ -829,55 +832,55 @@ describe('Composer', () => {
),
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
expect(lastFrame()).not.toContain('ShortcutsHint');
});
it('keeps shortcuts hint visible when no action is required', () => {
it('keeps shortcuts hint visible when no action is required', async () => {
const uiState = createMockUIState({
cleanUiDetailsVisible: false,
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
expect(lastFrame()).toContain('ShortcutsHint');
});
it('shows shortcuts hint when full UI details are visible', () => {
it('shows shortcuts hint when full UI details are visible', async () => {
const uiState = createMockUIState({
cleanUiDetailsVisible: true,
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
expect(lastFrame()).toContain('ShortcutsHint');
});
it('hides shortcuts hint while loading in minimal mode', () => {
it('hides shortcuts hint while loading in minimal mode', async () => {
const uiState = createMockUIState({
cleanUiDetailsVisible: false,
streamingState: StreamingState.Responding,
elapsedTime: 1,
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
expect(lastFrame()).not.toContain('ShortcutsHint');
});
it('shows shortcuts help in minimal mode when toggled on', () => {
it('shows shortcuts help in minimal mode when toggled on', async () => {
const uiState = createMockUIState({
cleanUiDetailsVisible: false,
shortcutsHelpVisible: true,
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
expect(lastFrame()).toContain('ShortcutsHelp');
});
it('hides shortcuts hint when suggestions are visible above input in alternate buffer', () => {
it('hides shortcuts hint when suggestions are visible above input in alternate buffer', async () => {
composerTestControls.isAlternateBuffer = true;
composerTestControls.suggestionsVisible = true;
@@ -886,13 +889,13 @@ describe('Composer', () => {
showApprovalModeIndicator: ApprovalMode.PLAN,
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
expect(lastFrame()).not.toContain('ShortcutsHint');
expect(lastFrame()).not.toContain('plan');
});
it('hides approval mode indicator when suggestions are visible above input in alternate buffer', () => {
it('hides approval mode indicator when suggestions are visible above input in alternate buffer', async () => {
composerTestControls.isAlternateBuffer = true;
composerTestControls.suggestionsVisible = true;
@@ -901,12 +904,12 @@ describe('Composer', () => {
showApprovalModeIndicator: ApprovalMode.YOLO,
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
expect(lastFrame()).not.toContain('ApprovalModeIndicator');
});
it('keeps shortcuts hint when suggestions are visible below input in regular buffer', () => {
it('keeps shortcuts hint when suggestions are visible below input in regular buffer', async () => {
composerTestControls.isAlternateBuffer = false;
composerTestControls.suggestionsVisible = true;
@@ -914,36 +917,36 @@ describe('Composer', () => {
cleanUiDetailsVisible: false,
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
expect(lastFrame()).toContain('ShortcutsHint');
});
});
describe('Shortcuts Help', () => {
it('shows shortcuts help in passive state', () => {
it('shows shortcuts help in passive state', async () => {
const uiState = createMockUIState({
shortcutsHelpVisible: true,
streamingState: StreamingState.Idle,
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
expect(lastFrame()).toContain('ShortcutsHelp');
});
it('hides shortcuts help while streaming', () => {
it('hides shortcuts help while streaming', async () => {
const uiState = createMockUIState({
shortcutsHelpVisible: true,
streamingState: StreamingState.Responding,
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
expect(lastFrame()).not.toContain('ShortcutsHelp');
});
it('hides shortcuts help when action is required', () => {
it('hides shortcuts help when action is required', async () => {
const uiState = createMockUIState({
shortcutsHelpVisible: true,
customDialog: (
@@ -953,20 +956,20 @@ describe('Composer', () => {
),
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
expect(lastFrame()).not.toContain('ShortcutsHelp');
});
});
describe('Snapshots', () => {
it('matches snapshot in idle state', () => {
it('matches snapshot in idle state', async () => {
const uiState = createMockUIState();
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
expect(lastFrame()).toMatchSnapshot();
});
it('matches snapshot while streaming', () => {
it('matches snapshot while streaming', async () => {
const uiState = createMockUIState({
streamingState: StreamingState.Responding,
thought: {
@@ -974,33 +977,33 @@ describe('Composer', () => {
description: 'Thinking about the meaning of life...',
},
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
expect(lastFrame()).toMatchSnapshot();
});
it('matches snapshot in narrow view', () => {
it('matches snapshot in narrow view', async () => {
const uiState = createMockUIState({
terminalWidth: 40,
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
expect(lastFrame()).toMatchSnapshot();
});
it('matches snapshot in minimal UI mode', () => {
it('matches snapshot in minimal UI mode', async () => {
const uiState = createMockUIState({
cleanUiDetailsVisible: false,
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
expect(lastFrame()).toMatchSnapshot();
});
it('matches snapshot in minimal UI mode while loading', () => {
it('matches snapshot in minimal UI mode while loading', async () => {
const uiState = createMockUIState({
cleanUiDetailsVisible: false,
streamingState: StreamingState.Responding,
elapsedTime: 1000,
});
const { lastFrame } = renderComposer(uiState);
const { lastFrame } = await renderComposer(uiState);
expect(lastFrame()).toMatchSnapshot();
});
});