mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-09 21:00:56 -07:00
Migrate core render util to use xterm.js as part of the rendering loop. (#19044)
This commit is contained in:
@@ -21,29 +21,36 @@ const mockChats: ChatDetail[] = [
|
||||
];
|
||||
|
||||
describe('<ChatList />', () => {
|
||||
it('renders correctly with a list of chats', () => {
|
||||
const { lastFrame, unmount } = render(<ChatList chats={mockChats} />);
|
||||
it('renders correctly with a list of chats', async () => {
|
||||
const { lastFrame, unmount, waitUntilReady } = render(
|
||||
<ChatList chats={mockChats} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('renders correctly with no chats', () => {
|
||||
const { lastFrame, unmount } = render(<ChatList chats={[]} />);
|
||||
it('renders correctly with no chats', async () => {
|
||||
const { lastFrame, unmount, waitUntilReady } = render(
|
||||
<ChatList chats={[]} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
expect(lastFrame()).toContain('No saved conversation checkpoints found.');
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('handles invalid date formats gracefully', () => {
|
||||
it('handles invalid date formats gracefully', async () => {
|
||||
const mockChatsWithInvalidDate: ChatDetail[] = [
|
||||
{
|
||||
name: 'bad-date-chat',
|
||||
mtime: 'an-invalid-date-string',
|
||||
},
|
||||
];
|
||||
const { lastFrame, unmount } = render(
|
||||
const { lastFrame, unmount, waitUntilReady } = render(
|
||||
<ChatList chats={mockChatsWithInvalidDate} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
expect(lastFrame()).toContain('(Invalid Date)');
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
unmount();
|
||||
|
||||
@@ -55,18 +55,22 @@ describe('<ExtensionsList />', () => {
|
||||
} as never);
|
||||
};
|
||||
|
||||
it('should render "No extensions installed." if there are no extensions', () => {
|
||||
it('should render "No extensions installed." if there are no extensions', async () => {
|
||||
mockUIState(new Map());
|
||||
const { lastFrame, unmount } = render(<ExtensionsList extensions={[]} />);
|
||||
const { lastFrame, waitUntilReady, unmount } = render(
|
||||
<ExtensionsList extensions={[]} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
expect(lastFrame()).toContain('No extensions installed.');
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should render a list of extensions with their version and status', () => {
|
||||
it('should render a list of extensions with their version and status', async () => {
|
||||
mockUIState(new Map());
|
||||
const { lastFrame, unmount } = render(
|
||||
const { lastFrame, waitUntilReady, unmount } = render(
|
||||
<ExtensionsList extensions={mockExtensions} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
const output = lastFrame();
|
||||
expect(output).toContain('ext-one (v1.0.0) - active');
|
||||
expect(output).toContain('ext-two (v2.1.0) - active');
|
||||
@@ -74,16 +78,17 @@ describe('<ExtensionsList />', () => {
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should display "unknown state" if an extension has no update state', () => {
|
||||
it('should display "unknown state" if an extension has no update state', async () => {
|
||||
mockUIState(new Map());
|
||||
const { lastFrame, unmount } = render(
|
||||
const { lastFrame, waitUntilReady, unmount } = render(
|
||||
<ExtensionsList extensions={[mockExtensions[0]]} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
expect(lastFrame()).toContain('(unknown state)');
|
||||
unmount();
|
||||
});
|
||||
|
||||
const stateTestCases = [
|
||||
it.each([
|
||||
{
|
||||
state: ExtensionUpdateState.CHECKING_FOR_UPDATES,
|
||||
expectedText: '(checking for updates)',
|
||||
@@ -112,21 +117,21 @@ describe('<ExtensionsList />', () => {
|
||||
state: ExtensionUpdateState.UP_TO_DATE,
|
||||
expectedText: '(up to date)',
|
||||
},
|
||||
];
|
||||
|
||||
for (const { state, expectedText } of stateTestCases) {
|
||||
it(`should correctly display the state: ${state}`, () => {
|
||||
])(
|
||||
'should correctly display the state: $state',
|
||||
async ({ state, expectedText }) => {
|
||||
const updateState = new Map([[mockExtensions[0].name, state]]);
|
||||
mockUIState(updateState);
|
||||
const { lastFrame, unmount } = render(
|
||||
const { lastFrame, waitUntilReady, unmount } = render(
|
||||
<ExtensionsList extensions={[mockExtensions[0]]} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
expect(lastFrame()).toContain(expectedText);
|
||||
unmount();
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it('should render resolved settings for an extension', () => {
|
||||
it('should render resolved settings for an extension', async () => {
|
||||
mockUIState(new Map());
|
||||
const extensionWithSettings = {
|
||||
...mockExtensions[0],
|
||||
@@ -155,9 +160,10 @@ describe('<ExtensionsList />', () => {
|
||||
},
|
||||
],
|
||||
};
|
||||
const { lastFrame, unmount } = render(
|
||||
const { lastFrame, waitUntilReady, unmount } = render(
|
||||
<ExtensionsList extensions={[extensionWithSettings]} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
const output = lastFrame();
|
||||
expect(output).toContain('settings:');
|
||||
expect(output).toContain('- sensitiveApiKey: ***');
|
||||
|
||||
@@ -53,35 +53,41 @@ describe('McpStatus', () => {
|
||||
showSchema: false,
|
||||
};
|
||||
|
||||
it('renders correctly with a connected server', () => {
|
||||
const { lastFrame, unmount } = render(<McpStatus {...baseProps} />);
|
||||
it('renders correctly with a connected server', async () => {
|
||||
const { lastFrame, unmount, waitUntilReady } = render(
|
||||
<McpStatus {...baseProps} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('renders correctly with authenticated OAuth status', () => {
|
||||
const { lastFrame, unmount } = render(
|
||||
it('renders correctly with authenticated OAuth status', async () => {
|
||||
const { lastFrame, unmount, waitUntilReady } = render(
|
||||
<McpStatus {...baseProps} authStatus={{ 'server-1': 'authenticated' }} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('renders correctly with expired OAuth status', () => {
|
||||
const { lastFrame, unmount } = render(
|
||||
it('renders correctly with expired OAuth status', async () => {
|
||||
const { lastFrame, unmount, waitUntilReady } = render(
|
||||
<McpStatus {...baseProps} authStatus={{ 'server-1': 'expired' }} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('renders correctly with unauthenticated OAuth status', () => {
|
||||
const { lastFrame, unmount } = render(
|
||||
it('renders correctly with unauthenticated OAuth status', async () => {
|
||||
const { lastFrame, unmount, waitUntilReady } = render(
|
||||
<McpStatus
|
||||
{...baseProps}
|
||||
authStatus={{ 'server-1': 'unauthenticated' }}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
unmount();
|
||||
});
|
||||
@@ -91,29 +97,34 @@ describe('McpStatus', () => {
|
||||
await import('@google/gemini-cli-core'),
|
||||
'getMCPServerStatus',
|
||||
).mockReturnValue(MCPServerStatus.DISCONNECTED);
|
||||
const { lastFrame, unmount } = render(<McpStatus {...baseProps} />);
|
||||
const { lastFrame, unmount, waitUntilReady } = render(
|
||||
<McpStatus {...baseProps} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('renders correctly when discovery is in progress', () => {
|
||||
const { lastFrame, unmount } = render(
|
||||
it('renders correctly when discovery is in progress', async () => {
|
||||
const { lastFrame, unmount, waitUntilReady } = render(
|
||||
<McpStatus {...baseProps} discoveryInProgress={true} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('renders correctly with schema enabled', () => {
|
||||
const { lastFrame, unmount } = render(
|
||||
it('renders correctly with schema enabled', async () => {
|
||||
const { lastFrame, unmount, waitUntilReady } = render(
|
||||
<McpStatus {...baseProps} showSchema={true} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('renders correctly with parametersJsonSchema', () => {
|
||||
const { lastFrame, unmount } = render(
|
||||
it('renders correctly with parametersJsonSchema', async () => {
|
||||
const { lastFrame, unmount, waitUntilReady } = render(
|
||||
<McpStatus
|
||||
{...baseProps}
|
||||
tools={[
|
||||
@@ -134,12 +145,13 @@ describe('McpStatus', () => {
|
||||
showSchema={true}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('renders correctly with prompts', () => {
|
||||
const { lastFrame, unmount } = render(
|
||||
it('renders correctly with prompts', async () => {
|
||||
const { lastFrame, unmount, waitUntilReady } = render(
|
||||
<McpStatus
|
||||
{...baseProps}
|
||||
prompts={[
|
||||
@@ -151,12 +163,13 @@ describe('McpStatus', () => {
|
||||
]}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('renders correctly with resources', () => {
|
||||
const { lastFrame, unmount } = render(
|
||||
it('renders correctly with resources', async () => {
|
||||
const { lastFrame, unmount, waitUntilReady } = render(
|
||||
<McpStatus
|
||||
{...baseProps}
|
||||
resources={[
|
||||
@@ -169,39 +182,43 @@ describe('McpStatus', () => {
|
||||
]}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('renders correctly with a blocked server', () => {
|
||||
const { lastFrame, unmount } = render(
|
||||
it('renders correctly with a blocked server', async () => {
|
||||
const { lastFrame, unmount, waitUntilReady } = render(
|
||||
<McpStatus
|
||||
{...baseProps}
|
||||
blockedServers={[{ name: 'server-1', extensionName: 'test-extension' }]}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('renders correctly with a connecting server', () => {
|
||||
const { lastFrame, unmount } = render(
|
||||
it('renders correctly with a connecting server', async () => {
|
||||
const { lastFrame, unmount, waitUntilReady } = render(
|
||||
<McpStatus {...baseProps} connectingServers={['server-1']} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('truncates resources when exceeding limit', () => {
|
||||
it('truncates resources when exceeding limit', async () => {
|
||||
const manyResources = Array.from({ length: 25 }, (_, i) => ({
|
||||
serverName: 'server-1',
|
||||
name: `resource-${i + 1}`,
|
||||
uri: `file:///tmp/resource-${i + 1}.txt`,
|
||||
}));
|
||||
|
||||
const { lastFrame, unmount } = render(
|
||||
const { lastFrame, unmount, waitUntilReady } = render(
|
||||
<McpStatus {...baseProps} resources={manyResources} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
expect(lastFrame()).toContain('15 resources hidden');
|
||||
unmount();
|
||||
});
|
||||
|
||||
@@ -34,10 +34,11 @@ describe('SkillsList Component', () => {
|
||||
},
|
||||
];
|
||||
|
||||
it('should render enabled and disabled skills separately', () => {
|
||||
const { lastFrame, unmount } = render(
|
||||
it('should render enabled and disabled skills separately', async () => {
|
||||
const { lastFrame, unmount, waitUntilReady } = render(
|
||||
<SkillsList skills={mockSkills} showDescriptions={true} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
const output = lastFrame();
|
||||
|
||||
expect(output).toContain('Available Agent Skills:');
|
||||
@@ -53,10 +54,11 @@ describe('SkillsList Component', () => {
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should not render descriptions when showDescriptions is false', () => {
|
||||
const { lastFrame, unmount } = render(
|
||||
it('should not render descriptions when showDescriptions is false', async () => {
|
||||
const { lastFrame, unmount, waitUntilReady } = render(
|
||||
<SkillsList skills={mockSkills} showDescriptions={false} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
const output = lastFrame();
|
||||
|
||||
expect(output).toContain('skill1');
|
||||
@@ -69,10 +71,11 @@ describe('SkillsList Component', () => {
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should render "No skills available" when skills list is empty', () => {
|
||||
const { lastFrame, unmount } = render(
|
||||
it('should render "No skills available" when skills list is empty', async () => {
|
||||
const { lastFrame, unmount, waitUntilReady } = render(
|
||||
<SkillsList skills={[]} showDescriptions={true} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
const output = lastFrame();
|
||||
|
||||
expect(output).toContain('No skills available');
|
||||
@@ -80,11 +83,12 @@ describe('SkillsList Component', () => {
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should only render Available Agent Skills section when all skills are enabled', () => {
|
||||
it('should only render Available Agent Skills section when all skills are enabled', async () => {
|
||||
const enabledOnly = mockSkills.filter((s) => !s.disabled);
|
||||
const { lastFrame, unmount } = render(
|
||||
const { lastFrame, unmount, waitUntilReady } = render(
|
||||
<SkillsList skills={enabledOnly} showDescriptions={true} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
const output = lastFrame();
|
||||
|
||||
expect(output).toContain('Available Agent Skills:');
|
||||
@@ -93,11 +97,12 @@ describe('SkillsList Component', () => {
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should only render Disabled Skills section when all skills are disabled', () => {
|
||||
it('should only render Disabled Skills section when all skills are disabled', async () => {
|
||||
const disabledOnly = mockSkills.filter((s) => s.disabled);
|
||||
const { lastFrame, unmount } = render(
|
||||
const { lastFrame, unmount, waitUntilReady } = render(
|
||||
<SkillsList skills={disabledOnly} showDescriptions={true} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
const output = lastFrame();
|
||||
|
||||
expect(output).not.toContain('Available Agent Skills:');
|
||||
@@ -106,7 +111,7 @@ describe('SkillsList Component', () => {
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should render [Built-in] tag for built-in skills', () => {
|
||||
it('should render [Built-in] tag for built-in skills', async () => {
|
||||
const builtinSkill: SkillDefinition = {
|
||||
name: 'builtin-skill',
|
||||
description: 'A built-in skill',
|
||||
@@ -116,9 +121,10 @@ describe('SkillsList Component', () => {
|
||||
isBuiltin: true,
|
||||
};
|
||||
|
||||
const { lastFrame, unmount } = render(
|
||||
const { lastFrame, unmount, waitUntilReady } = render(
|
||||
<SkillsList skills={[builtinSkill]} showDescriptions={true} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
const output = lastFrame();
|
||||
|
||||
expect(output).toContain('builtin-skill');
|
||||
|
||||
@@ -31,32 +31,35 @@ const mockTools: ToolDefinition[] = [
|
||||
];
|
||||
|
||||
describe('<ToolsList />', () => {
|
||||
it('renders correctly with descriptions', () => {
|
||||
const { lastFrame } = renderWithProviders(
|
||||
it('renders correctly with descriptions', async () => {
|
||||
const { lastFrame, waitUntilReady } = renderWithProviders(
|
||||
<ToolsList
|
||||
tools={mockTools}
|
||||
showDescriptions={true}
|
||||
terminalWidth={40}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders correctly without descriptions', () => {
|
||||
const { lastFrame } = renderWithProviders(
|
||||
it('renders correctly without descriptions', async () => {
|
||||
const { lastFrame, waitUntilReady } = renderWithProviders(
|
||||
<ToolsList
|
||||
tools={mockTools}
|
||||
showDescriptions={false}
|
||||
terminalWidth={40}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders correctly with no tools', () => {
|
||||
const { lastFrame } = renderWithProviders(
|
||||
it('renders correctly with no tools', async () => {
|
||||
const { lastFrame, waitUntilReady } = renderWithProviders(
|
||||
<ToolsList tools={[]} showDescriptions={true} terminalWidth={40} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,7 +5,8 @@ exports[`<ChatList /> > handles invalid date formats gracefully 1`] = `
|
||||
|
||||
- bad-date-chat (Invalid Date)
|
||||
|
||||
Note: Newest last, oldest first"
|
||||
Note: Newest last, oldest first
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`<ChatList /> > renders correctly with a list of chats 1`] = `
|
||||
@@ -14,7 +15,11 @@ exports[`<ChatList /> > renders correctly with a list of chats 1`] = `
|
||||
- chat-1 (2025-10-02 10:00:00)
|
||||
- another-chat (2025-10-01 12:30:00)
|
||||
|
||||
Note: Newest last, oldest first"
|
||||
Note: Newest last, oldest first
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`<ChatList /> > renders correctly with no chats 1`] = `"No saved conversation checkpoints found."`;
|
||||
exports[`<ChatList /> > renders correctly with no chats 1`] = `
|
||||
"No saved conversation checkpoints found.
|
||||
"
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user