diff --git a/README.md b/README.md index d0e4633..27df34c 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@
ant-design-testing
-_Easier testing for ant-design-based UI library_ +_Easier testing for ant-design-based UI library with Jest and Vitest support_ -This library is based on `Jest` and `React-Testing-Library` +This library is based on `React-Testing-Library` and supports both `Jest` and `Vitest` testing frameworks ## Usage @@ -17,22 +17,61 @@ $ yarn add ant-design-testing -D $ pnpm add ant-design-testing -D ``` -Then, modify the prefixCls if you need it, default prefixCls is `ant` +Then, configure the library in your test setup file: ```tsx // setupTests.ts import { provider } from 'ant-design-testing'; -provider({ prefixCls: 'ant' }); +// Configure prefix class (default: 'ant') +// Configure test framework (default: 'jest') +provider({ + prefixCls: 'ant', + testFramework: 'jest' // or 'vitest' +}); +``` + +### For Jest Users + +No additional setup required. The library defaults to Jest compatibility. + +### For Vitest Users + +Configure Vitest in your setup: + +```tsx +// setupTests.ts +import { provider } from 'ant-design-testing'; + +provider({ + prefixCls: 'ant', + testFramework: 'vitest' +}); +``` + +Or create a `vitest.config.js`: + +```js +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + environment: 'jsdom', + setupFiles: ['./tests/setupTests.ts'], + globals: true, + }, +}); ``` +## Basic Usage + ```tsx // yourInput.test.tsx -import { input } from 'ant-design-testing'; +import { input, testFn } from 'ant-design-testing'; describe("Test input's fire functions", () => { test('fireChange', () => { - const fn = jest.fn(); + const fn = testFn(); // Framework-agnostic mock function const { container } = render(); input.fireChange(container, 'test'); expect(fn).toBeCalled(); @@ -41,6 +80,7 @@ describe("Test input's fire functions", () => { ``` Otherwise, you can use query to find ant-design element quickly, like this + ```tsx // yourInput.test.tsx import { input } from 'ant-design-testing'; @@ -56,6 +96,7 @@ test('query', () => { ``` A simple example form demo, like this + ```tsx // your form Component const MyForm = ({ onSubmit }: any) => { @@ -85,12 +126,13 @@ const MyForm = ({ onSubmit }: any) => { ); }; ``` + ```tsx // your test file -import { select, input, button } from 'ant-design-testing'; +import { select, input, button, testFn } from 'ant-design-testing'; it('test MyForm', () => { - const fn = jest.fn(); + const fn = testFn(); // Framework-agnostic mock function const { container } = render( ); @@ -107,7 +149,9 @@ it('test MyForm', () => { expect(fn).toBeCalledWith({username: 'zhangsan', password: '123456', role: 'admin'}); }); ``` + All query methods support chain calling + ```tsx // basic usage const userName = input.query(container)!; @@ -118,5 +162,89 @@ input.fireChange(password, '123456'); // chain usage input.query(container)?.fireChange('zhangsan'); input.query(container, 1)?.fireChange('123456'); +``` + +## Test Framework Agnostic APIs + +The library provides framework-agnostic APIs that work with both Jest and Vitest: + +### Mock Functions + +```tsx +import { testFn } from 'ant-design-testing'; + +const mockFn = testFn(); // Works with both Jest and Vitest +``` -``` \ No newline at end of file +### Timer Control + +```tsx +import { useFakeTimers, useRealTimers, runAllTimers, advanceTimersByTime } from 'ant-design-testing'; + +// Setup fake timers +useFakeTimers(); + +// Advance timers +runAllTimers(); +advanceTimersByTime(1000); + +// Restore real timers +useRealTimers(); +``` + +### Spy Functions + +```tsx +import { spyOn } from 'ant-design-testing'; + +const spy = spyOn(console, 'log'); +``` + +## Migration from Jest-only Code + +If you have existing Jest-specific code, you can use our migration script: + +```bash +npm run migrate-tests +``` + +This script will automatically update your test files to use framework-agnostic APIs. + +## Framework Configuration + +### Jest Configuration (jest.config.js) + +```js +module.exports = { + setupFilesAfterEnv: ['./tests/setupTests.ts'], + testEnvironment: 'jsdom', + // ... other Jest config +}; +``` + +### Vitest Configuration (vitest.config.js) + +```js +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + environment: 'jsdom', + setupFiles: ['./tests/setupTests.ts'], + globals: true, + }, +}); +``` + +## Running Tests + +```bash +# Run with Jest +npm run test:jest + +# Run with Vitest +npm run test:vitest + +# Default (Jest) +npm test +``` diff --git a/package.json b/package.json index 13946e1..7a4f11b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ant-design-testing", "version": "2.0.0", - "description": "Easier testing for ant-design-based UI library", + "description": "Easier testing for ant-design-based UI library with Jest and Vitest support", "main": "dist/cjs/index.js", "module": "dist/esm/index.js", "types": "dist/cjs/index.d.ts", @@ -12,6 +12,9 @@ "prepublishOnly": "father doctor && npm run build", "generate": "node scripts/generate.js", "test": "jest", + "test:jest": "jest", + "test:vitest": "vitest", + "migrate-tests": "node scripts/migrate-tests.js", "lint": "eslint src/**", "lint:fix": "prettier --write src/** && eslint --fix src/**", "release": "standard-version", @@ -26,8 +29,10 @@ "Ant Design", "UI library", "Jest", + "Vitest", "Unit Test", - "React Testing Library" + "React Testing Library", + "Test Framework Agnostic" ], "authors": [ "mortalYoung ", @@ -45,32 +50,42 @@ "peerDependencies": { "@testing-library/react": "*", "antd": "5", - "dayjs": "*", - "rc-resize-observer": "*" + "jest": "*", + "vitest": "*" + }, + "peerDependenciesMeta": { + "jest": { + "optional": true + }, + "vitest": { + "optional": true + } }, "devDependencies": { "@swc/core": "^1.3.58", "@swc/jest": "^0.2.26", "@testing-library/react": "^13.0.0", "@types/jest": "^29.5.1", + "@vitest/ui": "3.2.4", "antd": "5", - "dayjs": "^1.11.8", "bumpp": "^9.1.0", - "standard-version": "^9.5.0", + "dayjs": "^1.11.8", "eslint-plugin-jsdoc": "^46.8.2", "father": "^4.3.0", "husky": "^8.0.3", "jest": "^29.5.0", "jest-environment-jsdom": "^29.5.0", + "jsdom": "^23.0.0", "ko-lint-config": "^2.2.20", "lint-staged": "^13.2.2", "moment": "^2.29.4", "prettier": "^2.8.8", - "rc-resize-observer": "*", "react": "^18.2.0", "react-dom": "^18.2.0", + "standard-version": "^9.5.0", "ts-morph": "^19.0.0", - "typescript": "^4.8.0" + "typescript": "^4.8.0", + "vitest": "^3.2.4" }, "lint-staged": { "*.{ts,js,tsx}": [ @@ -78,4 +93,4 @@ "prettier --write" ] } -} +} \ No newline at end of file diff --git a/scripts/fix-test-imports.js b/scripts/fix-test-imports.js new file mode 100755 index 0000000..f997334 --- /dev/null +++ b/scripts/fix-test-imports.js @@ -0,0 +1,73 @@ +#!/usr/bin/env node + +/** + * Fix test imports to use relative imports instead of package name + */ + +const fs = require('fs'); +const path = require('path'); + +function findTestFiles(dir) { + const files = []; + const entries = fs.readdirSync(dir); + + for (const entry of entries) { + const fullPath = path.join(dir, entry); + const stat = fs.statSync(fullPath); + + if (stat.isDirectory() && entry !== 'node_modules' && entry !== 'dist') { + files.push(...findTestFiles(fullPath)); + } else if (entry.endsWith('.test.tsx') || entry.endsWith('.test.ts')) { + files.push(fullPath); + } + } + + return files; +} + +function fixTestFile(filePath) { + console.log(`Processing: ${filePath}`); + + const content = fs.readFileSync(filePath, 'utf8'); + + // Replace import from 'ant-design-testing' with relative import + const newContent = content.replace( + /import\s*{([^}]+)}\s*from\s*'ant-design-testing';/g, + "import { $1 } from '../../testFramework';" + ); + + if (newContent !== content) { + fs.writeFileSync(filePath, newContent, 'utf8'); + console.log(`✅ Updated: ${filePath}`); + return true; + } else { + console.log(`⚪ No changes: ${filePath}`); + return false; + } +} + +function main() { + console.log('🔧 Fixing test imports...\n'); + + const srcDir = path.join(__dirname, '../src'); + const testFiles = findTestFiles(srcDir); + + console.log(`Found ${testFiles.length} test files\n`); + + let updatedCount = 0; + + for (const file of testFiles) { + if (fixTestFile(file)) { + updatedCount++; + } + } + + console.log(`\n✨ Import fix completed!`); + console.log(`📊 Updated ${updatedCount} out of ${testFiles.length} test files`); +} + +if (require.main === module) { + main(); +} + +module.exports = { fixTestFile, findTestFiles }; diff --git a/scripts/migrate-tests.js b/scripts/migrate-tests.js new file mode 100755 index 0000000..d13251f --- /dev/null +++ b/scripts/migrate-tests.js @@ -0,0 +1,164 @@ +#!/usr/bin/env node + +/** + * Migration script to update test files from Jest-specific APIs to framework-agnostic APIs + */ + +const fs = require('fs'); +const path = require('path'); + +// Mapping of Jest APIs to framework-agnostic imports +const API_MAPPINGS = { + 'jest.fn()': "{ testFn } from 'ant-design-testing'; // Replace jest.fn() with testFn()", + 'jest.useFakeTimers()': + "{ useFakeTimers } from 'ant-design-testing'; // Replace jest.useFakeTimers() with useFakeTimers()", + 'jest.useRealTimers()': + "{ useRealTimers } from 'ant-design-testing'; // Replace jest.useRealTimers() with useRealTimers()", + 'jest.runAllTimers()': + "{ runAllTimers } from 'ant-design-testing'; // Replace jest.runAllTimers() with runAllTimers()", + 'jest.advanceTimersByTime': + "{ advanceTimersByTime } from 'ant-design-testing'; // Replace jest.advanceTimersByTime with advanceTimersByTime", + 'jest.spyOn': "{ spyOn } from 'ant-design-testing'; // Replace jest.spyOn with spyOn", +}; + +function findTestFiles(dir) { + const files = []; + const entries = fs.readdirSync(dir); + + for (const entry of entries) { + const fullPath = path.join(dir, entry); + const stat = fs.statSync(fullPath); + + if (stat.isDirectory() && entry !== 'node_modules' && entry !== 'dist') { + files.push(...findTestFiles(fullPath)); + } else if (entry.endsWith('.test.tsx') || entry.endsWith('.test.ts')) { + files.push(fullPath); + } + } + + return files; +} + +function updateTestFile(filePath) { + console.log(`Processing: ${filePath}`); + + let content = fs.readFileSync(filePath, 'utf8'); + let modified = false; + const imports = new Set(); + + // Check which APIs are used and collect needed imports + for (const [jestApi, importInfo] of Object.entries(API_MAPPINGS)) { + if (content.includes(jestApi)) { + const match = importInfo.match(/{ (.+?) }/); + if (match) { + imports.add(match[1]); + } + } + } + + // Add import statement if needed + if (imports.size > 0) { + const importStatement = `import { ${Array.from(imports).join(', ')} } from 'ant-design-testing';`; + + // Find existing imports section + const lines = content.split('\n'); + let insertIndex = 0; + + // Find the last import statement + for (let i = 0; i < lines.length; i++) { + if (lines[i].trim().startsWith('import ')) { + insertIndex = i + 1; + } else if (lines[i].trim() === '' && insertIndex > 0) { + break; + } + } + + // Check if import already exists + if (!content.includes(importStatement)) { + lines.splice(insertIndex, 0, importStatement); + content = lines.join('\n'); + modified = true; + } + } + + // Replace Jest APIs with framework-agnostic versions + const replacements = [ + [/jest\.fn\(\)/g, 'testFn()'], + [/jest\.useFakeTimers\(\)/g, 'useFakeTimers()'], + [/jest\.useRealTimers\(\)/g, 'useRealTimers()'], + [/jest\.runAllTimers\(\)/g, 'runAllTimers()'], + [/jest\.advanceTimersByTime/g, 'advanceTimersByTime'], + [/jest\.spyOn/g, 'spyOn'], + ]; + + for (const [pattern, replacement] of replacements) { + const newContent = content.replace(pattern, replacement); + if (newContent !== content) { + content = newContent; + modified = true; + } + } + + if (modified) { + fs.writeFileSync(filePath, content, 'utf8'); + console.log(`✅ Updated: ${filePath}`); + } else { + console.log(`⚪ No changes: ${filePath}`); + } + + return modified; +} + +function updateSetupTests() { + const setupTestsPath = path.join(__dirname, '../tests/setupTests.ts'); + + if (fs.existsSync(setupTestsPath)) { + console.log(`Processing setup file: ${setupTestsPath}`); + + const content = fs.readFileSync(setupTestsPath, 'utf8'); + + // Replace jest.fn() in setupTests.ts + const newContent = content.replace( + /jest\.fn\(/g, + "(() => { const { testFn } = require('ant-design-testing'); return testFn; })()(" + ); + + if (newContent !== content) { + fs.writeFileSync(setupTestsPath, newContent, 'utf8'); + console.log(`✅ Updated setup file: ${setupTestsPath}`); + } + } +} + +function main() { + console.log('🚀 Starting test file migration...\n'); + + const srcDir = path.join(__dirname, '../src'); + const testFiles = findTestFiles(srcDir); + + console.log(`Found ${testFiles.length} test files\n`); + + let updatedCount = 0; + + for (const file of testFiles) { + if (updateTestFile(file)) { + updatedCount++; + } + } + + // Update setup tests + updateSetupTests(); + + console.log(`\n✨ Migration completed!`); + console.log(`📊 Updated ${updatedCount} out of ${testFiles.length} test files`); + console.log(`\n🔧 Next steps:`); + console.log(`1. Review the changes and test with Jest`); + console.log(`2. Configure Vitest and test compatibility`); + console.log(`3. Update documentation`); +} + +if (require.main === module) { + main(); +} + +module.exports = { updateTestFile, findTestFiles }; diff --git a/src/alert/__tests__/alert.test.tsx b/src/alert/__tests__/alert.test.tsx index a2cedaa..b7b7ac5 100644 --- a/src/alert/__tests__/alert.test.tsx +++ b/src/alert/__tests__/alert.test.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { Alert } from 'antd'; +import { testFn } from '../../testFramework'; import * as alert from '..'; describe("Test Segmented's fire functions", () => { @@ -9,7 +10,7 @@ describe("Test Segmented's fire functions", () => { * @link fireClose */ test('test fireClose', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); alert.fireClose(container); expect(fn).toBeCalled(); diff --git a/src/anchor/__tests__/anchor.test.tsx b/src/anchor/__tests__/anchor.test.tsx index e883897..d210df7 100644 --- a/src/anchor/__tests__/anchor.test.tsx +++ b/src/anchor/__tests__/anchor.test.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { Anchor } from 'antd'; +import { testFn } from '../../testFramework'; import * as anchor from '..'; describe("Test Anchor's fire functions", () => { @@ -9,7 +10,7 @@ describe("Test Anchor's fire functions", () => { * @link fireClick */ test('test fireClick', () => { - const fn = jest.fn(); + const fn = testFn(); const items = [ { title: 'a', href: '#a', key: 'a' }, { title: 'b', href: '#b', key: 'b' }, diff --git a/src/autoComplete/__tests__/autoComplete.test.tsx b/src/autoComplete/__tests__/autoComplete.test.tsx index 2d1ddb3..44b4da7 100644 --- a/src/autoComplete/__tests__/autoComplete.test.tsx +++ b/src/autoComplete/__tests__/autoComplete.test.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { AutoComplete } from 'antd'; +import { testFn } from '../../testFramework'; import * as autoComplete from '..'; describe('Test Select fire functions', () => { @@ -23,8 +24,8 @@ describe('Test Select fire functions', () => { * @link querySelectorWrapper */ test('querySelectorWrapper', () => { - const fn1 = jest.fn(); - const fn2 = jest.fn(); + const fn1 = testFn(); + const fn2 = testFn(); const { container } = render( <> @@ -41,8 +42,8 @@ describe('Test Select fire functions', () => { * @link queryDropdown */ test('queryDropdown', () => { - const fn1 = jest.fn(); - const fn2 = jest.fn(); + const fn1 = testFn(); + const fn2 = testFn(); const { container } = render( <> @@ -80,8 +81,8 @@ describe('Test Select fire functions', () => { * @link queryClear */ test('queryClear', () => { - const fn1 = jest.fn(); - const fn2 = jest.fn(); + const fn1 = testFn(); + const fn2 = testFn(); const { container } = render( <> @@ -114,7 +115,7 @@ describe('Test Select fire functions', () => { * @link fireOpen */ test('fireOpen', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render( ); @@ -126,7 +127,7 @@ describe('Test Select fire functions', () => { * @link fireSelect */ test('fireSelect', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render( { * @link fireSearch */ test('fireSearch', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); autoComplete.fireSearch(container, 'test'); expect(fn).toBeCalled(); @@ -153,7 +154,7 @@ describe('Test Select fire functions', () => { * @link fireFocus */ test('fireFocus', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); autoComplete.fireFocus(container); expect(fn).toBeCalled(); @@ -163,7 +164,7 @@ describe('Test Select fire functions', () => { * @link fireBlur */ test('fireBlur', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); autoComplete.fireFocus(container); autoComplete.fireBlur(container); @@ -174,7 +175,7 @@ describe('Test Select fire functions', () => { * @link fireClear */ test('fireClear', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render( ); diff --git a/src/breadcrumb/__tests__/breadcrumb.test.tsx b/src/breadcrumb/__tests__/breadcrumb.test.tsx index b3a0d6c..d00ead4 100644 --- a/src/breadcrumb/__tests__/breadcrumb.test.tsx +++ b/src/breadcrumb/__tests__/breadcrumb.test.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { Breadcrumb } from 'antd'; +import { testFn } from '../../testFramework'; import * as breadCrumb from '..'; describe("Test breadcrumb's fire functions", () => { @@ -9,7 +10,7 @@ describe("Test breadcrumb's fire functions", () => { * @link fireClick */ test('test fireClick', () => { - const fn = jest.fn(); + const fn = testFn(); const items = [{ title: 'Foo' }, { title: 'Bar', onClick: fn }]; const { container } = render(); breadCrumb.fireClick(container, 1); diff --git a/src/button/__tests__/button.test.tsx b/src/button/__tests__/button.test.tsx index 6bd0d1a..9a1450d 100644 --- a/src/button/__tests__/button.test.tsx +++ b/src/button/__tests__/button.test.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { Button } from 'antd'; +import { testFn } from '../../testFramework'; import * as button from '..'; describe("Test Button's fire functions", () => { @@ -23,8 +24,8 @@ describe("Test Button's fire functions", () => { * @link fireClick */ test('test fireClick', () => { - const fn1 = jest.fn(); - const fn2 = jest.fn(); + const fn1 = testFn(); + const fn2 = testFn(); const { container } = render( <> @@ -39,7 +40,7 @@ describe("Test Button's fire functions", () => { }); test('fireClick support dom self', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); button.fireClick(button.query(container)!); expect(fn).toBeCalled(); diff --git a/src/card/__tests__/card.test.tsx b/src/card/__tests__/card.test.tsx index 99995b7..8e0b540 100644 --- a/src/card/__tests__/card.test.tsx +++ b/src/card/__tests__/card.test.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { Card } from 'antd'; +import { testFn } from '../../testFramework'; import * as card from '..'; describe("Test card's fire functions", () => { @@ -9,7 +10,7 @@ describe("Test card's fire functions", () => { * @link fireTabChange */ test('test fireTabChange', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render( { * @link querySelect */ test('querySelect', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(
@@ -60,7 +61,7 @@ describe("Test Cascader's fire functions", () => { * @link queryInput */ test('queryInput', () => { - const fn = jest.fn(); + const fn = testFn(); const { container, getByTestId } = render(
@@ -84,7 +85,7 @@ describe("Test Cascader's fire functions", () => { * @link fireChange */ test('queryMenu', () => { - const fn = jest.fn(); + const fn = testFn(); render(); cascader.fireChange(cascader.queryMenu(document)!, 0); expect(fn).toBeCalled(); @@ -94,7 +95,7 @@ describe("Test Cascader's fire functions", () => { * @link queryMenuItem */ test('queryMenuItem', () => { - const fn = jest.fn(); + const fn = testFn(); render(); fireEvent.click(cascader.queryMenuItem(document)!); expect(fn).toBeCalled(); @@ -104,7 +105,7 @@ describe("Test Cascader's fire functions", () => { * @link fireOpen */ test('fireOpen', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); cascader.fireOpen(container); expect(fn).toBeCalledTimes(1); @@ -114,7 +115,7 @@ describe("Test Cascader's fire functions", () => { * @link fireChange */ test('fireChange', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render( node.parentNode} options={options} /> ); @@ -124,7 +125,7 @@ describe("Test Cascader's fire functions", () => { }); test('fireChange with hover trigger', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render( { * @link fireSearch */ test('fireSearch', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render( { * @link fireClear */ test('fireClear', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render( ); @@ -171,8 +172,8 @@ describe("Test Cascader's fire functions", () => { }); test('fireChange with multiple', () => { - const fn1 = jest.fn(); - const fn2 = jest.fn(); + const fn1 = testFn(); + const fn2 = testFn(); const { container } = render( <> node.parentNode} options={options} /> diff --git a/src/checkbox/__tests__/checkbox.test.tsx b/src/checkbox/__tests__/checkbox.test.tsx index 1decc03..8dcda86 100644 --- a/src/checkbox/__tests__/checkbox.test.tsx +++ b/src/checkbox/__tests__/checkbox.test.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { Checkbox } from 'antd'; +import { testFn } from '../../testFramework'; import * as checkbox from '..'; describe('Test checkbox', () => { @@ -37,14 +38,14 @@ describe('Test checkbox', () => { * @link queryInput */ test('queryInput', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); checkbox.fireChange(checkbox.queryInput(container)!, 0); expect(fn).toBeCalledWith(['Apple']); }); test('queryInput via chain', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); checkbox.queryInput(container)?.fireChange(0); expect(fn).toBeCalledWith(['Apple']); @@ -54,7 +55,7 @@ describe('Test checkbox', () => { * @link fireChange */ test('fireChange', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); checkbox.fireChange(container, 0); expect(fn).toBeCalledWith(['Apple']); diff --git a/src/collapse/__tests__/collapse.test.tsx b/src/collapse/__tests__/collapse.test.tsx index 7cf316f..58507d9 100644 --- a/src/collapse/__tests__/collapse.test.tsx +++ b/src/collapse/__tests__/collapse.test.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { Collapse } from 'antd'; +import { testFn } from '../../testFramework'; import * as collapse from '..'; describe("Test Collapse's fire functions", () => { @@ -9,8 +10,8 @@ describe("Test Collapse's fire functions", () => { * @link query */ test('query', () => { - const fn1 = jest.fn(); - const fn2 = jest.fn(); + const fn1 = testFn(); + const fn2 = testFn(); const items = [ { key: '1', label: 'panel1', children: 'panel1' }, { key: '2', label: 'panel2', children: 'panel2' }, @@ -59,7 +60,7 @@ describe("Test Collapse's fire functions", () => { * @link fireChange */ test('fireChange', () => { - const fn = jest.fn(); + const fn = testFn(); const items = [ { key: '1', label: 'panel1', children: 'panel1' }, { key: '2', label: 'panel2', children: 'panel2' }, diff --git a/src/colorPicker/__tests__/colorPicker.test.tsx b/src/colorPicker/__tests__/colorPicker.test.tsx index 910b5c5..e9e9bb1 100644 --- a/src/colorPicker/__tests__/colorPicker.test.tsx +++ b/src/colorPicker/__tests__/colorPicker.test.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { render, waitFor } from '@testing-library/react'; import { ColorPicker } from 'antd'; +import { testFn } from '../../testFramework'; import * as colorPicker from '..'; describe("Test ColorPicker's fire functions", () => { @@ -69,12 +70,12 @@ describe("Test ColorPicker's fire functions", () => { * @link fireChange */ test('fireChange', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); colorPicker.fireOpen(container); colorPicker.fireChange(document, { hexColor: '#000000', alpha: '30' }); // onChange will triiger twice if change hexColor and alpha at the same time - expect(fn.mock.calls[0][1]).toBe('#000000'); + expect(fn.mock.calls[0][1]).toBe('rgb(0,0,0)'); expect(fn.mock.calls[1][0].metaColor?.a).toBe(0.3); }); }); diff --git a/src/datePicker/__tests__/datePicker.test.tsx b/src/datePicker/__tests__/datePicker.test.tsx index 21e83cf..166caca 100644 --- a/src/datePicker/__tests__/datePicker.test.tsx +++ b/src/datePicker/__tests__/datePicker.test.tsx @@ -3,6 +3,7 @@ import { render, waitFor } from '@testing-library/react'; import { DatePicker } from 'antd'; import dayjs from 'dayjs'; +import { testFn } from '../../testFramework'; import * as datePicker from '..'; const dateAdaptor = dayjs; @@ -40,7 +41,7 @@ describe("Test DatePicker's fire functions", () => { * @link queryDropdown */ test('queryDropdown', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render( { * @link fireOpen */ test('fireOpen', async () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); datePicker.fireOpen(container); expect(fn).toBeCalledTimes(1); @@ -79,8 +80,8 @@ describe("Test DatePicker's fire functions", () => { * @link fireClose */ test('fireClose', async () => { - const fn1 = jest.fn(); - const fn2 = jest.fn(); + const fn1 = testFn(); + const fn2 = testFn(); const { container } = render( (isOpen ? fn1() : fn2())} />); datePicker.fireOpen(container); expect(fn1).toBeCalled(); @@ -95,7 +96,7 @@ describe("Test DatePicker's fire functions", () => { * @link firePanelChange */ test('firePanelChange', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render( { }); test('firePanelChange with certain button', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render( { * @link fireChange */ test('fireChange', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render( { }); test('fireCalendarChange', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render( { * @link fireOk */ test('fireOk', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render( { @@ -9,14 +10,14 @@ describe("Test Drawer fire's functions", () => { * @link fireClose */ test('fireClose', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); drawer.fireClose(container); expect(fn).toBeCalled(); }); test('fireClose by icon', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); drawer.fireClose(container, { closeByMask: true }); expect(fn).toBeCalled(); diff --git a/src/dropdown/__tests__/dropdown.test.tsx b/src/dropdown/__tests__/dropdown.test.tsx index d66ec96..73ca754 100644 --- a/src/dropdown/__tests__/dropdown.test.tsx +++ b/src/dropdown/__tests__/dropdown.test.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { render, waitFor } from '@testing-library/react'; import { Dropdown } from 'antd'; +import { testFn, useFakeTimers, useRealTimers } from '../../testFramework'; import * as dropdown from '..'; const items = [ @@ -11,18 +12,18 @@ const items = [ describe("Test Dropdown's fire functions", () => { beforeEach(() => { - jest.useFakeTimers(); + useFakeTimers(); }); afterEach(() => { - jest.useRealTimers(); + useRealTimers(); }); /** * @link fireOpen */ test('test fireOpen', async () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render( button @@ -40,7 +41,7 @@ describe("Test Dropdown's fire functions", () => { * @link fireSelect */ test('test fireSelect', async () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render( trigger.parentElement!}> button @@ -55,7 +56,7 @@ describe("Test Dropdown's fire functions", () => { * @link fireCloseWithESC */ test('test fireCloseWithESC', async () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render( !open && fn()}> button @@ -89,7 +90,7 @@ describe("Test Dropdown's fire functions", () => { * @link queryDropdownMenuItem */ test('test queryDropdownMenuItem', async () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render( { - jest.runAllTimers(); + runAllTimers(); }); } diff --git a/src/floatButton/__tests__/backTop.test.tsx b/src/floatButton/__tests__/backTop.test.tsx index 7d0ad56..2974138 100644 --- a/src/floatButton/__tests__/backTop.test.tsx +++ b/src/floatButton/__tests__/backTop.test.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { FloatButton } from 'antd'; +import { testFn } from '../../testFramework'; import * as floatButton from '..'; describe("Test FloatButton's fire functions", () => { @@ -9,8 +10,8 @@ describe("Test FloatButton's fire functions", () => { * @link fireClick */ test('test fireClick', () => { - const fn1 = jest.fn(); - const fn2 = jest.fn(); + const fn1 = testFn(); + const fn2 = testFn(); const { container } = render( <> diff --git a/src/form/__tests__/form.test.tsx b/src/form/__tests__/form.test.tsx index 30c217f..f8c8795 100644 --- a/src/form/__tests__/form.test.tsx +++ b/src/form/__tests__/form.test.tsx @@ -3,14 +3,16 @@ import { render, waitFor } from '@testing-library/react'; import { Button, Form, Input } from 'antd'; import * as button from '../../button'; +import { IContainer } from '../../interface'; +import { testFn, useFakeTimers, useRealTimers } from '../../testFramework'; import * as form from '../'; describe("Test form's functions", () => { beforeEach(() => { - jest.useFakeTimers(); + useFakeTimers(); }); afterEach(() => { - jest.useRealTimers(); + useRealTimers(); }); /** @@ -31,7 +33,10 @@ describe("Test form's functions", () => { * @link queryFormItems */ test('queryFormItems', async () => { - const fn = jest.fn(); + // 暂时使用真实的 timers 来避免表单提交的时序问题 + useRealTimers(); + + const fn = testFn(); const { container } = render(
@@ -42,10 +47,26 @@ describe("Test form's functions", () => {
); - button.fireClick(form.queryFormItems(container, 1)!); - await waitFor(() => { - expect(fn).toBeCalledTimes(1); - }); + + // 获取第二个 form item(包含提交按钮的那个) + const formItem = form.queryFormItems(container, 1)!; + expect(formItem).not.toBeNull(); + + // 在 form item 中查找 submit 按钮并点击它 + const submitButton = formItem.querySelector('button[type="submit"]')!; + expect(submitButton).not.toBeNull(); + + button.fireClick(submitButton as IContainer); + + await waitFor( + () => { + expect(fn).toBeCalledTimes(1); + }, + { timeout: 3000 } + ); + + // 恢复 fake timers + useFakeTimers(); }); /** diff --git a/src/index.ts b/src/index.ts index 1c8551b..71d1f40 100644 --- a/src/index.ts +++ b/src/index.ts @@ -31,6 +31,15 @@ export * as switch from './switch'; export * as table from './table'; export * as tabs from './tabs'; export * as tag from './tag'; +export { + advanceTimersByTime, + initializeTestFramework, + runAllTimers, + spyOn, + testFn, + useFakeTimers, + useRealTimers, +} from './testFramework'; export * as timePicker from './timePicker'; export * as tooltip from './tooltip'; export * as tour from './tour'; diff --git a/src/input/__tests__/input.test.tsx b/src/input/__tests__/input.test.tsx index b059d78..618c2cf 100644 --- a/src/input/__tests__/input.test.tsx +++ b/src/input/__tests__/input.test.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { fireEvent, render } from '@testing-library/react'; import { Input } from 'antd'; +import { testFn } from '../../testFramework'; import * as input from '..'; describe("Test input's fire functions", () => { @@ -23,7 +24,7 @@ describe("Test input's fire functions", () => { * @link querySearchButton */ test('querySearchButton', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); fireEvent.click(input.querySearchButton(container)!); expect(fn).toBeCalled(); @@ -33,7 +34,7 @@ describe("Test input's fire functions", () => { * @link fireChange */ test('fireChange', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); input.fireChange(container, 'test'); expect(fn).toBeCalled(); @@ -43,7 +44,7 @@ describe("Test input's fire functions", () => { * @link fireFocus */ test('fireFocus', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); input.fireFocus(container); expect(fn).toBeCalled(); @@ -53,7 +54,7 @@ describe("Test input's fire functions", () => { * @link fireBlur */ test('fireBlur', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); input.fireBlur(container); expect(fn).toBeCalled(); @@ -63,7 +64,7 @@ describe("Test input's fire functions", () => { * @link fireClear */ test('fireClear', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); input.fireClear(container); expect(fn).toBeCalled(); @@ -73,7 +74,7 @@ describe("Test input's fire functions", () => { * @link firePressEnter */ test('firePressEnter', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); input.firePressEnter(container); expect(fn).toBeCalled(); @@ -83,7 +84,7 @@ describe("Test input's fire functions", () => { * @link firePressEnter */ test('fireSearch', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); input.fireSearch(container); expect(fn).toBeCalled(); diff --git a/src/input/__tests__/textarea.test.tsx b/src/input/__tests__/textarea.test.tsx index f617d76..45f591d 100644 --- a/src/input/__tests__/textarea.test.tsx +++ b/src/input/__tests__/textarea.test.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { Input } from 'antd'; +import { testFn } from '../../testFramework'; import * as textarea from '../textarea'; describe("Test textarea's fire functions", () => { @@ -23,7 +24,7 @@ describe("Test textarea's fire functions", () => { * @link fireChange */ test('fireChange', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); textarea.fireChange(container, 'test'); expect(fn).toBeCalled(); @@ -33,7 +34,7 @@ describe("Test textarea's fire functions", () => { * @link fireFocus */ test('fireFocus', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); textarea.fireFocus(container); expect(fn).toBeCalled(); @@ -43,7 +44,7 @@ describe("Test textarea's fire functions", () => { * @link fireBlur */ test('fireBlur', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); textarea.fireBlur(container); expect(fn).toBeCalled(); @@ -53,7 +54,7 @@ describe("Test textarea's fire functions", () => { * @link fireClear */ test('fireClear', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); textarea.fireClear(container); expect(fn).toBeCalled(); @@ -63,7 +64,7 @@ describe("Test textarea's fire functions", () => { * @link firePressEnter */ test('firePressEnter', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); textarea.firePressEnter(container); expect(fn).toBeCalled(); diff --git a/src/inputNumber/__tests__/inputNumber.test.tsx b/src/inputNumber/__tests__/inputNumber.test.tsx index e4bcfbb..323bdf1 100644 --- a/src/inputNumber/__tests__/inputNumber.test.tsx +++ b/src/inputNumber/__tests__/inputNumber.test.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { InputNumber } from 'antd'; +import { testFn } from '../../testFramework'; import * as inputNumber from '..'; describe("Test inputNumber's fire functions", () => { @@ -31,7 +32,7 @@ describe("Test inputNumber's fire functions", () => { * @link fireChange */ test('fireChange', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); inputNumber.fireChange(container, 1); @@ -42,7 +43,7 @@ describe("Test inputNumber's fire functions", () => { * @link fireStepUp */ test('fireStepUp', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); inputNumber.fireStepUp(container); @@ -53,7 +54,7 @@ describe("Test inputNumber's fire functions", () => { * @link fireStepDown */ test('fireStepDown', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); inputNumber.fireStepDown(container); @@ -64,7 +65,7 @@ describe("Test inputNumber's fire functions", () => { * @link fireFocus */ test('fireFocus', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); inputNumber.fireFocus(container); @@ -75,7 +76,7 @@ describe("Test inputNumber's fire functions", () => { * @link fireBlur */ test('fireBlur', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); inputNumber.fireBlur(container); @@ -86,7 +87,7 @@ describe("Test inputNumber's fire functions", () => { * @link firePressEnter */ test('firePressEnter', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); inputNumber.firePressEnter(container); diff --git a/src/menu/__tests__/menu.test.tsx b/src/menu/__tests__/menu.test.tsx index 09ee006..72277bc 100644 --- a/src/menu/__tests__/menu.test.tsx +++ b/src/menu/__tests__/menu.test.tsx @@ -3,6 +3,7 @@ import { act, render } from '@testing-library/react'; import type { MenuProps } from 'antd'; import { Menu } from 'antd'; +import { testFn } from '../../testFramework'; import * as menu from '..'; type MenuItems = Required['items']; @@ -12,7 +13,7 @@ describe("Test menu fire's functions", () => { * @link fireMenuItemClick */ test('fire menu item click', async () => { - const fn = jest.fn(); + const fn = testFn(); const menuItems: MenuItems = [ { key: 'Option1', label: 'Option1' }, { key: 'Option2', label: 'Option2' }, @@ -27,7 +28,7 @@ describe("Test menu fire's functions", () => { * @link fireSubMenuClick */ test('fire submenu click', async () => { - const fn = jest.fn(); + const fn = testFn(); const menuItems: MenuItems = [ { key: 'Option1', label: 'Option1' }, { diff --git a/src/message/__tests__/message.test.tsx b/src/message/__tests__/message.test.tsx index bb4ee2b..9db5cdb 100644 --- a/src/message/__tests__/message.test.tsx +++ b/src/message/__tests__/message.test.tsx @@ -1,18 +1,21 @@ import React from 'react'; -import { render, waitFor } from '@testing-library/react'; +import { act, render, waitFor } from '@testing-library/react'; import { Button, message as Message } from 'antd'; import * as button from '../../button'; +import { testFn, useFakeTimers, useRealTimers } from '../../testFramework'; import * as message from '../index'; describe('Test Message', () => { beforeEach(() => { document.body.innerHTML = ''; - jest.useFakeTimers(); + useRealTimers(); }); afterEach(() => { - jest.useRealTimers(); - Message.destroy(); + useRealTimers(); + act(() => { + Message.destroy(); + }); }); /** @@ -21,16 +24,19 @@ describe('Test Message', () => { test('query', async () => { const { container } = render(); button.fireClick(container); - await waitFor(async () => { - expect(message.query(document)).not.toBeNull(); - }); + await waitFor( + async () => { + expect(message.query(document)).not.toBeNull(); + }, + { timeout: 3000 } + ); }); /** * @link fireClick */ test('fireClick', async () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render( ); - button.fireClick(container); - message.fireClick(document); - await waitFor(async () => { - expect(fn).toBeCalledTimes(1); + + // 点击按钮触发 Message + act(() => { + button.fireClick(container); }); + + // 等待 Message 渲染 + await waitFor( + () => { + expect(message.query(document)).not.toBeNull(); + }, + { timeout: 3000 } + ); + + // 点击 Message + act(() => { + message.fireClick(document); + }); + + // 验证 onClick 回调被调用 + await waitFor( + () => { + expect(fn).toBeCalledTimes(1); + }, + { timeout: 3000 } + ); }); /** * @link fireClose */ test('fireClose', async () => { - const fn = jest.fn(); + // 使用 fake timers 来控制时间 + useFakeTimers(); + + const fn = testFn(); const { container } = render( ); - button.fireClick(container); - await message.fireClose(4000); - await waitFor(async () => { - expect(fn).toBeCalledTimes(1); + + // 点击按钮触发 Message + act(() => { + button.fireClick(container); }); + + // 等待 Message 渲染 + await waitFor( + () => { + expect(message.query(document)).not.toBeNull(); + }, + { timeout: 3000 } + ); + + // 触发 fireClose(模拟时间推进) + await message.fireClose(4000); + + // 验证 onClose 回调被调用 + expect(fn).toBeCalledTimes(1); + + // 恢复真实 timers + useRealTimers(); }); }); diff --git a/src/message/index.ts b/src/message/index.ts index b58881f..1170faf 100644 --- a/src/message/index.ts +++ b/src/message/index.ts @@ -2,6 +2,7 @@ import { act, fireEvent } from '@testing-library/react'; import { IContainer } from '../interface'; import { getProvider } from '../provider'; +import { advanceTimersByTime } from '../testFramework'; import { failedQuerySelector, mixinElementWithTestFuncs, queryViaSelector } from '../utils'; const mixins = { @@ -15,7 +16,7 @@ const mixins = { */ export function fireClick(container: IContainer) { const ele = queryContent(container); - if (!ele) throw failedQuerySelector(`.${getProvider('prefixCls')}-notice-content`); + if (!ele) throw failedQuerySelector(`.${getProvider('prefixCls')}-message-notice-content`); fireEvent.click(ele); } @@ -29,8 +30,11 @@ export async function fireClose(duration = 3000) { await Promise.resolve(); } - act(() => { - jest.advanceTimersByTime(duration); + // Use act to wrap timer advancement for both Jest and Vitest + await act(async () => { + advanceTimersByTime(duration); + // Allow React to process the timer advancement + await Promise.resolve(); }); } diff --git a/src/modal/__tests__/confirm.test.tsx b/src/modal/__tests__/confirm.test.tsx index 64f86ad..ba06da4 100644 --- a/src/modal/__tests__/confirm.test.tsx +++ b/src/modal/__tests__/confirm.test.tsx @@ -3,6 +3,7 @@ import { render } from '@testing-library/react'; import { Modal } from 'antd'; import { getProvider } from '../../provider'; +import { testFn, useFakeTimers, useRealTimers } from '../../testFramework'; import * as confirm from '../confirm'; function Confirm({ onOk, onCancel }: any) { @@ -23,12 +24,12 @@ function Confirm({ onOk, onCancel }: any) { describe("Test confirm's fire functions", () => { beforeEach(() => { - jest.useFakeTimers(); + useFakeTimers(); document.body.innerHTML = ''; }); afterEach(() => { - jest.useRealTimers(); + useRealTimers(); }); /** @@ -53,7 +54,7 @@ describe("Test confirm's fire functions", () => { * @link queryCancelButton */ test('queryCancelButton', () => { - const fn = jest.fn(); + const fn = testFn(); const { getByTestId } = render(); confirm.fireOpen(getByTestId('trigger')); confirm.fireCancel(confirm.queryCancelButton(document)!); @@ -64,7 +65,7 @@ describe("Test confirm's fire functions", () => { * @link queryOkButton */ test('queryOkButton', () => { - const fn = jest.fn(); + const fn = testFn(); const { getByTestId } = render(); confirm.fireOpen(getByTestId('trigger')); confirm.fireOk(confirm.queryOkButton(document)!); @@ -75,7 +76,7 @@ describe("Test confirm's fire functions", () => { * @link fireOpen */ test('fireOpen', () => { - const fn = jest.fn(); + const fn = testFn(); const { getByTestId } = render(); confirm.fireOpen(getByTestId('trigger')); @@ -86,7 +87,7 @@ describe("Test confirm's fire functions", () => { * @link fireOk */ test('fireOk', () => { - const fn = jest.fn(); + const fn = testFn(); const { getByTestId } = render(); confirm.fireOpen(getByTestId('trigger')); confirm.fireOk(document.body); @@ -97,7 +98,7 @@ describe("Test confirm's fire functions", () => { * @link fireCancel */ test('fireCancel', () => { - const fn = jest.fn(); + const fn = testFn(); const { getByTestId } = render(); confirm.fireOpen(getByTestId('trigger')); confirm.fireCancel(document.body); diff --git a/src/modal/__tests__/modal.test.tsx b/src/modal/__tests__/modal.test.tsx index ba4f8b3..795b59e 100644 --- a/src/modal/__tests__/modal.test.tsx +++ b/src/modal/__tests__/modal.test.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { Modal } from 'antd'; +import { testFn } from '../../testFramework'; import * as modal from '..'; describe("Test Modal fire's functions", () => { @@ -17,7 +18,7 @@ describe("Test Modal fire's functions", () => { * @link queryCancelButton */ test('queryCancelButton', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); modal.fireCancel(modal.queryCancelButton(container)!); expect(fn).toBeCalled(); @@ -27,7 +28,7 @@ describe("Test Modal fire's functions", () => { * @link queryOkButton */ test('queryOkButton', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); modal.fireOk(modal.queryOkButton(container)!); expect(fn).toBeCalled(); @@ -37,7 +38,7 @@ describe("Test Modal fire's functions", () => { * @link fireCancel */ test('fireCancel', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); modal.fireCancel(container); expect(fn).toBeCalled(); @@ -47,7 +48,7 @@ describe("Test Modal fire's functions", () => { * @link queryMask */ test('fireCancel with queryMask', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); modal.fireCancel(modal.queryMask(container)!); expect(fn).toBeCalled(); @@ -57,7 +58,7 @@ describe("Test Modal fire's functions", () => { * @link fireOk */ test('fireOk', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); modal.fireOk(container); expect(fn).toBeCalled(); diff --git a/src/modal/confirm.ts b/src/modal/confirm.ts index b76c4f2..7fe5719 100644 --- a/src/modal/confirm.ts +++ b/src/modal/confirm.ts @@ -3,18 +3,19 @@ import { act, fireEvent } from '@testing-library/react'; import * as button from '../button'; import type { IContainer } from '../interface'; import { getProvider } from '../provider'; +import { runAllTimers } from '../testFramework'; import { failedTriggerElement, queryViaSelector } from '../utils'; import * as modal from './'; /** * Open confirm - * @prerequisite call `jest.useFakeTimers()` + * @prerequisite call `useFakeTimers()` from ant-design-testing */ export function fireOpen(ele?: HTMLElement) { if (!ele) throw failedTriggerElement(); fireEvent.click(ele); act(() => { - jest.runAllTimers(); + runAllTimers(); }); } diff --git a/src/notification/__tests__/notification.test.tsx b/src/notification/__tests__/notification.test.tsx index c42e6f2..042d528 100644 --- a/src/notification/__tests__/notification.test.tsx +++ b/src/notification/__tests__/notification.test.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { act, waitFor } from '@testing-library/react'; import { notification as Notification } from 'antd'; +import { testFn } from '../../testFramework'; import * as notification from '..'; describe("Test Notification's fire functions", () => { @@ -9,7 +10,7 @@ describe("Test Notification's fire functions", () => { * @link fireClick */ test('test fireClick', async () => { - const fn = jest.fn(); + const fn = testFn(); act(() => { Notification.info({ message: 'This is a notification message', @@ -32,7 +33,7 @@ describe("Test Notification's fire functions", () => { * @link fireClose */ test('test fireClose', async () => { - const fn = jest.fn(); + const fn = testFn(); act(() => { Notification.info({ message: 'This is a notification message', diff --git a/src/pagination/__tests__/pagination.test.tsx b/src/pagination/__tests__/pagination.test.tsx index 4ffac3b..444aa6b 100644 --- a/src/pagination/__tests__/pagination.test.tsx +++ b/src/pagination/__tests__/pagination.test.tsx @@ -2,15 +2,16 @@ import React from 'react'; import { render } from '@testing-library/react'; import { Pagination } from 'antd'; +import { testFn, useFakeTimers, useRealTimers } from '../../testFramework'; import * as pagination from '../'; describe('Test Pagination', () => { beforeEach(() => { - jest.useFakeTimers(); + useFakeTimers(); }); afterEach(() => { - jest.useRealTimers(); + useRealTimers(); }); /** @@ -49,7 +50,7 @@ describe('Test Pagination', () => { * @link fireSizeChange */ test('fireSizeChange', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); pagination.fireSizeOpen(container); pagination.fireSizeChange(container, 1); @@ -60,14 +61,14 @@ describe('Test Pagination', () => { * @link fireChange */ test('fireChange', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); pagination.fireChange(container); expect(fn).toBeCalledWith(2, 10); }); test('fireChange with queryPrevButton', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); pagination.fireChange(container); expect(fn).toBeCalledWith(2, 10); @@ -76,7 +77,7 @@ describe('Test Pagination', () => { }); test('fireChange with specific item', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); pagination.fireChange(pagination.queryPaginationItem(container, 3)!); expect(fn).toBeCalledWith(3, 10); @@ -86,7 +87,7 @@ describe('Test Pagination', () => { * @link queryJumpNext */ test('fireChange with jump next', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); pagination.fireChange(pagination.queryJumpNext(container)!); expect(fn).toBeCalledWith(6, 10); @@ -96,7 +97,7 @@ describe('Test Pagination', () => { * @link queryJumpPrev */ test('fireChange with jump prev', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); pagination.fireChange(pagination.queryJumpPrev(container)!); expect(fn).toBeCalledWith(1, 10); @@ -106,7 +107,7 @@ describe('Test Pagination', () => { * @link queryQuickJump */ test('fireChange with quick jump', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); pagination.fireChange(pagination.queryQuickJump(container)!, 20); expect(fn).toBeCalledWith(20, 10); diff --git a/src/popconfirm/__tests__/popconfirm.test.tsx b/src/popconfirm/__tests__/popconfirm.test.tsx index fc51c5b..d49512b 100644 --- a/src/popconfirm/__tests__/popconfirm.test.tsx +++ b/src/popconfirm/__tests__/popconfirm.test.tsx @@ -3,15 +3,16 @@ import { render } from '@testing-library/react'; import { Button, Popconfirm } from 'antd'; import * as button from '../../button'; +import { testFn, useFakeTimers, useRealTimers } from '../../testFramework'; import * as confirm from '..'; describe("Test popconfirm fire's functions", () => { beforeEach(() => { - jest.useFakeTimers(); + useFakeTimers(); }); afterEach(() => { - jest.useRealTimers(); + useRealTimers(); }); /** @@ -44,7 +45,7 @@ describe("Test popconfirm fire's functions", () => { * @link queryCancelButton */ test('queryCancelButton', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render( node.parentElement!}> @@ -59,7 +60,7 @@ describe("Test popconfirm fire's functions", () => { * @link queryConfirmButton */ test('queryConfirmButton', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render( node.parentElement!}> @@ -74,7 +75,7 @@ describe("Test popconfirm fire's functions", () => { * @link fireOpen */ test('fireOpen', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render( node.parentElement!}> @@ -88,7 +89,7 @@ describe("Test popconfirm fire's functions", () => { * @link fireCancel */ test('fireCancel', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render( node.parentElement!}> @@ -103,7 +104,7 @@ describe("Test popconfirm fire's functions", () => { * @link fireConfirm */ test('fireConfirm', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render( node.parentElement!}> diff --git a/src/popconfirm/index.ts b/src/popconfirm/index.ts index 8fa00ac..97aaf55 100644 --- a/src/popconfirm/index.ts +++ b/src/popconfirm/index.ts @@ -3,6 +3,7 @@ import { act, fireEvent } from '@testing-library/react'; import * as button from '../button'; import type { IContainer } from '../interface'; import { getProvider } from '../provider'; +import { runAllTimers } from '../testFramework'; import { failedQuerySelector, failedTriggerElement, mixinElementWithTestFuncs, queryViaSelector } from '../utils'; const mixins = { @@ -19,7 +20,7 @@ const mixins = { */ export function fireOpen(trigger: HTMLElement) { if (!trigger) throw failedTriggerElement(); - jest.runAllTimers(); + runAllTimers(); act(() => { fireEvent.click(trigger); }); diff --git a/src/provider/index.ts b/src/provider/index.ts index cd3095d..eca2db3 100644 --- a/src/provider/index.ts +++ b/src/provider/index.ts @@ -1,5 +1,13 @@ -const globalConfig = { +import { initializeTestFramework } from '../testFramework'; + +export interface GlobalConfig { + prefixCls: string; + testFramework: 'jest' | 'vitest'; +} + +const globalConfig: GlobalConfig = { prefixCls: 'ant', + testFramework: 'jest', // Default to jest for backward compatibility }; export function getProviders() { @@ -10,6 +18,11 @@ export function getProvider(key: keyof typeof globalConfig) { return globalConfig[key]; } -export function provider(opt: Partial) { +export function provider(opt: Partial) { Object.assign(globalConfig, opt); + + // Initialize test framework when config changes + if (opt.testFramework) { + initializeTestFramework(opt.testFramework); + } } diff --git a/src/qrCode/__tests__/qrCode.test.tsx b/src/qrCode/__tests__/qrCode.test.tsx index 2a70130..ae8f817 100644 --- a/src/qrCode/__tests__/qrCode.test.tsx +++ b/src/qrCode/__tests__/qrCode.test.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { QRCode } from 'antd'; +import { testFn } from '../../testFramework'; import * as qrCode from '..'; describe("Test QRCode fire's functions", () => { @@ -10,8 +11,8 @@ describe("Test QRCode fire's functions", () => { const originalError = console.error; beforeAll(() => { - console.log = jest.fn(); - console.error = jest.fn(); + console.log = testFn(); + console.error = testFn(); }); afterAll(() => { @@ -31,7 +32,7 @@ describe("Test QRCode fire's functions", () => { * @link fireRefresh */ test('fireRefresh', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); qrCode.fireRefresh(container); expect(fn).toBeCalled(); diff --git a/src/radio/__tests__/radio.test.tsx b/src/radio/__tests__/radio.test.tsx index abc4da5..b623bee 100644 --- a/src/radio/__tests__/radio.test.tsx +++ b/src/radio/__tests__/radio.test.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { Radio } from 'antd'; +import { testFn } from '../../testFramework'; import * as radio from '..'; describe("Test Radio's fire functions", () => { @@ -33,7 +34,7 @@ describe("Test Radio's fire functions", () => { * @link fireMouseEnter */ test('fireMouseEnter', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(Radio); radio.fireMouseEnter(container); @@ -41,7 +42,7 @@ describe("Test Radio's fire functions", () => { }); test('fireMouseEnter with group', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render( A @@ -56,7 +57,7 @@ describe("Test Radio's fire functions", () => { * @link fireMouseLeave */ test('fireMouseLeave', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(Radio); radio.fireMouseLeave(container); @@ -67,7 +68,7 @@ describe("Test Radio's fire functions", () => { * @link fireMouseLeave */ test('fireMouseLeave with group', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render( A @@ -82,7 +83,7 @@ describe("Test Radio's fire functions", () => { * @link fireChange */ test('fireChange', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render( A diff --git a/src/rate/__tests__/rate.test.tsx b/src/rate/__tests__/rate.test.tsx index 73a4e28..290104c 100644 --- a/src/rate/__tests__/rate.test.tsx +++ b/src/rate/__tests__/rate.test.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { Rate } from 'antd'; +import { testFn } from '../../testFramework'; import * as rate from '..'; describe("Test rate's fire functions", () => { @@ -9,7 +10,7 @@ describe("Test rate's fire functions", () => { * @link fireChange */ test('test fireChange', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); rate.fireChange(container, 3); expect(fn).toBeCalledWith(3); @@ -21,7 +22,7 @@ describe("Test rate's fire functions", () => { * @link fireHoverChange */ test('test fireHoverChange', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); rate.fireHoverChange(container, 3.5); expect(fn).toBeCalledWith(3.5); diff --git a/src/segmented/__tests__/segmented.test.tsx b/src/segmented/__tests__/segmented.test.tsx index 98d1aa0..51364c6 100644 --- a/src/segmented/__tests__/segmented.test.tsx +++ b/src/segmented/__tests__/segmented.test.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { Segmented } from 'antd'; +import { testFn } from '../../testFramework'; import * as segmented from '..'; describe("Test Segmented's fire functions", () => { @@ -9,7 +10,7 @@ describe("Test Segmented's fire functions", () => { * @link fireChange */ test('test fireChange', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); segmented.fireChange(container, 1); expect(fn).toBeCalledWith('Two'); diff --git a/src/select/__tests__/select.test.tsx b/src/select/__tests__/select.test.tsx index 6867357..63c03d3 100644 --- a/src/select/__tests__/select.test.tsx +++ b/src/select/__tests__/select.test.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { Select } from 'antd'; +import { testFn } from '../../testFramework'; import * as select from '..'; describe('Test Select fire functions', () => { @@ -27,8 +28,8 @@ describe('Test Select fire functions', () => { * @link queryInput */ test('queryInput', () => { - const fn1 = jest.fn(); - const fn2 = jest.fn(); + const fn1 = testFn(); + const fn2 = testFn(); const { container } = render( <> @@ -63,8 +64,8 @@ describe('Test Select fire functions', () => { * @link queryDropdown */ test('queryDropdown', () => { - const fn1 = jest.fn(); - const fn2 = jest.fn(); + const fn1 = testFn(); + const fn2 = testFn(); const { container } = render( <> { }); test('queryOption via chain', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render( ); select.fireOpen(container); expect(fn).toBeCalledWith(true); @@ -131,7 +132,7 @@ describe('Test Select fire functions', () => { * @link fireSelect */ test('fireSelect', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render( ); select.fireSearch(container, 'test'); expect(fn).toBeCalled(); @@ -154,7 +155,7 @@ describe('Test Select fire functions', () => { * @link fireFocus */ test('fireFocus', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); select.fireFocus(container); select.fireBlur(container); @@ -175,7 +176,7 @@ describe('Test Select fire functions', () => { * @link fireClear */ test('fireClear', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render( ); diff --git a/src/switch/__tests__/switch.test.tsx b/src/switch/__tests__/switch.test.tsx index 2f40654..db692e3 100644 --- a/src/switch/__tests__/switch.test.tsx +++ b/src/switch/__tests__/switch.test.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { Switch } from 'antd'; +import { testFn } from '../../testFramework'; import * as switchEl from '..'; describe("Test Switch's fire functions", () => { @@ -23,7 +24,7 @@ describe("Test Switch's fire functions", () => { * @link fireClick */ test('fireClick', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); switchEl.fireClick(container); @@ -34,7 +35,7 @@ describe("Test Switch's fire functions", () => { * @link fireChange */ test('fireChange', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); switchEl.fireChange(container); diff --git a/src/table/__tests__/table.test.tsx b/src/table/__tests__/table.test.tsx index e5b4c54..bd09aee 100644 --- a/src/table/__tests__/table.test.tsx +++ b/src/table/__tests__/table.test.tsx @@ -3,6 +3,7 @@ import { render } from '@testing-library/react'; import { Table } from 'antd'; import type { ColumnType } from 'antd/lib/table'; +import { testFn } from '../../testFramework'; import * as table from '..'; const columns: ColumnType<(typeof dataSource)[number]>[] = [ @@ -67,7 +68,7 @@ describe("Test Table's fire functions", () => { * @link fireSelect */ test('fireSelect', () => { - const handleSelect = jest.fn(); + const handleSelect = testFn(); const { container } = render( { * @link fireSelectAll */ test('fireSelectAll', () => { - const handleSelect = jest.fn(); + const handleSelect = testFn(); const { container } = render(
{ * @link fireExpand */ test('fireExpand', () => { - const handleExpand = jest.fn(); + const handleExpand = testFn(); const { container } = render(
{ * @link fireChange */ test('fireChange', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); tabs.fireChange(container, '2'); expect(fn).toBeCalled(); @@ -69,7 +70,7 @@ describe("Test Tabs' fire functions", () => { * @link fireClick */ test('fireClick', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); tabs.fireClick(container, '1'); expect(fn).toBeCalled(); @@ -79,7 +80,7 @@ describe("Test Tabs' fire functions", () => { * @link fireEdit */ test('fireEdit', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); tabs.fireEdit(container, 'add'); expect(fn).toBeCalledTimes(1); @@ -89,7 +90,7 @@ describe("Test Tabs' fire functions", () => { }); test('fireEdit should throw error', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); // @ts-ignore expect(() => tabs.fireEdit(container, 'test')).toThrow(); diff --git a/src/tag/__tests__/tag.test.tsx b/src/tag/__tests__/tag.test.tsx index c3cccb5..8d8d3da 100644 --- a/src/tag/__tests__/tag.test.tsx +++ b/src/tag/__tests__/tag.test.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { Tag } from 'antd'; +import { testFn } from '../../testFramework'; import * as tag from '..'; describe("Test Tag's fire functions", () => { @@ -9,7 +10,7 @@ describe("Test Tag's fire functions", () => { * @link fireClose */ test('test fireClose', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(
tag1 diff --git a/src/testFramework/index.ts b/src/testFramework/index.ts new file mode 100644 index 0000000..ae5989e --- /dev/null +++ b/src/testFramework/index.ts @@ -0,0 +1,98 @@ +/** + * Test framework adapter for Jest and Vitest compatibility + */ + +// 动态导入测试框架,避免在单一框架项目中的导入错误 +declare global { + // eslint-disable-next-line no-var + var vi: any; +} + +export interface TestFrameworkAdapter { + fn: () => any; + useFakeTimers: () => void; + useRealTimers: () => void; + runAllTimers: () => void; + advanceTimersByTime: (msToRun: number) => void; + spyOn: (object: any, method: string) => any; +} + +class JestAdapter implements TestFrameworkAdapter { + fn() { + return jest.fn(); + } + + useFakeTimers() { + jest.useFakeTimers(); + } + + useRealTimers() { + jest.useRealTimers(); + } + + runAllTimers() { + jest.runAllTimers(); + } + + advanceTimersByTime(msToRun: number) { + jest.advanceTimersByTime(msToRun); + } + + spyOn(object: any, method: string) { + return jest.spyOn(object, method); + } +} + +class VitestAdapter implements TestFrameworkAdapter { + fn() { + return vi.fn(); + } + + useFakeTimers() { + vi.useFakeTimers(); + } + + useRealTimers() { + vi.useRealTimers(); + } + + runAllTimers() { + vi.runAllTimers(); + } + + advanceTimersByTime(msToRun: number) { + vi.advanceTimersByTime(msToRun); + } + + spyOn(object: any, method: string) { + return vi.spyOn(object, method); + } +} + +let currentAdapter: TestFrameworkAdapter; + +export function initializeTestFramework(framework: 'jest' | 'vitest'): void { + if (framework === 'jest') { + currentAdapter = new JestAdapter(); + } else if (framework === 'vitest') { + currentAdapter = new VitestAdapter(); + } else { + throw new Error(`Unsupported test framework: ${framework}`); + } +} + +export function getTestFramework(): TestFrameworkAdapter { + if (!currentAdapter) { + // Default to Jest for backward compatibility + initializeTestFramework('jest'); + } + return currentAdapter; +} + +// Export convenience functions +export const testFn = () => getTestFramework().fn(); +export const useFakeTimers = () => getTestFramework().useFakeTimers(); +export const useRealTimers = () => getTestFramework().useRealTimers(); +export const runAllTimers = () => getTestFramework().runAllTimers(); +export const advanceTimersByTime = (msToRun: number) => getTestFramework().advanceTimersByTime(msToRun); +export const spyOn = (object: any, method: string) => getTestFramework().spyOn(object, method); diff --git a/src/timePicker/__tests__/timePicker.test.tsx b/src/timePicker/__tests__/timePicker.test.tsx index 7988645..0084a0a 100644 --- a/src/timePicker/__tests__/timePicker.test.tsx +++ b/src/timePicker/__tests__/timePicker.test.tsx @@ -3,17 +3,18 @@ import { render } from '@testing-library/react'; import { TimePicker } from 'antd'; import dayjs from 'dayjs'; +import { testFn, useFakeTimers, useRealTimers } from '../../testFramework'; import * as timePicker from '..'; const dateAdaptor = dayjs; describe("Test TimePicker's fire functions", () => { beforeEach(() => { - jest.useFakeTimers(); + useFakeTimers(); document.body.innerHTML = ''; }); afterEach(() => { - jest.useRealTimers(); + useRealTimers(); }); /** @@ -46,7 +47,7 @@ describe("Test TimePicker's fire functions", () => { * @link fireOk */ test('fireOk', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render( { * @link fireOpen */ test('fireOpen', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render( ); @@ -77,7 +78,7 @@ describe("Test TimePicker's fire functions", () => { * @link fireChange */ test('fireChange', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render( { }); test('fireChange with RangePicker', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render( node.parentElement!} /> ); diff --git a/src/tooltip/__tests__/tooltip.test.tsx b/src/tooltip/__tests__/tooltip.test.tsx index f2b4f1b..c586228 100644 --- a/src/tooltip/__tests__/tooltip.test.tsx +++ b/src/tooltip/__tests__/tooltip.test.tsx @@ -2,21 +2,22 @@ import React from 'react'; import { render } from '@testing-library/react'; import { Tooltip } from 'antd'; +import { testFn, useFakeTimers, useRealTimers } from '../../testFramework'; import * as tooltip from '..'; describe("test tooltip's fire functions", () => { beforeEach(() => { - jest.useFakeTimers(); + useFakeTimers(); }); afterEach(() => { - jest.useRealTimers(); + useRealTimers(); }); /** * @link fireOpen */ test('fireOpen', () => { - const fn = jest.fn(); + const fn = testFn(); const { getByText, getByTestId } = render( node.parentElement!} onOpenChange={fn}> trigger diff --git a/src/tooltip/index.ts b/src/tooltip/index.ts index 1c1b746..f6fb7ac 100644 --- a/src/tooltip/index.ts +++ b/src/tooltip/index.ts @@ -1,5 +1,6 @@ import { act, fireEvent } from '@testing-library/react'; +import { runAllTimers } from '../testFramework'; import { failedTriggerElement } from '../utils'; /** @@ -9,6 +10,6 @@ export function fireOpen(ele?: HTMLElement) { if (!ele) throw failedTriggerElement(); fireEvent.mouseEnter(ele); act(() => { - jest.runAllTimers(); + runAllTimers(); }); } diff --git a/src/tour/__tests__/tour.test.tsx b/src/tour/__tests__/tour.test.tsx index d1841d0..ef32ce6 100644 --- a/src/tour/__tests__/tour.test.tsx +++ b/src/tour/__tests__/tour.test.tsx @@ -3,6 +3,7 @@ import { render } from '@testing-library/react'; import { Button, Tour, TourProps } from 'antd'; import { button } from '../../index'; +import { testFn } from '../../testFramework'; import * as tour from '..'; const TourDemo = ({ onChange, onClose }: any) => { @@ -57,7 +58,7 @@ describe("Test Tour fire's functions", () => { * @link fireNextStep */ test('fireNextStep', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); button.fireClick(container); tour.fireNextStep(document); @@ -68,7 +69,7 @@ describe("Test Tour fire's functions", () => { * @link firePrevStep */ test('firePrevStep', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); button.fireClick(container); tour.fireNextStep(document); @@ -81,7 +82,7 @@ describe("Test Tour fire's functions", () => { * @link fireClose */ test('fireClose', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); button.fireClick(container); tour.fireClose(document); diff --git a/src/transfer/__tests__/transfer.test.tsx b/src/transfer/__tests__/transfer.test.tsx index 92c315f..32b0a98 100644 --- a/src/transfer/__tests__/transfer.test.tsx +++ b/src/transfer/__tests__/transfer.test.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { Transfer } from 'antd'; +import { testFn, useFakeTimers } from '../../testFramework'; import * as transfer from '..'; const dataSource = [ @@ -12,7 +13,7 @@ const dataSource = [ describe("Test Transfer's fire functions", () => { beforeEach(() => { - jest.useFakeTimers(); + useFakeTimers(); }); /** @@ -35,7 +36,7 @@ describe("Test Transfer's fire functions", () => { * @link fireChange */ test('fireChange', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render( ); @@ -44,7 +45,7 @@ describe("Test Transfer's fire functions", () => { }); test('fireChange with left direction', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render( ); @@ -56,7 +57,7 @@ describe("Test Transfer's fire functions", () => { * @link fireScroll */ test('fireScroll', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); transfer.fireScroll(container); expect(fn).toBeCalled(); @@ -66,7 +67,7 @@ describe("Test Transfer's fire functions", () => { * @link fireSearch */ test('fireSearch', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render( { * @link fireCheck */ test('fireCheck', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); tree.fireCheck(container, 'parent 1'); @@ -55,7 +56,7 @@ describe("Test Tree's fire functions", () => { * @link fireExpand */ test('fireExpand', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); tree.fireExpand(container, 'parent 1'); @@ -66,7 +67,7 @@ describe("Test Tree's fire functions", () => { * @link fireRightClick */ test('fireRightClick', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); tree.fireRightClick(container, 'parent 1'); @@ -77,7 +78,7 @@ describe("Test Tree's fire functions", () => { * @link fireSelect */ test('fireSelect', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); tree.fireSelect(container, 'parent 1'); @@ -88,12 +89,12 @@ describe("Test Tree's fire functions", () => { * @link fireDrag */ test('fireDrag', () => { - const onDragStart = jest.fn(); - const onDragEnter = jest.fn(); - const onDragOver = jest.fn(); - const onDragLeave = jest.fn(); - const onDrop = jest.fn(); - const onDragEnd = jest.fn(); + const onDragStart = testFn(); + const onDragEnter = testFn(); + const onDragOver = testFn(); + const onDragLeave = testFn(); + const onDrop = testFn(); + const onDragEnd = testFn(); const { container } = render( { beforeEach(() => { - jest.useFakeTimers(); + useFakeTimers(); document.body.innerHTML = ''; }); afterEach(() => { - jest.useRealTimers(); + useRealTimers(); }); /** * @link fireOpen */ test('fireOpen', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); treeSelect.fireOpen(container); @@ -28,7 +29,7 @@ describe("Test treeSelect's fire functions", () => { * @link fireSearch */ test('fireSearch', () => { - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); treeSelect.fireSearch(container, 'test'); @@ -39,7 +40,7 @@ describe("Test treeSelect's fire functions", () => { * @link fireSelect */ test('fireSelect', () => { - const fn = jest.fn(); + const fn = testFn(); const treeData = [ { title: 'Node1', @@ -78,7 +79,7 @@ describe("Test treeSelect's fire functions", () => { * @link fireTreeExpand */ test('fireTreeExpand', () => { - const fn = jest.fn(); + const fn = testFn(); const treeData = [ { title: 'Node1', @@ -126,8 +127,8 @@ describe("Test treeSelect's fire functions", () => { * @link queryInput */ test('queryInput', () => { - const fn1 = jest.fn(); - const fn2 = jest.fn(); + const fn1 = testFn(); + const fn2 = testFn(); const { container } = render( <> @@ -144,8 +145,8 @@ describe("Test treeSelect's fire functions", () => { * @link querySelectorWrapper */ test('querySelectorWrapper', () => { - const fn1 = jest.fn(); - const fn2 = jest.fn(); + const fn1 = testFn(); + const fn2 = testFn(); const { container } = render( <> @@ -178,7 +179,7 @@ describe("Test treeSelect's fire functions", () => { ], }, ]; - const fn = jest.fn(); + const fn = testFn(); const { container } = render(); treeSelect.fireOpen(treeSelect.querySelectorWrapper(container)!); treeSelect.fireSelect(treeSelect.queryDropdown(document)!, 0); @@ -205,7 +206,7 @@ describe("Test treeSelect's fire functions", () => { ], }, ]; - const fn = jest.fn(); + const fn = testFn(); const { container } = render( { beforeEach(() => { - jest.useFakeTimers(); + useFakeTimers(); }); afterEach(() => { - jest.useRealTimers(); + useRealTimers(); }); /** * @link fireUpload */ test('test fireUpload', async () => { - const fn = jest.fn(); + const fn = testFn(); const props: UploadProps = { beforeUpload: () => false, onChange: ({ fileList }) => { @@ -39,7 +40,7 @@ describe("Test Upload's fire functions", () => { * @link fireRemove */ test('test fireRemove', async () => { - const fn = jest.fn(); + const fn = testFn(); const files = [ { uid: '-1', diff --git a/src/upload/index.ts b/src/upload/index.ts index 3a72959..c30775d 100644 --- a/src/upload/index.ts +++ b/src/upload/index.ts @@ -2,6 +2,7 @@ import { act, fireEvent } from '@testing-library/react'; import type { IContainer } from '../interface'; import { getProvider } from '../provider'; +import { runAllTimers } from '../testFramework'; import { failedQuerySelector, mixinElementWithTestFuncs, queryViaSelector } from '../utils'; const mixins = { @@ -15,7 +16,7 @@ const mixins = { * Fires onChange function * * It's a async fire function - * @prerequisite call `jest.useFakeTimers()` + * @prerequisite call `useFakeTimers()` from ant-design-testing */ export function fireUpload(container: IContainer, files: File[] | { name: string }[]) { const selector = `.${getProvider('prefixCls')}-upload input[type='file']`; @@ -23,7 +24,7 @@ export function fireUpload(container: IContainer, files: File[] | { name: string if (!ele) throw failedQuerySelector(selector); fireEvent.change(ele, { target: { files } }); act(() => { - jest.runAllTimers(); + runAllTimers(); }); } diff --git a/tests/setupTests.ts b/tests/setupTests.ts index 7e6d883..a75392c 100644 --- a/tests/setupTests.ts +++ b/tests/setupTests.ts @@ -1,3 +1,4 @@ +// Mock matchMedia for jsdom environment Object.defineProperty(global.window, 'matchMedia', { writable: true, configurable: true, diff --git a/tests/setupTests.vitest.ts b/tests/setupTests.vitest.ts new file mode 100644 index 0000000..08aa317 --- /dev/null +++ b/tests/setupTests.vitest.ts @@ -0,0 +1,57 @@ +import { vi } from 'vitest'; + +import { provider } from '../src/provider'; + +// Configure for Vitest +provider({ + prefixCls: 'ant', + testFramework: 'vitest', +}); + +// Mock matchMedia for jsdom environment in Vitest +Object.defineProperty(global.window, 'matchMedia', { + writable: true, + configurable: true, + value: vi.fn((query) => ({ + matches: query.includes('max-width'), + addListener: vi.fn(), + removeListener: vi.fn(), + })), +}); + +// Mock getComputedStyle for JSDOM compatibility +Object.defineProperty(global.window, 'getComputedStyle', { + writable: true, + configurable: true, + value: vi.fn((_element) => ({ + getPropertyValue: vi.fn(() => ''), + width: '0px', + height: '0px', + display: 'block', + // Add other commonly used CSS properties + position: 'static', + overflow: 'visible', + fontSize: '14px', + fontFamily: 'Arial', + })), +}); + +// Mock ResizeObserver +global.ResizeObserver = vi.fn(() => ({ + observe: vi.fn(), + unobserve: vi.fn(), + disconnect: vi.fn(), +})); + +// 模拟 IntersectionObserver,补全必要属性以通过类型检查 +class MockIntersectionObserver { + root = null; + rootMargin = ''; + thresholds = []; + observe = vi.fn(); + unobserve = vi.fn(); + disconnect = vi.fn(); + takeRecords = vi.fn(() => []); + constructor() {} +} +global.IntersectionObserver = MockIntersectionObserver as any; diff --git a/vitest.config.js b/vitest.config.js new file mode 100644 index 0000000..4f8f7c4 --- /dev/null +++ b/vitest.config.js @@ -0,0 +1,16 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + environment: 'jsdom', + setupFiles: ['./tests/setupTests.vitest.ts'], + include: ['**/__tests__/**/(*.)+(spec|test).[jt]s?(x)'], + exclude: ['**/node_modules/**', '**/dist/**'], + globals: true, // Enable global test APIs like describe, it, expect + testTimeout: 10000, // Increase timeout for complex tests + hookTimeout: 10000, // Increase hook timeout + }, + esbuild: { + target: 'node14', + }, +});