/** * @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('', () => { 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, 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 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(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 ( {children} ); }; return renderWithProviders( , { 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)'); }); });