mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-08-04 22:21:15 -07:00
182 lines
6.1 KiB
TypeScript
182 lines
6.1 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2026 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import { describe, it, expect, vi } from 'vitest';
|
|
import { TopicMessage } from './TopicMessage.js';
|
|
import { renderWithProviders } from '../../../test-utils/render.js';
|
|
import {
|
|
TOPIC_PARAM_TITLE,
|
|
TOPIC_PARAM_SUMMARY,
|
|
TOPIC_PARAM_STRATEGIC_INTENT,
|
|
CoreToolCallStatus,
|
|
UPDATE_TOPIC_TOOL_NAME,
|
|
} from '@google/gemini-cli-core';
|
|
import { VirtualizedListContext } from '../shared/VirtualizedList.js';
|
|
import { Box, type DOMElement, getBoundingBox } from 'ink';
|
|
import { useMouse } from '../../contexts/MouseContext.js';
|
|
import { useCallback, useRef } from 'react';
|
|
import type React from 'react';
|
|
|
|
describe('<TopicMessage />', () => {
|
|
const baseArgs = {
|
|
[TOPIC_PARAM_TITLE]: 'Test Topic',
|
|
[TOPIC_PARAM_STRATEGIC_INTENT]: 'This is the strategic intent.',
|
|
[TOPIC_PARAM_SUMMARY]:
|
|
'This is the detailed summary that should be expandable.',
|
|
};
|
|
|
|
const renderTopic = async (
|
|
args: Record<string, unknown>,
|
|
height?: number,
|
|
toolActions?: {
|
|
isExpanded?: (callId: string) => boolean;
|
|
toggleExpansion?: (callId: string) => void;
|
|
},
|
|
virtualizedListProps?: {
|
|
itemKey?: string;
|
|
},
|
|
) => {
|
|
const defaultItemKey = virtualizedListProps?.itemKey || 'test-topic-key';
|
|
|
|
const MockVirtualizedListWrapper: React.FC<{
|
|
children: React.ReactNode;
|
|
}> = ({ children }) => {
|
|
const callbacks = useRef(new Map<string, () => void>());
|
|
|
|
const mockListContext = {
|
|
registerInteractivity: vi.fn(),
|
|
setItemState: vi.fn(),
|
|
getItemState: vi.fn(),
|
|
isItemToggled: vi.fn().mockReturnValue(false),
|
|
toggleItem: vi.fn(),
|
|
registerClickCallback: vi.fn((key, id, cb) => {
|
|
if (key === defaultItemKey) callbacks.current.set(id, cb);
|
|
}),
|
|
unregisterClickCallback: vi.fn((key, id) => {
|
|
if (key === defaultItemKey) callbacks.current.delete(id);
|
|
}),
|
|
};
|
|
|
|
const containerRef = useRef<DOMElement>(null);
|
|
const handleMouse = useCallback(
|
|
(event: { name: string; col: number; row: number }) => {
|
|
if (event.name === 'left-press' && containerRef.current) {
|
|
const {
|
|
x,
|
|
y,
|
|
width,
|
|
height: elHeight,
|
|
} = getBoundingBox(containerRef.current);
|
|
const mouseX = event.col - 1;
|
|
const mouseY = event.row - 1;
|
|
if (
|
|
mouseX >= x &&
|
|
mouseX < x + width &&
|
|
mouseY >= y &&
|
|
mouseY < y + elHeight
|
|
) {
|
|
const cb = callbacks.current.get('toggle');
|
|
if (cb) cb();
|
|
}
|
|
}
|
|
},
|
|
[callbacks],
|
|
);
|
|
useMouse(handleMouse, { isActive: true });
|
|
|
|
return (
|
|
<VirtualizedListContext.Provider value={mockListContext}>
|
|
<Box ref={containerRef}>{children}</Box>
|
|
</VirtualizedListContext.Provider>
|
|
);
|
|
};
|
|
|
|
return renderWithProviders(
|
|
<MockVirtualizedListWrapper>
|
|
<TopicMessage
|
|
args={args}
|
|
itemKey={defaultItemKey}
|
|
terminalWidth={80}
|
|
availableTerminalHeight={height}
|
|
callId="test-topic"
|
|
name={UPDATE_TOPIC_TOOL_NAME}
|
|
description="Updating topic"
|
|
status={CoreToolCallStatus.Success}
|
|
confirmationDetails={undefined}
|
|
resultDisplay={undefined}
|
|
/>
|
|
</MockVirtualizedListWrapper>,
|
|
{ toolActions, mouseEventsEnabled: true },
|
|
);
|
|
};
|
|
|
|
it('renders title and intent by default (collapsed)', async () => {
|
|
const { lastFrame } = await renderTopic(baseArgs, 40);
|
|
const frame = lastFrame();
|
|
expect(frame).toContain('Test Topic:');
|
|
expect(frame).toContain('This is the strategic intent.');
|
|
expect(frame).not.toContain('This is the detailed summary');
|
|
expect(frame).not.toContain('(ctrl+o to expand)');
|
|
});
|
|
|
|
it('renders summary when globally expanded (Ctrl+O)', async () => {
|
|
const { lastFrame } = await renderTopic(baseArgs, undefined);
|
|
const frame = lastFrame();
|
|
expect(frame).toContain('Test Topic:');
|
|
expect(frame).toContain('This is the strategic intent.');
|
|
expect(frame).toContain('This is the detailed summary');
|
|
expect(frame).not.toContain('(ctrl+o to collapse)');
|
|
});
|
|
|
|
it('renders summary when selectively expanded via context', async () => {
|
|
const isExpanded = vi.fn((id) => id === 'test-topic');
|
|
const { lastFrame } = await renderTopic(baseArgs, 40, { isExpanded });
|
|
const frame = lastFrame();
|
|
expect(frame).toContain('Test Topic:');
|
|
expect(frame).toContain('This is the detailed summary');
|
|
expect(frame).not.toContain('(ctrl+o to collapse)');
|
|
});
|
|
|
|
it('calls toggleExpansion when clicked', async () => {
|
|
const toggleExpansion = vi.fn();
|
|
const { simulateClick } = await renderTopic(baseArgs, 40, {
|
|
toggleExpansion,
|
|
});
|
|
|
|
// In renderWithProviders, the component is wrapped in a Box with terminalWidth.
|
|
// The TopicMessage has marginLeft={2}.
|
|
// So col 5 should definitely hit the text content.
|
|
// row 1 is the first line of the TopicMessage.
|
|
await simulateClick(5, 1);
|
|
|
|
expect(toggleExpansion).toHaveBeenCalledWith('test-topic');
|
|
});
|
|
|
|
it('falls back to summary if strategic_intent is missing', async () => {
|
|
const args = {
|
|
[TOPIC_PARAM_TITLE]: 'Test Topic',
|
|
[TOPIC_PARAM_SUMMARY]: 'Only summary is present.',
|
|
};
|
|
const { lastFrame } = await renderTopic(args, 40);
|
|
const frame = lastFrame();
|
|
expect(frame).toContain('Test Topic:');
|
|
expect(frame).toContain('Only summary is present.');
|
|
expect(frame).not.toContain('(ctrl+o to expand)');
|
|
});
|
|
|
|
it('renders only strategic_intent if summary is missing', async () => {
|
|
const args = {
|
|
[TOPIC_PARAM_TITLE]: 'Test Topic',
|
|
[TOPIC_PARAM_STRATEGIC_INTENT]: 'Only intent is present.',
|
|
};
|
|
const { lastFrame } = await renderTopic(args, 40);
|
|
const frame = lastFrame();
|
|
expect(frame).toContain('Test Topic:');
|
|
expect(frame).toContain('Only intent is present.');
|
|
expect(frame).not.toContain('(ctrl+o to expand)');
|
|
});
|
|
});
|