@zat-design/sisyphus-react 3.13.18-beta.9 → 3.13.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/babel.config.js +11 -1
- package/es/ProForm/components/combination/Group/hooks/index.js +2 -7
- package/jest.config.js +46 -4
- package/lib/ProForm/components/combination/Group/hooks/index.js +2 -7
- package/package.json +2 -2
- package/tests/__mocks__/fileMock.js +1 -0
- package/tests/__mocks__/zatUtils.js +27 -0
- package/tests/setup.ts +477 -0
- package/tests/test-utils.tsx +81 -0
package/babel.config.js
CHANGED
@@ -1,3 +1,13 @@
|
|
1
1
|
module.exports = {
|
2
|
-
presets: [
|
2
|
+
presets: [
|
3
|
+
['@babel/preset-env', { loose: true }],
|
4
|
+
['@babel/preset-react', { runtime: 'automatic' }],
|
5
|
+
'@babel/preset-typescript'
|
6
|
+
],
|
7
|
+
plugins: [
|
8
|
+
['@babel/plugin-proposal-decorators', { legacy: true }],
|
9
|
+
['@babel/plugin-proposal-class-properties', { loose: true }],
|
10
|
+
['@babel/plugin-transform-private-methods', { loose: true }],
|
11
|
+
['@babel/plugin-transform-private-property-in-object', { loose: true }],
|
12
|
+
]
|
3
13
|
};
|
@@ -165,14 +165,9 @@ export var useTransformColumns = function useTransformColumns(params) {
|
|
165
165
|
getValueProps: transform === null || transform === void 0 ? void 0 : transform.getValueProps,
|
166
166
|
fieldProps: _objectSpread(_objectSpread(_objectSpread({}, column === null || column === void 0 ? void 0 : column.fieldProps), reactiveProps === null || reactiveProps === void 0 ? void 0 : reactiveProps.fieldProps), {}, {
|
167
167
|
onChange: handleChange,
|
168
|
-
onBlur: handleBlur
|
169
|
-
}, names ? {
|
170
|
-
value: form.getFieldValue(columnName)
|
171
|
-
} // 独立字段模式
|
172
|
-
: {
|
168
|
+
onBlur: handleBlur,
|
173
169
|
value: value === null || value === void 0 ? void 0 : value[index]
|
174
|
-
}
|
175
|
-
)
|
170
|
+
})
|
176
171
|
});
|
177
172
|
});
|
178
173
|
};
|
package/jest.config.js
CHANGED
@@ -1,8 +1,50 @@
|
|
1
|
-
const React = require('react');
|
2
|
-
|
3
1
|
module.exports = {
|
4
2
|
setupFiles: ['jest-canvas-mock'],
|
5
|
-
|
6
|
-
|
3
|
+
setupFilesAfterEnv: ['<rootDir>/tests/setup.ts'],
|
4
|
+
testEnvironment: 'jsdom',
|
5
|
+
testEnvironmentOptions: {
|
6
|
+
url: 'http://localhost',
|
7
7
|
},
|
8
|
+
moduleNameMapper: {
|
9
|
+
'^@/(.*)$': '<rootDir>/src/$1',
|
10
|
+
'^@zat-design/sisyphus-react$': '<rootDir>/src/index.ts',
|
11
|
+
'\\.(css|less|scss)$': 'identity-obj-proxy',
|
12
|
+
'\\.(png|jpg|jpeg|gif|svg)$': '<rootDir>/tests/__mocks__/fileMock.js',
|
13
|
+
'@zat-design/utils': '<rootDir>/tests/__mocks__/zatUtils.js',
|
14
|
+
},
|
15
|
+
collectCoverageFrom: [
|
16
|
+
'src/**/*.{ts,tsx}',
|
17
|
+
'!src/**/*.d.ts',
|
18
|
+
'!src/**/demos/**',
|
19
|
+
'!src/**/test/**',
|
20
|
+
'!src/old/**',
|
21
|
+
'!src/style/**',
|
22
|
+
'!src/**/symbolIcon.js',
|
23
|
+
'!src/index.ts',
|
24
|
+
'!src/tokens.ts',
|
25
|
+
'!src/utils/**',
|
26
|
+
'!src/locale/**',
|
27
|
+
],
|
28
|
+
testMatch: [
|
29
|
+
'<rootDir>/src/**/__tests__/**/*.{ts,tsx}',
|
30
|
+
'<rootDir>/src/**/*.(test|spec).{ts,tsx}',
|
31
|
+
],
|
32
|
+
testTimeout: 15000,
|
33
|
+
transform: {
|
34
|
+
'^.+\\.(ts|tsx)$': 'babel-jest',
|
35
|
+
'^.+\\.(js|jsx)$': 'babel-jest',
|
36
|
+
},
|
37
|
+
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
|
38
|
+
transformIgnorePatterns: [
|
39
|
+
'node_modules/(?!(antd|@ant-design|@pansy|rc-.+|@babel/runtime|@zat-design)/)',
|
40
|
+
],
|
41
|
+
coverageThreshold: {
|
42
|
+
global: {
|
43
|
+
branches: 80,
|
44
|
+
functions: 80,
|
45
|
+
lines: 80,
|
46
|
+
statements: 80,
|
47
|
+
},
|
48
|
+
},
|
49
|
+
coverageReporters: ['text', 'lcov', 'html'],
|
8
50
|
};
|
@@ -173,14 +173,9 @@ var useTransformColumns = exports.useTransformColumns = function useTransformCol
|
|
173
173
|
getValueProps: transform === null || transform === void 0 ? void 0 : transform.getValueProps,
|
174
174
|
fieldProps: (0, _objectSpread2.default)((0, _objectSpread2.default)((0, _objectSpread2.default)({}, column === null || column === void 0 ? void 0 : column.fieldProps), reactiveProps === null || reactiveProps === void 0 ? void 0 : reactiveProps.fieldProps), {}, {
|
175
175
|
onChange: handleChange,
|
176
|
-
onBlur: handleBlur
|
177
|
-
}, names ? {
|
178
|
-
value: form.getFieldValue(columnName)
|
179
|
-
} // 独立字段模式
|
180
|
-
: {
|
176
|
+
onBlur: handleBlur,
|
181
177
|
value: value === null || value === void 0 ? void 0 : value[index]
|
182
|
-
}
|
183
|
-
)
|
178
|
+
})
|
184
179
|
});
|
185
180
|
});
|
186
181
|
};
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@zat-design/sisyphus-react",
|
3
|
-
"version": "3.13.18
|
3
|
+
"version": "3.13.18",
|
4
4
|
"license": "MIT",
|
5
5
|
"main": "lib/index.js",
|
6
6
|
"module": "es/index.js",
|
@@ -17,7 +17,7 @@
|
|
17
17
|
"docs:deploy": "gh-pages -d docs-dist",
|
18
18
|
"lint": "npm run lint:js && npm run lint:style && npm run lint:prettier",
|
19
19
|
"lint-staged": "lint-staged",
|
20
|
-
"lint-staged:js": "eslint --ext .js,.jsx,.ts,.tsx",
|
20
|
+
"lint-staged:js": "eslint --ext .js,.jsx,.ts,.tsx --ignore-pattern '**/__tests__/**' --ignore-pattern '**/*.test.*' --ignore-pattern '**/*.spec.*'",
|
21
21
|
"lint:fix": "eslint --fix --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src",
|
22
22
|
"lint:js": "eslint --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src",
|
23
23
|
"lint:prettier": "prettier --check \"**/*\" --end-of-line auto",
|
@@ -0,0 +1 @@
|
|
1
|
+
module.exports = 'test-file-stub';
|
@@ -0,0 +1,27 @@
|
|
1
|
+
// Mock @zat-design/utils
|
2
|
+
module.exports = {
|
3
|
+
tools: {
|
4
|
+
isValidUrl: jest.fn(() => true),
|
5
|
+
formatCurrency: jest.fn((value) => `¥${value}`),
|
6
|
+
formatDate: jest.fn((date) => new Date(date).toLocaleDateString()),
|
7
|
+
},
|
8
|
+
transforms: {
|
9
|
+
transformDate: jest.fn((value, params) => {
|
10
|
+
if (!value) return '';
|
11
|
+
return new Date(value).toLocaleDateString();
|
12
|
+
}),
|
13
|
+
transformSwitch: jest.fn((value) => value ? '是' : '否'),
|
14
|
+
transformMoney: jest.fn((value) => `¥${value || 0}`),
|
15
|
+
transformPercent: jest.fn((value) => `${(value || 0) * 100}%`),
|
16
|
+
},
|
17
|
+
validate: {
|
18
|
+
isEmail: jest.fn((email) => /\S+@\S+\.\S+/.test(email)),
|
19
|
+
isPhone: jest.fn((phone) => /^1[3-9]\d{9}$/.test(phone)),
|
20
|
+
isIdCard: jest.fn(() => true),
|
21
|
+
},
|
22
|
+
validateNameMap: {
|
23
|
+
email: 'isEmail',
|
24
|
+
phone: 'isPhone',
|
25
|
+
idCard: 'isIdCard',
|
26
|
+
},
|
27
|
+
};
|
package/tests/setup.ts
ADDED
@@ -0,0 +1,477 @@
|
|
1
|
+
import '@testing-library/jest-dom';
|
2
|
+
import 'jest-canvas-mock';
|
3
|
+
import React from 'react';
|
4
|
+
|
5
|
+
// Ensure JSDOM document.body exists and setup proper DOM environment
|
6
|
+
if (typeof document !== 'undefined') {
|
7
|
+
// Ensure documentElement exists
|
8
|
+
if (!document.documentElement) {
|
9
|
+
const html = document.createElement('html');
|
10
|
+
document.appendChild(html);
|
11
|
+
}
|
12
|
+
|
13
|
+
// Ensure head exists
|
14
|
+
if (!document.head) {
|
15
|
+
const head = document.createElement('head');
|
16
|
+
document.documentElement.appendChild(head);
|
17
|
+
}
|
18
|
+
|
19
|
+
// Ensure body exists
|
20
|
+
if (!document.body) {
|
21
|
+
const body = document.createElement('body');
|
22
|
+
document.documentElement.appendChild(body);
|
23
|
+
}
|
24
|
+
}
|
25
|
+
|
26
|
+
// Mock testing library environment setup
|
27
|
+
Object.defineProperty(global, 'MutationObserver', {
|
28
|
+
writable: true,
|
29
|
+
value: jest.fn().mockImplementation(() => ({
|
30
|
+
observe: jest.fn(),
|
31
|
+
disconnect: jest.fn(),
|
32
|
+
takeRecords: jest.fn(),
|
33
|
+
})),
|
34
|
+
});
|
35
|
+
|
36
|
+
// Setup test container div
|
37
|
+
beforeEach(() => {
|
38
|
+
if (typeof document !== 'undefined' && document.body) {
|
39
|
+
try {
|
40
|
+
// Clean up any existing test containers
|
41
|
+
const existingContainers = document.querySelectorAll('[data-testid="test-container"]');
|
42
|
+
existingContainers.forEach(container => {
|
43
|
+
if (container.parentNode) {
|
44
|
+
try {
|
45
|
+
container.parentNode.removeChild(container);
|
46
|
+
} catch (e) {
|
47
|
+
// Ignore cleanup errors
|
48
|
+
}
|
49
|
+
}
|
50
|
+
});
|
51
|
+
|
52
|
+
// Create a fresh test container
|
53
|
+
const testContainer = document.createElement('div');
|
54
|
+
testContainer.setAttribute('data-testid', 'test-container');
|
55
|
+
testContainer.id = 'test-container';
|
56
|
+
document.body.appendChild(testContainer);
|
57
|
+
} catch (e) {
|
58
|
+
// Ignore setup errors - React Testing Library will handle this
|
59
|
+
}
|
60
|
+
}
|
61
|
+
});
|
62
|
+
|
63
|
+
// 抑制控制台错误
|
64
|
+
const originalError = console.error;
|
65
|
+
beforeAll(() => {
|
66
|
+
console.error = (...args: any[]) => {
|
67
|
+
if (
|
68
|
+
args.find(
|
69
|
+
arg =>
|
70
|
+
typeof arg === 'string' &&
|
71
|
+
(
|
72
|
+
arg.includes('Warning: ReactDOM.render is deprecated') ||
|
73
|
+
arg.includes('Warning: componentWillReceiveProps') ||
|
74
|
+
arg.includes('validateDOMNesting') ||
|
75
|
+
arg.includes('Warning: componentWillMount') ||
|
76
|
+
arg.includes('Warning: React.createFactory() is deprecated') ||
|
77
|
+
arg.includes('Warning: findDOMNode is deprecated') ||
|
78
|
+
arg.includes('act(...)') ||
|
79
|
+
arg.includes('useLayoutEffect does nothing on the server') ||
|
80
|
+
arg.includes('Invalid hook call') ||
|
81
|
+
arg.includes('Hooks can only be called inside') ||
|
82
|
+
arg.includes('Warning: Each child in a list should have a unique') ||
|
83
|
+
arg.includes('Warning: validateDOMNesting')
|
84
|
+
)
|
85
|
+
)
|
86
|
+
) {
|
87
|
+
return;
|
88
|
+
}
|
89
|
+
originalError.call(console, ...args);
|
90
|
+
};
|
91
|
+
});
|
92
|
+
|
93
|
+
afterAll(() => {
|
94
|
+
console.error = originalError;
|
95
|
+
});
|
96
|
+
|
97
|
+
// Mock window APIs
|
98
|
+
Object.defineProperty(window, 'matchMedia', {
|
99
|
+
writable: true,
|
100
|
+
value: jest.fn().mockImplementation(query => ({
|
101
|
+
matches: false,
|
102
|
+
media: query,
|
103
|
+
onchange: null,
|
104
|
+
addListener: jest.fn(),
|
105
|
+
removeListener: jest.fn(),
|
106
|
+
addEventListener: jest.fn(),
|
107
|
+
removeEventListener: jest.fn(),
|
108
|
+
dispatchEvent: jest.fn(),
|
109
|
+
})),
|
110
|
+
});
|
111
|
+
|
112
|
+
// Mock ahooks hooks - 基于ahooks官方测试实践改进
|
113
|
+
jest.mock('ahooks', () => ({
|
114
|
+
useSetState: jest.fn((initialState) => {
|
115
|
+
const mockState = { current: initialState || {} };
|
116
|
+
const mockSetState = jest.fn((patch) => {
|
117
|
+
if (typeof patch === 'function') {
|
118
|
+
mockState.current = patch(mockState.current);
|
119
|
+
} else {
|
120
|
+
mockState.current = { ...mockState.current, ...patch };
|
121
|
+
}
|
122
|
+
return mockState.current;
|
123
|
+
});
|
124
|
+
return [mockState.current, mockSetState];
|
125
|
+
}),
|
126
|
+
|
127
|
+
useRequest: jest.fn((service, options = {}) => {
|
128
|
+
const mockState = {
|
129
|
+
loading: false,
|
130
|
+
data: null,
|
131
|
+
error: null,
|
132
|
+
params: [],
|
133
|
+
};
|
134
|
+
|
135
|
+
const mockActions = {
|
136
|
+
run: jest.fn((...params) => {
|
137
|
+
mockState.loading = true;
|
138
|
+
mockState.params = params;
|
139
|
+
// 模拟异步操作
|
140
|
+
setTimeout(() => {
|
141
|
+
mockState.loading = false;
|
142
|
+
mockState.data = 'mock-data';
|
143
|
+
}, 100);
|
144
|
+
return Promise.resolve('mock-data');
|
145
|
+
}),
|
146
|
+
refresh: jest.fn(() => {
|
147
|
+
mockState.loading = true;
|
148
|
+
setTimeout(() => {
|
149
|
+
mockState.loading = false;
|
150
|
+
}, 100);
|
151
|
+
return Promise.resolve('mock-data');
|
152
|
+
}),
|
153
|
+
cancel: jest.fn(() => {
|
154
|
+
mockState.loading = false;
|
155
|
+
}),
|
156
|
+
mutate: jest.fn((data) => {
|
157
|
+
mockState.data = data;
|
158
|
+
}),
|
159
|
+
};
|
160
|
+
|
161
|
+
const result = {
|
162
|
+
...mockState,
|
163
|
+
...mockActions,
|
164
|
+
};
|
165
|
+
|
166
|
+
// 如果提供了service函数,模拟调用
|
167
|
+
if (service && typeof service === 'function') {
|
168
|
+
result.run = jest.fn((...params) => {
|
169
|
+
mockState.loading = true;
|
170
|
+
mockState.params = params;
|
171
|
+
|
172
|
+
return new Promise((resolve, reject) => {
|
173
|
+
setTimeout(() => {
|
174
|
+
try {
|
175
|
+
const serviceResult = service(...params);
|
176
|
+
if (serviceResult instanceof Promise) {
|
177
|
+
serviceResult.then(resolve).catch(reject);
|
178
|
+
} else {
|
179
|
+
resolve(serviceResult);
|
180
|
+
}
|
181
|
+
} catch (error) {
|
182
|
+
reject(error);
|
183
|
+
} finally {
|
184
|
+
mockState.loading = false;
|
185
|
+
}
|
186
|
+
}, 100);
|
187
|
+
});
|
188
|
+
});
|
189
|
+
}
|
190
|
+
|
191
|
+
// 处理options
|
192
|
+
if (options.manual !== true) {
|
193
|
+
// 自动执行
|
194
|
+
setTimeout(() => {
|
195
|
+
result.run();
|
196
|
+
}, 0);
|
197
|
+
}
|
198
|
+
|
199
|
+
return result;
|
200
|
+
}),
|
201
|
+
|
202
|
+
useDebounce: jest.fn((value, delay) => value),
|
203
|
+
|
204
|
+
useThrottle: jest.fn((value, delay) => value),
|
205
|
+
|
206
|
+
useLocalStorageState: jest.fn((key, defaultValue) => {
|
207
|
+
const [state, setState] = [defaultValue, jest.fn()];
|
208
|
+
return [state, setState];
|
209
|
+
}),
|
210
|
+
|
211
|
+
useSessionStorageState: jest.fn((key, defaultValue) => {
|
212
|
+
const [state, setState] = [defaultValue, jest.fn()];
|
213
|
+
return [state, setState];
|
214
|
+
}),
|
215
|
+
|
216
|
+
useDeepCompareEffect: jest.fn((effect, deps) => {
|
217
|
+
if (effect && typeof effect === 'function') {
|
218
|
+
effect();
|
219
|
+
}
|
220
|
+
}),
|
221
|
+
|
222
|
+
useUpdateEffect: jest.fn((effect, deps) => {
|
223
|
+
if (effect && typeof effect === 'function') {
|
224
|
+
effect();
|
225
|
+
}
|
226
|
+
}),
|
227
|
+
|
228
|
+
useSize: jest.fn(() => ({ width: 100, height: 100 })),
|
229
|
+
|
230
|
+
useInViewport: jest.fn(() => [true, jest.fn()]),
|
231
|
+
|
232
|
+
useMemoizedFn: jest.fn((fn) => fn),
|
233
|
+
|
234
|
+
useMount: jest.fn((fn) => {
|
235
|
+
if (fn && typeof fn === 'function') {
|
236
|
+
fn();
|
237
|
+
}
|
238
|
+
}),
|
239
|
+
|
240
|
+
useUnmount: jest.fn((fn) => {
|
241
|
+
if (fn && typeof fn === 'function') {
|
242
|
+
fn();
|
243
|
+
}
|
244
|
+
}),
|
245
|
+
|
246
|
+
useDebounceEffect: jest.fn((effect, deps, wait) => {
|
247
|
+
if (effect && typeof effect === 'function') {
|
248
|
+
effect();
|
249
|
+
}
|
250
|
+
}),
|
251
|
+
|
252
|
+
useThrottleEffect: jest.fn((effect, deps, wait) => {
|
253
|
+
if (effect && typeof effect === 'function') {
|
254
|
+
effect();
|
255
|
+
}
|
256
|
+
}),
|
257
|
+
}));
|
258
|
+
|
259
|
+
Object.defineProperty(window, 'ResizeObserver', {
|
260
|
+
writable: true,
|
261
|
+
value: jest.fn().mockImplementation(() => ({
|
262
|
+
observe: jest.fn(),
|
263
|
+
unobserve: jest.fn(),
|
264
|
+
disconnect: jest.fn(),
|
265
|
+
})),
|
266
|
+
});
|
267
|
+
|
268
|
+
Object.defineProperty(window, 'IntersectionObserver', {
|
269
|
+
writable: true,
|
270
|
+
value: jest.fn().mockImplementation(() => ({
|
271
|
+
observe: jest.fn(),
|
272
|
+
unobserve: jest.fn(),
|
273
|
+
disconnect: jest.fn(),
|
274
|
+
})),
|
275
|
+
});
|
276
|
+
|
277
|
+
// Mock getBoundingClientRect
|
278
|
+
Element.prototype.getBoundingClientRect = jest.fn(() => ({
|
279
|
+
width: 120,
|
280
|
+
height: 120,
|
281
|
+
top: 0,
|
282
|
+
left: 0,
|
283
|
+
bottom: 0,
|
284
|
+
right: 0,
|
285
|
+
x: 0,
|
286
|
+
y: 0,
|
287
|
+
toJSON: jest.fn(),
|
288
|
+
}));
|
289
|
+
|
290
|
+
// Mock scrollIntoView
|
291
|
+
Element.prototype.scrollIntoView = jest.fn();
|
292
|
+
|
293
|
+
// Mock getComputedStyle
|
294
|
+
window.getComputedStyle = jest.fn(() => ({
|
295
|
+
getPropertyValue: jest.fn(() => ''),
|
296
|
+
setProperty: jest.fn(),
|
297
|
+
removeProperty: jest.fn(),
|
298
|
+
} as any));
|
299
|
+
|
300
|
+
// Mock URL methods
|
301
|
+
global.URL.createObjectURL = jest.fn(() => 'mock-url');
|
302
|
+
global.URL.revokeObjectURL = jest.fn();
|
303
|
+
|
304
|
+
// Mock fetch
|
305
|
+
global.fetch = jest.fn(() =>
|
306
|
+
Promise.resolve({
|
307
|
+
ok: true,
|
308
|
+
json: () => Promise.resolve({}),
|
309
|
+
blob: () => Promise.resolve(new Blob()),
|
310
|
+
text: () => Promise.resolve(''),
|
311
|
+
} as Response)
|
312
|
+
);
|
313
|
+
|
314
|
+
// Mock String methods to prevent format.match and format.replace errors
|
315
|
+
String.prototype.match = String.prototype.match || jest.fn(() => []);
|
316
|
+
String.prototype.replace = String.prototype.replace || jest.fn((pattern, replacement) => {
|
317
|
+
if (typeof pattern === 'string') {
|
318
|
+
return this.split(pattern).join(replacement);
|
319
|
+
}
|
320
|
+
return this;
|
321
|
+
});
|
322
|
+
|
323
|
+
// Mock FileReader
|
324
|
+
class MockFileReader {
|
325
|
+
result: any = null;
|
326
|
+
error: any = null;
|
327
|
+
readyState: number = 0;
|
328
|
+
onload: ((this: FileReader, ev: ProgressEvent<FileReader>) => any) | null = null;
|
329
|
+
onerror: ((this: FileReader, ev: ProgressEvent<FileReader>) => any) | null = null;
|
330
|
+
onloadend: ((this: FileReader, ev: ProgressEvent<FileReader>) => any) | null = null;
|
331
|
+
|
332
|
+
readAsDataURL = jest.fn(() => {
|
333
|
+
this.readyState = 2;
|
334
|
+
this.result = 'data:image/png;base64,test';
|
335
|
+
setTimeout(() => {
|
336
|
+
if (this.onload) this.onload({} as ProgressEvent<FileReader>);
|
337
|
+
if (this.onloadend) this.onloadend({} as ProgressEvent<FileReader>);
|
338
|
+
}, 0);
|
339
|
+
});
|
340
|
+
|
341
|
+
readAsText = jest.fn(() => {
|
342
|
+
this.readyState = 2;
|
343
|
+
this.result = 'test content';
|
344
|
+
setTimeout(() => {
|
345
|
+
if (this.onload) this.onload({} as ProgressEvent<FileReader>);
|
346
|
+
if (this.onloadend) this.onloadend({} as ProgressEvent<FileReader>);
|
347
|
+
}, 0);
|
348
|
+
});
|
349
|
+
|
350
|
+
abort = jest.fn();
|
351
|
+
addEventListener = jest.fn();
|
352
|
+
removeEventListener = jest.fn();
|
353
|
+
dispatchEvent = jest.fn();
|
354
|
+
}
|
355
|
+
|
356
|
+
global.FileReader = MockFileReader as any;
|
357
|
+
|
358
|
+
// Mock Blob
|
359
|
+
global.Blob = jest.fn().mockImplementation(() => ({
|
360
|
+
size: 1024,
|
361
|
+
type: 'image/png',
|
362
|
+
slice: jest.fn(),
|
363
|
+
stream: jest.fn(),
|
364
|
+
text: jest.fn(() => Promise.resolve('mock text')),
|
365
|
+
arrayBuffer: jest.fn(() => Promise.resolve(new ArrayBuffer(8))),
|
366
|
+
}));
|
367
|
+
|
368
|
+
// Store original createElement
|
369
|
+
const originalCreateElement = document.createElement;
|
370
|
+
|
371
|
+
// Simple mock for specific elements only when needed
|
372
|
+
document.createElement = jest.fn((tagName: string) => {
|
373
|
+
try {
|
374
|
+
return originalCreateElement.call(document, tagName);
|
375
|
+
} catch (e) {
|
376
|
+
// Fallback for special cases
|
377
|
+
const mockElement = {
|
378
|
+
tagName: tagName.toUpperCase(),
|
379
|
+
style: {},
|
380
|
+
className: '',
|
381
|
+
textContent: '',
|
382
|
+
innerHTML: '',
|
383
|
+
addEventListener: jest.fn(),
|
384
|
+
removeEventListener: jest.fn(),
|
385
|
+
dispatchEvent: jest.fn(),
|
386
|
+
setAttribute: jest.fn(),
|
387
|
+
getAttribute: jest.fn(),
|
388
|
+
removeAttribute: jest.fn(),
|
389
|
+
appendChild: jest.fn(),
|
390
|
+
removeChild: jest.fn(),
|
391
|
+
insertBefore: jest.fn(),
|
392
|
+
querySelector: jest.fn(),
|
393
|
+
querySelectorAll: jest.fn(() => []),
|
394
|
+
parentNode: null,
|
395
|
+
childNodes: [],
|
396
|
+
children: [],
|
397
|
+
};
|
398
|
+
|
399
|
+
// Add specific properties for different elements
|
400
|
+
if (tagName === 'canvas') {
|
401
|
+
Object.assign(mockElement, {
|
402
|
+
getContext: jest.fn(() => ({
|
403
|
+
drawImage: jest.fn(),
|
404
|
+
getImageData: jest.fn(),
|
405
|
+
putImageData: jest.fn(),
|
406
|
+
toDataURL: jest.fn(() => 'data:image/png;base64,test'),
|
407
|
+
})),
|
408
|
+
toDataURL: jest.fn(() => 'data:image/png;base64,test'),
|
409
|
+
width: 100,
|
410
|
+
height: 100,
|
411
|
+
});
|
412
|
+
} else if (tagName === 'a') {
|
413
|
+
Object.assign(mockElement, {
|
414
|
+
click: jest.fn(),
|
415
|
+
download: '',
|
416
|
+
href: '',
|
417
|
+
});
|
418
|
+
}
|
419
|
+
|
420
|
+
return mockElement as any;
|
421
|
+
}
|
422
|
+
});
|
423
|
+
|
424
|
+
// Store original methods
|
425
|
+
const originalAppendChild = document.body.appendChild;
|
426
|
+
const originalRemoveChild = document.body.removeChild;
|
427
|
+
|
428
|
+
// Mock but still allow actual functionality for testing
|
429
|
+
document.body.appendChild = jest.fn((node) => {
|
430
|
+
try {
|
431
|
+
// Check if node is a valid DOM element
|
432
|
+
if (node && typeof node === 'object' && node.nodeType !== undefined) {
|
433
|
+
return originalAppendChild.call(document.body, node);
|
434
|
+
}
|
435
|
+
// For non-DOM elements, just return the node
|
436
|
+
return node;
|
437
|
+
} catch (e) {
|
438
|
+
// If original fails, just return the node
|
439
|
+
return node;
|
440
|
+
}
|
441
|
+
});
|
442
|
+
|
443
|
+
document.body.removeChild = jest.fn((node) => {
|
444
|
+
try {
|
445
|
+
// Check if node is a valid DOM element
|
446
|
+
if (node && typeof node === 'object' && node.nodeType !== undefined) {
|
447
|
+
return originalRemoveChild.call(document.body, node);
|
448
|
+
}
|
449
|
+
// For non-DOM elements, just return the node
|
450
|
+
return node;
|
451
|
+
} catch (e) {
|
452
|
+
// If original fails, just return the node
|
453
|
+
return node;
|
454
|
+
}
|
455
|
+
});
|
456
|
+
|
457
|
+
// Mock getElementsByTagName to prevent ProIcon DOM access issues
|
458
|
+
const originalGetElementsByTagName = document.getElementsByTagName;
|
459
|
+
document.getElementsByTagName = jest.fn((tagName: string) => {
|
460
|
+
if (tagName === 'script') {
|
461
|
+
// Return empty collection for script tags to prevent ProIcon issues
|
462
|
+
return [];
|
463
|
+
}
|
464
|
+
try {
|
465
|
+
return originalGetElementsByTagName.call(document, tagName);
|
466
|
+
} catch (e) {
|
467
|
+
// Fallback for testing
|
468
|
+
return [];
|
469
|
+
}
|
470
|
+
});
|
471
|
+
|
472
|
+
// Mock ProIcon to prevent DOM manipulation
|
473
|
+
jest.mock('../src/ProIcon', () => ({
|
474
|
+
__esModule: true,
|
475
|
+
default: jest.fn(() => null),
|
476
|
+
ProIcon: jest.fn(() => null),
|
477
|
+
}));
|
@@ -0,0 +1,81 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { render, RenderOptions } from '@testing-library/react';
|
3
|
+
import { ConfigProvider } from 'antd';
|
4
|
+
import zhCN from 'antd/locale/zh_CN';
|
5
|
+
|
6
|
+
// 确保document.body存在
|
7
|
+
const ensureDocumentSetup = () => {
|
8
|
+
if (typeof document !== 'undefined') {
|
9
|
+
if (!document.body) {
|
10
|
+
document.body = document.createElement('body');
|
11
|
+
document.documentElement.appendChild(document.body);
|
12
|
+
}
|
13
|
+
|
14
|
+
// 创建一个测试容器
|
15
|
+
let testContainer = document.getElementById('test-container');
|
16
|
+
if (!testContainer) {
|
17
|
+
testContainer = document.createElement('div');
|
18
|
+
testContainer.id = 'test-container';
|
19
|
+
document.body.appendChild(testContainer);
|
20
|
+
}
|
21
|
+
}
|
22
|
+
};
|
23
|
+
|
24
|
+
// Mock ProConfigProvider
|
25
|
+
const MockProConfigProvider = ({ children }: { children: React.ReactNode }) => {
|
26
|
+
const mockValue = {
|
27
|
+
state: {
|
28
|
+
ProTable: {},
|
29
|
+
ProForm: {},
|
30
|
+
ProEnum: {},
|
31
|
+
ProSelect: {},
|
32
|
+
storage: {},
|
33
|
+
theme: {},
|
34
|
+
locale: {},
|
35
|
+
},
|
36
|
+
setState: jest.fn(),
|
37
|
+
};
|
38
|
+
|
39
|
+
const MockContext = React.createContext(mockValue);
|
40
|
+
return React.createElement(MockContext.Provider, { value: mockValue }, children);
|
41
|
+
};
|
42
|
+
|
43
|
+
// Mock FieldProvider
|
44
|
+
const MockFieldProvider = ({ children }: { children: React.ReactNode }) => {
|
45
|
+
const mockValue = {
|
46
|
+
readonly: false,
|
47
|
+
disabled: false,
|
48
|
+
};
|
49
|
+
|
50
|
+
const MockFieldContext = React.createContext(mockValue);
|
51
|
+
return React.createElement(MockFieldContext.Provider, { value: mockValue }, children);
|
52
|
+
};
|
53
|
+
|
54
|
+
// All providers wrapper
|
55
|
+
const AllTheProviders = ({ children }: { children: React.ReactNode }) => {
|
56
|
+
ensureDocumentSetup();
|
57
|
+
|
58
|
+
return (
|
59
|
+
<ConfigProvider locale={zhCN}>
|
60
|
+
<MockProConfigProvider>
|
61
|
+
<MockFieldProvider>
|
62
|
+
{children}
|
63
|
+
</MockFieldProvider>
|
64
|
+
</MockProConfigProvider>
|
65
|
+
</ConfigProvider>
|
66
|
+
);
|
67
|
+
};
|
68
|
+
|
69
|
+
const customRender = (ui: React.ReactElement, options?: Omit<RenderOptions, 'wrapper'>) => {
|
70
|
+
ensureDocumentSetup();
|
71
|
+
|
72
|
+
return render(ui, {
|
73
|
+
wrapper: AllTheProviders,
|
74
|
+
container: document.getElementById('test-container') || document.body,
|
75
|
+
...options
|
76
|
+
});
|
77
|
+
};
|
78
|
+
|
79
|
+
export * from '@testing-library/react';
|
80
|
+
export { customRender as render };
|
81
|
+
export { AllTheProviders, ensureDocumentSetup };
|