/** * @license * Copyright 2025 Qwen * SPDX-License-Identifier: Apache-2.0 */ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { render } from 'ink-testing-library'; import { Text } from 'ink'; import { CompactToolGroupDisplay } from './CompactToolGroupDisplay.js'; import { ToolCallStatus } from '../../types.js'; import type { IndividualToolCallDisplay } from '../../types.js'; // ToolStatusIndicator pulls in GeminiRespondingSpinner which requires // StreamingContext; stub it out so we can test the elapsed/timeout // plumbing in isolation. vi.mock('../shared/ToolStatusIndicator.js', () => ({ ToolStatusIndicator: () => , STATUS_INDICATOR_WIDTH: 2, })); const NOW = 1_700_000_000_000; function shellTool( overrides: Partial = {}, ): IndividualToolCallDisplay { return { callId: 'c1', name: 'Shell', description: 'sleep 10', status: ToolCallStatus.Executing, executionStartTime: NOW, resultDisplay: undefined, confirmationDetails: undefined, ...overrides, }; } describe('', () => { beforeEach(() => { vi.useFakeTimers(); vi.setSystemTime(NOW); }); afterEach(() => { vi.useRealTimers(); }); it('surfaces shell timeoutMs inline via ToolElapsedTime', () => { const tool = shellTool({ resultDisplay: { ansiOutput: [], totalLines: 0, totalBytes: 0, timeoutMs: 30_000, }, }); const { lastFrame } = render( , ); expect(lastFrame()).toContain('(0s · timeout 30s)'); }); it('falls back to quiet elapsed-only when no timeout is surfaced', () => { const tool = shellTool({ resultDisplay: { ansiOutput: [], totalLines: 0, totalBytes: 0, }, }); const { lastFrame } = render( , ); // Sub-3s without a timeout budget → indicator is quiet. expect(lastFrame()).not.toContain('timeout'); expect(lastFrame()).not.toContain('0s'); }); it('ignores non-ansi resultDisplay shapes', () => { const tool = shellTool({ resultDisplay: 'plain text output', }); const { lastFrame, rerender } = render( , ); vi.advanceTimersByTime(5_000); rerender(); // No timeout in display → legacy 3s-threshold elapsed. expect(lastFrame()).toContain('5s'); expect(lastFrame()).not.toContain('timeout'); }); });