207 lines
6.5 KiB
TypeScript
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();
|
|
});
|
|
});
|