test: 修复测试中的 act() 警告和 clipboard mock 问题
- TextInputArea: 使用 userEvent 替代 fireEvent,用 vi.spyOn 替代 Object.assign mock clipboard - ImageMode/FileMode: 添加 waitForStorageReady() 等待 useStorageState 异步初始化
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import { describe, expect, it, vi } from 'vitest';
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
import { fireEvent, render, screen } from '@testing-library/react';
|
import { fireEvent, render, screen } from '@testing-library/react';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
import TextInputArea from '@/components/TextInputArea';
|
import TextInputArea from '@/components/TextInputArea';
|
||||||
|
|
||||||
describe('TextInputArea 组件', () => {
|
describe('TextInputArea 组件', () => {
|
||||||
@@ -107,38 +108,32 @@ describe('TextInputArea 组件', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('复制时调用 showMessage', async () => {
|
it('复制时调用 showMessage', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
const showMessage = vi.fn();
|
const showMessage = vi.fn();
|
||||||
Object.assign(navigator, {
|
const writeTextSpy = vi.spyOn(navigator.clipboard, 'writeText').mockResolvedValue(undefined);
|
||||||
clipboard: { writeText: vi.fn().mockResolvedValue(undefined) },
|
|
||||||
});
|
|
||||||
|
|
||||||
render(
|
render(
|
||||||
<TextInputArea value="测试" onChange={() => {}} allowCopy showMessage={showMessage} />,
|
<TextInputArea value="测试" onChange={() => {}} allowCopy showMessage={showMessage} />,
|
||||||
);
|
);
|
||||||
|
|
||||||
fireEvent.click(screen.getByRole('button', { name: 'textInputArea.copyContent' }));
|
await user.click(screen.getByRole('button', { name: 'textInputArea.copyContent' }));
|
||||||
|
|
||||||
expect(navigator.clipboard.writeText).toHaveBeenCalledWith('测试');
|
expect(writeTextSpy).toHaveBeenCalledWith('测试');
|
||||||
await vi.waitFor(() => {
|
expect(showMessage).toHaveBeenCalledWith('messages.copySuccess', { severity: 'success' });
|
||||||
expect(showMessage).toHaveBeenCalledWith('messages.copySuccess', { severity: 'success' });
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('复制失败时调用 showMessage 错误提示', async () => {
|
it('复制失败时调用 showMessage 错误提示', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
const showMessage = vi.fn();
|
const showMessage = vi.fn();
|
||||||
Object.assign(navigator, {
|
vi.spyOn(navigator.clipboard, 'writeText').mockRejectedValue(new Error('失败'));
|
||||||
clipboard: { writeText: vi.fn().mockRejectedValue(new Error('失败')) },
|
|
||||||
});
|
|
||||||
|
|
||||||
render(
|
render(
|
||||||
<TextInputArea value="测试" onChange={() => {}} allowCopy showMessage={showMessage} />,
|
<TextInputArea value="测试" onChange={() => {}} allowCopy showMessage={showMessage} />,
|
||||||
);
|
);
|
||||||
|
|
||||||
fireEvent.click(screen.getByRole('button', { name: 'textInputArea.copyContent' }));
|
await user.click(screen.getByRole('button', { name: 'textInputArea.copyContent' }));
|
||||||
|
|
||||||
await vi.waitFor(() => {
|
expect(showMessage).toHaveBeenCalledWith('messages.copyError', { severity: 'error' });
|
||||||
expect(showMessage).toHaveBeenCalledWith('messages.copyError', { severity: 'error' });
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -377,19 +372,16 @@ describe('TextInputArea 组件', () => {
|
|||||||
|
|
||||||
describe('showMessage prop', () => {
|
describe('showMessage prop', () => {
|
||||||
it('复制成功时调用 showMessage', async () => {
|
it('复制成功时调用 showMessage', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
const showMessage = vi.fn();
|
const showMessage = vi.fn();
|
||||||
Object.assign(navigator, {
|
vi.spyOn(navigator.clipboard, 'writeText').mockResolvedValue(undefined);
|
||||||
clipboard: { writeText: vi.fn().mockResolvedValue(undefined) },
|
|
||||||
});
|
|
||||||
|
|
||||||
render(
|
render(
|
||||||
<TextInputArea value="测试" onChange={() => {}} allowCopy showMessage={showMessage} />,
|
<TextInputArea value="测试" onChange={() => {}} allowCopy showMessage={showMessage} />,
|
||||||
);
|
);
|
||||||
fireEvent.click(screen.getByRole('button', { name: 'textInputArea.copyContent' }));
|
await user.click(screen.getByRole('button', { name: 'textInputArea.copyContent' }));
|
||||||
|
|
||||||
await vi.waitFor(() => {
|
expect(showMessage).toHaveBeenCalledWith('messages.copySuccess', { severity: 'success' });
|
||||||
expect(showMessage).toHaveBeenCalledWith('messages.copySuccess', { severity: 'success' });
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -20,14 +20,16 @@ beforeEach(() => {
|
|||||||
const waitForStorageReady = () => act(() => Promise.resolve());
|
const waitForStorageReady = () => act(() => Promise.resolve());
|
||||||
|
|
||||||
describe('FileMode', () => {
|
describe('FileMode', () => {
|
||||||
it('应该渲染文件上传区域', () => {
|
it('应该渲染文件上传区域', async () => {
|
||||||
render(<FileMode />);
|
render(<FileMode />);
|
||||||
|
await waitForStorageReady();
|
||||||
expect(screen.getByText('clickOrDropToFile')).toBeInTheDocument();
|
expect(screen.getByText('clickOrDropToFile')).toBeInTheDocument();
|
||||||
expect(screen.getByText('maxFileSize')).toBeInTheDocument();
|
expect(screen.getByText('maxFileSize')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('应该处理有效的文件选择', async () => {
|
it('应该处理有效的文件选择', async () => {
|
||||||
render(<FileMode />);
|
render(<FileMode />);
|
||||||
|
await waitForStorageReady();
|
||||||
|
|
||||||
const file = new File(['test content'], 'test.txt', { type: 'text/plain' });
|
const file = new File(['test content'], 'test.txt', { type: 'text/plain' });
|
||||||
|
|
||||||
@@ -43,6 +45,7 @@ describe('FileMode', () => {
|
|||||||
|
|
||||||
it('应该拒绝超出大小限制的文件', async () => {
|
it('应该拒绝超出大小限制的文件', async () => {
|
||||||
render(<FileMode />);
|
render(<FileMode />);
|
||||||
|
await waitForStorageReady();
|
||||||
|
|
||||||
// 创建一个超过 10MB 的文件
|
// 创建一个超过 10MB 的文件
|
||||||
const largeContent = new Uint8Array(11 * 1024 * 1024);
|
const largeContent = new Uint8Array(11 * 1024 * 1024);
|
||||||
@@ -58,6 +61,7 @@ describe('FileMode', () => {
|
|||||||
|
|
||||||
it('点击清除按钮应该清空文件状态', async () => {
|
it('点击清除按钮应该清空文件状态', async () => {
|
||||||
render(<FileMode />);
|
render(<FileMode />);
|
||||||
|
await waitForStorageReady();
|
||||||
|
|
||||||
const file = new File(['test'], 'test.txt', { type: 'text/plain' });
|
const file = new File(['test'], 'test.txt', { type: 'text/plain' });
|
||||||
const hiddenInput = document.querySelector('input[type="file"]') as HTMLInputElement;
|
const hiddenInput = document.querySelector('input[type="file"]') as HTMLInputElement;
|
||||||
@@ -77,6 +81,7 @@ describe('FileMode', () => {
|
|||||||
|
|
||||||
it('应该显示文件大小和类型信息', async () => {
|
it('应该显示文件大小和类型信息', async () => {
|
||||||
render(<FileMode />);
|
render(<FileMode />);
|
||||||
|
await waitForStorageReady();
|
||||||
|
|
||||||
const file = new File(['test content'], 'test.txt', { type: 'text/plain' });
|
const file = new File(['test content'], 'test.txt', { type: 'text/plain' });
|
||||||
const hiddenInput = document.querySelector('input[type="file"]') as HTMLInputElement;
|
const hiddenInput = document.querySelector('input[type="file"]') as HTMLInputElement;
|
||||||
@@ -91,6 +96,7 @@ describe('FileMode', () => {
|
|||||||
|
|
||||||
it('应该显示原始大小和编码大小', async () => {
|
it('应该显示原始大小和编码大小', async () => {
|
||||||
render(<FileMode />);
|
render(<FileMode />);
|
||||||
|
await waitForStorageReady();
|
||||||
|
|
||||||
const file = new File(['test content'], 'test.txt', { type: 'text/plain' });
|
const file = new File(['test content'], 'test.txt', { type: 'text/plain' });
|
||||||
const hiddenInput = document.querySelector('input[type="file"]') as HTMLInputElement;
|
const hiddenInput = document.querySelector('input[type="file"]') as HTMLInputElement;
|
||||||
@@ -104,6 +110,7 @@ describe('FileMode', () => {
|
|||||||
|
|
||||||
it('应该提供复制按钮', async () => {
|
it('应该提供复制按钮', async () => {
|
||||||
render(<FileMode />);
|
render(<FileMode />);
|
||||||
|
await waitForStorageReady();
|
||||||
|
|
||||||
const file = new File(['test'], 'test.txt', { type: 'text/plain' });
|
const file = new File(['test'], 'test.txt', { type: 'text/plain' });
|
||||||
const hiddenInput = document.querySelector('input[type="file"]') as HTMLInputElement;
|
const hiddenInput = document.querySelector('input[type="file"]') as HTMLInputElement;
|
||||||
|
|||||||
@@ -18,14 +18,16 @@ beforeEach(() => {
|
|||||||
const waitForStorageReady = () => act(() => Promise.resolve());
|
const waitForStorageReady = () => act(() => Promise.resolve());
|
||||||
|
|
||||||
describe('ImageMode', () => {
|
describe('ImageMode', () => {
|
||||||
it('应该渲染图像上传区域', () => {
|
it('应该渲染图像上传区域', async () => {
|
||||||
render(<ImageMode />);
|
render(<ImageMode />);
|
||||||
|
await waitForStorageReady();
|
||||||
expect(screen.getByText('clickOrDropToImage')).toBeInTheDocument();
|
expect(screen.getByText('clickOrDropToImage')).toBeInTheDocument();
|
||||||
expect(screen.getByText('supportedFormats')).toBeInTheDocument();
|
expect(screen.getByText('supportedFormats')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('应该接受有效的图像文件', async () => {
|
it('应该接受有效的图像文件', async () => {
|
||||||
render(<ImageMode />);
|
render(<ImageMode />);
|
||||||
|
await waitForStorageReady();
|
||||||
|
|
||||||
const file = new File(['fake-image-data'], 'test.png', { type: 'image/png' });
|
const file = new File(['fake-image-data'], 'test.png', { type: 'image/png' });
|
||||||
const hiddenInput = document.querySelector('input[type="file"]') as HTMLInputElement;
|
const hiddenInput = document.querySelector('input[type="file"]') as HTMLInputElement;
|
||||||
@@ -39,6 +41,7 @@ describe('ImageMode', () => {
|
|||||||
|
|
||||||
it('应该拒绝非图像文件', async () => {
|
it('应该拒绝非图像文件', async () => {
|
||||||
render(<ImageMode />);
|
render(<ImageMode />);
|
||||||
|
await waitForStorageReady();
|
||||||
|
|
||||||
const file = new File(['not an image'], 'test.txt', { type: 'text/plain' });
|
const file = new File(['not an image'], 'test.txt', { type: 'text/plain' });
|
||||||
const hiddenInput = document.querySelector('input[type="file"]') as HTMLInputElement;
|
const hiddenInput = document.querySelector('input[type="file"]') as HTMLInputElement;
|
||||||
@@ -51,6 +54,7 @@ describe('ImageMode', () => {
|
|||||||
|
|
||||||
it('应该拒绝超出大小限制的图像', async () => {
|
it('应该拒绝超出大小限制的图像', async () => {
|
||||||
render(<ImageMode />);
|
render(<ImageMode />);
|
||||||
|
await waitForStorageReady();
|
||||||
|
|
||||||
const largeContent = new Uint8Array(11 * 1024 * 1024);
|
const largeContent = new Uint8Array(11 * 1024 * 1024);
|
||||||
const file = new File([largeContent], 'large.png', { type: 'image/png' });
|
const file = new File([largeContent], 'large.png', { type: 'image/png' });
|
||||||
@@ -65,6 +69,7 @@ describe('ImageMode', () => {
|
|||||||
|
|
||||||
it('应该通过扩展名识别图像', async () => {
|
it('应该通过扩展名识别图像', async () => {
|
||||||
render(<ImageMode />);
|
render(<ImageMode />);
|
||||||
|
await waitForStorageReady();
|
||||||
|
|
||||||
// 没有 MIME 类型但有正确扩展名
|
// 没有 MIME 类型但有正确扩展名
|
||||||
const file = new File(['fake'], 'test.jpg');
|
const file = new File(['fake'], 'test.jpg');
|
||||||
@@ -78,6 +83,7 @@ describe('ImageMode', () => {
|
|||||||
|
|
||||||
it('点击清除按钮应该清空图像状态', async () => {
|
it('点击清除按钮应该清空图像状态', async () => {
|
||||||
render(<ImageMode />);
|
render(<ImageMode />);
|
||||||
|
await waitForStorageReady();
|
||||||
|
|
||||||
const file = new File(['fake'], 'test.png', { type: 'image/png' });
|
const file = new File(['fake'], 'test.png', { type: 'image/png' });
|
||||||
const hiddenInput = document.querySelector('input[type="file"]') as HTMLInputElement;
|
const hiddenInput = document.querySelector('input[type="file"]') as HTMLInputElement;
|
||||||
@@ -97,6 +103,7 @@ describe('ImageMode', () => {
|
|||||||
|
|
||||||
it('应该显示图像预览', async () => {
|
it('应该显示图像预览', async () => {
|
||||||
render(<ImageMode />);
|
render(<ImageMode />);
|
||||||
|
await waitForStorageReady();
|
||||||
|
|
||||||
const file = new File(['fake-image'], 'test.png', { type: 'image/png' });
|
const file = new File(['fake-image'], 'test.png', { type: 'image/png' });
|
||||||
const hiddenInput = document.querySelector('input[type="file"]') as HTMLInputElement;
|
const hiddenInput = document.querySelector('input[type="file"]') as HTMLInputElement;
|
||||||
|
|||||||
Reference in New Issue
Block a user