From f1f0047928f1961a5de8723b135ef249cce12802 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=A8=E9=9C=96=E9=93=83?= Date: Thu, 21 May 2026 22:06:09 +0800 Subject: [PATCH] =?UTF-8?q?test:=20=E4=BF=AE=E5=A4=8D=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E4=B8=AD=E7=9A=84=20act()=20=E8=AD=A6=E5=91=8A=E5=92=8C=20clip?= =?UTF-8?q?board=20mock=20=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - TextInputArea: 使用 userEvent 替代 fireEvent,用 vi.spyOn 替代 Object.assign mock clipboard - ImageMode/FileMode: 添加 waitForStorageReady() 等待 useStorageState 异步初始化 --- components/__tests__/TextInputArea.test.tsx | 36 ++++++++----------- .../__tests__/FileMode.test.tsx | 9 ++++- .../__tests__/ImageMode.test.tsx | 9 ++++- 3 files changed, 30 insertions(+), 24 deletions(-) diff --git a/components/__tests__/TextInputArea.test.tsx b/components/__tests__/TextInputArea.test.tsx index eff68e8..ec11f0b 100644 --- a/components/__tests__/TextInputArea.test.tsx +++ b/components/__tests__/TextInputArea.test.tsx @@ -1,5 +1,6 @@ import { describe, expect, it, vi } from 'vitest'; import { fireEvent, render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import TextInputArea from '@/components/TextInputArea'; describe('TextInputArea 组件', () => { @@ -107,38 +108,32 @@ describe('TextInputArea 组件', () => { }); it('复制时调用 showMessage', async () => { + const user = userEvent.setup(); const showMessage = vi.fn(); - Object.assign(navigator, { - clipboard: { writeText: vi.fn().mockResolvedValue(undefined) }, - }); + const writeTextSpy = vi.spyOn(navigator.clipboard, 'writeText').mockResolvedValue(undefined); render( {}} 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('测试'); - await vi.waitFor(() => { - expect(showMessage).toHaveBeenCalledWith('messages.copySuccess', { severity: 'success' }); - }); + expect(writeTextSpy).toHaveBeenCalledWith('测试'); + expect(showMessage).toHaveBeenCalledWith('messages.copySuccess', { severity: 'success' }); }); it('复制失败时调用 showMessage 错误提示', async () => { + const user = userEvent.setup(); const showMessage = vi.fn(); - Object.assign(navigator, { - clipboard: { writeText: vi.fn().mockRejectedValue(new Error('失败')) }, - }); + vi.spyOn(navigator.clipboard, 'writeText').mockRejectedValue(new Error('失败')); render( {}} 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', () => { it('复制成功时调用 showMessage', async () => { + const user = userEvent.setup(); const showMessage = vi.fn(); - Object.assign(navigator, { - clipboard: { writeText: vi.fn().mockResolvedValue(undefined) }, - }); + vi.spyOn(navigator.clipboard, 'writeText').mockResolvedValue(undefined); render( {}} 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' }); }); }); diff --git a/pages/Base64Converter/__tests__/FileMode.test.tsx b/pages/Base64Converter/__tests__/FileMode.test.tsx index 6ea313f..09cd890 100644 --- a/pages/Base64Converter/__tests__/FileMode.test.tsx +++ b/pages/Base64Converter/__tests__/FileMode.test.tsx @@ -20,14 +20,16 @@ beforeEach(() => { const waitForStorageReady = () => act(() => Promise.resolve()); describe('FileMode', () => { - it('应该渲染文件上传区域', () => { + it('应该渲染文件上传区域', async () => { render(); + await waitForStorageReady(); expect(screen.getByText('clickOrDropToFile')).toBeInTheDocument(); expect(screen.getByText('maxFileSize')).toBeInTheDocument(); }); it('应该处理有效的文件选择', async () => { render(); + await waitForStorageReady(); const file = new File(['test content'], 'test.txt', { type: 'text/plain' }); @@ -43,6 +45,7 @@ describe('FileMode', () => { it('应该拒绝超出大小限制的文件', async () => { render(); + await waitForStorageReady(); // 创建一个超过 10MB 的文件 const largeContent = new Uint8Array(11 * 1024 * 1024); @@ -58,6 +61,7 @@ describe('FileMode', () => { it('点击清除按钮应该清空文件状态', async () => { render(); + await waitForStorageReady(); const file = new File(['test'], 'test.txt', { type: 'text/plain' }); const hiddenInput = document.querySelector('input[type="file"]') as HTMLInputElement; @@ -77,6 +81,7 @@ describe('FileMode', () => { it('应该显示文件大小和类型信息', async () => { render(); + await waitForStorageReady(); const file = new File(['test content'], 'test.txt', { type: 'text/plain' }); const hiddenInput = document.querySelector('input[type="file"]') as HTMLInputElement; @@ -91,6 +96,7 @@ describe('FileMode', () => { it('应该显示原始大小和编码大小', async () => { render(); + await waitForStorageReady(); const file = new File(['test content'], 'test.txt', { type: 'text/plain' }); const hiddenInput = document.querySelector('input[type="file"]') as HTMLInputElement; @@ -104,6 +110,7 @@ describe('FileMode', () => { it('应该提供复制按钮', async () => { render(); + await waitForStorageReady(); const file = new File(['test'], 'test.txt', { type: 'text/plain' }); const hiddenInput = document.querySelector('input[type="file"]') as HTMLInputElement; diff --git a/pages/Base64Converter/__tests__/ImageMode.test.tsx b/pages/Base64Converter/__tests__/ImageMode.test.tsx index 18fa006..bcc163a 100644 --- a/pages/Base64Converter/__tests__/ImageMode.test.tsx +++ b/pages/Base64Converter/__tests__/ImageMode.test.tsx @@ -18,14 +18,16 @@ beforeEach(() => { const waitForStorageReady = () => act(() => Promise.resolve()); describe('ImageMode', () => { - it('应该渲染图像上传区域', () => { + it('应该渲染图像上传区域', async () => { render(); + await waitForStorageReady(); expect(screen.getByText('clickOrDropToImage')).toBeInTheDocument(); expect(screen.getByText('supportedFormats')).toBeInTheDocument(); }); it('应该接受有效的图像文件', async () => { render(); + await waitForStorageReady(); const file = new File(['fake-image-data'], 'test.png', { type: 'image/png' }); const hiddenInput = document.querySelector('input[type="file"]') as HTMLInputElement; @@ -39,6 +41,7 @@ describe('ImageMode', () => { it('应该拒绝非图像文件', async () => { render(); + await waitForStorageReady(); const file = new File(['not an image'], 'test.txt', { type: 'text/plain' }); const hiddenInput = document.querySelector('input[type="file"]') as HTMLInputElement; @@ -51,6 +54,7 @@ describe('ImageMode', () => { it('应该拒绝超出大小限制的图像', async () => { render(); + await waitForStorageReady(); const largeContent = new Uint8Array(11 * 1024 * 1024); const file = new File([largeContent], 'large.png', { type: 'image/png' }); @@ -65,6 +69,7 @@ describe('ImageMode', () => { it('应该通过扩展名识别图像', async () => { render(); + await waitForStorageReady(); // 没有 MIME 类型但有正确扩展名 const file = new File(['fake'], 'test.jpg'); @@ -78,6 +83,7 @@ describe('ImageMode', () => { it('点击清除按钮应该清空图像状态', async () => { render(); + await waitForStorageReady(); const file = new File(['fake'], 'test.png', { type: 'image/png' }); const hiddenInput = document.querySelector('input[type="file"]') as HTMLInputElement; @@ -97,6 +103,7 @@ describe('ImageMode', () => { it('应该显示图像预览', async () => { render(); + await waitForStorageReady(); const file = new File(['fake-image'], 'test.png', { type: 'image/png' }); const hiddenInput = document.querySelector('input[type="file"]') as HTMLInputElement;