Files
iofficeai--aionui/tests/unit/chat/MessageToolGroupSummary.dom.test.tsx
2026-07-13 13:20:22 +08:00

207 lines
6.5 KiB
TypeScript

/**
* @license
* Copyright 2025 AionUi (aionui.com)
* SPDX-License-Identifier: Apache-2.0
*/
import React from 'react';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { IMessageAcpToolCall, IMessageToolCall } from '@/common/chat/chatLib';
import MessageToolGroupSummary from '@/renderer/pages/conversation/Messages/components/MessageToolGroupSummary';
const mockDownloadFileFromPath = vi.fn().mockResolvedValue(undefined);
const mockMessageSuccess = vi.fn();
const mockMessageError = vi.fn();
vi.mock('@/renderer/components/media/LocalImageView', () => ({
__esModule: true,
default: ({ src, alt, className }: { src: string; alt: string; className?: string }) => (
<img src={src} alt={alt} className={className} data-testid='local-image' />
),
}));
vi.mock('@/renderer/utils/file/download', () => ({
downloadFileFromPath: (...args: unknown[]) => mockDownloadFileFromPath(...args),
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}));
vi.mock('@arco-design/web-react', async () => {
const actual = await vi.importActual<typeof import('@arco-design/web-react')>('@arco-design/web-react');
return {
...actual,
Message: {
useMessage: () => [{ success: mockMessageSuccess, error: mockMessageError }, null],
},
};
});
describe('MessageToolGroupSummary ACP image output', () => {
beforeEach(() => {
mockDownloadFileFromPath.mockReset();
mockDownloadFileFromPath.mockResolvedValue(undefined);
mockMessageSuccess.mockClear();
mockMessageError.mockClear();
});
it('renders generated image preview when an ACP image tool call is expanded', () => {
const message: IMessageAcpToolCall = {
id: 'ig_test_image',
conversation_id: 'conv-1',
type: 'acp_tool_call',
content: {
sessionId: 'sess-1',
update: {
sessionUpdate: 'tool_call_update',
tool_call_id: 'ig_test_image',
status: 'completed',
title: 'Image generation',
kind: 'execute',
raw_output: {
image: {
path: '/Users/test/.codex/generated_images/session/ig_test_image.png',
},
},
content: [
{
type: 'content',
content: {
type: 'text',
text: 'Revised prompt: 一张小猫照片',
},
},
],
},
},
};
render(<MessageToolGroupSummary messages={[message]} />);
fireEvent.click(screen.getByText('View Steps · 1'));
const image = screen.getByTestId('local-image');
expect(image).toHaveAttribute('src', '/Users/test/.codex/generated_images/session/ig_test_image.png');
expect(image).toHaveAttribute('alt', 'ig_test_image.png');
});
it('downloads the generated image from its local path', () => {
const imagePath = '/Users/test/.codex/generated_images/session/ig_test_image.png';
const message: IMessageAcpToolCall = {
id: 'ig_test_image',
conversation_id: 'conv-1',
type: 'acp_tool_call',
content: {
sessionId: 'sess-1',
update: {
sessionUpdate: 'tool_call_update',
tool_call_id: 'ig_test_image',
status: 'completed',
title: 'Image generation',
kind: 'execute',
raw_output: {
image: {
path: imagePath,
},
},
},
},
};
render(<MessageToolGroupSummary messages={[message]} />);
fireEvent.click(screen.getByText('View Steps · 1'));
fireEvent.click(screen.getByLabelText('acp.image.download_aria'));
expect(mockDownloadFileFromPath).toHaveBeenCalledWith(imagePath, 'ig_test_image.png');
});
it('shows an error when generated image download fails', async () => {
const imagePath = '/Users/test/.codex/generated_images/session/ig_test_image.png';
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {});
mockDownloadFileFromPath.mockRejectedValueOnce(new Error('denied'));
const message: IMessageAcpToolCall = {
id: 'ig_test_image',
conversation_id: 'conv-1',
type: 'acp_tool_call',
content: {
sessionId: 'sess-1',
update: {
sessionUpdate: 'tool_call_update',
tool_call_id: 'ig_test_image',
status: 'completed',
title: 'Image generation',
kind: 'execute',
raw_output: {
image: {
path: imagePath,
},
},
},
},
};
render(<MessageToolGroupSummary messages={[message]} />);
fireEvent.click(screen.getByText('View Steps · 1'));
fireEvent.click(screen.getByLabelText('acp.image.download_aria'));
await waitFor(() => {
expect(mockMessageError).toHaveBeenCalledWith('acp.image.download_error');
});
expect(consoleError).toHaveBeenCalledWith('[MessageToolGroupSummary] Failed to download image:', expect.any(Error));
expect(mockMessageSuccess).not.toHaveBeenCalled();
consoleError.mockRestore();
});
it('uses i18n keys for the image download control', () => {
const message: IMessageAcpToolCall = {
id: 'ig_test_image',
conversation_id: 'conv-1',
type: 'acp_tool_call',
content: {
sessionId: 'sess-1',
update: {
sessionUpdate: 'tool_call_update',
tool_call_id: 'ig_test_image',
status: 'completed',
title: 'Image generation',
kind: 'execute',
raw_output: {
image: {
path: '/Users/test/.codex/generated_images/session/ig_test_image.png',
},
},
},
},
};
render(<MessageToolGroupSummary messages={[message]} />);
fireEvent.click(screen.getByText('View Steps · 1'));
expect(screen.getByLabelText('acp.image.download_aria')).toBeInTheDocument();
});
it('does not render image controls for tool calls without image output', () => {
const message: IMessageToolCall = {
id: 'tool-1',
conversation_id: 'conv-1',
type: 'tool_call',
content: {
call_id: 'tool-1',
name: 'Shell Command',
args: {},
status: 'completed',
},
};
render(<MessageToolGroupSummary messages={[message]} />);
fireEvent.click(screen.getByText('View Steps · 1'));
expect(screen.queryByTestId('local-image')).not.toBeInTheDocument();
expect(screen.queryByLabelText('acp.image.download_aria')).not.toBeInTheDocument();
});
});