Files
2026-07-13 13:20:22 +08:00

190 lines
5.6 KiB
TypeScript

/**
* @license
* Copyright 2025 AionUi (aionui.com)
* SPDX-License-Identifier: Apache-2.0
*/
import { afterEach, describe, it, expect, vi } from 'vitest';
import { render, waitFor } from '@testing-library/react';
import React from 'react';
const writeRendererLogInvoke = vi.hoisted(() => vi.fn(() => Promise.resolve()));
vi.mock('@/common', () => ({
ipcBridge: {
fs: {
getImageBase64: { invoke: vi.fn(() => Promise.resolve('')) },
readFile: { invoke: vi.fn(() => Promise.resolve('')) },
},
application: {
writeRendererLog: { invoke: writeRendererLogInvoke },
},
},
}));
vi.mock('@monaco-editor/react', () => ({
default: ({ value }: { value: string }) => <div data-testid='monaco-editor'>{value}</div>,
}));
vi.mock('@arco-design/web-react', () => ({
Message: {
useMessage: () => [{ info: vi.fn(), success: vi.fn(), error: vi.fn() }, null],
},
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}));
import HTMLViewer from '@/renderer/pages/conversation/Preview/components/viewers/HTMLViewer';
import HTMLRenderer from '@/renderer/pages/conversation/Preview/components/renderers/HTMLRenderer';
function createConsoleMessageEvent({
level,
line,
message,
sourceId,
}: {
level: number;
line: number;
message: string;
sourceId: string;
}): Event {
const event = new Event('console-message');
Object.defineProperties(event, {
level: { value: level },
line: { value: line },
message: { value: message },
sourceId: { value: sourceId },
});
return event;
}
describe('HTMLViewer', () => {
it('renders iframe with HTML content', () => {
const { container } = render(<HTMLViewer content='<h1>Test</h1>' />);
const iframe = container.querySelector('iframe');
expect(iframe).toBeInTheDocument();
});
it('hides toolbar when hideToolbar is true', () => {
const { container } = render(<HTMLViewer content='<h1>Test</h1>' hideToolbar />);
expect(container.querySelector('[class*="toolbar"]')).not.toBeInTheDocument();
});
it('accepts file_path prop', () => {
const { container } = render(<HTMLViewer content='<h1>Test</h1>' file_path='/test/index.html' />);
expect(container.querySelector('iframe')).toBeInTheDocument();
});
});
describe('HTMLRenderer', () => {
const electronAPI = {};
afterEach(() => {
Reflect.deleteProperty(window, 'electronAPI');
vi.clearAllMocks();
});
it('loads clean local HTML files through file URL in Electron', () => {
Object.defineProperty(window, 'electronAPI', {
configurable: true,
value: electronAPI,
});
const { container } = render(
<HTMLRenderer
content='<script src="https://cdn.example.com/app.js"></script><script>localStorage.getItem("theme")</script>'
file_path='/workspace/financial-wechat-miniapp.html'
/>
);
const webview = container.querySelector('webview');
expect(webview).toBeInTheDocument();
expect(webview?.getAttribute('src')).toBe('file:///workspace/financial-wechat-miniapp.html');
});
it('keeps dirty local HTML content in memory in Electron', () => {
Object.defineProperty(window, 'electronAPI', {
configurable: true,
value: electronAPI,
});
const dirtyProps = {
content: '<h1>Unsaved edit</h1>',
file_path: '/workspace/index.html',
isDirty: true,
} as React.ComponentProps<typeof HTMLRenderer> & { isDirty: boolean };
const { container } = render(<HTMLRenderer {...dirtyProps} />);
const webview = container.querySelector('webview');
expect(webview).toBeInTheDocument();
expect(webview?.getAttribute('src')).toContain('data:text/html');
expect(webview?.getAttribute('src')).toContain('Unsaved%20edit');
});
it('writes preview source selection to the renderer log bridge', async () => {
Object.defineProperty(window, 'electronAPI', {
configurable: true,
value: electronAPI,
});
render(<HTMLRenderer content='<h1>Test</h1>' file_path='/workspace/financial-wechat-miniapp.html' />);
await waitFor(() =>
expect(writeRendererLogInvoke).toHaveBeenCalledWith({
level: 'info',
tag: 'HTMLRenderer',
message: 'html_preview_source_selected',
data: expect.objectContaining({
source: 'file',
reason: 'clean-local-file',
fileName: 'financial-wechat-miniapp.html',
hasFilePath: true,
contentLength: 13,
src: 'file://financial-wechat-miniapp.html',
}),
})
);
});
it('writes level 2 preview console messages as renderer warnings', async () => {
Object.defineProperty(window, 'electronAPI', {
configurable: true,
value: electronAPI,
});
const { container } = render(<HTMLRenderer content='<h1>Test</h1>' file_path='/workspace/index.html' />);
const webview = container.querySelector('webview');
await waitFor(() => expect(writeRendererLogInvoke).toHaveBeenCalled());
vi.clearAllMocks();
webview?.dispatchEvent(
createConsoleMessageEvent({
level: 2,
line: 64,
message: 'cdn.tailwindcss.com should not be used in production.',
sourceId: 'https://cdn.tailwindcss.com/',
})
);
await waitFor(() =>
expect(writeRendererLogInvoke).toHaveBeenCalledWith({
level: 'warn',
tag: 'HTMLRenderer',
message: 'html_preview_console_warning',
data: {
level: 2,
line: 64,
message: 'cdn.tailwindcss.com should not be used in production.',
source: 'https://cdn.tailwindcss.com/',
},
})
);
});
});