From 07d59c00785f01975dec07bdd7bac07f5579885f Mon Sep 17 00:00:00 2001 From: zhangchaojie <1098626505@qq.com> Date: Sat, 22 May 2021 11:47:37 +0800 Subject: [PATCH 01/34] =?UTF-8?q?test:=20=E5=AE=8C=E6=88=90=20src/shared/u?= =?UTF-8?q?tils=20=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jest.config.js | 9 +- package.json | 2 +- scripts/setupTests.js | 92 +- src/shared/src/utils/index.ts | 1 + src/shared/src/utils/util.ts | 9 +- tests/shared/utils/dataMapping.test.ts | 6 + tests/shared/utils/getTag.test.ts | 19 + tests/shared/utils/is.test.ts | 120 +++ tests/shared/utils/set.test.ts | 99 ++ tests/shared/utils/util.test.ts | 64 ++ yarn.lock | 1260 +++++++++++++----------- 11 files changed, 1089 insertions(+), 592 deletions(-) create mode 100644 tests/shared/utils/dataMapping.test.ts create mode 100644 tests/shared/utils/getTag.test.ts create mode 100644 tests/shared/utils/is.test.ts create mode 100644 tests/shared/utils/set.test.ts create mode 100644 tests/shared/utils/util.test.ts diff --git a/jest.config.js b/jest.config.js index 96a5f05..ef8a1d5 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,6 +1,13 @@ +const { join } = require('path'); + module.exports = { setupFilesAfterEnv: ['./scripts/setupTests.js'], moduleNameMapper: { - 'super-antd': '/src/index.ts', + '@/': join(__dirname, './src/'), + 'super-antd': join(__dirname, './src/index.ts'), }, + roots: [ + '/tests/' + ], + verbose: true }; diff --git a/package.json b/package.json index 6bb6541..5f8f9a8 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,7 @@ "@types/react": "^17.0.2", "@types/react-dom": "^17.0.2", "@umijs/fabric": "^2.5.10", - "@umijs/test": "^3.0.5", + "@umijs/test": "^3.4.22", "antd": "^4.14.0", "axios": "^0.21.1", "babel-plugin-import": "^1.13.3", diff --git a/scripts/setupTests.js b/scripts/setupTests.js index 58a8c52..3c0277c 100644 --- a/scripts/setupTests.js +++ b/scripts/setupTests.js @@ -1,25 +1,81 @@ -// jest-dom adds custom jest matchers for asserting on DOM nodes. -// allows you to do things like: -// expect(element).toHaveTextContent(/react/i) -// learn more: https://github.com/testing-library/jest-dom import '@testing-library/jest-dom'; import { cleanup } from '@testing-library/react'; -beforeEach(() => { - Object.defineProperty(window, 'matchMedia', { - writable: true, - value: jest.fn().mockImplementation((query) => ({ - matches: false, - media: query, - onchange: null, - addListener: jest.fn(), // deprecated - removeListener: jest.fn(), // deprecated - addEventListener: jest.fn(), - removeEventListener: jest.fn(), - dispatchEvent: jest.fn(), - })), - }); +jest.mock('react', () => ({ + ...jest.requireActual('react'), + useLayoutEffect: jest.requireActual('react').useEffect, +})); +if (typeof window !== 'undefined') { + global.window.resizeTo = (width, height) => { + global.window.innerWidth = width || global.window.innerWidth; + global.window.innerHeight = height || global.window.innerHeight; + global.window.dispatchEvent(new Event('resize')); + }; + global.window.scrollTo = () => {}; + if (!window.matchMedia) { + Object.defineProperty(global.window, 'matchMedia', { + writable: true, + configurable: true, + value: jest.fn(() => ({ + matches: false, + addListener: jest.fn(), + removeListener: jest.fn(), + })), + }); + } + if (!window.matchMedia) { + Object.defineProperty(global.window, 'matchMedia', { + writable: true, + configurable: true, + value: jest.fn((query) => ({ + matches: query.includes('max-width'), + addListener: jest.fn(), + removeListener: jest.fn(), + })), + }); + } +} + +global.requestAnimationFrame = + global.requestAnimationFrame || + function requestAnimationFrame(cb) { + return setTimeout(cb, 0); + }; + +global.cancelAnimationFrame = + global.cancelAnimationFrame || + function cancelAnimationFrame() { + return null; + }; + +const localStorageMock = (() => { + let store = { + umi_locale: 'zh-CN', + }; + + return { + getItem(key) { + return store[key] || null; + }, + setItem(key, value) { + store[key] = value.toString(); + }, + removeItem(key) { + store[key] = null; + }, + clear() { + store = {}; + }, + }; +})(); + +Object.defineProperty(window, 'localStorage', { + value: localStorageMock, +}); + +Object.defineProperty(window, 'cancelAnimationFrame', { + value: () => null, }); afterEach(() => { diff --git a/src/shared/src/utils/index.ts b/src/shared/src/utils/index.ts index 9266629..f8a01a8 100644 --- a/src/shared/src/utils/index.ts +++ b/src/shared/src/utils/index.ts @@ -2,3 +2,4 @@ export * from './is'; export * from './util'; export * from './getTag'; export { set } from './set'; +export * from './dataMapping'; diff --git a/src/shared/src/utils/util.ts b/src/shared/src/utils/util.ts index c15323b..e716c15 100644 --- a/src/shared/src/utils/util.ts +++ b/src/shared/src/utils/util.ts @@ -44,15 +44,16 @@ export function nextTick(fn: () => void) { // 空函数 export type NoopType = () => void; -export const NOOP: NoopType = () => {}; +export const NOOP: NoopType = () => { }; export const toPathArr = (path: Key | Key[]) => isArray(path) ? path : String(path) - .replace(/\[([^[\]]*)\]/g, '.$1.') - .split('.') - .filter((t) => t !== ''); + .replace(/\[([^[\]]*)\]/g, '.$1.') + .split('.') + .map(t => /^\d+$/.test(t) ? Number(t) : t) + .filter((t) => t !== ''); // lodash get export const get = (obj?: Record, path?: Key | Key[]) => { diff --git a/tests/shared/utils/dataMapping.test.ts b/tests/shared/utils/dataMapping.test.ts new file mode 100644 index 0000000..a96eac4 --- /dev/null +++ b/tests/shared/utils/dataMapping.test.ts @@ -0,0 +1,6 @@ +import { getSchemaData } from 'super-antd'; + +test("getSchemaData", () => { + expect(getSchemaData({ schema: (data) => data.name, data: { name: 'zhang' } })).toBe('zhang') + expect(getSchemaData({ schema: '{{data.name}}', data: { name: 'zhang' } })).toBe('zhang') +}) \ No newline at end of file diff --git a/tests/shared/utils/getTag.test.ts b/tests/shared/utils/getTag.test.ts new file mode 100644 index 0000000..71f2ce3 --- /dev/null +++ b/tests/shared/utils/getTag.test.ts @@ -0,0 +1,19 @@ +import { getTypeString, getTag } from 'super-antd' + +test('getTypeString', () => { + expect(getTypeString(undefined)).toBe('[object Undefined]'); + expect(getTypeString(null)).toBe('[object Null]'); + expect(getTypeString(123)).toBe('[object Number]'); + expect(getTypeString('str')).toBe('[object String]'); + expect(getTypeString(false)).toBe('[object Boolean]'); + expect(getTypeString(getTag)).toBe('[object Function]'); +}) + +test('getTag', () => { + expect(getTag(undefined)).toBe('Undefined'); + expect(getTag(null)).toBe('Null'); + expect(getTag(123)).toBe('Number'); + expect(getTag('str')).toBe('String'); + expect(getTag(false)).toBe('Boolean'); + expect(getTag(getTag)).toBe('Function'); +}); \ No newline at end of file diff --git a/tests/shared/utils/is.test.ts b/tests/shared/utils/is.test.ts new file mode 100644 index 0000000..f23f87f --- /dev/null +++ b/tests/shared/utils/is.test.ts @@ -0,0 +1,120 @@ +import { + checkType, + isArray, + isBoolean, + isDate, + isNil, + isNull, + isNumber, + isPlainObject, + isString, + isObject, + isFunction, + isUndefined, +} from 'super-antd' + + +describe('is', () => { + test('isObject', () => { + expect(isObject(new Date())).toBe(true) + expect(isObject({})).toBe(true) + expect(isObject(123)).toBe(false) + expect(isObject(true)).toBe(false) + }) + + test('isNil', () => { + // true + expect(isNil(undefined)).toBeTruthy(); + expect(isNil(null)).toBeTruthy(); + + // false + expect(isNil(0)).toBeFalsy(); + expect(isNil('')).toBeFalsy(); + expect(isNil([])).toBeFalsy(); + }); + test('isDate', () => { + // true + expect(isDate(new Date())).toBeTruthy(); + + // false + expect(isDate('2020-10-10')).toBeFalsy(); + expect(isDate(Number(new Date()))).toBeFalsy(); + }); + test('isNull', () => { + // true + expect(isNull(null)).toBeTruthy(); + + // false + expect(isNull(undefined)).toBeFalsy(); + expect(isNull(0)).toBeFalsy(); + expect(isNull('')).toBeFalsy(); + }); + test('isArray', () => { + // true + expect(isArray([])).toBeTruthy(); + expect(isArray([1, 2])).toBeTruthy(); + + // false + expect(isArray({})).toBeFalsy(); + expect(isArray(100)).toBeFalsy(); + }); + test('isString', () => { + // true + expect(isString('')).toBeTruthy(); + expect(isString(' ')).toBeTruthy(); + expect(isString('123')).toBeTruthy(); + + // false + expect(isString({})).toBeFalsy(); + expect(isString(123)).toBeFalsy(); + expect(isString([])).toBeFalsy(); + }); + test('isNumber', () => { + // true + expect(isNumber(0)).toBeTruthy(); + expect(isNumber(100)).toBeTruthy(); + + // false + expect(isNumber('0')).toBeFalsy(); + expect(isNumber('')).toBeFalsy(); + }); + test('checkType', () => { + expect(checkType(123, 'number')).toBeTruthy(); + expect(checkType('str', 'String')).toBeTruthy(); + }); + test('isBoolean', () => { + // true + expect(isBoolean(true)).toBeTruthy(); + expect(isBoolean(false)).toBeTruthy(); + + // false + expect(isBoolean('')).toBeFalsy(); + expect(isBoolean(123)).toBeFalsy(); + }); + test('isUndefined', () => { + // true + expect(isUndefined(undefined)).toBeTruthy(); + + // false + expect(isUndefined(null)).toBeFalsy(); + expect(isUndefined('')).toBeFalsy(); + }); + test('isPlainObject', () => { + // true + expect(isPlainObject({})).toBeTruthy(); + expect(isPlainObject({ name: 'foo' })).toBeTruthy(); + + // false + expect(isPlainObject(null)).toBeFalsy(); + expect(isPlainObject(() => { })).toBeFalsy(); + expect(isPlainObject(new Date())).toBeFalsy(); + }); + + test('isFunction', () => { + expect(isFunction(() => { })).toBeTruthy(); + expect(isFunction(Math.random)).toBeTruthy(); + + expect(isFunction({})).toBeFalsy(); + expect(isFunction(123)).toBeFalsy(); + }) +}); diff --git a/tests/shared/utils/set.test.ts b/tests/shared/utils/set.test.ts new file mode 100644 index 0000000..9976245 --- /dev/null +++ b/tests/shared/utils/set.test.ts @@ -0,0 +1,99 @@ +import { set } from 'super-antd'; + +describe('set', () => { + describe('unsafe properties', () => { + test('should not allow setting constructor', () => { + expect(() => set({}, 'a.constructor.b', 'c')).toThrowError(); + expect(() => set({}, 'a.constructor', 'c')).toThrowError(); + expect(() => set({}, 'constructor', 'c')).toThrowError(); + }); + + test('should not allow setting prototype', () => { + expect(() => set({}, 'a.prototype.b', 'c')).toThrowError(); + expect(() => set({}, 'a.prototype', 'c')).toThrowError(); + expect(() => set({}, 'prototype', 'c')).toThrowError(); + }); + + test('should not allow setting __proto__', () => { + expect(() => set({}, 'a.__proto__.b', 'c')).toThrowError(); + expect(() => set({}, 'a.__proto__', 'c')).toThrowError(); + expect(() => set({}, '__proto__', 'c')).toThrowError(); + }); + }); + + describe('test', () => { + test('should render object on path is undefined', () => { + expect(set({ a: 1 })).toEqual({ a: 1 }) + }) + test('should delete prop on value is undefined', () => { + let o = { a: { b: 1 } } + set(o, 'a.b') + expect(o).toEqual({ a: {} }) + }) + + test('should return non-objects', () => { + const str = set('foo' as any, 'a.b', 'c'); + expect(str).toEqual('foo'); + const _null = set(null as any, 'a.b', 'c'); + expect(_null).toEqual(null); + }); + + test('should set on the root of the object', () => { + const o = {}; + set(o, 'foo', 'bar'); + expect((o as any).foo).toEqual('bar'); + }); + + test('should set the specified property.', () => { + expect(set({ a: 'aaa', b: 'b' }, 'a', 'bbb')).toEqual({ a: 'bbb', b: 'b' }); + }); + + test('should set a nested property', () => { + const o: any = {}; + set(o, 'a.b', 'c'); + expect(o.a.b).toEqual('c'); + }); + + test('should support passing an array as the key', () => { + const actual = set({ a: 'a', b: { c: 'd' } }, ['b', 'c', 'd'], 'eee'); + expect(actual).toEqual({ a: 'a', b: { c: { d: 'eee' } } }); + }); + + test('should set a deeply nested value.', () => { + const actual = set({ a: 'a', b: { c: 'd' } }, 'b.c.d', 'eee'); + expect(actual).toEqual({ a: 'a', b: { c: { d: 'eee' } } }); + }); + + + test('should allow keys to be whtestespace', () => { + const o: any = {}; + set(o, 'a. .a', { y: 'z' }); + expect(o.a[' '].a).toEqual({ y: 'z' }); + }); + + test('should create an array if test does not already exist', () => { + const o: any = {}; + set(o, 'a.0.a', { y: 'z' }); + expect(Array.isArray(o.a)).toEqual(true); + expect(o.a[0].a).toEqual({ y: 'z' }); + + set(o, 'a.1.b', { y: 'z' }); + expect(o.a[1].b).toEqual({ y: 'z' }); + + set(o, 'a.2.c', { y: 'z' }); + expect(o.a[2].c).toEqual({ y: 'z' }); + + set(o, 'b.0', { y: 'z' }); + expect(o.b).toEqual([{ y: 'z' }]); + + set(o, '0', { y: 'z' }); + expect(o['0']).toEqual({ y: 'z' }); + }); + + test('should create a deeply nested property if test does not already exist', () => { + const o: any = {}; + set(o, 'a.b.c.d.e', 'c'); + expect(o.a.b.c.d.e).toEqual('c'); + }); + }); +}); diff --git a/tests/shared/utils/util.test.ts b/tests/shared/utils/util.test.ts new file mode 100644 index 0000000..0a72af4 --- /dev/null +++ b/tests/shared/utils/util.test.ts @@ -0,0 +1,64 @@ +import { castToArray, getCol, nextTick, toPathArr, get, omit } from 'super-antd'; + +test('castToArray', () => { + expect(castToArray([])).toEqual([]); + expect(castToArray([1, 2, 3])).toEqual([1, 2, 3]); + expect(castToArray(1)).toEqual([1]); + expect(castToArray(undefined)).toEqual([]); + expect(castToArray(null)).toEqual([]); + expect(castToArray({})).toEqual([{}]); +}); + +test('getCol', () => { + expect(getCol(undefined)).toEqual(undefined); + + expect(getCol(3)).toEqual({ span: 3 }); + expect(getCol('3')).toEqual({ span: '3' }); + expect(getCol({ span: 3 })).toEqual({ span: 3 }); +}); + + +test('nextTick', (done) => { + expect.assertions(1) + let count = 1 + nextTick(() => { + expect(count).toEqual(2) + done() + }) + count += 1 +}) + +test('toPathArr', () => { + expect(toPathArr('a.b')).toEqual(['a', 'b']) + expect(toPathArr(['a', 'b'])).toEqual(['a', 'b']) + expect(toPathArr('a.0.a')).toEqual(['a', 0, 'a']) + expect(toPathArr('a[0].a')).toEqual(['a', 0, 'a']) +}) + +test('get', () => { + // 参数异常情况 + const date = new Date() + expect(get()).toEqual(undefined) + expect(get(date, ['a'])).toEqual(date) + + // 正常情况 + const obj = { a: { b: 1, c: [2, { d: 3 }] } } + expect(get(obj, ['a', 'b'])).toEqual(1) + expect(get(obj, 'a.b')).toEqual(1) + expect(get(obj, 'a.c[0]')).toEqual(2) + expect(get(obj, 'a.c.0')).toEqual(2) + expect(get(obj, 'a.c[1].d')).toEqual(3) + + // 不存在情况 + expect(get(obj, 'a.e.f')).toEqual(undefined) + expect(get(obj, ['a', 'b', 3])).toEqual(undefined) + expect(get(obj, 'a.e[1]')).toEqual(undefined) +}) + +test('omit', () => { + const obj = { a: 1, b: 2 } + expect(omit(obj)).toEqual(obj) + + expect(omit(obj, [])).toEqual({ a: 1, b: 2 }) + expect(omit(obj, ['a'])).toEqual({ b: 2 }) +}) \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 976e02c..0b29868 100644 --- a/yarn.lock +++ b/yarn.lock @@ -958,7 +958,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-syntax-top-level-await@^7.12.1", "@babel/plugin-syntax-top-level-await@^7.12.13": +"@babel/plugin-syntax-top-level-await@^7.12.1", "@babel/plugin-syntax-top-level-await@^7.12.13", "@babel/plugin-syntax-top-level-await@^7.8.3": version "7.12.13" resolved "https://registry.npm.taobao.org/@babel/plugin-syntax-top-level-await/download/@babel/plugin-syntax-top-level-await-7.12.13.tgz#c5f0fa6e249f5b739727f923540cf7a806130178" integrity sha1-xfD6biSfW3OXJ/kjVAz3qAYTAXg= @@ -1802,6 +1802,11 @@ resolved "https://registry.npm.taobao.org/@bcoe/v8-coverage/download/@bcoe/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha1-daLotRy3WKdVPWgEpZMteqznXDk= +"@bloomberg/record-tuple-polyfill@0.0.3": + version "0.0.3" + resolved "https://registry.npm.taobao.org/@bloomberg/record-tuple-polyfill/download/@bloomberg/record-tuple-polyfill-0.0.3.tgz#0b03d18b88a30894caab14abd669b1cbbf47b843" + integrity sha1-CwPRi4ijCJTKqxSr1mmxy79HuEM= + "@cnakazawa/watch@^1.0.3": version "1.0.4" resolved "https://registry.npm.taobao.org/@cnakazawa/watch/download/@cnakazawa/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" @@ -1915,47 +1920,48 @@ chalk "^2.0.1" slash "^2.0.0" -"@jest/console@^25.5.0": - version "25.5.0" - resolved "https://registry.npm.taobao.org/@jest/console/download/@jest/console-25.5.0.tgz#770800799d510f37329c508a9edd0b7b447d9abb" - integrity sha1-dwgAeZ1RDzcynFCKnt0Le0R9mrs= +"@jest/console@^26.6.2": + version "26.6.2" + resolved "https://registry.nlark.com/@jest/console/download/@jest/console-26.6.2.tgz#4e04bc464014358b03ab4937805ee36a0aeb98f2" + integrity sha1-TgS8RkAUNYsDq0k3gF7jagrrmPI= dependencies: - "@jest/types" "^25.5.0" - chalk "^3.0.0" - jest-message-util "^25.5.0" - jest-util "^25.5.0" + "@jest/types" "^26.6.2" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^26.6.2" + jest-util "^26.6.2" slash "^3.0.0" -"@jest/core@^25.5.4": - version "25.5.4" - resolved "https://registry.npm.taobao.org/@jest/core/download/@jest/core-25.5.4.tgz?cache=0&sync_timestamp=1615814275657&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40jest%2Fcore%2Fdownload%2F%40jest%2Fcore-25.5.4.tgz#3ef7412f7339210f003cdf36646bbca786efe7b4" - integrity sha1-PvdBL3M5IQ8APN82ZGu8p4bv57Q= +"@jest/core@^26.6.3": + version "26.6.3" + resolved "https://registry.nlark.com/@jest/core/download/@jest/core-26.6.3.tgz?cache=0&sync_timestamp=1621550032790&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40jest%2Fcore%2Fdownload%2F%40jest%2Fcore-26.6.3.tgz#7639fcb3833d748a4656ada54bde193051e45fad" + integrity sha1-djn8s4M9dIpGVq2lS94ZMFHkX60= dependencies: - "@jest/console" "^25.5.0" - "@jest/reporters" "^25.5.1" - "@jest/test-result" "^25.5.0" - "@jest/transform" "^25.5.1" - "@jest/types" "^25.5.0" + "@jest/console" "^26.6.2" + "@jest/reporters" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/transform" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" ansi-escapes "^4.2.1" - chalk "^3.0.0" + chalk "^4.0.0" exit "^0.1.2" graceful-fs "^4.2.4" - jest-changed-files "^25.5.0" - jest-config "^25.5.4" - jest-haste-map "^25.5.1" - jest-message-util "^25.5.0" - jest-regex-util "^25.2.6" - jest-resolve "^25.5.1" - jest-resolve-dependencies "^25.5.4" - jest-runner "^25.5.4" - jest-runtime "^25.5.4" - jest-snapshot "^25.5.1" - jest-util "^25.5.0" - jest-validate "^25.5.0" - jest-watcher "^25.5.0" + jest-changed-files "^26.6.2" + jest-config "^26.6.3" + jest-haste-map "^26.6.2" + jest-message-util "^26.6.2" + jest-regex-util "^26.0.0" + jest-resolve "^26.6.2" + jest-resolve-dependencies "^26.6.3" + jest-runner "^26.6.3" + jest-runtime "^26.6.3" + jest-snapshot "^26.6.2" + jest-util "^26.6.2" + jest-validate "^26.6.2" + jest-watcher "^26.6.2" micromatch "^4.0.2" p-each-series "^2.1.0" - realpath-native "^2.0.0" rimraf "^3.0.0" slash "^3.0.0" strip-ansi "^6.0.0" @@ -1970,14 +1976,15 @@ "@jest/types" "^24.9.0" jest-mock "^24.9.0" -"@jest/environment@^25.5.0": - version "25.5.0" - resolved "https://registry.npm.taobao.org/@jest/environment/download/@jest/environment-25.5.0.tgz#aa33b0c21a716c65686638e7ef816c0e3a0c7b37" - integrity sha1-qjOwwhpxbGVoZjjn74FsDjoMezc= +"@jest/environment@^26.6.2": + version "26.6.2" + resolved "https://registry.nlark.com/@jest/environment/download/@jest/environment-26.6.2.tgz?cache=0&sync_timestamp=1621550020731&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40jest%2Fenvironment%2Fdownload%2F%40jest%2Fenvironment-26.6.2.tgz#ba364cc72e221e79cc8f0a99555bf5d7577cf92c" + integrity sha1-ujZMxy4iHnnMjwqZVVv111d8+Sw= dependencies: - "@jest/fake-timers" "^25.5.0" - "@jest/types" "^25.5.0" - jest-mock "^25.5.0" + "@jest/fake-timers" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + jest-mock "^26.6.2" "@jest/fake-timers@^24.3.0", "@jest/fake-timers@^24.9.0": version "24.9.0" @@ -1988,57 +1995,58 @@ jest-message-util "^24.9.0" jest-mock "^24.9.0" -"@jest/fake-timers@^25.5.0": - version "25.5.0" - resolved "https://registry.npm.taobao.org/@jest/fake-timers/download/@jest/fake-timers-25.5.0.tgz#46352e00533c024c90c2bc2ad9f2959f7f114185" - integrity sha1-RjUuAFM8AkyQwrwq2fKVn38RQYU= +"@jest/fake-timers@^26.6.2": + version "26.6.2" + resolved "https://registry.nlark.com/@jest/fake-timers/download/@jest/fake-timers-26.6.2.tgz?cache=0&sync_timestamp=1621550013648&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40jest%2Ffake-timers%2Fdownload%2F%40jest%2Ffake-timers-26.6.2.tgz#459c329bcf70cee4af4d7e3f3e67848123535aad" + integrity sha1-RZwym89wzuSvTX4/PmeEgSNTWq0= dependencies: - "@jest/types" "^25.5.0" - jest-message-util "^25.5.0" - jest-mock "^25.5.0" - jest-util "^25.5.0" - lolex "^5.0.0" + "@jest/types" "^26.6.2" + "@sinonjs/fake-timers" "^6.0.1" + "@types/node" "*" + jest-message-util "^26.6.2" + jest-mock "^26.6.2" + jest-util "^26.6.2" -"@jest/globals@^25.5.2": - version "25.5.2" - resolved "https://registry.npm.taobao.org/@jest/globals/download/@jest/globals-25.5.2.tgz?cache=0&sync_timestamp=1615813497928&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40jest%2Fglobals%2Fdownload%2F%40jest%2Fglobals-25.5.2.tgz#5e45e9de8d228716af3257eeb3991cc2e162ca88" - integrity sha1-XkXp3o0ihxavMlfus5kcwuFiyog= +"@jest/globals@^26.6.2": + version "26.6.2" + resolved "https://registry.nlark.com/@jest/globals/download/@jest/globals-26.6.2.tgz#5b613b78a1aa2655ae908eba638cc96a20df720a" + integrity sha1-W2E7eKGqJlWukI66Y4zJaiDfcgo= dependencies: - "@jest/environment" "^25.5.0" - "@jest/types" "^25.5.0" - expect "^25.5.0" + "@jest/environment" "^26.6.2" + "@jest/types" "^26.6.2" + expect "^26.6.2" -"@jest/reporters@^25.5.1": - version "25.5.1" - resolved "https://registry.npm.taobao.org/@jest/reporters/download/@jest/reporters-25.5.1.tgz#cb686bcc680f664c2dbaf7ed873e93aa6811538b" - integrity sha1-y2hrzGgPZkwtuvfthz6TqmgRU4s= +"@jest/reporters@^26.6.2": + version "26.6.2" + resolved "https://registry.nlark.com/@jest/reporters/download/@jest/reporters-26.6.2.tgz?cache=0&sync_timestamp=1621550024396&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40jest%2Freporters%2Fdownload%2F%40jest%2Freporters-26.6.2.tgz#1f518b99637a5f18307bd3ecf9275f6882a667f6" + integrity sha1-H1GLmWN6Xxgwe9Ps+SdfaIKmZ/Y= dependencies: "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^25.5.0" - "@jest/test-result" "^25.5.0" - "@jest/transform" "^25.5.1" - "@jest/types" "^25.5.0" - chalk "^3.0.0" + "@jest/console" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/transform" "^26.6.2" + "@jest/types" "^26.6.2" + chalk "^4.0.0" collect-v8-coverage "^1.0.0" exit "^0.1.2" glob "^7.1.2" graceful-fs "^4.2.4" istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^4.0.0" + istanbul-lib-instrument "^4.0.3" istanbul-lib-report "^3.0.0" istanbul-lib-source-maps "^4.0.0" istanbul-reports "^3.0.2" - jest-haste-map "^25.5.1" - jest-resolve "^25.5.1" - jest-util "^25.5.0" - jest-worker "^25.5.0" + jest-haste-map "^26.6.2" + jest-resolve "^26.6.2" + jest-util "^26.6.2" + jest-worker "^26.6.2" slash "^3.0.0" source-map "^0.6.0" - string-length "^3.1.0" + string-length "^4.0.1" terminal-link "^2.0.0" - v8-to-istanbul "^4.1.3" + v8-to-istanbul "^7.0.0" optionalDependencies: - node-notifier "^6.0.0" + node-notifier "^8.0.0" "@jest/source-map@^24.9.0": version "24.9.0" @@ -2049,10 +2057,10 @@ graceful-fs "^4.1.15" source-map "^0.6.0" -"@jest/source-map@^25.5.0": - version "25.5.0" - resolved "https://registry.npm.taobao.org/@jest/source-map/download/@jest/source-map-25.5.0.tgz#df5c20d6050aa292c2c6d3f0d2c7606af315bd1b" - integrity sha1-31wg1gUKopLCxtPw0sdgavMVvRs= +"@jest/source-map@^26.6.2": + version "26.6.2" + resolved "https://registry.npm.taobao.org/@jest/source-map/download/@jest/source-map-26.6.2.tgz#29af5e1e2e324cafccc936f218309f54ab69d535" + integrity sha1-Ka9eHi4yTK/MyTbyGDCfVKtp1TU= dependencies: callsites "^3.0.0" graceful-fs "^4.2.4" @@ -2067,26 +2075,26 @@ "@jest/types" "^24.9.0" "@types/istanbul-lib-coverage" "^2.0.0" -"@jest/test-result@^25.5.0": - version "25.5.0" - resolved "https://registry.npm.taobao.org/@jest/test-result/download/@jest/test-result-25.5.0.tgz?cache=0&sync_timestamp=1615813579445&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40jest%2Ftest-result%2Fdownload%2F%40jest%2Ftest-result-25.5.0.tgz#139a043230cdeffe9ba2d8341b27f2efc77ce87c" - integrity sha1-E5oEMjDN7/6botg0Gyfy78d86Hw= +"@jest/test-result@^26.6.2": + version "26.6.2" + resolved "https://registry.nlark.com/@jest/test-result/download/@jest/test-result-26.6.2.tgz?cache=0&sync_timestamp=1621550017984&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40jest%2Ftest-result%2Fdownload%2F%40jest%2Ftest-result-26.6.2.tgz#55da58b62df134576cc95476efa5f7949e3f5f18" + integrity sha1-VdpYti3xNFdsyVR276X3lJ4/Xxg= dependencies: - "@jest/console" "^25.5.0" - "@jest/types" "^25.5.0" + "@jest/console" "^26.6.2" + "@jest/types" "^26.6.2" "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" -"@jest/test-sequencer@^25.5.4": - version "25.5.4" - resolved "https://registry.npm.taobao.org/@jest/test-sequencer/download/@jest/test-sequencer-25.5.4.tgz#9b4e685b36954c38d0f052e596d28161bdc8b737" - integrity sha1-m05oWzaVTDjQ8FLlltKBYb3Itzc= +"@jest/test-sequencer@^26.6.3": + version "26.6.3" + resolved "https://registry.nlark.com/@jest/test-sequencer/download/@jest/test-sequencer-26.6.3.tgz?cache=0&sync_timestamp=1621550032443&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40jest%2Ftest-sequencer%2Fdownload%2F%40jest%2Ftest-sequencer-26.6.3.tgz#98e8a45100863886d074205e8ffdc5a7eb582b17" + integrity sha1-mOikUQCGOIbQdCBej/3Fp+tYKxc= dependencies: - "@jest/test-result" "^25.5.0" + "@jest/test-result" "^26.6.2" graceful-fs "^4.2.4" - jest-haste-map "^25.5.1" - jest-runner "^25.5.4" - jest-runtime "^25.5.4" + jest-haste-map "^26.6.2" + jest-runner "^26.6.3" + jest-runtime "^26.6.3" "@jest/transform@^24.9.0": version "24.9.0" @@ -2110,24 +2118,23 @@ source-map "^0.6.1" write-file-atomic "2.4.1" -"@jest/transform@^25.5.1": - version "25.5.1" - resolved "https://registry.npm.taobao.org/@jest/transform/download/@jest/transform-25.5.1.tgz?cache=0&sync_timestamp=1615814188782&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40jest%2Ftransform%2Fdownload%2F%40jest%2Ftransform-25.5.1.tgz#0469ddc17699dd2bf985db55fa0fb9309f5c2db3" - integrity sha1-BGndwXaZ3Sv5hdtV+g+5MJ9cLbM= +"@jest/transform@^26.6.2": + version "26.6.2" + resolved "https://registry.nlark.com/@jest/transform/download/@jest/transform-26.6.2.tgz#5ac57c5fa1ad17b2aae83e73e45813894dcf2e4b" + integrity sha1-WsV8X6GtF7Kq6D5z5FgTiU3PLks= dependencies: "@babel/core" "^7.1.0" - "@jest/types" "^25.5.0" + "@jest/types" "^26.6.2" babel-plugin-istanbul "^6.0.0" - chalk "^3.0.0" + chalk "^4.0.0" convert-source-map "^1.4.0" fast-json-stable-stringify "^2.0.0" graceful-fs "^4.2.4" - jest-haste-map "^25.5.1" - jest-regex-util "^25.2.6" - jest-util "^25.5.0" + jest-haste-map "^26.6.2" + jest-regex-util "^26.0.0" + jest-util "^26.6.2" micromatch "^4.0.2" pirates "^4.0.1" - realpath-native "^2.0.0" slash "^3.0.0" source-map "^0.6.1" write-file-atomic "^3.0.0" @@ -2141,16 +2148,6 @@ "@types/istanbul-reports" "^1.1.1" "@types/yargs" "^13.0.0" -"@jest/types@^25.5.0": - version "25.5.0" - resolved "https://registry.npm.taobao.org/@jest/types/download/@jest/types-25.5.0.tgz#4d6a4793f7b9599fc3680877b856a97dbccf2a9d" - integrity sha1-TWpHk/e5WZ/DaAh3uFapfbzPKp0= - dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^1.1.1" - "@types/yargs" "^15.0.0" - chalk "^3.0.0" - "@jest/types@^26.6.2": version "26.6.2" resolved "https://registry.npm.taobao.org/@jest/types/download/@jest/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" @@ -2337,6 +2334,13 @@ dependencies: type-detect "4.0.8" +"@sinonjs/fake-timers@^6.0.1": + version "6.0.1" + resolved "https://registry.nlark.com/@sinonjs/fake-timers/download/@sinonjs/fake-timers-6.0.1.tgz?cache=0&sync_timestamp=1621603846626&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40sinonjs%2Ffake-timers%2Fdownload%2F%40sinonjs%2Ffake-timers-6.0.1.tgz#293674fccb3262ac782c7aadfdeca86b10c75c40" + integrity sha1-KTZ0/MsyYqx4LHqt/eyoaxDHXEA= + dependencies: + "@sinonjs/commons" "^1.7.0" + "@stylelint/postcss-css-in-js@^0.37.2": version "0.37.2" resolved "https://registry.npm.taobao.org/@stylelint/postcss-css-in-js/download/@stylelint/postcss-css-in-js-0.37.2.tgz#7e5a84ad181f4234a2480803422a47b8749af3d2" @@ -2630,6 +2634,17 @@ resolved "https://registry.npm.taobao.org/@types/aria-query/download/@types/aria-query-4.2.1.tgz#78b5433344e2f92e8b306c06a5622c50c245bf6b" integrity sha1-eLVDM0Ti+S6LMGwGpWIsUMJFv2s= +"@types/babel__core@^7.0.0": + version "7.1.14" + resolved "https://registry.nlark.com/@types/babel__core/download/@types/babel__core-7.1.14.tgz#faaeefc4185ec71c389f4501ee5ec84b170cc402" + integrity sha1-+q7vxBhexxw4n0UB7l7ISxcMxAI= + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + "@types/babel__core@^7.1.7": version "7.1.13" resolved "https://registry.npm.taobao.org/@types/babel__core/download/@types/babel__core-7.1.13.tgz?cache=0&sync_timestamp=1615834715823&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fbabel__core%2Fdownload%2F%40types%2Fbabel__core-7.1.13.tgz#bc6eea53975fdf163aff66c086522c6f293ae4cf" @@ -2656,7 +2671,7 @@ "@babel/parser" "^7.1.0" "@babel/types" "^7.0.0" -"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.4", "@types/babel__traverse@^7.0.6": version "7.11.1" resolved "https://registry.npm.taobao.org/@types/babel__traverse/download/@types/babel__traverse-7.11.1.tgz#654f6c4f67568e24c23b367e947098c6206fa639" integrity sha1-ZU9sT2dWjiTCOzZ+lHCYxiBvpjk= @@ -2998,11 +3013,6 @@ resolved "https://registry.npm.taobao.org/@types/prettier/download/@types/prettier-2.1.6.tgz?cache=0&sync_timestamp=1615834927359&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fprettier%2Fdownload%2F%40types%2Fprettier-2.1.6.tgz#f4b1efa784e8db479cdb8b14403e2144b1e9ff03" integrity sha1-9LHvp4To20ec24sUQD4hRLHp/wM= -"@types/prettier@^1.19.0": - version "1.19.1" - resolved "https://registry.npm.taobao.org/@types/prettier/download/@types/prettier-1.19.1.tgz?cache=0&sync_timestamp=1615834927359&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fprettier%2Fdownload%2F%40types%2Fprettier-1.19.1.tgz#33509849f8e679e4add158959fdb086440e9553f" - integrity sha1-M1CYSfjmeeSt0ViVn9sIZEDpVT8= - "@types/prettier@^2.0.0": version "2.2.3" resolved "https://registry.npm.taobao.org/@types/prettier/download/@types/prettier-2.2.3.tgz?cache=0&sync_timestamp=1615834927359&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fprettier%2Fdownload%2F%40types%2Fprettier-2.2.3.tgz#ef65165aea2924c9359205bf748865b8881753c0" @@ -3195,6 +3205,11 @@ resolved "https://registry.npm.taobao.org/@types/stack-utils/download/@types/stack-utils-1.0.1.tgz?cache=0&sync_timestamp=1613384491701&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fstack-utils%2Fdownload%2F%40types%2Fstack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" integrity sha1-CoUdO9lkmPolwzq3J47TvWXwbD4= +"@types/stack-utils@^2.0.0": + version "2.0.0" + resolved "https://registry.nlark.com/@types/stack-utils/download/@types/stack-utils-2.0.0.tgz#7036640b4e21cc2f259ae826ce843d277dad8cff" + integrity sha1-cDZkC04hzC8lmugmzoQ9J32tjP8= + "@types/tapable@*", "@types/tapable@1.0.6": version "1.0.6" resolved "https://registry.npm.taobao.org/@types/tapable/download/@types/tapable-1.0.6.tgz#a9ca4b70a18b270ccb2bc0aaafefd1d486b7ea74" @@ -3405,6 +3420,11 @@ resolved "https://registry.npm.taobao.org/@umijs/babel-plugin-auto-css-modules/download/@umijs/babel-plugin-auto-css-modules-3.4.2.tgz?cache=0&sync_timestamp=1616036949358&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40umijs%2Fbabel-plugin-auto-css-modules%2Fdownload%2F%40umijs%2Fbabel-plugin-auto-css-modules-3.4.2.tgz#81387533c0f4a00417a9a7b38c130359c63ca956" integrity sha1-gTh1M8D0oAQXqaezjBMDWcY8qVY= +"@umijs/babel-plugin-auto-css-modules@3.4.22": + version "3.4.22" + resolved "https://registry.nlark.com/@umijs/babel-plugin-auto-css-modules/download/@umijs/babel-plugin-auto-css-modules-3.4.22.tgz#cbcec6cc389965150e9393dec7c5b57d5afb4be7" + integrity sha1-y87GzDiZZRUOk5Pex8W1fVr7S+c= + "@umijs/babel-plugin-import-to-await-require@3.3.11": version "3.3.11" resolved "https://registry.npm.taobao.org/@umijs/babel-plugin-import-to-await-require/download/@umijs/babel-plugin-import-to-await-require-3.3.11.tgz?cache=0&sync_timestamp=1616036950728&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40umijs%2Fbabel-plugin-import-to-await-require%2Fdownload%2F%40umijs%2Fbabel-plugin-import-to-await-require-3.3.11.tgz#4c05cb85c00af02698398fddf8ff557c69b36b23" @@ -3419,6 +3439,13 @@ dependencies: "@umijs/utils" "3.4.2" +"@umijs/babel-plugin-import-to-await-require@3.4.22": + version "3.4.22" + resolved "https://registry.nlark.com/@umijs/babel-plugin-import-to-await-require/download/@umijs/babel-plugin-import-to-await-require-3.4.22.tgz#44e4aafa4ffbace86b1fcec32523e484b28ef64c" + integrity sha1-ROSq+k/7rOhrH87DJSPkhLKO9kw= + dependencies: + "@umijs/utils" "3.4.22" + "@umijs/babel-plugin-lock-core-js-3@3.3.11": version "3.3.11" resolved "https://registry.npm.taobao.org/@umijs/babel-plugin-lock-core-js-3/download/@umijs/babel-plugin-lock-core-js-3-3.3.11.tgz?cache=0&sync_timestamp=1616036951128&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40umijs%2Fbabel-plugin-lock-core-js-3%2Fdownload%2F%40umijs%2Fbabel-plugin-lock-core-js-3-3.3.11.tgz#75e229a4dd0e792d43e42cc15104416cc6244e01" @@ -3435,6 +3462,14 @@ "@umijs/utils" "3.4.2" core-js "3.6.5" +"@umijs/babel-plugin-lock-core-js-3@3.4.22": + version "3.4.22" + resolved "https://registry.nlark.com/@umijs/babel-plugin-lock-core-js-3/download/@umijs/babel-plugin-lock-core-js-3-3.4.22.tgz#98d0bbba706a2c99d55ddd0aad0dcb9cfeeae703" + integrity sha1-mNC7unBqLJnVXd0KrQ3LnP7q5wM= + dependencies: + "@umijs/utils" "3.4.22" + core-js "3.6.5" + "@umijs/babel-plugin-no-anonymous-default-export@3.4.2": version "3.4.2" resolved "https://registry.npm.taobao.org/@umijs/babel-plugin-no-anonymous-default-export/download/@umijs/babel-plugin-no-anonymous-default-export-3.4.2.tgz?cache=0&sync_timestamp=1615799314283&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40umijs%2Fbabel-plugin-no-anonymous-default-export%2Fdownload%2F%40umijs%2Fbabel-plugin-no-anonymous-default-export-3.4.2.tgz#05763caafb69c83a787ffec3d52517301365088c" @@ -3442,6 +3477,13 @@ dependencies: "@umijs/utils" "3.4.2" +"@umijs/babel-plugin-no-anonymous-default-export@3.4.22": + version "3.4.22" + resolved "https://registry.nlark.com/@umijs/babel-plugin-no-anonymous-default-export/download/@umijs/babel-plugin-no-anonymous-default-export-3.4.22.tgz#9fa6b03c0f419eb9dd26466a7154a4b060f21675" + integrity sha1-n6awPA9BnrndJkZqcVSksGDyFnU= + dependencies: + "@umijs/utils" "3.4.22" + "@umijs/babel-preset-umi@3.3.11", "@umijs/babel-preset-umi@3.x": version "3.3.11" resolved "https://registry.npm.taobao.org/@umijs/babel-preset-umi/download/@umijs/babel-preset-umi-3.3.11.tgz?cache=0&sync_timestamp=1616036952455&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40umijs%2Fbabel-preset-umi%2Fdownload%2F%40umijs%2Fbabel-preset-umi-3.3.11.tgz#b469998aeb0c24d5c817a7ae133610987d126cfa" @@ -3485,6 +3527,17 @@ "@umijs/babel-plugin-lock-core-js-3" "3.4.2" "@umijs/babel-plugin-no-anonymous-default-export" "3.4.2" +"@umijs/babel-preset-umi@3.4.22": + version "3.4.22" + resolved "https://registry.nlark.com/@umijs/babel-preset-umi/download/@umijs/babel-preset-umi-3.4.22.tgz#e097171983512514fc7389a0f837ffa9805d24b2" + integrity sha1-4JcXGYNRJRT8c4mg+Df/qYBdJLI= + dependencies: + "@babel/runtime" "7.12.5" + "@umijs/babel-plugin-auto-css-modules" "3.4.22" + "@umijs/babel-plugin-import-to-await-require" "3.4.22" + "@umijs/babel-plugin-lock-core-js-3" "3.4.22" + "@umijs/babel-plugin-no-anonymous-default-export" "3.4.22" + "@umijs/bundler-utils@3.4.2": version "3.4.2" resolved "https://registry.npm.taobao.org/@umijs/bundler-utils/download/@umijs/bundler-utils-3.4.2.tgz?cache=0&sync_timestamp=1616036951565&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40umijs%2Fbundler-utils%2Fdownload%2F%40umijs%2Fbundler-utils-3.4.2.tgz#5b4b32211ab1c503497971631fe39ee66dc463d4" @@ -3561,6 +3614,17 @@ jest-worker "24.9.0" prettier "2.2.1" +"@umijs/deps@3.4.22": + version "3.4.22" + resolved "https://registry.nlark.com/@umijs/deps/download/@umijs/deps-3.4.22.tgz#087d7ef3a74fd3122decf6a5a20a70035434f08a" + integrity sha1-CH1+86dP0xIt7PalogpwA1Q08Io= + dependencies: + "@bloomberg/record-tuple-polyfill" "0.0.3" + chokidar "3.5.1" + clipboardy "2.3.0" + jest-worker "24.9.0" + prettier "2.2.1" + "@umijs/error-code-map@^1.0.1": version "1.0.1" resolved "https://registry.npm.taobao.org/@umijs/error-code-map/download/@umijs/error-code-map-1.0.1.tgz#2a2dd7b4bcd11869e968264ff3dabacfabe8b874" @@ -3757,20 +3821,20 @@ "@umijs/deps" "0.4.2" "@umijs/utils" "3.4.2" -"@umijs/test@^3.0.5": - version "3.3.11" - resolved "https://registry.npm.taobao.org/@umijs/test/download/@umijs/test-3.3.11.tgz?cache=0&sync_timestamp=1616036955080&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40umijs%2Ftest%2Fdownload%2F%40umijs%2Ftest-3.3.11.tgz#4046193db67848d67eabc9fa29d02bc105f24da7" - integrity sha1-QEYZPbZ4SNZ+q8n6KdArwQXyTac= +"@umijs/test@^3.4.22": + version "3.4.22" + resolved "https://registry.nlark.com/@umijs/test/download/@umijs/test-3.4.22.tgz?cache=0&sync_timestamp=1621565322241&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40umijs%2Ftest%2Fdownload%2F%40umijs%2Ftest-3.4.22.tgz#73eef818e69d04b51ecb4b312d9871710eb3b18c" + integrity sha1-c+74GOadBLUey0sxLZhxcQ6zsYw= dependencies: "@babel/core" "7.12.10" - "@umijs/babel-preset-umi" "3.3.11" - "@umijs/utils" "3.3.11" + "@umijs/babel-preset-umi" "3.4.22" + "@umijs/utils" "3.4.22" babel-core "7.0.0-bridge.0" - babel-jest "^25.4.0" + babel-jest "^26.6.3" core-js "3.8.2" identity-obj-proxy "3.0.0" - jest "^25.4.0" - jest-cli "^25.4.0" + jest "^26.6.3" + jest-cli "^26.6.3" jest-environment-jsdom-fourteen "1.0.1" regenerator-runtime "^0.13.7" whatwg-fetch "^3.5.0" @@ -3863,6 +3927,13 @@ dependencies: "@umijs/deps" "0.4.2" +"@umijs/utils@3.4.22": + version "3.4.22" + resolved "https://registry.nlark.com/@umijs/utils/download/@umijs/utils-3.4.22.tgz?cache=0&sync_timestamp=1621565338323&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40umijs%2Futils%2Fdownload%2F%40umijs%2Futils-3.4.22.tgz#9f7f0c7e5542a778375fc232ad10f1b62d2731e4" + integrity sha1-n38MflVCp3g3X8IyrRDxti0nMeQ= + dependencies: + "@umijs/deps" "3.4.22" + "@webassemblyjs/ast@1.11.0": version "1.11.0" resolved "https://registry.npm.taobao.org/@webassemblyjs/ast/download/@webassemblyjs/ast-1.11.0.tgz?cache=0&sync_timestamp=1610041484025&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40webassemblyjs%2Fast%2Fdownload%2F%40webassemblyjs%2Fast-1.11.0.tgz#a5aa679efdc9e51707a4207139da57920555961f" @@ -4011,7 +4082,7 @@ resolved "https://registry.nlark.com/@xtuc/long/download/@xtuc/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" integrity sha1-0pHGpOl5ibXGHZrPOWrk/hM6cY0= -abab@^2.0.0: +abab@^2.0.0, abab@^2.0.3, abab@^2.0.5: version "2.0.5" resolved "https://registry.npm.taobao.org/abab/download/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" integrity sha1-wLZ4+zLWD8EhnHhNaoJv44Wut5o= @@ -4044,7 +4115,7 @@ accord@^0.29.0: uglify-js "^2.8.22" when "^3.7.8" -acorn-globals@^4.3.0, acorn-globals@^4.3.2: +acorn-globals@^4.3.0: version "4.3.4" resolved "https://registry.npm.taobao.org/acorn-globals/download/acorn-globals-4.3.4.tgz#9fa1926addc11c97308c4e66d7add0d40c3272e7" integrity sha1-n6GSat3BHJcwjE5m163Q1Awycuc= @@ -4052,6 +4123,14 @@ acorn-globals@^4.3.0, acorn-globals@^4.3.2: acorn "^6.0.1" acorn-walk "^6.0.1" +acorn-globals@^6.0.0: + version "6.0.0" + resolved "https://registry.npm.taobao.org/acorn-globals/download/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" + integrity sha1-Rs3Tnw+P8IqHZhm1X1rIptx3C0U= + dependencies: + acorn "^7.1.1" + acorn-walk "^7.1.1" + acorn-jsx@^5.3.1: version "5.3.1" resolved "https://registry.npm.taobao.org/acorn-jsx/download/acorn-jsx-5.3.1.tgz?cache=0&sync_timestamp=1599499155970&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Facorn-jsx%2Fdownload%2Facorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" @@ -4062,17 +4141,22 @@ acorn-walk@^6.0.1: resolved "https://registry.npm.taobao.org/acorn-walk/download/acorn-walk-6.2.0.tgz?cache=0&sync_timestamp=1611560713023&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Facorn-walk%2Fdownload%2Facorn-walk-6.2.0.tgz#123cb8f3b84c2171f1f7fb252615b1c78a6b1a8c" integrity sha1-Ejy487hMIXHx9/slJhWxx4prGow= +acorn-walk@^7.1.1: + version "7.2.0" + resolved "https://registry.nlark.com/acorn-walk/download/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" + integrity sha1-DeiJpgEgOQmw++B7iTjcIdLpZ7w= + acorn@^6.0.1, acorn@^6.0.4: version "6.4.2" resolved "https://registry.npm.taobao.org/acorn/download/acorn-6.4.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Facorn%2Fdownload%2Facorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6" integrity sha1-NYZv1xBSjpLeEM8GAWSY5H454eY= -acorn@^7.1.0, acorn@^7.4.0: +acorn@^7.1.1, acorn@^7.4.0: version "7.4.1" resolved "https://registry.npm.taobao.org/acorn/download/acorn-7.4.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Facorn%2Fdownload%2Facorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha1-/q7SVZc9LndVW4PbwIhRpsY1IPo= -acorn@^8.2.1: +acorn@^8.1.0, acorn@^8.2.1: version "8.2.4" resolved "https://registry.nlark.com/acorn/download/acorn-8.2.4.tgz?cache=0&sync_timestamp=1620134123724&other_urls=https%3A%2F%2Fregistry.nlark.com%2Facorn%2Fdownload%2Facorn-8.2.4.tgz#caba24b08185c3b56e3168e97d15ed17f4d31fd0" integrity sha1-yroksIGFw7VuMWjpfRXtF/TTH9A= @@ -4640,17 +4724,17 @@ babel-core@7.0.0-bridge.0: resolved "https://registry.npm.taobao.org/babel-core/download/babel-core-7.0.0-bridge.0.tgz?cache=0&sync_timestamp=1599054224282&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbabel-core%2Fdownload%2Fbabel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece" integrity sha1-laSS3dkPm06aSh2hTrM1uHtjTs4= -babel-jest@^25.4.0, babel-jest@^25.5.1: - version "25.5.1" - resolved "https://registry.npm.taobao.org/babel-jest/download/babel-jest-25.5.1.tgz?cache=0&sync_timestamp=1615813501445&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbabel-jest%2Fdownload%2Fbabel-jest-25.5.1.tgz#bc2e6101f849d6f6aec09720ffc7bc5332e62853" - integrity sha1-vC5hAfhJ1vauwJcg/8e8UzLmKFM= +babel-jest@^26.6.3: + version "26.6.3" + resolved "https://registry.nlark.com/babel-jest/download/babel-jest-26.6.3.tgz?cache=0&sync_timestamp=1621549907892&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fbabel-jest%2Fdownload%2Fbabel-jest-26.6.3.tgz#d87d25cb0037577a0c89f82e5755c5d293c01056" + integrity sha1-2H0lywA3V3oMifguV1XF0pPAEFY= dependencies: - "@jest/transform" "^25.5.1" - "@jest/types" "^25.5.0" + "@jest/transform" "^26.6.2" + "@jest/types" "^26.6.2" "@types/babel__core" "^7.1.7" babel-plugin-istanbul "^6.0.0" - babel-preset-jest "^25.5.0" - chalk "^3.0.0" + babel-preset-jest "^26.6.2" + chalk "^4.0.0" graceful-fs "^4.2.4" slash "^3.0.0" @@ -4690,13 +4774,14 @@ babel-plugin-istanbul@^6.0.0: istanbul-lib-instrument "^4.0.0" test-exclude "^6.0.0" -babel-plugin-jest-hoist@^25.5.0: - version "25.5.0" - resolved "https://registry.npm.taobao.org/babel-plugin-jest-hoist/download/babel-plugin-jest-hoist-25.5.0.tgz#129c80ba5c7fc75baf3a45b93e2e372d57ca2677" - integrity sha1-EpyAulx/x1uvOkW5Pi43LVfKJnc= +babel-plugin-jest-hoist@^26.6.2: + version "26.6.2" + resolved "https://registry.nlark.com/babel-plugin-jest-hoist/download/babel-plugin-jest-hoist-26.6.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fbabel-plugin-jest-hoist%2Fdownload%2Fbabel-plugin-jest-hoist-26.6.2.tgz#8185bd030348d254c6d7dd974355e6a28b21e62d" + integrity sha1-gYW9AwNI0lTG192XQ1Xmoosh5i0= dependencies: "@babel/template" "^7.3.3" "@babel/types" "^7.3.3" + "@types/babel__core" "^7.0.0" "@types/babel__traverse" "^7.0.6" babel-plugin-module-resolver@^4.1.0: @@ -4785,10 +4870,10 @@ babel-plugin-transform-typescript-metadata@0.3.1: dependencies: "@babel/helper-plugin-utils" "^7.0.0" -babel-preset-current-node-syntax@^0.1.2: - version "0.1.4" - resolved "https://registry.npm.taobao.org/babel-preset-current-node-syntax/download/babel-preset-current-node-syntax-0.1.4.tgz?cache=0&sync_timestamp=1608036139015&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbabel-preset-current-node-syntax%2Fdownload%2Fbabel-preset-current-node-syntax-0.1.4.tgz#826f1f8e7245ad534714ba001f84f7e906c3b615" - integrity sha1-gm8fjnJFrVNHFLoAH4T36QbDthU= +babel-preset-current-node-syntax@^1.0.0: + version "1.0.1" + resolved "https://registry.npm.taobao.org/babel-preset-current-node-syntax/download/babel-preset-current-node-syntax-1.0.1.tgz?cache=0&sync_timestamp=1608036139015&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbabel-preset-current-node-syntax%2Fdownload%2Fbabel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" + integrity sha1-tDmSObibKgEfndvj5PQB/EDP9zs= dependencies: "@babel/plugin-syntax-async-generators" "^7.8.4" "@babel/plugin-syntax-bigint" "^7.8.3" @@ -4801,14 +4886,15 @@ babel-preset-current-node-syntax@^0.1.2: "@babel/plugin-syntax-object-rest-spread" "^7.8.3" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-top-level-await" "^7.8.3" -babel-preset-jest@^25.5.0: - version "25.5.0" - resolved "https://registry.npm.taobao.org/babel-preset-jest/download/babel-preset-jest-25.5.0.tgz#c1d7f191829487a907764c65307faa0e66590b49" - integrity sha1-wdfxkYKUh6kHdkxlMH+qDmZZC0k= +babel-preset-jest@^26.6.2: + version "26.6.2" + resolved "https://registry.nlark.com/babel-preset-jest/download/babel-preset-jest-26.6.2.tgz#747872b1171df032252426586881d62d31798fee" + integrity sha1-dHhysRcd8DIlJCZYaIHWLTF5j+4= dependencies: - babel-plugin-jest-hoist "^25.5.0" - babel-preset-current-node-syntax "^0.1.2" + babel-plugin-jest-hoist "^26.6.2" + babel-preset-current-node-syntax "^1.0.0" bail@^1.0.0: version "1.0.5" @@ -4958,13 +5044,6 @@ browser-process-hrtime@^1.0.0: resolved "https://registry.npm.taobao.org/browser-process-hrtime/download/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" integrity sha1-PJtLfXgsgSHlbxAQbYTA0P/JRiY= -browser-resolve@^1.11.3: - version "1.11.3" - resolved "https://registry.npm.taobao.org/browser-resolve/download/browser-resolve-1.11.3.tgz#9b7cbb3d0f510e4cb86bdbd796124d28b5890af6" - integrity sha1-m3y7PQ9RDky4a9vXlhJNKLWJCvY= - dependencies: - resolve "1.1.7" - browserify-aes@^1.0.0, browserify-aes@^1.0.4: version "1.2.0" resolved "https://registry.npm.taobao.org/browserify-aes/download/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" @@ -5207,7 +5286,7 @@ camelcase@^5.0.0, camelcase@^5.3.1: resolved "https://registry.npm.taobao.org/camelcase/download/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha1-48mzFWnhBoEd8kL3FXJaH0xJQyA= -camelcase@^6.2.0: +camelcase@^6.0.0, camelcase@^6.2.0: version "6.2.0" resolved "https://registry.npm.taobao.org/camelcase/download/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" integrity sha1-kkr4gcnVJaydh/QNlk5c6pgqGAk= @@ -5309,6 +5388,11 @@ chalk@^4.1.1: ansi-styles "^4.1.0" supports-color "^7.1.0" +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.npm.taobao.org/char-regex/download/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha1-10Q1giYhf5ge1Y9Hmx1rzClUXc8= + character-entities-html4@^1.0.0: version "1.1.4" resolved "https://registry.npm.taobao.org/character-entities-html4/download/character-entities-html4-1.1.4.tgz?cache=0&sync_timestamp=1615375704372&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcharacter-entities-html4%2Fdownload%2Fcharacter-entities-html4-1.1.4.tgz#0e64b0a3753ddbf1fdc044c5fd01d0199a02e125" @@ -5394,6 +5478,11 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: inherits "^2.0.1" safe-buffer "^5.0.1" +cjs-module-lexer@^0.6.0: + version "0.6.0" + resolved "https://registry.nlark.com/cjs-module-lexer/download/cjs-module-lexer-0.6.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fcjs-module-lexer%2Fdownload%2Fcjs-module-lexer-0.6.0.tgz#4186fcca0eae175970aee870b9fe2d6cf8d5655f" + integrity sha1-QYb8yg6uF1lwruhwuf4tbPjVZV8= + class-utils@^0.3.5: version "0.3.6" resolved "https://registry.npm.taobao.org/class-utils/download/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" @@ -6219,7 +6308,7 @@ cssom@0.3.x, cssom@^0.3.4, cssom@~0.3.6: resolved "https://registry.npm.taobao.org/cssom/download/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" integrity sha1-nxJ29bK0Y/IRTT8sdSUK+MGjb0o= -cssom@^0.4.1: +cssom@^0.4.4: version "0.4.4" resolved "https://registry.npm.taobao.org/cssom/download/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" integrity sha1-WmbPk9LQtmHYC/akT7ZfXC5OChA= @@ -6231,7 +6320,7 @@ cssstyle@^1.1.1: dependencies: cssom "0.3.x" -cssstyle@^2.0.0: +cssstyle@^2.3.0: version "2.3.0" resolved "https://registry.npm.taobao.org/cssstyle/download/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" integrity sha1-/2ZaDdvcMYZLCWR/NBY0Q9kLCFI= @@ -6271,6 +6360,15 @@ data-urls@^1.1.0: whatwg-mimetype "^2.2.0" whatwg-url "^7.0.0" +data-urls@^2.0.0: + version "2.0.0" + resolved "https://registry.npm.taobao.org/data-urls/download/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" + integrity sha1-FWSFpyljqXD11YIar2Qr7yvy25s= + dependencies: + abab "^2.0.3" + whatwg-mimetype "^2.3.0" + whatwg-url "^8.0.0" + date-fns@^2.15.0: version "2.19.0" resolved "https://registry.npm.taobao.org/date-fns/download/date-fns-2.19.0.tgz?cache=0&sync_timestamp=1614916549936&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdate-fns%2Fdownload%2Fdate-fns-2.19.0.tgz#65193348635a28d5d916c43ec7ce6fbd145059e1" @@ -6315,6 +6413,11 @@ decamelize@^1.0.0, decamelize@^1.1.0, decamelize@^1.2.0: resolved "https://registry.npm.taobao.org/decamelize/download/decamelize-1.2.0.tgz?cache=0&sync_timestamp=1610348634503&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdecamelize%2Fdownload%2Fdecamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= +decimal.js@^10.2.1: + version "10.2.1" + resolved "https://registry.npm.taobao.org/decimal.js/download/decimal.js-10.2.1.tgz#238ae7b0f0c793d3e3cea410108b35a2c01426a3" + integrity sha1-I4rnsPDHk9PjzqQQEIs1osAUJqM= + decode-uri-component@^0.2.0: version "0.2.0" resolved "https://registry.npm.taobao.org/decode-uri-component/download/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" @@ -6444,11 +6547,6 @@ detect-node@^2.0.4: resolved "https://registry.npm.taobao.org/detect-node/download/detect-node-2.0.5.tgz?cache=0&sync_timestamp=1615921059414&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdetect-node%2Fdownload%2Fdetect-node-2.0.5.tgz#9d270aa7eaa5af0b72c4c9d9b814e7f4ce738b79" integrity sha1-nScKp+qlrwtyxMnZuBTn9M5zi3k= -diff-sequences@^25.2.6: - version "25.2.6" - resolved "https://registry.npm.taobao.org/diff-sequences/download/diff-sequences-25.2.6.tgz?cache=0&sync_timestamp=1607352548704&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdiff-sequences%2Fdownload%2Fdiff-sequences-25.2.6.tgz#5f467c00edd35352b7bca46d7927d60e687a76dd" - integrity sha1-X0Z8AO3TU1K3vKRteSfWDmh6dt0= - diff-sequences@^26.6.2: version "26.6.2" resolved "https://registry.npm.taobao.org/diff-sequences/download/diff-sequences-26.6.2.tgz?cache=0&sync_timestamp=1607352548704&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdiff-sequences%2Fdownload%2Fdiff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1" @@ -6563,6 +6661,13 @@ domexception@^1.0.1: dependencies: webidl-conversions "^4.0.2" +domexception@^2.0.1: + version "2.0.1" + resolved "https://registry.npm.taobao.org/domexception/download/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304" + integrity sha1-+0Su+6eT4VdLCvau0oAdBXUp8wQ= + dependencies: + webidl-conversions "^5.0.0" + domhandler@^2.3.0: version "2.4.2" resolved "https://registry.npm.taobao.org/domhandler/download/domhandler-2.4.2.tgz?cache=0&sync_timestamp=1606872211430&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdomhandler%2Fdownload%2Fdomhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" @@ -6692,6 +6797,11 @@ elliptic@^6.5.3: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" +emittery@^0.7.1: + version "0.7.2" + resolved "https://registry.npm.taobao.org/emittery/download/emittery-0.7.2.tgz#25595908e13af0f5674ab419396e2fb394cdfa82" + integrity sha1-JVlZCOE68PVnSrQZOW4vs5TN+oI= + "emoji-regex@>=6.0.0 <=6.1.1": version "6.1.1" resolved "https://registry.npm.taobao.org/emoji-regex/download/emoji-regex-6.1.1.tgz?cache=0&sync_timestamp=1614682725186&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Femoji-regex%2Fdownload%2Femoji-regex-6.1.1.tgz#c6cd0ec1b0642e2a3c67a1137efc5e796da4f88e" @@ -6848,7 +6958,7 @@ escape-string-regexp@^4.0.0: resolved "https://registry.npm.taobao.org/escape-string-regexp/download/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha1-FLqDpdNz49MR5a/KKc9b+tllvzQ= -escodegen@^1.11.0, escodegen@^1.11.1: +escodegen@^1.11.0: version "1.14.3" resolved "https://registry.npm.taobao.org/escodegen/download/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" integrity sha1-TnuB+6YVgdyXWC7XjKt/Do1j9QM= @@ -6860,6 +6970,18 @@ escodegen@^1.11.0, escodegen@^1.11.1: optionalDependencies: source-map "~0.6.1" +escodegen@^2.0.0: + version "2.0.0" + resolved "https://registry.npm.taobao.org/escodegen/download/escodegen-2.0.0.tgz?cache=0&sync_timestamp=1596669832613&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fescodegen%2Fdownload%2Fescodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd" + integrity sha1-XjKxKDPoqo+jXhvwvvqJOASEx90= + dependencies: + esprima "^4.0.1" + estraverse "^5.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + eslint-ast-utils@^1.1.0: version "1.1.0" resolved "https://registry.npm.taobao.org/eslint-ast-utils/download/eslint-ast-utils-1.1.0.tgz#3d58ba557801cfb1c941d68131ee9f8c34bd1586" @@ -7270,22 +7392,6 @@ execa@^1.0.0: signal-exit "^3.0.0" strip-eof "^1.0.0" -execa@^3.2.0: - version "3.4.0" - resolved "https://registry.npm.taobao.org/execa/download/execa-3.4.0.tgz?cache=0&sync_timestamp=1606972869049&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fexeca%2Fdownload%2Fexeca-3.4.0.tgz#c08ed4550ef65d858fac269ffc8572446f37eb89" - integrity sha1-wI7UVQ72XYWPrCaf/IVyRG8364k= - dependencies: - cross-spawn "^7.0.0" - get-stream "^5.0.0" - human-signals "^1.1.1" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.0" - onetime "^5.1.0" - p-finally "^2.0.0" - signal-exit "^3.0.2" - strip-final-newline "^2.0.0" - execa@^4.0.0, execa@^4.1.0: version "4.1.0" resolved "https://registry.npm.taobao.org/execa/download/execa-4.1.0.tgz?cache=0&sync_timestamp=1606972869049&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fexeca%2Fdownload%2Fexeca-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" @@ -7348,17 +7454,17 @@ expand-brackets@^2.1.4: snapdragon "^0.8.1" to-regex "^3.0.1" -expect@^25.5.0: - version "25.5.0" - resolved "https://registry.npm.taobao.org/expect/download/expect-25.5.0.tgz#f07f848712a2813bb59167da3fb828ca21f58bba" - integrity sha1-8H+EhxKigTu1kWfaP7goyiH1i7o= +expect@^26.6.2: + version "26.6.2" + resolved "https://registry.nlark.com/expect/download/expect-26.6.2.tgz#c6b996bf26bf3fe18b67b2d0f51fc981ba934417" + integrity sha1-xrmWvya/P+GLZ7LQ9R/JgbqTRBc= dependencies: - "@jest/types" "^25.5.0" + "@jest/types" "^26.6.2" ansi-styles "^4.0.0" - jest-get-type "^25.2.6" - jest-matcher-utils "^25.5.0" - jest-message-util "^25.5.0" - jest-regex-util "^25.2.6" + jest-get-type "^26.3.0" + jest-matcher-utils "^26.6.2" + jest-message-util "^26.6.2" + jest-regex-util "^26.0.0" express@4.17.1: version "4.17.1" @@ -8566,6 +8672,13 @@ html-encoding-sniffer@^1.0.2: dependencies: whatwg-encoding "^1.0.1" +html-encoding-sniffer@^2.0.1: + version "2.0.1" + resolved "https://registry.npm.taobao.org/html-encoding-sniffer/download/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3" + integrity sha1-QqbcT9M/ACgRduiyN1nKTk+hhfM= + dependencies: + whatwg-encoding "^1.0.5" + html-escaper@^2.0.0: version "2.0.2" resolved "https://registry.npm.taobao.org/html-escaper/download/html-escaper-2.0.2.tgz?cache=0&sync_timestamp=1613643546928&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fhtml-escaper%2Fdownload%2Fhtml-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" @@ -8937,11 +9050,6 @@ invert-kv@^3.0.0: resolved "https://registry.npm.taobao.org/invert-kv/download/invert-kv-3.0.1.tgz#a93c7a3d4386a1dc8325b97da9bb1620c0282523" integrity sha1-qTx6PUOGodyDJbl9qbsWIMAoJSM= -ip-regex@^2.1.0: - version "2.1.0" - resolved "https://registry.npm.taobao.org/ip-regex/download/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" - integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= - ipaddr.js@1.9.1: version "1.9.1" resolved "https://registry.npm.taobao.org/ipaddr.js/download/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" @@ -9270,6 +9378,11 @@ is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" +is-potential-custom-element-name@^1.0.0: + version "1.0.1" + resolved "https://registry.npm.taobao.org/is-potential-custom-element-name/download/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" + integrity sha1-Fx7W8Z46xVQ5Tt94yqBXhKRb67U= + is-reference@^1.2.1: version "1.2.1" resolved "https://registry.npm.taobao.org/is-reference/download/is-reference-1.2.1.tgz#8b2dac0b371f4bc994fdeaba9eb542d03002d0b7" @@ -9388,7 +9501,7 @@ is-word-character@^1.0.0: resolved "https://registry.npm.taobao.org/is-word-character/download/is-word-character-1.0.4.tgz#ce0e73216f98599060592f62ff31354ddbeb0230" integrity sha1-zg5zIW+YWZBgWS9i/zE1TdvrAjA= -is-wsl@^2.1.1: +is-wsl@^2.1.1, is-wsl@^2.2.0: version "2.2.0" resolved "https://registry.npm.taobao.org/is-wsl/download/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" integrity sha1-dKTHbnfKn9P5MvKQwX6jJs0VcnE= @@ -9455,7 +9568,7 @@ istanbul-lib-instrument@^3.3.0: istanbul-lib-coverage "^2.0.5" semver "^6.0.0" -istanbul-lib-instrument@^4.0.0: +istanbul-lib-instrument@^4.0.0, istanbul-lib-instrument@^4.0.3: version "4.0.3" resolved "https://registry.npm.taobao.org/istanbul-lib-instrument/download/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d" integrity sha1-hzxv/4l0UBGCIndGlqPyiQLXfB0= @@ -9506,71 +9619,59 @@ javascript-stringify@^2.0.1: resolved "https://registry.npm.taobao.org/javascript-stringify/download/javascript-stringify-2.0.1.tgz#6ef358035310e35d667c675ed63d3eb7c1aa19e5" integrity sha1-bvNYA1MQ411mfGde1j0+t8GqGeU= -jest-changed-files@^25.5.0: - version "25.5.0" - resolved "https://registry.npm.taobao.org/jest-changed-files/download/jest-changed-files-25.5.0.tgz?cache=0&sync_timestamp=1615211149685&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjest-changed-files%2Fdownload%2Fjest-changed-files-25.5.0.tgz#141cc23567ceb3f534526f8614ba39421383634c" - integrity sha1-FBzCNWfOs/U0Um+GFLo5QhODY0w= +jest-changed-files@^26.6.2: + version "26.6.2" + resolved "https://registry.nlark.com/jest-changed-files/download/jest-changed-files-26.6.2.tgz?cache=0&sync_timestamp=1621520009043&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fjest-changed-files%2Fdownload%2Fjest-changed-files-26.6.2.tgz#f6198479e1cc66f22f9ae1e22acaa0b429c042d0" + integrity sha1-9hmEeeHMZvIvmuHiKsqgtCnAQtA= dependencies: - "@jest/types" "^25.5.0" - execa "^3.2.0" + "@jest/types" "^26.6.2" + execa "^4.0.0" throat "^5.0.0" -jest-cli@^25.4.0, jest-cli@^25.5.4: - version "25.5.4" - resolved "https://registry.npm.taobao.org/jest-cli/download/jest-cli-25.5.4.tgz?cache=0&sync_timestamp=1615814188144&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjest-cli%2Fdownload%2Fjest-cli-25.5.4.tgz#b9f1a84d1301a92c5c217684cb79840831db9f0d" - integrity sha1-ufGoTRMBqSxcIXaEy3mECDHbnw0= +jest-cli@^26.6.3: + version "26.6.3" + resolved "https://registry.nlark.com/jest-cli/download/jest-cli-26.6.3.tgz?cache=0&sync_timestamp=1621550288656&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fjest-cli%2Fdownload%2Fjest-cli-26.6.3.tgz#43117cfef24bc4cd691a174a8796a532e135e92a" + integrity sha1-QxF8/vJLxM1pGhdKh5alMuE16So= dependencies: - "@jest/core" "^25.5.4" - "@jest/test-result" "^25.5.0" - "@jest/types" "^25.5.0" - chalk "^3.0.0" + "@jest/core" "^26.6.3" + "@jest/test-result" "^26.6.2" + "@jest/types" "^26.6.2" + chalk "^4.0.0" exit "^0.1.2" graceful-fs "^4.2.4" import-local "^3.0.2" is-ci "^2.0.0" - jest-config "^25.5.4" - jest-util "^25.5.0" - jest-validate "^25.5.0" + jest-config "^26.6.3" + jest-util "^26.6.2" + jest-validate "^26.6.2" prompts "^2.0.1" - realpath-native "^2.0.0" - yargs "^15.3.1" + yargs "^15.4.1" -jest-config@^25.5.4: - version "25.5.4" - resolved "https://registry.npm.taobao.org/jest-config/download/jest-config-25.5.4.tgz?cache=0&sync_timestamp=1615813579935&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjest-config%2Fdownload%2Fjest-config-25.5.4.tgz#38e2057b3f976ef7309b2b2c8dcd2a708a67f02c" - integrity sha1-OOIFez+Xbvcwmyssjc0qcIpn8Cw= +jest-config@^26.6.3: + version "26.6.3" + resolved "https://registry.nlark.com/jest-config/download/jest-config-26.6.3.tgz#64f41444eef9eb03dc51d5c53b75c8c71f645349" + integrity sha1-ZPQURO756wPcUdXFO3XIxx9kU0k= dependencies: "@babel/core" "^7.1.0" - "@jest/test-sequencer" "^25.5.4" - "@jest/types" "^25.5.0" - babel-jest "^25.5.1" - chalk "^3.0.0" + "@jest/test-sequencer" "^26.6.3" + "@jest/types" "^26.6.2" + babel-jest "^26.6.3" + chalk "^4.0.0" deepmerge "^4.2.2" glob "^7.1.1" graceful-fs "^4.2.4" - jest-environment-jsdom "^25.5.0" - jest-environment-node "^25.5.0" - jest-get-type "^25.2.6" - jest-jasmine2 "^25.5.4" - jest-regex-util "^25.2.6" - jest-resolve "^25.5.1" - jest-util "^25.5.0" - jest-validate "^25.5.0" + jest-environment-jsdom "^26.6.2" + jest-environment-node "^26.6.2" + jest-get-type "^26.3.0" + jest-jasmine2 "^26.6.3" + jest-regex-util "^26.0.0" + jest-resolve "^26.6.2" + jest-util "^26.6.2" + jest-validate "^26.6.2" micromatch "^4.0.2" - pretty-format "^25.5.0" - realpath-native "^2.0.0" - -jest-diff@^25.5.0: - version "25.5.0" - resolved "https://registry.npm.taobao.org/jest-diff/download/jest-diff-25.5.0.tgz#1dd26ed64f96667c068cef026b677dfa01afcfa9" - integrity sha1-HdJu1k+WZnwGjO8Ca2d9+gGvz6k= - dependencies: - chalk "^3.0.0" - diff-sequences "^25.2.6" - jest-get-type "^25.2.6" - pretty-format "^25.5.0" + pretty-format "^26.6.2" -jest-diff@^26.0.0: +jest-diff@^26.0.0, jest-diff@^26.6.2: version "26.6.2" resolved "https://registry.npm.taobao.org/jest-diff/download/jest-diff-26.6.2.tgz#1aa7468b52c3a68d7d5c5fdcdfcd5e49bd164394" integrity sha1-GqdGi1LDpo19XF/c381eSb0WQ5Q= @@ -9580,23 +9681,23 @@ jest-diff@^26.0.0: jest-get-type "^26.3.0" pretty-format "^26.6.2" -jest-docblock@^25.3.0: - version "25.3.0" - resolved "https://registry.npm.taobao.org/jest-docblock/download/jest-docblock-25.3.0.tgz?cache=0&sync_timestamp=1607352761462&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjest-docblock%2Fdownload%2Fjest-docblock-25.3.0.tgz#8b777a27e3477cd77a168c05290c471a575623ef" - integrity sha1-i3d6J+NHfNd6FowFKQxHGldWI+8= +jest-docblock@^26.0.0: + version "26.0.0" + resolved "https://registry.nlark.com/jest-docblock/download/jest-docblock-26.0.0.tgz?cache=0&sync_timestamp=1621520301956&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fjest-docblock%2Fdownload%2Fjest-docblock-26.0.0.tgz#3e2fa20899fc928cb13bd0ff68bd3711a36889b5" + integrity sha1-Pi+iCJn8koyxO9D/aL03EaNoibU= dependencies: detect-newline "^3.0.0" -jest-each@^25.5.0: - version "25.5.0" - resolved "https://registry.npm.taobao.org/jest-each/download/jest-each-25.5.0.tgz#0c3c2797e8225cb7bec7e4d249dcd96b934be516" - integrity sha1-DDwnl+giXLe+x+TSSdzZa5NL5RY= +jest-each@^26.6.2: + version "26.6.2" + resolved "https://registry.nlark.com/jest-each/download/jest-each-26.6.2.tgz#02526438a77a67401c8a6382dfe5999952c167cb" + integrity sha1-AlJkOKd6Z0AcimOC3+WZmVLBZ8s= dependencies: - "@jest/types" "^25.5.0" - chalk "^3.0.0" - jest-get-type "^25.2.6" - jest-util "^25.5.0" - pretty-format "^25.5.0" + "@jest/types" "^26.6.2" + chalk "^4.0.0" + jest-get-type "^26.3.0" + jest-util "^26.6.2" + pretty-format "^26.6.2" jest-environment-jsdom-fourteen@1.0.1: version "1.0.1" @@ -9610,34 +9711,30 @@ jest-environment-jsdom-fourteen@1.0.1: jest-util "^24.0.0" jsdom "^14.1.0" -jest-environment-jsdom@^25.5.0: - version "25.5.0" - resolved "https://registry.npm.taobao.org/jest-environment-jsdom/download/jest-environment-jsdom-25.5.0.tgz#dcbe4da2ea997707997040ecf6e2560aec4e9834" - integrity sha1-3L5NouqZdweZcEDs9uJWCuxOmDQ= - dependencies: - "@jest/environment" "^25.5.0" - "@jest/fake-timers" "^25.5.0" - "@jest/types" "^25.5.0" - jest-mock "^25.5.0" - jest-util "^25.5.0" - jsdom "^15.2.1" - -jest-environment-node@^25.5.0: - version "25.5.0" - resolved "https://registry.npm.taobao.org/jest-environment-node/download/jest-environment-node-25.5.0.tgz?cache=0&sync_timestamp=1615814258211&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjest-environment-node%2Fdownload%2Fjest-environment-node-25.5.0.tgz#0f55270d94804902988e64adca37c6ce0f7d07a1" - integrity sha1-D1UnDZSASQKYjmStyjfGzg99B6E= - dependencies: - "@jest/environment" "^25.5.0" - "@jest/fake-timers" "^25.5.0" - "@jest/types" "^25.5.0" - jest-mock "^25.5.0" - jest-util "^25.5.0" - semver "^6.3.0" +jest-environment-jsdom@^26.6.2: + version "26.6.2" + resolved "https://registry.nlark.com/jest-environment-jsdom/download/jest-environment-jsdom-26.6.2.tgz?cache=0&sync_timestamp=1621549984972&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fjest-environment-jsdom%2Fdownload%2Fjest-environment-jsdom-26.6.2.tgz#78d09fe9cf019a357009b9b7e1f101d23bd1da3e" + integrity sha1-eNCf6c8BmjVwCbm34fEB0jvR2j4= + dependencies: + "@jest/environment" "^26.6.2" + "@jest/fake-timers" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + jest-mock "^26.6.2" + jest-util "^26.6.2" + jsdom "^16.4.0" -jest-get-type@^25.2.6: - version "25.2.6" - resolved "https://registry.npm.taobao.org/jest-get-type/download/jest-get-type-25.2.6.tgz?cache=0&sync_timestamp=1607352755729&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjest-get-type%2Fdownload%2Fjest-get-type-25.2.6.tgz#0b0a32fab8908b44d508be81681487dbabb8d877" - integrity sha1-Cwoy+riQi0TVCL6BaBSH26u42Hc= +jest-environment-node@^26.6.2: + version "26.6.2" + resolved "https://registry.nlark.com/jest-environment-node/download/jest-environment-node-26.6.2.tgz?cache=0&sync_timestamp=1621550023896&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fjest-environment-node%2Fdownload%2Fjest-environment-node-26.6.2.tgz#824e4c7fb4944646356f11ac75b229b0035f2b0c" + integrity sha1-gk5Mf7SURkY1bxGsdbIpsANfKww= + dependencies: + "@jest/environment" "^26.6.2" + "@jest/fake-timers" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + jest-mock "^26.6.2" + jest-util "^26.6.2" jest-get-type@^26.3.0: version "26.3.0" @@ -9663,66 +9760,68 @@ jest-haste-map@^24.9.0: optionalDependencies: fsevents "^1.2.7" -jest-haste-map@^25.5.1: - version "25.5.1" - resolved "https://registry.npm.taobao.org/jest-haste-map/download/jest-haste-map-25.5.1.tgz#1df10f716c1d94e60a1ebf7798c9fb3da2620943" - integrity sha1-HfEPcWwdlOYKHr93mMn7PaJiCUM= +jest-haste-map@^26.6.2: + version "26.6.2" + resolved "https://registry.nlark.com/jest-haste-map/download/jest-haste-map-26.6.2.tgz#dd7e60fe7dc0e9f911a23d79c5ff7fb5c2cafeaa" + integrity sha1-3X5g/n3A6fkRoj15xf9/tcLK/qo= dependencies: - "@jest/types" "^25.5.0" + "@jest/types" "^26.6.2" "@types/graceful-fs" "^4.1.2" + "@types/node" "*" anymatch "^3.0.3" fb-watchman "^2.0.0" graceful-fs "^4.2.4" - jest-serializer "^25.5.0" - jest-util "^25.5.0" - jest-worker "^25.5.0" + jest-regex-util "^26.0.0" + jest-serializer "^26.6.2" + jest-util "^26.6.2" + jest-worker "^26.6.2" micromatch "^4.0.2" sane "^4.0.3" walker "^1.0.7" - which "^2.0.2" optionalDependencies: fsevents "^2.1.2" -jest-jasmine2@^25.5.4: - version "25.5.4" - resolved "https://registry.npm.taobao.org/jest-jasmine2/download/jest-jasmine2-25.5.4.tgz#66ca8b328fb1a3c5364816f8958f6970a8526968" - integrity sha1-ZsqLMo+xo8U2SBb4lY9pcKhSaWg= +jest-jasmine2@^26.6.3: + version "26.6.3" + resolved "https://registry.nlark.com/jest-jasmine2/download/jest-jasmine2-26.6.3.tgz?cache=0&sync_timestamp=1621550029563&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fjest-jasmine2%2Fdownload%2Fjest-jasmine2-26.6.3.tgz#adc3cf915deacb5212c93b9f3547cd12958f2edd" + integrity sha1-rcPPkV3qy1ISyTufNUfNEpWPLt0= dependencies: "@babel/traverse" "^7.1.0" - "@jest/environment" "^25.5.0" - "@jest/source-map" "^25.5.0" - "@jest/test-result" "^25.5.0" - "@jest/types" "^25.5.0" - chalk "^3.0.0" + "@jest/environment" "^26.6.2" + "@jest/source-map" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + chalk "^4.0.0" co "^4.6.0" - expect "^25.5.0" + expect "^26.6.2" is-generator-fn "^2.0.0" - jest-each "^25.5.0" - jest-matcher-utils "^25.5.0" - jest-message-util "^25.5.0" - jest-runtime "^25.5.4" - jest-snapshot "^25.5.1" - jest-util "^25.5.0" - pretty-format "^25.5.0" + jest-each "^26.6.2" + jest-matcher-utils "^26.6.2" + jest-message-util "^26.6.2" + jest-runtime "^26.6.3" + jest-snapshot "^26.6.2" + jest-util "^26.6.2" + pretty-format "^26.6.2" throat "^5.0.0" -jest-leak-detector@^25.5.0: - version "25.5.0" - resolved "https://registry.npm.taobao.org/jest-leak-detector/download/jest-leak-detector-25.5.0.tgz?cache=0&sync_timestamp=1615813552877&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjest-leak-detector%2Fdownload%2Fjest-leak-detector-25.5.0.tgz#2291c6294b0ce404241bb56fe60e2d0c3e34f0bb" - integrity sha1-IpHGKUsM5AQkG7Vv5g4tDD408Ls= +jest-leak-detector@^26.6.2: + version "26.6.2" + resolved "https://registry.nlark.com/jest-leak-detector/download/jest-leak-detector-26.6.2.tgz#7717cf118b92238f2eba65054c8a0c9c653a91af" + integrity sha1-dxfPEYuSI48uumUFTIoMnGU6ka8= dependencies: - jest-get-type "^25.2.6" - pretty-format "^25.5.0" + jest-get-type "^26.3.0" + pretty-format "^26.6.2" -jest-matcher-utils@^25.5.0: - version "25.5.0" - resolved "https://registry.npm.taobao.org/jest-matcher-utils/download/jest-matcher-utils-25.5.0.tgz#fbc98a12d730e5d2453d7f1ed4a4d948e34b7867" - integrity sha1-+8mKEtcw5dJFPX8e1KTZSONLeGc= +jest-matcher-utils@^26.6.2: + version "26.6.2" + resolved "https://registry.nlark.com/jest-matcher-utils/download/jest-matcher-utils-26.6.2.tgz#8e6fd6e863c8b2d31ac6472eeb237bc595e53e7a" + integrity sha1-jm/W6GPIstMaxkcu6yN7xZXlPno= dependencies: - chalk "^3.0.0" - jest-diff "^25.5.0" - jest-get-type "^25.2.6" - pretty-format "^25.5.0" + chalk "^4.0.0" + jest-diff "^26.6.2" + jest-get-type "^26.3.0" + pretty-format "^26.6.2" jest-message-util@^24.9.0: version "24.9.0" @@ -9738,19 +9837,20 @@ jest-message-util@^24.9.0: slash "^2.0.0" stack-utils "^1.0.1" -jest-message-util@^25.5.0: - version "25.5.0" - resolved "https://registry.npm.taobao.org/jest-message-util/download/jest-message-util-25.5.0.tgz#ea11d93204cc7ae97456e1d8716251185b8880ea" - integrity sha1-6hHZMgTMeul0VuHYcWJRGFuIgOo= +jest-message-util@^26.6.2: + version "26.6.2" + resolved "https://registry.nlark.com/jest-message-util/download/jest-message-util-26.6.2.tgz?cache=0&sync_timestamp=1621549984414&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fjest-message-util%2Fdownload%2Fjest-message-util-26.6.2.tgz#58173744ad6fc0506b5d21150b9be56ef001ca07" + integrity sha1-WBc3RK1vwFBrXSEVC5vlbvABygc= dependencies: "@babel/code-frame" "^7.0.0" - "@jest/types" "^25.5.0" - "@types/stack-utils" "^1.0.1" - chalk "^3.0.0" + "@jest/types" "^26.6.2" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" graceful-fs "^4.2.4" micromatch "^4.0.2" + pretty-format "^26.6.2" slash "^3.0.0" - stack-utils "^1.0.1" + stack-utils "^2.0.2" jest-mock@^24.0.0, jest-mock@^24.9.0: version "24.9.0" @@ -9759,16 +9859,17 @@ jest-mock@^24.0.0, jest-mock@^24.9.0: dependencies: "@jest/types" "^24.9.0" -jest-mock@^25.5.0: - version "25.5.0" - resolved "https://registry.npm.taobao.org/jest-mock/download/jest-mock-25.5.0.tgz#a91a54dabd14e37ecd61665d6b6e06360a55387a" - integrity sha1-qRpU2r0U437NYWZda24GNgpVOHo= +jest-mock@^26.6.2: + version "26.6.2" + resolved "https://registry.nlark.com/jest-mock/download/jest-mock-26.6.2.tgz?cache=0&sync_timestamp=1621520317009&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fjest-mock%2Fdownload%2Fjest-mock-26.6.2.tgz#d6cb712b041ed47fe0d9b6fc3474bc6543feb302" + integrity sha1-1stxKwQe1H/g2bb8NHS8ZUP+swI= dependencies: - "@jest/types" "^25.5.0" + "@jest/types" "^26.6.2" + "@types/node" "*" -jest-pnp-resolver@^1.2.1: +jest-pnp-resolver@^1.2.2: version "1.2.2" - resolved "https://registry.npm.taobao.org/jest-pnp-resolver/download/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" + resolved "https://registry.npm.taobao.org/jest-pnp-resolver/download/jest-pnp-resolver-1.2.2.tgz?cache=0&sync_timestamp=1592991731398&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjest-pnp-resolver%2Fdownload%2Fjest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" integrity sha1-twSsCuAoqJEIpNBAs/kZ393I4zw= jest-regex-util@^24.9.0: @@ -9776,124 +9877,127 @@ jest-regex-util@^24.9.0: resolved "https://registry.npm.taobao.org/jest-regex-util/download/jest-regex-util-24.9.0.tgz?cache=0&sync_timestamp=1607352728942&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjest-regex-util%2Fdownload%2Fjest-regex-util-24.9.0.tgz#c13fb3380bde22bf6575432c493ea8fe37965636" integrity sha1-wT+zOAveIr9ldUMsST6o/jeWVjY= -jest-regex-util@^25.2.6: - version "25.2.6" - resolved "https://registry.npm.taobao.org/jest-regex-util/download/jest-regex-util-25.2.6.tgz?cache=0&sync_timestamp=1607352728942&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjest-regex-util%2Fdownload%2Fjest-regex-util-25.2.6.tgz#d847d38ba15d2118d3b06390056028d0f2fd3964" - integrity sha1-2EfTi6FdIRjTsGOQBWAo0PL9OWQ= +jest-regex-util@^26.0.0: + version "26.0.0" + resolved "https://registry.npm.taobao.org/jest-regex-util/download/jest-regex-util-26.0.0.tgz?cache=0&sync_timestamp=1607352728942&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjest-regex-util%2Fdownload%2Fjest-regex-util-26.0.0.tgz#d25e7184b36e39fd466c3bc41be0971e821fee28" + integrity sha1-0l5xhLNuOf1GbDvEG+CXHoIf7ig= -jest-resolve-dependencies@^25.5.4: - version "25.5.4" - resolved "https://registry.npm.taobao.org/jest-resolve-dependencies/download/jest-resolve-dependencies-25.5.4.tgz#85501f53957c8e3be446e863a74777b5a17397a7" - integrity sha1-hVAfU5V8jjvkRuhjp0d3taFzl6c= +jest-resolve-dependencies@^26.6.3: + version "26.6.3" + resolved "https://registry.nlark.com/jest-resolve-dependencies/download/jest-resolve-dependencies-26.6.3.tgz#6680859ee5d22ee5dcd961fe4871f59f4c784fb6" + integrity sha1-ZoCFnuXSLuXc2WH+SHH1n0x4T7Y= dependencies: - "@jest/types" "^25.5.0" - jest-regex-util "^25.2.6" - jest-snapshot "^25.5.1" + "@jest/types" "^26.6.2" + jest-regex-util "^26.0.0" + jest-snapshot "^26.6.2" -jest-resolve@^25.5.1: - version "25.5.1" - resolved "https://registry.npm.taobao.org/jest-resolve/download/jest-resolve-25.5.1.tgz?cache=0&sync_timestamp=1615814186817&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjest-resolve%2Fdownload%2Fjest-resolve-25.5.1.tgz#0e6fbcfa7c26d2a5fe8f456088dc332a79266829" - integrity sha1-Dm+8+nwm0qX+j0VgiNwzKnkmaCk= +jest-resolve@^26.6.2: + version "26.6.2" + resolved "https://registry.nlark.com/jest-resolve/download/jest-resolve-26.6.2.tgz#a3ab1517217f469b504f1b56603c5bb541fbb507" + integrity sha1-o6sVFyF/RptQTxtWYDxbtUH7tQc= dependencies: - "@jest/types" "^25.5.0" - browser-resolve "^1.11.3" - chalk "^3.0.0" + "@jest/types" "^26.6.2" + chalk "^4.0.0" graceful-fs "^4.2.4" - jest-pnp-resolver "^1.2.1" + jest-pnp-resolver "^1.2.2" + jest-util "^26.6.2" read-pkg-up "^7.0.1" - realpath-native "^2.0.0" - resolve "^1.17.0" + resolve "^1.18.1" slash "^3.0.0" -jest-runner@^25.5.4: - version "25.5.4" - resolved "https://registry.npm.taobao.org/jest-runner/download/jest-runner-25.5.4.tgz#ffec5df3875da5f5c878ae6d0a17b8e4ecd7c71d" - integrity sha1-/+xd84ddpfXIeK5tChe45OzXxx0= +jest-runner@^26.6.3: + version "26.6.3" + resolved "https://registry.nlark.com/jest-runner/download/jest-runner-26.6.3.tgz?cache=0&sync_timestamp=1621549985406&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fjest-runner%2Fdownload%2Fjest-runner-26.6.3.tgz#2d1fed3d46e10f233fd1dbd3bfaa3fe8924be159" + integrity sha1-LR/tPUbhDyM/0dvTv6o/6JJL4Vk= dependencies: - "@jest/console" "^25.5.0" - "@jest/environment" "^25.5.0" - "@jest/test-result" "^25.5.0" - "@jest/types" "^25.5.0" - chalk "^3.0.0" + "@jest/console" "^26.6.2" + "@jest/environment" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.7.1" exit "^0.1.2" graceful-fs "^4.2.4" - jest-config "^25.5.4" - jest-docblock "^25.3.0" - jest-haste-map "^25.5.1" - jest-jasmine2 "^25.5.4" - jest-leak-detector "^25.5.0" - jest-message-util "^25.5.0" - jest-resolve "^25.5.1" - jest-runtime "^25.5.4" - jest-util "^25.5.0" - jest-worker "^25.5.0" + jest-config "^26.6.3" + jest-docblock "^26.0.0" + jest-haste-map "^26.6.2" + jest-leak-detector "^26.6.2" + jest-message-util "^26.6.2" + jest-resolve "^26.6.2" + jest-runtime "^26.6.3" + jest-util "^26.6.2" + jest-worker "^26.6.2" source-map-support "^0.5.6" throat "^5.0.0" -jest-runtime@^25.5.4: - version "25.5.4" - resolved "https://registry.npm.taobao.org/jest-runtime/download/jest-runtime-25.5.4.tgz?cache=0&sync_timestamp=1615813583940&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjest-runtime%2Fdownload%2Fjest-runtime-25.5.4.tgz#dc981fe2cb2137abcd319e74ccae7f7eeffbfaab" - integrity sha1-3Jgf4sshN6vNMZ50zK5/fu/7+qs= - dependencies: - "@jest/console" "^25.5.0" - "@jest/environment" "^25.5.0" - "@jest/globals" "^25.5.2" - "@jest/source-map" "^25.5.0" - "@jest/test-result" "^25.5.0" - "@jest/transform" "^25.5.1" - "@jest/types" "^25.5.0" +jest-runtime@^26.6.3: + version "26.6.3" + resolved "https://registry.nlark.com/jest-runtime/download/jest-runtime-26.6.3.tgz#4f64efbcfac398331b74b4b3c82d27d401b8fa2b" + integrity sha1-T2TvvPrDmDMbdLSzyC0n1AG4+is= + dependencies: + "@jest/console" "^26.6.2" + "@jest/environment" "^26.6.2" + "@jest/fake-timers" "^26.6.2" + "@jest/globals" "^26.6.2" + "@jest/source-map" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/transform" "^26.6.2" + "@jest/types" "^26.6.2" "@types/yargs" "^15.0.0" - chalk "^3.0.0" + chalk "^4.0.0" + cjs-module-lexer "^0.6.0" collect-v8-coverage "^1.0.0" exit "^0.1.2" glob "^7.1.3" graceful-fs "^4.2.4" - jest-config "^25.5.4" - jest-haste-map "^25.5.1" - jest-message-util "^25.5.0" - jest-mock "^25.5.0" - jest-regex-util "^25.2.6" - jest-resolve "^25.5.1" - jest-snapshot "^25.5.1" - jest-util "^25.5.0" - jest-validate "^25.5.0" - realpath-native "^2.0.0" + jest-config "^26.6.3" + jest-haste-map "^26.6.2" + jest-message-util "^26.6.2" + jest-mock "^26.6.2" + jest-regex-util "^26.0.0" + jest-resolve "^26.6.2" + jest-snapshot "^26.6.2" + jest-util "^26.6.2" + jest-validate "^26.6.2" slash "^3.0.0" strip-bom "^4.0.0" - yargs "^15.3.1" + yargs "^15.4.1" jest-serializer@^24.9.0: version "24.9.0" resolved "https://registry.npm.taobao.org/jest-serializer/download/jest-serializer-24.9.0.tgz#e6d7d7ef96d31e8b9079a714754c5d5c58288e73" integrity sha1-5tfX75bTHouQeacUdUxdXFgojnM= -jest-serializer@^25.5.0: - version "25.5.0" - resolved "https://registry.npm.taobao.org/jest-serializer/download/jest-serializer-25.5.0.tgz#a993f484e769b4ed54e70e0efdb74007f503072b" - integrity sha1-qZP0hOdptO1U5w4O/bdAB/UDBys= +jest-serializer@^26.6.2: + version "26.6.2" + resolved "https://registry.nlark.com/jest-serializer/download/jest-serializer-26.6.2.tgz?cache=0&sync_timestamp=1620109856601&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fjest-serializer%2Fdownload%2Fjest-serializer-26.6.2.tgz#d139aafd46957d3a448f3a6cdabe2919ba0742d1" + integrity sha1-0Tmq/UaVfTpEjzps2r4pGboHQtE= dependencies: + "@types/node" "*" graceful-fs "^4.2.4" -jest-snapshot@^25.5.1: - version "25.5.1" - resolved "https://registry.npm.taobao.org/jest-snapshot/download/jest-snapshot-25.5.1.tgz?cache=0&sync_timestamp=1615813554947&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjest-snapshot%2Fdownload%2Fjest-snapshot-25.5.1.tgz#1a2a576491f9961eb8d00c2e5fd479bc28e5ff7f" - integrity sha1-GipXZJH5lh640AwuX9R5vCjl/38= +jest-snapshot@^26.6.2: + version "26.6.2" + resolved "https://registry.nlark.com/jest-snapshot/download/jest-snapshot-26.6.2.tgz#f3b0af1acb223316850bd14e1beea9837fb39c84" + integrity sha1-87CvGssiMxaFC9FOG+6pg3+znIQ= dependencies: "@babel/types" "^7.0.0" - "@jest/types" "^25.5.0" - "@types/prettier" "^1.19.0" - chalk "^3.0.0" - expect "^25.5.0" + "@jest/types" "^26.6.2" + "@types/babel__traverse" "^7.0.4" + "@types/prettier" "^2.0.0" + chalk "^4.0.0" + expect "^26.6.2" graceful-fs "^4.2.4" - jest-diff "^25.5.0" - jest-get-type "^25.2.6" - jest-matcher-utils "^25.5.0" - jest-message-util "^25.5.0" - jest-resolve "^25.5.1" - make-dir "^3.0.0" + jest-diff "^26.6.2" + jest-get-type "^26.3.0" + jest-haste-map "^26.6.2" + jest-matcher-utils "^26.6.2" + jest-message-util "^26.6.2" + jest-resolve "^26.6.2" natural-compare "^1.4.0" - pretty-format "^25.5.0" - semver "^6.3.0" + pretty-format "^26.6.2" + semver "^7.3.2" jest-util@^24.0.0, jest-util@^24.9.0: version "24.9.0" @@ -9913,40 +10017,42 @@ jest-util@^24.0.0, jest-util@^24.9.0: slash "^2.0.0" source-map "^0.6.0" -jest-util@^25.5.0: - version "25.5.0" - resolved "https://registry.npm.taobao.org/jest-util/download/jest-util-25.5.0.tgz#31c63b5d6e901274d264a4fec849230aa3fa35b0" - integrity sha1-McY7XW6QEnTSZKT+yEkjCqP6NbA= +jest-util@^26.6.2: + version "26.6.2" + resolved "https://registry.nlark.com/jest-util/download/jest-util-26.6.2.tgz#907535dbe4d5a6cb4c47ac9b926f6af29576cbc1" + integrity sha1-kHU12+TVpstMR6ybkm9q8pV2y8E= dependencies: - "@jest/types" "^25.5.0" - chalk "^3.0.0" + "@jest/types" "^26.6.2" + "@types/node" "*" + chalk "^4.0.0" graceful-fs "^4.2.4" is-ci "^2.0.0" - make-dir "^3.0.0" + micromatch "^4.0.2" -jest-validate@^25.5.0: - version "25.5.0" - resolved "https://registry.npm.taobao.org/jest-validate/download/jest-validate-25.5.0.tgz#fb4c93f332c2e4cf70151a628e58a35e459a413a" - integrity sha1-+0yT8zLC5M9wFRpijlijXkWaQTo= +jest-validate@^26.6.2: + version "26.6.2" + resolved "https://registry.nlark.com/jest-validate/download/jest-validate-26.6.2.tgz?cache=0&sync_timestamp=1621549986197&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fjest-validate%2Fdownload%2Fjest-validate-26.6.2.tgz#23d380971587150467342911c3d7b4ac57ab20ec" + integrity sha1-I9OAlxWHFQRnNCkRw9e0rFerIOw= dependencies: - "@jest/types" "^25.5.0" - camelcase "^5.3.1" - chalk "^3.0.0" - jest-get-type "^25.2.6" + "@jest/types" "^26.6.2" + camelcase "^6.0.0" + chalk "^4.0.0" + jest-get-type "^26.3.0" leven "^3.1.0" - pretty-format "^25.5.0" + pretty-format "^26.6.2" -jest-watcher@^25.5.0: - version "25.5.0" - resolved "https://registry.npm.taobao.org/jest-watcher/download/jest-watcher-25.5.0.tgz?cache=0&sync_timestamp=1615813555410&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjest-watcher%2Fdownload%2Fjest-watcher-25.5.0.tgz#d6110d101df98badebe435003956fd4a465e8456" - integrity sha1-1hENEB35i63r5DUAOVb9SkZehFY= +jest-watcher@^26.6.2: + version "26.6.2" + resolved "https://registry.nlark.com/jest-watcher/download/jest-watcher-26.6.2.tgz#a5b683b8f9d68dbcb1d7dae32172d2cca0592975" + integrity sha1-pbaDuPnWjbyx19rjIXLSzKBZKXU= dependencies: - "@jest/test-result" "^25.5.0" - "@jest/types" "^25.5.0" + "@jest/test-result" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" ansi-escapes "^4.2.1" - chalk "^3.0.0" - jest-util "^25.5.0" - string-length "^3.1.0" + chalk "^4.0.0" + jest-util "^26.6.2" + string-length "^4.0.1" jest-worker@24.9.0, jest-worker@^24.9.0: version "24.9.0" @@ -9965,22 +10071,14 @@ jest-worker@26.6.2, jest-worker@^26.2.1, jest-worker@^26.6.2: merge-stream "^2.0.0" supports-color "^7.0.0" -jest-worker@^25.5.0: - version "25.5.0" - resolved "https://registry.npm.taobao.org/jest-worker/download/jest-worker-25.5.0.tgz#2611d071b79cea0f43ee57a3d118593ac1547db1" - integrity sha1-JhHQcbec6g9D7lej0RhZOsFUfbE= - dependencies: - merge-stream "^2.0.0" - supports-color "^7.0.0" - -jest@^25.4.0: - version "25.5.4" - resolved "https://registry.npm.taobao.org/jest/download/jest-25.5.4.tgz?cache=0&sync_timestamp=1615813629642&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjest%2Fdownload%2Fjest-25.5.4.tgz#f21107b6489cfe32b076ce2adcadee3587acb9db" - integrity sha1-8hEHtkic/jKwds4q3K3uNYesuds= +jest@^26.6.3: + version "26.6.3" + resolved "https://registry.nlark.com/jest/download/jest-26.6.3.tgz#40e8fdbe48f00dfa1f0ce8121ca74b88ac9148ef" + integrity sha1-QOj9vkjwDfofDOgSHKdLiKyRSO8= dependencies: - "@jest/core" "^25.5.4" + "@jest/core" "^26.6.3" import-local "^3.0.2" - jest-cli "^25.5.4" + jest-cli "^26.6.3" js-cookie@^2.2.1: version "2.2.1" @@ -10037,36 +10135,36 @@ jsdom@^14.1.0: ws "^6.1.2" xml-name-validator "^3.0.0" -jsdom@^15.2.1: - version "15.2.1" - resolved "https://registry.npm.taobao.org/jsdom/download/jsdom-15.2.1.tgz?cache=0&sync_timestamp=1615594892831&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjsdom%2Fdownload%2Fjsdom-15.2.1.tgz#d2feb1aef7183f86be521b8c6833ff5296d07ec5" - integrity sha1-0v6xrvcYP4a+UhuMaDP/UpbQfsU= - dependencies: - abab "^2.0.0" - acorn "^7.1.0" - acorn-globals "^4.3.2" - array-equal "^1.0.0" - cssom "^0.4.1" - cssstyle "^2.0.0" - data-urls "^1.1.0" - domexception "^1.0.1" - escodegen "^1.11.1" - html-encoding-sniffer "^1.0.2" +jsdom@^16.4.0: + version "16.5.3" + resolved "https://registry.npm.taobao.org/jsdom/download/jsdom-16.5.3.tgz#13a755b3950eb938b4482c407238ddf16f0d2136" + integrity sha1-E6dVs5UOuTi0SCxAcjjd8W8NITY= + dependencies: + abab "^2.0.5" + acorn "^8.1.0" + acorn-globals "^6.0.0" + cssom "^0.4.4" + cssstyle "^2.3.0" + data-urls "^2.0.0" + decimal.js "^10.2.1" + domexception "^2.0.1" + escodegen "^2.0.0" + html-encoding-sniffer "^2.0.1" + is-potential-custom-element-name "^1.0.0" nwsapi "^2.2.0" - parse5 "5.1.0" - pn "^1.1.0" - request "^2.88.0" - request-promise-native "^1.0.7" - saxes "^3.1.9" - symbol-tree "^3.2.2" - tough-cookie "^3.0.1" - w3c-hr-time "^1.0.1" - w3c-xmlserializer "^1.1.2" - webidl-conversions "^4.0.2" + parse5 "6.0.1" + request "^2.88.2" + request-promise-native "^1.0.9" + saxes "^5.0.1" + symbol-tree "^3.2.4" + tough-cookie "^4.0.0" + w3c-hr-time "^1.0.2" + w3c-xmlserializer "^2.0.0" + webidl-conversions "^6.1.0" whatwg-encoding "^1.0.5" whatwg-mimetype "^2.3.0" - whatwg-url "^7.0.0" - ws "^7.0.0" + whatwg-url "^8.5.0" + ws "^7.4.4" xml-name-validator "^3.0.0" jsesc@^2.5.1: @@ -10636,7 +10734,7 @@ lodash@4.17.20: resolved "https://registry.npm.taobao.org/lodash/download/lodash-4.17.20.tgz?cache=0&sync_timestamp=1613835838133&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Flodash%2Fdownload%2Flodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha1-tEqbYpe8tpjxxRo1RaKzs2jVnFI= -lodash@^4.0.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4: +lodash@^4.0.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.7.0: version "4.17.21" resolved "https://registry.npm.taobao.org/lodash/download/lodash-4.17.21.tgz?cache=0&sync_timestamp=1613835838133&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Flodash%2Fdownload%2Flodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha1-Z5WRxWTDv/quhFTPCz3zcMPWkRw= @@ -10666,13 +10764,6 @@ log-update@^4.0.0: slice-ansi "^4.0.0" wrap-ansi "^6.2.0" -lolex@^5.0.0: - version "5.1.2" - resolved "https://registry.npm.taobao.org/lolex/download/lolex-5.1.2.tgz#953694d098ce7c07bc5ed6d0e42bc6c0c6d5a367" - integrity sha1-lTaU0JjOfAe8XtbQ5CvGwMbVo2c= - dependencies: - "@sinonjs/commons" "^1.7.0" - longest-streak@^2.0.0, longest-streak@^2.0.1: version "2.0.4" resolved "https://registry.npm.taobao.org/longest-streak/download/longest-streak-2.0.4.tgz?cache=0&sync_timestamp=1615193315155&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Flongest-streak%2Fdownload%2Flongest-streak-2.0.4.tgz#b8599957da5b5dab64dee3fe316fa774597d90e4" @@ -11484,16 +11575,17 @@ node-modules-regexp@^1.0.0: resolved "https://registry.npm.taobao.org/node-modules-regexp/download/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= -node-notifier@^6.0.0: - version "6.0.0" - resolved "https://registry.npm.taobao.org/node-notifier/download/node-notifier-6.0.0.tgz?cache=0&sync_timestamp=1615492999434&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fnode-notifier%2Fdownload%2Fnode-notifier-6.0.0.tgz#cea319e06baa16deec8ce5cd7f133c4a46b68e12" - integrity sha1-zqMZ4GuqFt7sjOXNfxM8Ska2jhI= +node-notifier@^8.0.0: + version "8.0.2" + resolved "https://registry.npm.taobao.org/node-notifier/download/node-notifier-8.0.2.tgz?cache=0&sync_timestamp=1615492999434&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fnode-notifier%2Fdownload%2Fnode-notifier-8.0.2.tgz#f3167a38ef0d2c8a866a83e318c1ba0efeb702c5" + integrity sha1-8xZ6OO8NLIqGaoPjGMG6Dv63AsU= dependencies: growly "^1.3.0" - is-wsl "^2.1.1" - semver "^6.3.0" + is-wsl "^2.2.0" + semver "^7.3.2" shellwords "^0.1.1" - which "^1.3.1" + uuid "^8.3.0" + which "^2.0.2" node-releases@^1.1.70, node-releases@^1.1.71: version "1.1.71" @@ -11841,11 +11933,6 @@ p-finally@^1.0.0: resolved "https://registry.npm.taobao.org/p-finally/download/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= -p-finally@^2.0.0: - version "2.0.1" - resolved "https://registry.npm.taobao.org/p-finally/download/p-finally-2.0.1.tgz#bd6fcaa9c559a096b680806f4d657b3f0f240561" - integrity sha1-vW/KqcVZoJa2gIBvTWV7Pw8kBWE= - p-is-promise@^2.1.0: version "2.1.0" resolved "https://registry.npm.taobao.org/p-is-promise/download/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e" @@ -12022,6 +12109,11 @@ parse5@5.1.0: resolved "https://registry.npm.taobao.org/parse5/download/parse5-5.1.0.tgz#c59341c9723f414c452975564c7c00a68d58acd2" integrity sha1-xZNByXI/QUxFKXVWTHwApo1YrNI= +parse5@6.0.1, parse5@^6.0.0: + version "6.0.1" + resolved "https://registry.npm.taobao.org/parse5/download/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" + integrity sha1-4aHAhcVps9wIMhGE8Zo5zCf3wws= + parse5@^3.0.1: version "3.0.3" resolved "https://registry.npm.taobao.org/parse5/download/parse5-3.0.3.tgz#042f792ffdd36851551cf4e9e066b3874ab45b5c" @@ -12029,11 +12121,6 @@ parse5@^3.0.1: dependencies: "@types/node" "*" -parse5@^6.0.0: - version "6.0.1" - resolved "https://registry.npm.taobao.org/parse5/download/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" - integrity sha1-4aHAhcVps9wIMhGE8Zo5zCf3wws= - parseurl@~1.3.3: version "1.3.3" resolved "https://registry.npm.taobao.org/parseurl/download/parseurl-1.3.3.tgz?cache=0&sync_timestamp=1599054201722&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fparseurl%2Fdownload%2Fparseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" @@ -13166,16 +13253,6 @@ prettier@2.2.1, prettier@^2.2.1: resolved "https://registry.npm.taobao.org/prettier/download/prettier-2.2.1.tgz?cache=0&sync_timestamp=1606521141305&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fprettier%2Fdownload%2Fprettier-2.2.1.tgz#795a1a78dd52f073da0cd42b21f9c91381923ff5" integrity sha1-eVoaeN1S8HPaDNQrIfnJE4GSP/U= -pretty-format@^25.5.0: - version "25.5.0" - resolved "https://registry.npm.taobao.org/pretty-format/download/pretty-format-25.5.0.tgz?cache=0&sync_timestamp=1615813499265&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpretty-format%2Fdownload%2Fpretty-format-25.5.0.tgz#7873c1d774f682c34b8d48b6743a2bf2ac55791a" - integrity sha1-eHPB13T2gsNLjUi2dDor8qxVeRo= - dependencies: - "@jest/types" "^25.5.0" - ansi-regex "^5.0.0" - ansi-styles "^4.0.0" - react-is "^16.12.0" - pretty-format@^26.0.0, pretty-format@^26.6.2: version "26.6.2" resolved "https://registry.npm.taobao.org/pretty-format/download/pretty-format-26.6.2.tgz?cache=0&sync_timestamp=1615813499265&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpretty-format%2Fdownload%2Fpretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93" @@ -13274,7 +13351,7 @@ pseudomap@^1.0.2: resolved "https://registry.npm.taobao.org/pseudomap/download/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= -psl@^1.1.28: +psl@^1.1.28, psl@^1.1.33: version "1.8.0" resolved "https://registry.npm.taobao.org/psl/download/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" integrity sha1-kyb4vPsBOtzABf3/BWrM4CDlHCQ= @@ -14037,11 +14114,6 @@ realpath-native@^1.1.0: dependencies: util.promisify "^1.0.0" -realpath-native@^2.0.0: - version "2.0.0" - resolved "https://registry.npm.taobao.org/realpath-native/download/realpath-native-2.0.0.tgz#7377ac429b6e1fd599dc38d08ed942d0d7beb866" - integrity sha1-c3esQptuH9WZ3DjQjtlC0Ne+uGY= - rechoir@^0.7.0: version "0.7.0" resolved "https://registry.npm.taobao.org/rechoir/download/rechoir-0.7.0.tgz#32650fd52c21ab252aa5d65b19310441c7e03aca" @@ -14384,7 +14456,7 @@ request-promise-core@1.1.4: dependencies: lodash "^4.17.19" -request-promise-native@^1.0.5, request-promise-native@^1.0.7: +request-promise-native@^1.0.5, request-promise-native@^1.0.9: version "1.0.9" resolved "https://registry.npm.taobao.org/request-promise-native/download/request-promise-native-1.0.9.tgz#e407120526a5efdc9a39b28a5679bf47b9d9dc28" integrity sha1-5AcSBSal79yaObKKVnm/R7nZ3Cg= @@ -14393,7 +14465,7 @@ request-promise-native@^1.0.5, request-promise-native@^1.0.7: stealthy-require "^1.1.1" tough-cookie "^2.3.3" -request@^2.83.0, request@^2.88.0: +request@^2.83.0, request@^2.88.0, request@^2.88.2: version "2.88.2" resolved "https://registry.npm.taobao.org/request/download/request-2.88.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Frequest%2Fdownload%2Frequest-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha1-1zyRhzHLWofaBH4gcjQUb2ZNErM= @@ -14493,11 +14565,6 @@ resolve-url@^0.2.1: resolved "https://registry.npm.taobao.org/resolve-url/download/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= -resolve@1.1.7, resolve@~1.1.6: - version "1.1.7" - resolved "https://registry.npm.taobao.org/resolve/download/resolve-1.1.7.tgz?cache=0&sync_timestamp=1613054822645&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fresolve%2Fdownload%2Fresolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" - integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= - resolve@1.17.0: version "1.17.0" resolved "https://registry.npm.taobao.org/resolve/download/resolve-1.17.0.tgz?cache=0&sync_timestamp=1613054822645&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fresolve%2Fdownload%2Fresolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" @@ -14513,7 +14580,7 @@ resolve@1.19.0: is-core-module "^2.1.0" path-parse "^1.0.6" -resolve@^1.10.0, resolve@^1.13.1, resolve@^1.14.2, resolve@^1.15.1, resolve@^1.16.1, resolve@^1.17.0, resolve@^1.20.0, resolve@^1.3.2, resolve@^1.5.0, resolve@^1.8.1, resolve@^1.9.0: +resolve@^1.10.0, resolve@^1.13.1, resolve@^1.14.2, resolve@^1.15.1, resolve@^1.16.1, resolve@^1.17.0, resolve@^1.18.1, resolve@^1.20.0, resolve@^1.3.2, resolve@^1.5.0, resolve@^1.8.1, resolve@^1.9.0: version "1.20.0" resolved "https://registry.npm.taobao.org/resolve/download/resolve-1.20.0.tgz?cache=0&sync_timestamp=1613054822645&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fresolve%2Fdownload%2Fresolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" integrity sha1-YpoBP7P3B1XW8LeTXMHCxTeLGXU= @@ -14529,6 +14596,11 @@ resolve@^2.0.0-next.3: is-core-module "^2.2.0" path-parse "^1.0.6" +resolve@~1.1.6: + version "1.1.7" + resolved "https://registry.npm.taobao.org/resolve/download/resolve-1.1.7.tgz?cache=0&sync_timestamp=1613054822645&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fresolve%2Fdownload%2Fresolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" + integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= + responselike@^1.0.2: version "1.0.2" resolved "https://registry.npm.taobao.org/responselike/download/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" @@ -14728,6 +14800,13 @@ saxes@^3.1.9: dependencies: xmlchars "^2.1.1" +saxes@^5.0.1: + version "5.0.1" + resolved "https://registry.npm.taobao.org/saxes/download/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" + integrity sha1-7rq5U/o7dgjb6U5drbFciI+maW0= + dependencies: + xmlchars "^2.2.0" + scheduler@^0.19.1: version "0.19.1" resolved "https://registry.npm.taobao.org/scheduler/download/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196" @@ -15271,6 +15350,13 @@ stack-utils@^1.0.1: dependencies: escape-string-regexp "^2.0.0" +stack-utils@^2.0.2: + version "2.0.3" + resolved "https://registry.npm.taobao.org/stack-utils/download/stack-utils-2.0.3.tgz#cd5f030126ff116b78ccb3c027fe302713b61277" + integrity sha1-zV8DASb/EWt4zLPAJ/4wJxO2Enc= + dependencies: + escape-string-regexp "^2.0.0" + state-toggle@^1.0.0: version "1.0.3" resolved "https://registry.npm.taobao.org/state-toggle/download/state-toggle-1.0.3.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fstate-toggle%2Fdownload%2Fstate-toggle-1.0.3.tgz#e123b16a88e143139b09c6852221bc9815917dfe" @@ -15343,13 +15429,13 @@ string-hash@^1.1.1: resolved "https://registry.npm.taobao.org/string-hash/download/string-hash-1.1.3.tgz#e8aafc0ac1855b4666929ed7dd1275df5d6c811b" integrity sha1-6Kr8CsGFW0Zmkp7X3RJ1311sgRs= -string-length@^3.1.0: - version "3.1.0" - resolved "https://registry.npm.taobao.org/string-length/download/string-length-3.1.0.tgz#107ef8c23456e187a8abd4a61162ff4ac6e25837" - integrity sha1-EH74wjRW4Yeoq9SmEWL/SsbiWDc= +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.npm.taobao.org/string-length/download/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha1-qKjce9XBqCubPIuH4SX2aHG25Xo= dependencies: - astral-regex "^1.0.0" - strip-ansi "^5.2.0" + char-regex "^1.0.2" + strip-ansi "^6.0.0" string-width@^1.0.1: version "1.0.2" @@ -15476,7 +15562,7 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" -strip-ansi@^5.1.0, strip-ansi@^5.2.0: +strip-ansi@^5.1.0: version "5.2.0" resolved "https://registry.npm.taobao.org/strip-ansi/download/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" integrity sha1-jJpTb+tq/JYr36WxBKUJHBrZwK4= @@ -15821,7 +15907,7 @@ swr@^0.3.6: dependencies: dequal "2.0.2" -symbol-tree@^3.2.2: +symbol-tree@^3.2.2, symbol-tree@^3.2.4: version "3.2.4" resolved "https://registry.npm.taobao.org/symbol-tree/download/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" integrity sha1-QwY30ki6d+B4iDlR+5qg7tfGP6I= @@ -16130,14 +16216,14 @@ tough-cookie@^2.3.3, tough-cookie@^2.5.0, tough-cookie@~2.5.0: psl "^1.1.28" punycode "^2.1.1" -tough-cookie@^3.0.1: - version "3.0.1" - resolved "https://registry.npm.taobao.org/tough-cookie/download/tough-cookie-3.0.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ftough-cookie%2Fdownload%2Ftough-cookie-3.0.1.tgz#9df4f57e739c26930a018184887f4adb7dca73b2" - integrity sha1-nfT1fnOcJpMKAYGEiH9K233Kc7I= +tough-cookie@^4.0.0: + version "4.0.0" + resolved "https://registry.npm.taobao.org/tough-cookie/download/tough-cookie-4.0.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ftough-cookie%2Fdownload%2Ftough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4" + integrity sha1-2CIjTuyogvmR8PkIgkrSYi3b7OQ= dependencies: - ip-regex "^2.1.0" - psl "^1.1.28" + psl "^1.1.33" punycode "^2.1.1" + universalify "^0.1.2" tr46@^1.0.1: version "1.0.1" @@ -16146,6 +16232,13 @@ tr46@^1.0.1: dependencies: punycode "^2.1.0" +tr46@^2.0.2: + version "2.0.2" + resolved "https://registry.npm.taobao.org/tr46/download/tr46-2.0.2.tgz#03273586def1595ae08fedb38d7733cee91d2479" + integrity sha1-Ayc1ht7xWVrgj+2zjXczzukdJHk= + dependencies: + punycode "^2.1.1" + trim-newlines@^2.0.0: version "2.0.0" resolved "https://registry.nlark.com/trim-newlines/download/trim-newlines-2.0.0.tgz?cache=0&sync_timestamp=1619005721489&other_urls=https%3A%2F%2Fregistry.nlark.com%2Ftrim-newlines%2Fdownload%2Ftrim-newlines-2.0.0.tgz#b403d0b91be50c331dfc4b82eeceb22c3de16d20" @@ -16585,7 +16678,7 @@ unist-util-visit@^2.0.0, unist-util-visit@^2.0.1: unist-util-is "^4.0.0" unist-util-visit-parents "^3.0.0" -universalify@^0.1.0: +universalify@^0.1.0, universalify@^0.1.2: version "0.1.2" resolved "https://registry.npm.taobao.org/universalify/download/universalify-0.1.2.tgz?cache=0&sync_timestamp=1603179967633&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Funiversalify%2Fdownload%2Funiversalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha1-tkb2m+OULavOzJ1mOcgNwQXvqmY= @@ -16720,15 +16813,20 @@ uuid@^3.3.2, uuid@^3.4.0: resolved "https://registry.npm.taobao.org/uuid/download/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha1-sj5DWK+oogL+ehAK8fX4g/AgB+4= +uuid@^8.3.0: + version "8.3.2" + resolved "https://registry.npm.taobao.org/uuid/download/uuid-8.3.2.tgz?cache=0&sync_timestamp=1607460081656&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fuuid%2Fdownload%2Fuuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha1-gNW1ztJxu5r2xEXyGhoExgbO++I= + v8-compile-cache@^2.0.3, v8-compile-cache@^2.2.0, v8-compile-cache@^2.3.0: version "2.3.0" resolved "https://registry.nlark.com/v8-compile-cache/download/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" integrity sha1-LeGWGMZtwkfc+2+ZM4A12CRaLO4= -v8-to-istanbul@^4.1.3: - version "4.1.4" - resolved "https://registry.npm.taobao.org/v8-to-istanbul/download/v8-to-istanbul-4.1.4.tgz#b97936f21c0e2d9996d4985e5c5156e9d4e49cd6" - integrity sha1-uXk28hwOLZmW1JheXFFW6dTknNY= +v8-to-istanbul@^7.0.0: + version "7.1.2" + resolved "https://registry.nlark.com/v8-to-istanbul/download/v8-to-istanbul-7.1.2.tgz?cache=0&sync_timestamp=1620245852441&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fv8-to-istanbul%2Fdownload%2Fv8-to-istanbul-7.1.2.tgz#30898d1a7fa0c84d225a2c1434fb958f290883c1" + integrity sha1-MImNGn+gyE0iWiwUNPuVjykIg8E= dependencies: "@types/istanbul-lib-coverage" "^2.0.1" convert-source-map "^1.6.0" @@ -16893,7 +16991,7 @@ vm-browserify@^1.0.1: resolved "https://registry.npm.taobao.org/vm-browserify/download/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" integrity sha1-eGQcSIuObKkadfUR56OzKobl3aA= -w3c-hr-time@^1.0.1: +w3c-hr-time@^1.0.1, w3c-hr-time@^1.0.2: version "1.0.2" resolved "https://registry.npm.taobao.org/w3c-hr-time/download/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" integrity sha1-ConN9cwVgi35w2BUNnaWPgzDCM0= @@ -16909,6 +17007,13 @@ w3c-xmlserializer@^1.1.2: webidl-conversions "^4.0.2" xml-name-validator "^3.0.0" +w3c-xmlserializer@^2.0.0: + version "2.0.0" + resolved "https://registry.npm.taobao.org/w3c-xmlserializer/download/w3c-xmlserializer-2.0.0.tgz#3e7104a05b75146cc60f564380b7f683acf1020a" + integrity sha1-PnEEoFt1FGzGD1ZDgLf2g6zxAgo= + dependencies: + xml-name-validator "^3.0.0" + walker@^1.0.7, walker@~1.0.5: version "1.0.7" resolved "https://registry.npm.taobao.org/walker/download/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" @@ -16948,6 +17053,16 @@ webidl-conversions@^4.0.2: resolved "https://registry.npm.taobao.org/webidl-conversions/download/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" integrity sha1-qFWYCx8LazWbodXZ+zmulB+qY60= +webidl-conversions@^5.0.0: + version "5.0.0" + resolved "https://registry.npm.taobao.org/webidl-conversions/download/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" + integrity sha1-rlnIoAsSFUOirMZcBDT1ew/BGv8= + +webidl-conversions@^6.1.0: + version "6.1.0" + resolved "https://registry.npm.taobao.org/webidl-conversions/download/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" + integrity sha1-kRG01+qArNQPUnDWZmIa+ni2lRQ= + webpack-chain@6.5.1: version "6.5.1" resolved "https://registry.npm.taobao.org/webpack-chain/download/webpack-chain-6.5.1.tgz#4f27284cbbb637e3c8fbdef43eef588d4d861206" @@ -17060,6 +17175,15 @@ whatwg-url@^7.0.0: tr46 "^1.0.1" webidl-conversions "^4.0.2" +whatwg-url@^8.0.0, whatwg-url@^8.5.0: + version "8.5.0" + resolved "https://registry.npm.taobao.org/whatwg-url/download/whatwg-url-8.5.0.tgz?cache=0&sync_timestamp=1616617970905&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fwhatwg-url%2Fdownload%2Fwhatwg-url-8.5.0.tgz#7752b8464fc0903fec89aa9846fc9efe07351fd3" + integrity sha1-d1K4Rk/AkD/siaqYRvye/gc1H9M= + dependencies: + lodash "^4.7.0" + tr46 "^2.0.2" + webidl-conversions "^6.1.0" + when@^3.7.8: version "3.7.8" resolved "https://registry.npm.taobao.org/when/download/when-3.7.8.tgz#c7130b6a7ea04693e842cdc9e7a1f2aa39a39f82" @@ -17226,10 +17350,10 @@ ws@^6.1.2: dependencies: async-limiter "~1.0.0" -ws@^7.0.0: - version "7.4.4" - resolved "https://registry.npm.taobao.org/ws/download/ws-7.4.4.tgz?cache=0&sync_timestamp=1615063746103&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fws%2Fdownload%2Fws-7.4.4.tgz#383bc9742cb202292c9077ceab6f6047b17f2d59" - integrity sha1-ODvJdCyyAikskHfOq29gR7F/LVk= +ws@^7.4.4: + version "7.4.5" + resolved "https://registry.npm.taobao.org/ws/download/ws-7.4.5.tgz#a484dd851e9beb6fdb420027e3885e8ce48986c1" + integrity sha1-pITdhR6b62/bQgAn44hejOSJhsE= x-is-string@^0.1.0: version "0.1.0" @@ -17246,7 +17370,7 @@ xml-name-validator@^3.0.0: resolved "https://registry.npm.taobao.org/xml-name-validator/download/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" integrity sha1-auc+Bt5NjG5H+fsYH3jWSK1FfGo= -xmlchars@^2.1.1: +xmlchars@^2.1.1, xmlchars@^2.2.0: version "2.2.0" resolved "https://registry.npm.taobao.org/xmlchars/download/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha1-Bg/hvLf5x2/ioX24apvDq4lCEMs= @@ -17309,7 +17433,7 @@ yargs-parser@^20.2.2, yargs-parser@^20.2.3: resolved "https://registry.npm.taobao.org/yargs-parser/download/yargs-parser-20.2.7.tgz#61df85c113edfb5a7a4e36eb8aa60ef423cbc90a" integrity sha1-Yd+FwRPt+1p6TjbriqYO9CPLyQo= -yargs@15.4.1, yargs@^15.3.1: +yargs@15.4.1, yargs@^15.4.1: version "15.4.1" resolved "https://registry.npm.taobao.org/yargs/download/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" integrity sha1-DYehbeAa7p2L7Cv7909nhRcw9Pg= From 359999a8f9159f84255fce5aa45f73989d1afda8 Mon Sep 17 00:00:00 2001 From: zhangchaojie <1098626505@qq.com> Date: Sat, 22 May 2021 16:09:23 +0800 Subject: [PATCH 02/34] =?UTF-8?q?test:=20=E5=AE=8C=E6=88=90=20src/shared/s?= =?UTF-8?q?rc/filters=20=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/src/filters.ts | 12 ++-- tests/shared/filters.test.ts | 123 +++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+), 6 deletions(-) create mode 100644 tests/shared/filters.test.ts diff --git a/src/shared/src/filters.ts b/src/shared/src/filters.ts index 4178bf3..6076ec9 100644 --- a/src/shared/src/filters.ts +++ b/src/shared/src/filters.ts @@ -7,9 +7,9 @@ export const json = (data: any, tabSize?: number) => { return JSON.stringify(data, null, tabSize); }; -// 站位数字 +// 转为数字 export const toNumber = (val?: unknown) => { - if (isString(val)) return parseInt(val, 10); + if (isString(val)) return Number(val); return val; }; @@ -41,8 +41,8 @@ export const trim = (val?: unknown) => { }; // 日期 -export const date = (val?: string | number | Date, template: string = 'YYYY-DD-MM') => { - if (isNumber(val) || isString(val) || isDate(val)) return dayjs(val).format(template); +export const date = (val?: unknown, template: string = 'YYYY-MM-DD') => { + if (dayjs(val as any).isValid()) return dayjs(val as any).format(template); return val; }; @@ -65,7 +65,7 @@ export const round = (val: unknown) => { // 截取字符串 export const truncate = (val: unknown, length: number = 200, mask: string = '...') => { - if (isString(val)) return val.slice(0, length) + mask; + if (isString(val) && val.length >= length) return val.slice(0, length) + mask; return val; }; @@ -84,7 +84,7 @@ export const join = (val: unknown, glue = ',') => { // 求和 export const sum = (val: unknown) => { if (isArray(val)) { - return val.reduce((acc, cur) => acc + cur, 0); + return val.reduce((acc, cur) => acc + Number(cur), 0); } return val; }; diff --git a/tests/shared/filters.test.ts b/tests/shared/filters.test.ts new file mode 100644 index 0000000..6a13056 --- /dev/null +++ b/tests/shared/filters.test.ts @@ -0,0 +1,123 @@ +import { json, toNumber, toInt, toFloat, toPrice, trim, date, toPercent, round, truncate, split, join, sum, abs, toLowerCase, toUpperCase, isTrue, isTruly, isFalse, isFalsely } from 'super-antd' + +const o = { a: 'a' } +describe('filters', () => { + test('json', () => { + expect(json(o)).toBe(JSON.stringify(o)) + expect(json(o, 2)).toBe(JSON.stringify(o, null, 2)) + }) + test('toNumber', () => { + expect(toNumber('123')).toBe(123) + expect(toNumber('123abc')).toBe(NaN) + expect(toNumber(o)).toBe(o) + }) + test('toInt', () => { + expect(toInt('123')).toBe(123) + expect(toInt(123.123)).toBe(123) + expect(toInt('123abc')).toBe(123) + expect(toInt('abc')).toBe(NaN) + expect(toInt(o)).toBe(o) + }) + test('toFloat', () => { + expect(toFloat('123.123')).toBe(123.123) + expect(toFloat(123.123)).toBe(123.123) + expect(toFloat('123.123abc')).toBe(123.123) + expect(toFloat('abc')).toBe(NaN) + expect(toFloat(o)).toBe(o) + }) + test('toPrice', () => { + expect(toPrice(12345)).toBe('12,345') + expect(toPrice(1234)).toBe('1,234') + expect(toPrice('12345')).toBe('12345') + expect(toPrice(o)).toBe(o) + }) + test('trim', () => { + expect(trim(' a ')).toBe('a') + expect(trim(123)).toBe(123) + expect(trim(o)).toBe(o) + }) + test('date', () => { + expect(date(new Date('2023/01/20'))).toBe('2023-01-20') + expect(date('2023/01/20')).toBe('2023-01-20') + expect(date(Number(new Date('2023/01/20')))).toBe('2023-01-20') + expect(date(o)).toBe(o) + }) + test('toPercent', () => { + expect(toPercent(0.8)).toBe('80%') + expect(toPercent('abc')).toBe('abc') + expect(toPercent(o)).toBe(o) + }) + test('round', () => { + expect(round(1.9)).toBe(2) + expect(round(1.4)).toBe(1) + expect(round('123a')).toBe('123a') + expect(round(o)).toBe(o) + }) + + test('truncate', () => { + expect(truncate('abcdef')).toBe('abcdef') + expect(truncate('abcdef', 3)).toBe('abc...') + expect(truncate('abcdef', 3, '---')).toBe('abc---') + expect(truncate(o)).toBe(o) + }) + test('split', () => { + expect(split('a,b,c')).toEqual(['a', 'b', 'c']) + expect(split('a.b.c', '.')).toEqual(['a', 'b', 'c']) + expect(split('a-b-c')).toEqual(['a-b-c']) + expect(split(123)).toBe(123) + expect(split(o)).toBe(o) + }) + test('join', () => { + expect(join(['a', 'b', 'c'])).toBe('a,b,c') + expect(join(['a', 'b', 'c'], '.')).toBe('a.b.c') + expect(join(123)).toBe(123) + expect(join(o)).toBe(o) + }) + test('sum', () => { + expect(sum([1, 2, 3])).toBe(6) + expect(sum([1, 2, 'a'])).toBe(NaN) + expect(sum(123)).toBe(123) + expect(sum(o)).toBe(o) + }) + test('abs', () => { + expect(abs(-1)).toBe(1) + expect(abs(1)).toBe(1) + expect(abs('a')).toBe('a') + expect(abs(o)).toBe(o) + }) + test('toLowerCase', () => { + expect(toLowerCase('ABC')).toBe('abc') + expect(toLowerCase(123)).toBe(123) + expect(toLowerCase(o)).toBe(o) + }) + test('toUpperCase', () => { + expect(toUpperCase('abc')).toBe('ABC') + expect(toUpperCase(123)).toBe(123) + expect(toUpperCase(o)).toBe(o) + }) + + test('isTrue', () => { + expect(isTrue(true, '显示', '隐藏')).toBe('显示') + expect(isTrue(false, '显示', '隐藏')).toBe('隐藏') + expect(isTrue(123, '显示', '隐藏')).toBe(123) + expect(isTrue(o, '显示', '隐藏')).toBe(o) + }) + test('isTruly', () => { + expect(isTruly(true, '显示', '隐藏')).toBe('显示') + expect(isTruly(false, '显示', '隐藏')).toBe('隐藏') + expect(isTruly(123, '显示', '隐藏')).toBe('显示') + expect(isTruly(o, '显示', '隐藏')).toBe('显示') + }) + test('isFalse', () => { + expect(isFalse(true, '隐藏', '显示')).toBe('显示') + expect(isFalse(false, '隐藏', '显示')).toBe('隐藏') + expect(isFalse(123, '隐藏', '显示')).toBe(123) + expect(isFalse(o, '隐藏', '显示')).toBe(o) + }) + test('isFalsely', () => { + expect(isFalsely(true, '隐藏', '显示')).toBe('显示') + expect(isFalsely(false, '隐藏', '显示')).toBe('隐藏') + expect(isFalsely(123, '隐藏', '显示')).toBe('显示') + expect(isFalsely(o, '隐藏', '显示')).toBe('显示') + }) +}) \ No newline at end of file From 8475125ada3d4280f9174dcde19760693865c84f Mon Sep 17 00:00:00 2001 From: zhangchaojie <1098626505@qq.com> Date: Sat, 22 May 2021 18:20:37 +0800 Subject: [PATCH 03/34] =?UTF-8?q?test:=20=E5=AE=8C=E6=88=90=20src/shared/s?= =?UTF-8?q?rc/hooks/useJump=20=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .umirc.ts | 16 +++++ tests/shared/hooks/useJump.test.tsx | 100 ++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 tests/shared/hooks/useJump.test.tsx diff --git a/.umirc.ts b/.umirc.ts index eb01a41..30af407 100644 --- a/.umirc.ts +++ b/.umirc.ts @@ -21,4 +21,20 @@ export default defineConfig({ 'antd', ], ], + navs: [ + { + title: '生态', + children: [ + { + title: 'react-schema-render 动态渲染', + path: 'https://gitee.com/dream2023/react-schema-render', + }, + { + title: '@dream2023/data-mapping 数据映射', + path: 'https://gitee.com/dream2023/data-mapping', + }, + ], + }, + null, + ] }); diff --git a/tests/shared/hooks/useJump.test.tsx b/tests/shared/hooks/useJump.test.tsx new file mode 100644 index 0000000..6cdb075 --- /dev/null +++ b/tests/shared/hooks/useJump.test.tsx @@ -0,0 +1,100 @@ +import React, { FC, useState } from 'react' +import { act, renderHook } from '@testing-library/react-hooks'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { BrowserRouter, HashRouter, Route, Switch, useHistory, useLocation } from 'react-router-dom'; + +import { useJump } from 'super-antd' + +describe('useJump', () => { + describe('use jump by window.open', () => { + test('当为 url 为 string 类型时,应该采用 window.open 默认参数', () => { + const { result } = renderHook(() => useJump()) + const fn = jest.fn() + window.open = fn + act(() => { + result.current('https://www.baidu.com') + }) + expect(fn).toBeCalledWith('https://www.baidu.com', '_self', undefined, undefined) + }) + + test('当为 url 为对象类型时,则根据对象类型跳转', () => { + const { result } = renderHook(() => useJump()) + const fn = jest.fn() + window.open = fn + act(() => { + result.current({ url: 'https://www.baidu.com', target: '_blank'}) + }) + expect(fn).toBeCalledWith('https://www.baidu.com', '_blank', undefined, undefined) + }) + }) + + describe('should jump by useHistory', () => { + const Demo = () => { + const jump = useJump(); + const location = useLocation(); + const history = useHistory(); + const [len, setLen] = useState(0); + React.useEffect(() => { + setLen(history.length); + }, [location]); + return (<> +
{ len }
+ + + home + user + + ) + } + + test('无 RouterRouter', () => { + expect.assertions(1) + const { result } = renderHook(() => useJump()) + act(() => { + expect(() => result.current('/user')).toThrowError() + }) + }) + + test('base BrowserRouter', async () => { + render() + const len = Number(screen.getByTestId('len').textContent) + expect(screen.queryByText('home')).toBeInTheDocument() + fireEvent.click(screen.getByTestId('jump')) + expect(screen.queryByText('user')).toBeInTheDocument() + expect(screen.getByTestId('len')).toHaveTextContent(String(len + 1)) + }) + + test('base HashRouter', () => { + render() + const len = Number(screen.getByTestId('len').textContent) + expect(screen.queryByText('home')).toBeInTheDocument() + fireEvent.click(screen.getByTestId('jump')) + expect(screen.queryByText('user')).toBeInTheDocument() + expect(screen.getByTestId('len')).toHaveTextContent(String(len + 1)) + }) + + test('当传入 replace 参数应该调用 history replace', () => { + const Demo = () => { + const jump = useJump(); + const location = useLocation(); + const history = useHistory(); + const [len, setLen] = useState(0); + React.useEffect(() => { + setLen(history.length); + }, [location]); + return (<> +
{ len }
+ + + home + user + + ) + } + render() + const len = (screen.getByTestId('len').textContent) || '' + fireEvent.click(screen.getByTestId('jump')) + expect(screen.getByTestId('len')).toHaveTextContent(len) + }) + }) +}); \ No newline at end of file From 89431e64ee60cfc65e9c40693a192967c3c6d090 Mon Sep 17 00:00:00 2001 From: zhangchaojie <1098626505@qq.com> Date: Sun, 23 May 2021 15:31:21 +0800 Subject: [PATCH 04/34] =?UTF-8?q?test:=20=E5=AE=8C=E6=88=90=20useMock=20?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .umirc.ts | 4 ++ package.json | 2 +- src/form/src/Form.tsx | 19 ++++-- src/shared/src/hooks/useMock/extends.ts | 13 ++-- src/shared/src/hooks/useMock/useMock.ts | 34 +++++----- src/shared/src/hooks/useMock/util.ts | 30 ++++----- tests/shared/hooks/useJump.test.tsx | 4 +- tests/shared/hooks/useMock/extends.test.ts | 40 ++++++++++++ tests/shared/hooks/useMock/useMock.test.ts | 72 ++++++++++++++++++++++ tests/shared/hooks/useMock/utils.test.ts | 20 ++++++ yarn.lock | 18 +++--- 11 files changed, 199 insertions(+), 57 deletions(-) create mode 100644 tests/shared/hooks/useMock/extends.test.ts create mode 100644 tests/shared/hooks/useMock/useMock.test.ts create mode 100644 tests/shared/hooks/useMock/utils.test.ts diff --git a/.umirc.ts b/.umirc.ts index 30af407..aaf4859 100644 --- a/.umirc.ts +++ b/.umirc.ts @@ -36,5 +36,9 @@ export default defineConfig({ ], }, null, + { + title: 'GitHub', + path: 'https://github.com/dream2023/super-antd', + }, ] }); diff --git a/package.json b/package.json index 5f8f9a8..cd6e1d3 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "@dream2023/data-mapping": "^2.1.1", "@types/classnames": "^2.2.11", "@types/mockjs": "^1.0.3", - "ahooks": "^2.10.0", + "ahooks": "^2.10.4", "classnames": "^2.2.6", "dayjs": "^1.10.4", "map-obj": "^4.2.1", diff --git a/src/form/src/Form.tsx b/src/form/src/Form.tsx index eebc742..978154e 100644 --- a/src/form/src/Form.tsx +++ b/src/form/src/Form.tsx @@ -4,7 +4,8 @@ import ProForm from '@ant-design/pro-form'; import type { ProFormProps } from '@ant-design/pro-form'; import { useCreation, useLocalStorageState, usePersistFn, useThrottleFn } from 'ahooks'; import { Form, Spin } from 'antd'; -import type { Key } from 'react'; +import type { Key} from 'react'; +import { useContext } from 'react'; import React, { useState } from 'react'; import warning from 'tiny-warning'; @@ -16,6 +17,8 @@ import { useJump } from '@/shared/src/hooks/useJump'; import type { SuperFormContextProps } from './context'; import { SuperFormContext } from './context'; import { SuperFormDebugger } from './FormDebugger'; +import { SuperAntdContext } from '@/provider'; + import type { ActionProps, FormCommunicationProps, @@ -98,6 +101,9 @@ export function SuperForm = any>(props: SuperFor ...resetProps } = props; + // 全局上下文 + const { mockjs } = useContext(SuperAntdContext) + // 表单引用 const [formInstance] = Form.useForm(form); @@ -231,9 +237,14 @@ export function SuperForm = any>(props: SuperFor const initMockRules = useCreation(() => { return isPlainObject(mock) ? mock : {}; }, [mock]); - const { hasMockRules, setMock, mockRules } = useMock(initMockRules, (mockData: Values) => { - const data: any = { ...(formInstance.getFieldsValue() || {}), ...mockData }; - formInstance.setFieldsValue(data); + + const { hasMockRules, setMock, mockRules } = useMock({ + Mock: mockjs, + initMockRules, + onMockCallback: (mockData: Values) => { + const data: any = { ...(formInstance.getFieldsValue() || {}), ...mockData }; + formInstance.setFieldsValue(data); + } }); // form context 相关 diff --git a/src/shared/src/hooks/useMock/extends.ts b/src/shared/src/hooks/useMock/extends.ts index 8858f97..18e0948 100644 --- a/src/shared/src/hooks/useMock/extends.ts +++ b/src/shared/src/hooks/useMock/extends.ts @@ -1,15 +1,16 @@ import type { Mockjs } from 'mockjs'; // 多选 -export function mockMultiple(Mock: Mockjs, arr: any[]) { +export function mockMultiple(Mock: Mockjs, arr?: any[]) { + if (!arr) return [] const count = Mock.Random.integer(0, arr.length); return Array.from(new Set(Array.from({ length: count }, () => Mock.Random.pick(arr)))); } // 复选 -export function mockCheckbox(Mock: Mockjs, options: Record, valueName = 'value') { +export function mockCheckbox(Mock: Mockjs, options?: { label: string, value: any }[]) { if (Array.isArray(options)) { - const values = options.map((item) => item[valueName]); + const values = options.map((item) => item.value); const count = Mock.Random.integer(0, options.length); return Array.from(new Set(Array.from({ length: count }, () => Mock.Random.pick(values)))); } @@ -17,10 +18,10 @@ export function mockCheckbox(Mock: Mockjs, options: Record, valueNa } // 单选 -export function mockRadio(Mock: Mockjs, options: Record, valueName = 'value') { +export function mockRadio(Mock: Mockjs, options?: { label: string, value: any }[]) { if (Array.isArray(options)) { - const values = options.map((item) => item[valueName]); + const values = options.map((item) => item.value); return Mock.Random.pick(values); } - return null; + return undefined; } diff --git a/src/shared/src/hooks/useMock/useMock.ts b/src/shared/src/hooks/useMock/useMock.ts index 5a377e4..1e8585b 100644 --- a/src/shared/src/hooks/useMock/useMock.ts +++ b/src/shared/src/hooks/useMock/useMock.ts @@ -1,12 +1,10 @@ -import { useCreation, usePersistFn, useReactive } from 'ahooks'; -import { useContext } from 'react'; +import { useCreation, usePersistFn, useReactive, useUpdateLayoutEffect } from 'ahooks'; import warning from 'tiny-warning'; -import { SuperAntdContext } from '@/provider'; - import { __DOCS_URL__ } from '../../constants'; import type { MockRules } from './types'; import { getMockValues } from './util'; +import type { Mockjs } from 'mockjs'; /** * Mock 数据 @@ -14,23 +12,28 @@ import { getMockValues } from './util'; * @param initMockRules Mock 初始规则,当为 boolean,可以理解为 {},主要为支持外部的 * @param onMockCallback 当 mock 时的回调函数 */ -export function useMock(initMockRules: MockRules, onMockCallback?: (data: T) => void) { - const context = useContext(SuperAntdContext); +interface IUseMockOptions { + initMockRules: MockRules; + onMockCallback: (data: T) => void; + Mock?: Mockjs +} + +export function useMock(options: IUseMockOptions) { + const { Mock, initMockRules, onMockCallback } = options const mockRules = useReactive(initMockRules); - // // 当 initMockRules 发生变化时,需要更新 mockRules - // useEffect(() => { - // Object.assign(mockRules, initMockRules); - // }, [initMockRules]); + // 当 initMockRules 发生变化时,需要更新 mockRules + useUpdateLayoutEffect(() => { + Object.assign(mockRules, initMockRules) + }, [initMockRules, mockRules]); // 是否有 MockRules const hasMockRules = useCreation(() => { - return Object.keys(mockRules).length > 0; - }, [Object.keys(mockRules)]); + return Object.keys(mockRules).length > 0 + }, [Object.keys(mockRules)]) // 回调函数 const setMock = usePersistFn(() => { - const Mock = context.mockjs; // 参数校检 if (!Mock) { warning( @@ -40,10 +43,7 @@ export function useMock(initMockRules: MockRules, onMockCallback?: (dat return; } - // 如果有回调函数,就执行 - if (onMockCallback) { - onMockCallback(getMockValues(Mock, mockRules)); - } + onMockCallback(getMockValues(Mock, mockRules)); }); return { diff --git a/src/shared/src/hooks/useMock/util.ts b/src/shared/src/hooks/useMock/util.ts index f388320..3f3a878 100644 --- a/src/shared/src/hooks/useMock/util.ts +++ b/src/shared/src/hooks/useMock/util.ts @@ -3,38 +3,32 @@ import type { Mockjs } from 'mockjs'; import { isFunction, isString } from '../../utils/is'; import { set } from '../../utils/set'; +import { toPathArr } from '../../utils/util'; import type { MockRules } from './types'; -/** - * 为了解决 key 的问题,需要先转换 mockRules - * - * @param mockRules {'foo.bar': '@string'} => { 'foo': {bar: "@string"} } - * @returns - */ -export function getMockRules(Mock: Mockjs, mockRules: MockRules) { +// 获取 Mock 的值 +export function getMockValues(Mock: Mockjs, mockRules: MockRules): T { // 深度遍历 mockRules - return mapObject( + const res: any = mapObject( mockRules, (key: any, val: any) => { - if (isString(val) && key.includes('.')) { + if (isString(val)) { // 'a.b.c' => [a, { b: { c: value }}] - const keyArr = key.split('.'); - const newKey = keyArr.shift(); - const newValue = set({}, keyArr, 'zhang'); + const keyArr = toPathArr(key); + if (keyArr.length <= 1) return [key, Mock.mock(val)] + + const newKey = keyArr.shift(); // 弹出顶级 + const newValue = set({}, keyArr, Mock.mock(val)); return [newKey, newValue]; } if (isFunction(val)) { // 如果是函数类型的话,则执行函数 return [key, val(Mock)]; } - return [key, val]; + return [key, Mock.mock(val)]; }, { deep: true }, ); -} -// 获取 Mock 的值 -export function getMockValues(Mock: Mockjs, mockRules: MockRules) { - const changedMockRules = getMockRules(Mock, mockRules); - return Mock.mock(changedMockRules); + return res as T } diff --git a/tests/shared/hooks/useJump.test.tsx b/tests/shared/hooks/useJump.test.tsx index 6cdb075..c8dd5ab 100644 --- a/tests/shared/hooks/useJump.test.tsx +++ b/tests/shared/hooks/useJump.test.tsx @@ -1,6 +1,6 @@ -import React, { FC, useState } from 'react' +import React, { useState } from 'react' import { act, renderHook } from '@testing-library/react-hooks'; -import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { fireEvent, render, screen } from '@testing-library/react'; import { BrowserRouter, HashRouter, Route, Switch, useHistory, useLocation } from 'react-router-dom'; import { useJump } from 'super-antd' diff --git a/tests/shared/hooks/useMock/extends.test.ts b/tests/shared/hooks/useMock/extends.test.ts new file mode 100644 index 0000000..d1f177d --- /dev/null +++ b/tests/shared/hooks/useMock/extends.test.ts @@ -0,0 +1,40 @@ +// import Mock from 'mockjs'; +import { mockCheckbox, mockRadio, mockMultiple } from 'super-antd' + +describe('useMock extends', () => { + test('mockMultiple', () => { + const Mock: any = { + Random: { + integer: () => 2, + pick: (arr: any[]) => arr[0] + } + } + + expect(mockMultiple(Mock, [1, 2, 3])).toEqual([1]) + expect(mockMultiple(Mock)).toEqual([]) + }) + + test('mockRadio', () => { + const Mock: any = { + Random: { + integer: () => 2, + pick: (arr: any[]) => arr[0] + } + } + + expect(mockRadio(Mock)).toEqual(undefined) + expect(mockRadio(Mock, [{ label: 'a', value: 'a' }, { label: 'b', value: 'b' }, { label: 'c', value: 'c' }])).toEqual('a') + }) + + test('mockCheckbox', () => { + const Mock: any = { + Random: { + integer: () => 2, + pick: (arr: any[]) => arr[0] + } + } + + expect(mockCheckbox(Mock)).toEqual([]) + expect(mockCheckbox(Mock, [{ label: 'a', value: 'a' }, { label: 'b', value: 'b' }, { label: 'c', value: 'c' }])).toEqual(['a']) + }) +}) \ No newline at end of file diff --git a/tests/shared/hooks/useMock/useMock.test.ts b/tests/shared/hooks/useMock/useMock.test.ts new file mode 100644 index 0000000..3dacb9b --- /dev/null +++ b/tests/shared/hooks/useMock/useMock.test.ts @@ -0,0 +1,72 @@ +import { act, renderHook } from '@testing-library/react-hooks'; +import Mock, { Mockjs } from 'mockjs'; + +import { MockRules, useMock } from 'super-antd'; + +const setUp = (initMockRules: MockRules = {}, onMockCallback: (data: any) => void, Mock?: Mockjs) => + renderHook(({ initMockRules, onMockCallback }) => useMock({ Mock, initMockRules, onMockCallback }), { + initialProps: { + initMockRules, + onMockCallback, + }, + }); + +describe('useMock', () => { + test('无 Mock 时发出警告', () => { + const { result } = setUp({ a: '@string' }, () => { }); + let originConsoleWarn = console.warn + const fn = jest.fn() + console.warn = fn + act(() => { + result.current.setMock() + }) + expect(fn).toBeCalled() + console.warn = originConsoleWarn + }) + + + test('initMockRules 变化时,mockRules 应该跟随变化', () => { + // 初始化 + const callback = jest.fn() + const { result, rerender } = setUp({}, callback); + expect(result.current.mockRules).toEqual({}); + expect(result.current.hasMockRules).toBeFalsy(); + + // 重新更改 + rerender({ initMockRules: { a: '@string' }, onMockCallback: callback }); + expect(result.current.mockRules).toEqual({ a: '@string' }); + }); + + + test('setMockRule', () => { + const { result } = setUp({}, () => { }); + act(() => { + result.current.mockRules.a = '@integer'; + }); + + expect(result.current.mockRules).toEqual({ a: '@integer' }); + expect(result.current.hasMockRules).toBeTruthy(); + }); + + test('removeMockRule', async () => { + const { result } = setUp({ a: '@integer' }, () => { }); + expect(result.current.mockRules).toEqual({ a: '@integer' }); + expect(result.current.hasMockRules).toBeTruthy(); + + act(() => { + delete result.current.mockRules.a + }); + + expect(result.current.mockRules).toEqual({}); + expect(result.current.hasMockRules).toBeFalsy(); + }); + + test('onMockCallback 应该传递 mock 数据', () => { + const fn = jest.fn() + const { result } = setUp({ a: '@integer' }, fn, Mock); + act(() => { + result.current.setMock() + }) + expect(fn).toBeCalledWith({ a: expect.any(Number) }) + }) +}); \ No newline at end of file diff --git a/tests/shared/hooks/useMock/utils.test.ts b/tests/shared/hooks/useMock/utils.test.ts new file mode 100644 index 0000000..1adb2e3 --- /dev/null +++ b/tests/shared/hooks/useMock/utils.test.ts @@ -0,0 +1,20 @@ +import Mock, { Mockjs } from 'mockjs' +import { getMockValues } from 'super-antd' + +describe('useMock utils', () => { + test('基础使用方式', () => { + expect(getMockValues(Mock, { a: '@string', b: '@integer' })).toEqual({ a: expect.any(String), b: expect.any(Number) }) + }) + + test('深度嵌套', () => { + expect(getMockValues(Mock, { a: { b: { c: '@integer' } } })).toEqual({ a: { b: { c: expect.any(Number) } } }) + }) + + test('字符串形式深度嵌套', () => { + expect(getMockValues(Mock, { 'a.b.c': '@integer' })).toEqual({ a: { b: { c: expect.any(Number) } } }) + }) + + test('函数形式的值', () => { + expect(getMockValues(Mock, { 'a': (Mock: Mockjs) => Mock.Random.boolean() })).toEqual({ a: expect.any(Boolean) }) + }) +}); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 0b29868..3a7b8a7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,10 +2,10 @@ # yarn lockfile v1 -"@ahooksjs/use-request@^2.8.4": - version "2.8.4" - resolved "https://registry.npm.taobao.org/@ahooksjs/use-request/download/@ahooksjs/use-request-2.8.4.tgz?cache=0&sync_timestamp=1616144076543&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40ahooksjs%2Fuse-request%2Fdownload%2F%40ahooksjs%2Fuse-request-2.8.4.tgz#c404dce2c984f1ce8573e6577582cc9656a71e37" - integrity sha1-xATc4smE8c6Fc+ZXdYLMllanHjc= +"@ahooksjs/use-request@^2.8.6": + version "2.8.6" + resolved "https://registry.nlark.com/@ahooksjs/use-request/download/@ahooksjs/use-request-2.8.6.tgz?cache=0&sync_timestamp=1620461371963&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40ahooksjs%2Fuse-request%2Fdownload%2F%40ahooksjs%2Fuse-request-2.8.6.tgz#974d4e19c5f8f08c5d0bcfb03bb00a05d0de920b" + integrity sha1-l01OGcX48IxdC8+wO7AKBdDekgs= dependencies: lodash.debounce "^4.0.8" lodash.throttle "^4.1.1" @@ -4174,12 +4174,12 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" -ahooks@^2.10.0: - version "2.10.0" - resolved "https://registry.npm.taobao.org/ahooks/download/ahooks-2.10.0.tgz?cache=0&sync_timestamp=1616154039376&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fahooks%2Fdownload%2Fahooks-2.10.0.tgz#bec2c0b928115afd6d268e67770146988347675c" - integrity sha1-vsLAuSgRWv1tJo5ndwFGmINHZ1w= +ahooks@^2.10.4: + version "2.10.4" + resolved "https://registry.nlark.com/ahooks/download/ahooks-2.10.4.tgz?cache=0&sync_timestamp=1621224457572&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fahooks%2Fdownload%2Fahooks-2.10.4.tgz#50a71c37c72544997d55686c73cc601adcefe30f" + integrity sha1-UKccN8clRJl9VWhsc8xgGtzv4w8= dependencies: - "@ahooksjs/use-request" "^2.8.4" + "@ahooksjs/use-request" "^2.8.6" "@types/js-cookie" "^2.2.6" dayjs "^1.9.1" intersection-observer "^0.7.0" From 4a72cf099f7bbdc0a2c846e6cd935b114ed42f49 Mon Sep 17 00:00:00 2001 From: zhangchaojie <1098626505@qq.com> Date: Sun, 23 May 2021 16:38:18 +0800 Subject: [PATCH 05/34] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=20form=20m?= =?UTF-8?q?ock?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/CheckboxGroup.tsx | 4 +- src/form-item/src/components/RadioGroup.tsx | 4 +- src/form-item/src/components/Select.tsx | 11 ++-- src/form-item/src/createSuperFormItem.ts | 16 +++-- src/form-item/src/hoc/index.ts | 1 + src/form-item/src/hoc/withFormItem.tsx | 16 +---- src/form-item/src/hoc/withFormItemTypes.ts | 3 - src/form-item/src/hoc/withMock.tsx | 65 +++++++++++++++++++ src/form-item/src/hooks/useFormMock.ts | 20 +++--- src/shared/src/hooks/useMock/extends.ts | 6 +- 10 files changed, 102 insertions(+), 44 deletions(-) create mode 100644 src/form-item/src/hoc/withMock.tsx diff --git a/src/form-item/src/components/CheckboxGroup.tsx b/src/form-item/src/components/CheckboxGroup.tsx index be271ed..c2fc086 100644 --- a/src/form-item/src/components/CheckboxGroup.tsx +++ b/src/form-item/src/components/CheckboxGroup.tsx @@ -9,8 +9,8 @@ import { createSuperFormItemWithOptions } from '../createSuperFormItem'; export type SuperCheckboxGroupProps = CreateSuperFormItemWithOptionsProps; export const SuperCheckboxGroup = createSuperFormItemWithOptions(ProFormCheckbox.Group, { - defaultMockRule: (props: ProFormCheckboxGroupProps) => { - return (Mock: Mockjs) => mockCheckbox(Mock, props.options || []); + defaultMockRule: (props: Omit & { options?: { label: string, value: any }[] }) => { + return (Mock: Mockjs) => mockCheckbox(Mock, props.options); }, }); SuperCheckboxGroup.displayName = 'SuperCheckboxGroup'; diff --git a/src/form-item/src/components/RadioGroup.tsx b/src/form-item/src/components/RadioGroup.tsx index 3c72691..f2b6897 100644 --- a/src/form-item/src/components/RadioGroup.tsx +++ b/src/form-item/src/components/RadioGroup.tsx @@ -9,8 +9,8 @@ import { createSuperFormItemWithOptions } from '../createSuperFormItem'; export type SuperRadioGroupProps = CreateSuperFormItemProps; export const SuperRadioGroup = createSuperFormItemWithOptions(ProFormRadio.Group, { - defaultMockRule: (props: ProFormRadioGroupProps) => { - return (Mock: Mockjs) => mockRadio(Mock, props.options || []); + defaultMockRule: (props: Omit & { options?: {label: string, value: any}[] }) => { + return (Mock: Mockjs) => mockRadio(Mock, props.options); }, }); SuperRadioGroup.displayName = 'SuperRadioGroup'; diff --git a/src/form-item/src/components/Select.tsx b/src/form-item/src/components/Select.tsx index 121e106..e51f798 100644 --- a/src/form-item/src/components/Select.tsx +++ b/src/form-item/src/components/Select.tsx @@ -10,11 +10,12 @@ import { createSuperFormItemWithOptions } from '../createSuperFormItem'; export type SuperSelectProps = CreateSuperFormItemWithOptionsProps; export const SuperSelect = createSuperFormItemWithOptions(ProFormSelect, { placeholderPrefix: '请选择', - defaultMockRule: (props: ProFormSelectProps) => { - return (Mock: Mockjs) => - props.fieldProps?.mode === 'multiple' || props.fieldProps?.mode === 'tags' - ? mockCheckbox(Mock, props.options || []) - : mockRadio(Mock, props.options || []); + defaultMockRule: (props: Omit & { options?: {label: string, value: any}[] }) => { + return (Mock: Mockjs) => { + return props.fieldProps?.mode === 'multiple' || props.fieldProps?.mode === 'tags' + ? mockCheckbox(Mock, props.options) + : mockRadio(Mock, props.options); + } }, }); diff --git a/src/form-item/src/createSuperFormItem.ts b/src/form-item/src/createSuperFormItem.ts index 83ceb6a..2d8d688 100644 --- a/src/form-item/src/createSuperFormItem.ts +++ b/src/form-item/src/createSuperFormItem.ts @@ -7,30 +7,34 @@ import { withFormItem } from './hoc/withFormItem'; import type { WithFormItemProps } from './hoc/withFormItemTypes'; import type { WithOptionsConfigType, WithOptionsProps } from './hoc/withOptions'; import { withOptions } from './hoc/withOptions'; +import { withMock } from './hoc/withMock' +import type { WithMockProps, WithMockConfigType } from './hoc/withMock' // 方便别人导出 Props -export type CreateSuperFormItemProps = WithDependencyProps>; +export type CreateSuperFormItemProps = WithDependencyProps>>; // 创建 form-item 组件 export function createSuperFormItem>( Component: ComponentType, - config: WithFormItemConfigType, + config: WithFormItemConfigType & WithMockConfigType, ) { - const ComponentWithFormItem = withFormItem(Component, config); + const ComponentWithMock = withMock(Component, config); + const ComponentWithFormItem = withFormItem(ComponentWithMock, config); const ComponentWithDependency = withDependency>(ComponentWithFormItem); return ComponentWithDependency; } // 方便别人导出 props -export type CreateSuperFormItemWithOptionsProps = WithDependencyProps>>; +export type CreateSuperFormItemWithOptionsProps = WithDependencyProps>>>; // 创建带 options 功能的 form-item export function createSuperFormItemWithOptions>( Component: ComponentType, - config: WithFormItemConfigType & WithOptionsConfigType = {}, + config: WithFormItemConfigType & WithOptionsConfigType & WithMockConfigType = {}, ) { - const ComponentWithOptions = withOptions(Component, { + const ComponentWithMock = withMock(Component, config); + const ComponentWithOptions = withOptions(ComponentWithMock, { hasLoadingProp: config.hasLoadingProp, needData: config.needData, }); diff --git a/src/form-item/src/hoc/index.ts b/src/form-item/src/hoc/index.ts index 9d36421..d705eba 100644 --- a/src/form-item/src/hoc/index.ts +++ b/src/form-item/src/hoc/index.ts @@ -1,3 +1,4 @@ +export * from './withMock' export * from './withOptions'; export * from './withFormItem'; export * from './withDependency'; diff --git a/src/form-item/src/hoc/withFormItem.tsx b/src/form-item/src/hoc/withFormItem.tsx index 727aced..fdb6acb 100644 --- a/src/form-item/src/hoc/withFormItem.tsx +++ b/src/form-item/src/hoc/withFormItem.tsx @@ -8,14 +8,11 @@ import { SuperFormContext } from '@/form'; import { SuperAntdContext } from '@/provider'; import { castToArray, get, getCol, isString, isUndefined, omit, set } from '@/shared'; -import { useFormMock } from '../hooks/useFormMock'; import { getColon, getLabel, getLinkageValue, getName, getOppositionValue, getPlaceholder } from '../utils'; import type { WithFormItemProps } from './withFormItemTypes'; import { omitWithFormItemKeys } from './withFormItemTypes'; export interface WithFormItemConfigType { - /** Mock 规则 */ - defaultMockRule?: any; /** * 如果有 placeholder 属性时,前缀 * @@ -28,13 +25,12 @@ export interface WithFormItemConfigType { // eslint-disable-next-line @typescript-eslint/ban-types export function withFormItem

(FormItemComponent: ComponentType

, config: WithFormItemConfigType) { - const { defaultMockRule, placeholderPrefix, needData } = config; + const { placeholderPrefix, needData } = config; const EnhancedFormComponent: FC> = (props) => { const { name, form, data, - mock, help, colon, label, @@ -188,16 +184,6 @@ export function withFormItem

(FormItemComponent: ComponentType< nameStr, ]); - // mock 数据相关 - useFormMock({ - mock, - formContext, - disabledMock: !!(linkageReadonly || linkageHidden || linkageDisabled), - name: nameStr, - defaultMockRule, - props, - }); - // 远程错误信息展示 const errorProps = useCreation(() => { if (computedName) { diff --git a/src/form-item/src/hoc/withFormItemTypes.ts b/src/form-item/src/hoc/withFormItemTypes.ts index 1d97f43..d5a38de 100644 --- a/src/form-item/src/hoc/withFormItemTypes.ts +++ b/src/form-item/src/hoc/withFormItemTypes.ts @@ -38,8 +38,6 @@ export interface NewWithFormItemProps { clearValueAfterDisabled?: boolean; /** 当表单项只读以后,是否清除其值。 */ clearValueAfterReadonly?: boolean; - /** Mock 数据 */ - mock?: any; /** 是否隐藏 label。当我们想要保留 label 作为校检的名称,又不想显示 label 时,可以将其设置为 true。 */ hideLabel?: boolean; } @@ -67,7 +65,6 @@ export type OmitWithFormItemProps = InjectedWithFormItemProps & OverwriteWithFor export const omitWithFormItemKeys: Record = { data: '', form: '', - mock: '', active: '', visible: '', activeOn: '', diff --git a/src/form-item/src/hoc/withMock.tsx b/src/form-item/src/hoc/withMock.tsx new file mode 100644 index 0000000..a2e348c --- /dev/null +++ b/src/form-item/src/hoc/withMock.tsx @@ -0,0 +1,65 @@ +import { useCreation } from 'ahooks'; +import type { NamePath } from 'rc-field-form/lib/interface'; +import type { ComponentType, FC} from 'react'; +import { useContext } from 'react'; +import React from 'react'; + +import type { OptionsType } from '@/shared'; +import { castToArray } from '@/shared'; +import { useFormMock } from '../hooks/useFormMock'; +import type { SuperFormContextProps } from '@/form'; +import { SuperFormContext } from '@/form'; + +export type WithMockProps = T & { + // 表单项 name + name?: NamePath; + // 是否隐藏 + hidden?: boolean; + // 是否禁用 + disabled?: boolean; + // 是否只读 + readonly?: boolean + /** Mock 数据 */ + mock?: any; +}; + +// 配置项 +export interface WithMockConfigType { + /** Mock 规则 */ + defaultMockRule?: any; +} + +// eslint-disable-next-line @typescript-eslint/ban-types +export function withMock

(Component: ComponentType

, config: WithMockConfigType) { + const { defaultMockRule } = config + const ComponentWithMock: FC> = (props) => { + const { hidden, readonly, disabled, mock, name, ...resetOptions } = props; + // 表单 context + const formContext = useContext(SuperFormContext); + + // 将 name 转为字符串, ['info', 'name'] => 'info.name' + const nameStr = useCreation(() => { + return castToArray(name).join('.'); + }, [name]); + + // mock 数据相关 + useFormMock({ + mock, + formContext, + disabledMock: !!(readonly || hidden || disabled), + name: nameStr, + defaultMockRule, + props, + }); + return ( +

+
    + {data.map((item) => ( +
  • {item}
  • + ))} +
+
+ + + +
+
+ ); + }; + + const App = () => { + const component$ = useEventEmitter(); + return <> + + + + + } + + test('基础使用', async () => { + render(); + + // 原始数量 + expect(screen.getByTestId('a-data').childElementCount).toBe(0); + expect(screen.getByTestId('b-data').childElementCount).toBe(0); + expect(screen.getByTestId('c-data').childElementCount).toBe(0); + + // 点击,增加 a 一项 + userEvent.click(screen.getByTestId('a-add')); + // 断言 + expect(screen.getByTestId('a-data').childElementCount).toBe(1); + expect(screen.getByTestId('b-data').childElementCount).toBe(0); + expect(screen.getByTestId('c-data').childElementCount).toBe(0); + + // 将 a 的数据同步到 b + userEvent.click(screen.getByTestId('a-update-b')); + + // 断言,则两者数据应该相等 + expect(screen.getByTestId('a-data').childElementCount).toBe(1); + expect(screen.getByTestId('b-data').childElementCount).toBe(1); + expect(screen.getByTestId('c-data').childElementCount).toBe(0); + + // 点击刷新后 b + userEvent.click(screen.getByTestId('a-refresh-b')); + + // 断言,刷新后,b 为 0 + expect(screen.getByTestId('a-data').childElementCount).toBe(1); + expect(screen.getByTestId('b-data').childElementCount).toBe(0); + expect(screen.getByTestId('c-data').childElementCount).toBe(0); + }); + + test('当未设置 target 时,应无变化', () => { + render(); + userEvent.click(screen.getByTestId('b-add')); + expect(screen.getByTestId('a-data').childElementCount).toBe(0); + expect(screen.getByTestId('b-data').childElementCount).toBe(1); + expect(screen.getByTestId('c-data').childElementCount).toBe(0); + + // 点击 b + userEvent.click(screen.getByTestId('b-update-undefined')); + + // 无任何变化 + expect(screen.getByTestId('a-data').childElementCount).toBe(0); + expect(screen.getByTestId('b-data').childElementCount).toBe(1); + expect(screen.getByTestId('c-data').childElementCount).toBe(0); + }) + + test('当未设置 component$ 应该报警告', async () => { + render( + <> + + + , + ); + const errorFn = jest.fn(); + console.warn = errorFn; + const originalError = console.warn; + + userEvent.click(screen.getByTestId('a-add')); + userEvent.click(screen.getByTestId('a-update-b')); + expect(errorFn).toBeCalled(); + console.error = originalError; + }); +}); From b16dbe256481164e154f779f6d717fb93e4a6811 Mon Sep 17 00:00:00 2001 From: zhangchaojie <1098626505@qq.com> Date: Sun, 23 May 2021 23:27:10 +0800 Subject: [PATCH 07/34] =?UTF-8?q?test:=20useOptions=20=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/src/hooks/useOptions/index.ts | 2 +- src/shared/src/hooks/useOptions/useOptions.ts | 6 +- .../useOptions/{getOptions.ts => util.ts} | 0 .../hooks/useOptions/useOptions.test.tsx | 138 ++++++++++++++++++ tests/shared/hooks/useOptions/util.test.ts | 22 +++ 5 files changed, 164 insertions(+), 4 deletions(-) rename src/shared/src/hooks/useOptions/{getOptions.ts => util.ts} (100%) create mode 100644 tests/shared/hooks/useOptions/useOptions.test.tsx create mode 100644 tests/shared/hooks/useOptions/util.test.ts diff --git a/src/shared/src/hooks/useOptions/index.ts b/src/shared/src/hooks/useOptions/index.ts index ead42cf..5bd14e8 100644 --- a/src/shared/src/hooks/useOptions/index.ts +++ b/src/shared/src/hooks/useOptions/index.ts @@ -1,3 +1,3 @@ +export * from './util'; export * from './types'; export * from './useOptions'; -export * from './getOptions'; diff --git a/src/shared/src/hooks/useOptions/useOptions.ts b/src/shared/src/hooks/useOptions/useOptions.ts index a1ec0ac..4103d89 100644 --- a/src/shared/src/hooks/useOptions/useOptions.ts +++ b/src/shared/src/hooks/useOptions/useOptions.ts @@ -6,7 +6,7 @@ import { __DOCS_URL__ } from '../../constants'; import { isArray } from '../../utils'; import type { ApiType } from '../useAxios'; import { useAxios } from '../useAxios'; -import { getOptions } from './getOptions'; +import { getOptions } from './util'; import type { IUseOptions, OptionList } from './types'; export const useOptions = ({ @@ -42,12 +42,12 @@ export const useOptions = ({ const hasRequested = useRef(false); useEffect(() => { if (api && !loading && !hidden) { - if (hasRequested) { + if (!hasRequested.current) { run(); + hasRequested.current = true; } else { refresh(); } - hasRequested.current = true; } // eslint-disable-next-line react-hooks/exhaustive-deps }, [api, data, hidden]); diff --git a/src/shared/src/hooks/useOptions/getOptions.ts b/src/shared/src/hooks/useOptions/util.ts similarity index 100% rename from src/shared/src/hooks/useOptions/getOptions.ts rename to src/shared/src/hooks/useOptions/util.ts diff --git a/tests/shared/hooks/useOptions/useOptions.test.tsx b/tests/shared/hooks/useOptions/useOptions.test.tsx new file mode 100644 index 0000000..0d5dd64 --- /dev/null +++ b/tests/shared/hooks/useOptions/useOptions.test.tsx @@ -0,0 +1,138 @@ +import React, { FC, useState } from 'react' +import { renderHook } from '@testing-library/react-hooks'; +import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { ApiType, IUseOptions, SuperProvider, useOptions } from 'super-antd' +import axios from 'axios'; + +describe('useOptions', () => { + describe('options 为数组', () => { + const setup = (config: IUseOptions) => renderHook(() => useOptions(config)) + test('options 为数组', () => { + const { result } = setup({ options: ['a', 'b'] }) + expect(result.current.loading).toBe(false) + expect(result.current.options).toEqual([{ label: 'a', value: 'a' }, { label: 'b', value: 'b' }]) + }) + + test('optionsProp 存在', () => { + const { result } = setup({ + options: [{ name: 'a', id: 1 }, { name: 'b', id: 2 }], + optionsProp: { labelKey: 'name', valueKey: 'id' } + }) + expect(result.current.loading).toBe(false) + expect(result.current.options).toEqual([{ label: 'a', value: 1 }, { label: 'b', value: 2 }]) + }) + }) + + describe('options 为 API', () => { + + const Demo: FC<{ api: ApiType, hidden?: boolean, data?: object }> = ({ api, hidden, data}) => { + const { options, loading } = useOptions({ + options: api, + hidden, + data + }); + + return ( +
+
{loading ? "true" : "false"}
+
{JSON.stringify(options)}
+
+ ); + }; + + test('正常请求', async () => { + const api = async () => { + return new Promise((resolve) => { + setTimeout(() => { + return resolve(['a', 'b']) + }) + }) + }; + + render( + + ) + expect(screen.getByTestId('loading').textContent).toBe('true') + expect(screen.getByTestId('options').textContent).toBe('[]') + + await waitFor(() => { + expect(screen.getByTestId('loading').textContent).toBe('false') + expect(screen.getByTestId('options').textContent).toBe(JSON.stringify([{label: 'a', value: 'a'}, {label: 'b', value: 'b'}])) + }, { timeout: 1000 }) + }) + + test('返回不为数组时,应发出警告', async () => { + const fn = jest.fn() + const originWarn = console.warn + console.warn = fn + const api = async () => { + return new Promise((resolve) => { + setTimeout(() => { + return resolve({}) + }) + }) + }; + + render( + + ) + expect(screen.getByTestId('loading').textContent).toBe('true') + expect(screen.getByTestId('options').textContent).toBe('[]') + + await waitFor(() => { + expect(fn).toBeCalled() + expect(screen.getByTestId('loading').textContent).toBe('false') + expect(screen.getByTestId('options').textContent).toBe('[]') + }) + + console.warn = originWarn + }) + + test('当 hidden 为 true 时,不请求', async () => { + const api = async () => { + return new Promise((resolve) => { + setTimeout(() => { + return resolve(['a', 'b']) + }) + }) + }; + + render( + ) + + expect(screen.getByTestId('loading').textContent).toBe('false') + expect(screen.getByTestId('options').textContent).toBe('[]') + }) + + test('当依赖项发生时,重新请求', async () => { + const api = async () => { + return new Promise((resolve) => { + setTimeout(() => { + return resolve(['a', 'b']) + }) + }) + }; + + const App = () => { + const [data, setData] = useState({}) + return + + + + } + render() + + expect(screen.getByTestId('loading').textContent).toBe('true') + + await waitFor(() => { + expect(screen.getByTestId('loading').textContent).toBe('false') + }) + + fireEvent.click(screen.getByTestId('setdata')) + await waitFor(() => { + expect(screen.getByTestId('loading').textContent).toBe('true') + }) + }) + }) +}) \ No newline at end of file diff --git a/tests/shared/hooks/useOptions/util.test.ts b/tests/shared/hooks/useOptions/util.test.ts new file mode 100644 index 0000000..ef97d12 --- /dev/null +++ b/tests/shared/hooks/useOptions/util.test.ts @@ -0,0 +1,22 @@ +import { getOptions } from 'super-antd'; + +describe('useOptions utils', () => { + test('options 默认值', () => { + expect(getOptions()).toEqual([]) + }) + test('options 为字符串数组', () => { + expect(getOptions(['a', 'b'])).toEqual([{ label: 'a', value: 'a' }, { label: 'b', value: 'b' }]) + }) + test('options 为数字数组', () => { + expect(getOptions([1, 2])).toEqual([{ label: 1, value: 1 }, { label: 2, value: 2 }]) + }) + test('options 为对象数组', () => { + expect(getOptions([{ label: 'a', value: 1 }, { label: 'b', value: 2 }])).toEqual([{ label: 'a', value: 1 }, { label: 'b', value: 2 }]) + }) + test('options 为混合数组', () => { + expect(getOptions([{ label: 'a', value: 1 }, 2, 'c'])).toEqual([{ label: 'a', value: 1 }, { label: 2, value: 2 }, { label: 'c', value: 'c' }]) + }) + test('optionsProp 存在', () => { + expect(getOptions([{ name: 'a', id: 1 }, { name: 'b', id: 2 }], { labelKey: 'name', valueKey: 'id' })).toEqual([{ label: 'a', value: 1 }, { label: 'b', value: 2 }]) + }) +}); \ No newline at end of file From 1b01b44e4baf0d922d990cb3c1d7ee0b60c6105b Mon Sep 17 00:00:00 2001 From: zhangchaojie <1098626505@qq.com> Date: Wed, 26 May 2021 22:49:40 +0800 Subject: [PATCH 08/34] =?UTF-8?q?docs:=20=E6=9B=B4=E6=94=B9=20readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/workflow.yml | 14 +++++--------- README.md | 33 ++++++++++++++++++++++++++++----- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 84b0c74..10d9dfd 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -11,15 +11,11 @@ jobs: steps: - uses: actions/checkout@v2 - run: yarn - # - run: yarn test:coverage - # - name: Upload coverage to Codecov - # uses: codecov/codecov-action@v1 - # with: - # files: ./coverage/clover.xml - # directory: ./coverage/lcov-report/ - # name: codecov-umbrella - # fail_ci_if_error: true - # verbose: true + - run: yarn test:coverage + - name: Codecov + uses: codecov/codecov-action@v1 + with: + token: ${{ secrets.CODECOV_TOKEN }} - run: yarn docs:build - name: Deploy uses: peaceiris/actions-gh-pages@v3 diff --git a/README.md b/README.md index e4de9ec..8f733e0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,20 @@ -# super-antd + +

super-antd

+
+ +[![Codecov](https://img.shields.io/codecov/c/github/dream2023/super-antd?token=BKaeenZ4wi)](https://codecov.io/gh/dream2023/super-antd) +[![GitHub Discussions](https://img.shields.io/github/discussions/dream2023/super-antd?label=吐槽/讨论)](https://github.com/dream2023/super-antd/discussions) +[![文档](https://img.shields.io/static/v1?label=Docs&message=文档&color=blue)](https://dream2023.github.io/super-antd) +[![GitHub Repo stars](https://img.shields.io/github/stars/dream2023/super-antd)](https://github.com/dream2023/super-antd) +[![star](https://gitee.com/dream2023/super-antd/badge/star.svg?theme=dark)](https://gitee.com/dream2023/super-antd) + +[![npm](https://img.shields.io/npm/v/super-antd)](https://www.npmjs.com/package/super-antd) +[![npm](https://img.shields.io/npm/dt/super-antd)](https://www.npmjs.com/package/super-antd) +[![David](https://img.shields.io/david/dream2023/super-antd)](https://github.com/dream2023/super-antd) +[![GitHub top language](https://img.shields.io/github/languages/top/dream2023/super-antd)](https://github.com/dream2023/super-antd) +[![github pages](https://github.com/dream2023/super-antd/actions/workflows/workflow.yml/badge.svg)](https://github.com/dream2023/super-antd/actions/workflows/workflow.yml) + +
`super-antd` 是一个基于 [ant design](https://ant.design/) 和 [pro-components](https://procomponents.ant.design/) 的数据驱动友好、易用且强大的 UI 库。 @@ -13,7 +29,14 @@ ## 🎯 Roadmap -- [x] 0.1 alpha 版:完成框架基础(schema 渲染、组件通信、数据请求、数据联动)和表单组件(表单组件、表单项组件、内置表单组件) +- [x] 0.1 alpha 版 + - [x] [数据模板](https://dream2023.github.io/super-antd/guide/concept/template) + - [x] [数据联动](https://dream2023.github.io/super-antd/guide/concept/linkage) + - [x] [数据请求](https://dream2023.github.io/super-antd/guide/concept/api) + - [x] [schema 动态渲染](https://dream2023.github.io/super-antd/guide/concept/schema) + - [x] [表单组件](https://dream2023.github.io/super-antd/components/form) + - [x] [表单项组件](https://dream2023.github.io/super-antd/components/form/form-item) + - [x] [内置表单组件](https://dream2023.github.io/super-antd/components/form/form-components) - [ ] 0.1 正式版:测试覆盖率 80% 以上、完成 100% 文档、0️⃣ issue - [ ] 0.2 版:增加各种扩展的表单组件(富文本组件、地图组件、上传组件、代码编辑器组件...),覆盖 95% 表单组件场景 - [ ] 0.3 版:增加各种与表单关联的组件(Dialog 弹窗组件、Tabs 选项卡组件...) @@ -26,7 +49,7 @@ ## 👬 Ecosystem -其实 `super-antd` 仅是一个大系统中的一部分,我个人是希望做一个支 **\*持海量组件的高扩展通用型** 的低代码平台,此平台包括以下组件部分: +其实 `super-antd` 仅是一个大系统中的一部分,我个人是希望做一个支 **持海量组件的高扩展通用型** 的低代码平台,此平台包括以下组件部分: | Project | Status | Description | | --- | --- | --- | @@ -42,8 +65,8 @@ 虽然要做的东西很多,但整体思路还是很清晰的,期待都实现的一天。 -## Support +## support -如果你觉得项目对自己和公司有用,请跳转到 [gitee](https://gitee.com/dream2023/super-antd#Support) 底部进行打赏,并且可以多留言鼓励作者一下,你的支持就是我更新的动力! +开源不易,如果你觉得项目对自己和公司有用,请跳转到 [gitee](https://gitee.com/dream2023/super-antd#Support) 底部进行打赏,并且可以多留言鼓励作者一下,你的支持就是我更新的动力! [![reward](./reward.png)](https://gitee.com/dream2023/super-antd#support) From 707fd6c55193cb155d4b3e93ea346db19a13dc9e Mon Sep 17 00:00:00 2001 From: zhangchaojie <1098626505@qq.com> Date: Thu, 27 May 2021 22:54:16 +0800 Subject: [PATCH 09/34] test: useResponsiveCol --- .../src/hooks/useResponsiveCol/index.ts | 2 +- tests/shared/hooks/useResponsiveCol.test.tsx | 67 +++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 tests/shared/hooks/useResponsiveCol.test.tsx diff --git a/src/shared/src/hooks/useResponsiveCol/index.ts b/src/shared/src/hooks/useResponsiveCol/index.ts index 385ea3d..96d9a6b 100644 --- a/src/shared/src/hooks/useResponsiveCol/index.ts +++ b/src/shared/src/hooks/useResponsiveCol/index.ts @@ -77,8 +77,8 @@ export const useResponsiveCol = ({ // 获取响应式的 LabelCol const responsiveLabelCol = useCreation(() => { + if (hideLabel) return { span: 0 }; if (labelCol) return getCol(labelCol); - if (hideLabel && labelCol === undefined) return { span: 0 }; return point ? { span: labelAndWrapperCol[point].labelCol } : undefined; }, [point, labelCol, hideLabel]); diff --git a/tests/shared/hooks/useResponsiveCol.test.tsx b/tests/shared/hooks/useResponsiveCol.test.tsx new file mode 100644 index 0000000..d3972c2 --- /dev/null +++ b/tests/shared/hooks/useResponsiveCol.test.tsx @@ -0,0 +1,67 @@ +import { renderHook } from '@testing-library/react-hooks'; +import { useResponsiveCol, ResponsiveColOptions } from 'super-antd' + +const setup = (options: ResponsiveColOptions) => renderHook(() => useResponsiveCol(options)) + +// 因为 node 环境下,无法实现 useRef,所以动态变化的无法测试,后面可以使用 e2e 测试 +describe('useResponsiveCol', () => { + describe('shouldResponsive', () => { + test('默认情况为 true', () => { + const { result } = setup({}) + expect(result.current.shouldResponsive).toBeTruthy() + }) + test('isResponsive 为 false,则为 false', () => { + const { result } = setup({ isResponsive: false }) + expect(result.current.shouldResponsive).toBeFalsy() + }) + test('labelCol 为存在,则为 false', () => { + const { result } = setup({ labelCol: 9 }) + expect(result.current.shouldResponsive).toBeFalsy() + }) + test('wrapperCol 为存在,则为 false', () => { + const { result } = setup({ wrapperCol: 9 }) + expect(result.current.shouldResponsive).toBeFalsy() + }) + test('layout 非 horizontal,则为 false', () => { + const { result } = setup({ layout: 'inline' }) + expect(result.current.shouldResponsive).toBeFalsy() + }) + test('align 非 left,则为 false', () => { + const { result } = setup({ align: 'center' }) + expect(result.current.shouldResponsive).toBeFalsy() + }) + }) + + describe('responsiveRef', () => { + test('当 shouldResponsive 为 true 时,应为 ref', () => { + const { result } = setup({}) + expect(result.current.shouldResponsive).toBeTruthy() + expect(result.current.responsiveRef).toEqual({current: null}) + }) + + test('当 shouldResponsive 为 false 时,应为 null', () => { + const { result } = setup({isResponsive: false}) + expect(result.current.shouldResponsive).toBeFalsy() + expect(result.current.responsiveRef).toEqual(null) + }) + }) + + describe('responsiveLabelCol', () => { + test('当 labelCol 参数存在时,应返回 labelCol 的值', () => { + const { result } = setup({ labelCol: 9 }) + expect(result.current.responsiveLabelCol).toEqual({span: 9}) + }) + + test('当 hideLabel 参数存在时,应返回 { span: 0 }', () => { + const { result } = setup({ hideLabel: true, labelCol: 9 }) + expect(result.current.responsiveLabelCol).toEqual({ span: 0 }) + }) + }); + + describe('responsiveWrapperCol', () => { + test('当 wrapperCol 参数存在时,应返回 wrapperCol 的值', () => { + const { result } = setup({ wrapperCol: 9 }) + expect(result.current.responsiveWrapperCol).toEqual({span: 9}) + }) + }); +}) \ No newline at end of file From aeb547987ac705cedc9f88fff6655d298bb8b921 Mon Sep 17 00:00:00 2001 From: zhangchaojie <1098626505@qq.com> Date: Sun, 30 May 2021 09:27:31 +0800 Subject: [PATCH 10/34] =?UTF-8?q?test:=20useAxios=20=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .eslintrc.js | 1 + package.json | 2 +- src/shared/src/hooks/useAxios/types.ts | 2 +- src/shared/src/hooks/useAxios/util.ts | 66 +++++--- src/shared/src/utils/is.ts | 2 +- src/shared/src/utils/util.ts | 2 +- tests/shared/hooks/useAxios/useAxios.test.tsx | 38 +++++ tests/shared/hooks/useAxios/util.test.ts | 151 ++++++++++++++++++ 8 files changed, 234 insertions(+), 30 deletions(-) create mode 100644 tests/shared/hooks/useAxios/useAxios.test.tsx create mode 100644 tests/shared/hooks/useAxios/util.test.ts diff --git a/.eslintrc.js b/.eslintrc.js index bcdeb8b..d34b822 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -3,5 +3,6 @@ module.exports = { rules: { 'import/no-extraneous-dependencies': 0, 'import/no-unresolved': 0, + 'prefer-object-spread': 0 }, }; diff --git a/package.json b/package.json index cd6e1d3..1eeeb3e 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "lint": "eslint --cache --ext .js,.jsx,.ts,.tsx --fix --format=pretty ./src && npm run lint:prettier", "lint:prettier": "npm run prettier && git diff && prettier --version && prettier --check \"packages/**/**.{js,jsx,tsx,ts,less,md,json}\" --end-of-line auto", "prettier": "prettier --write \"**/**.{js,jsx,tsx,ts,less,md,json}\"", - "test": "umi-test", + "test": "umi-test --watch", "test:coverage": "umi-test --coverage", "tsc": "tsc --noEmit", "update:deps": "yarn upgrade-interactive --latest" diff --git a/src/shared/src/hooks/useAxios/types.ts b/src/shared/src/hooks/useAxios/types.ts index 1315211..a96a6a6 100644 --- a/src/shared/src/hooks/useAxios/types.ts +++ b/src/shared/src/hooks/useAxios/types.ts @@ -21,7 +21,7 @@ export type ApiFunction = ( data: Record, params: Record, contextData: Record, -) => Promise; +) => any; export type ApiType = string | SuperAxiosRequestConfig | ApiFunction; // 请求信息 diff --git a/src/shared/src/hooks/useAxios/util.ts b/src/shared/src/hooks/useAxios/util.ts index 7e546bf..9a8ab25 100644 --- a/src/shared/src/hooks/useAxios/util.ts +++ b/src/shared/src/hooks/useAxios/util.ts @@ -17,21 +17,21 @@ import type { * 将任意类型转为对象 * * @example - * castToObj(null); //=> {} - * castToObj({ name: '123' }, 'data', true); // => {data: {name: '123'}} - * castToObj({ name: '123' }, 'data'); // { name: '123' } + * castToObj('foo', null); //=> {} + * castToObj('foo', 123); // { foo: 123 } + * castToObj('foo', { name: 'a' }); // { name: 'a' } * * @param val 要转化的值 * @param key 要转化值的 key - * @param forceSetKey 当为对象时,是否强制设置 key */ -export function castToObj(val?: any, key?: string, forceSetKey?: boolean) { +export function castToObj(key: string, val?: any): Record { if (isNil(val)) return {}; - if (val instanceof Object) return key && forceSetKey ? { [key]: val } : val; - return { [key || val]: val }; + if (typeof val === 'object') return val; + return { [key]: val }; } -export function changeObjToUndefined(obj?: Record) { +// 将对象的值转为 undefined +export function changeObjValueToUndefined(obj?: Record) { if (!obj) return {}; return Object.keys(obj).reduce((acc: Record, key) => { acc[key] = undefined; @@ -46,17 +46,26 @@ interface FormatResultOptions { currentData?: Record; } export function processFormatResult({ api, currentData = {}, delimiters }: FormatResultOptions) { - return (response: T) => { + return (response?: T) => { + // 当 api 为函数类型时,则确定没有 responseSchema,则直接返回 if (isFunction(api)) return response; - const { response: responseSchema, replaceData } = castToObj(api, 'url'); + + // 当 api 为其他类型,转为对象 + const { response: responseSchema, replaceData } = castToObj('url', api); let data = response; + + // 如果 response 为对象,则需要替换数据 if (isPlainObject(response) && replaceData) { - const changedCurrentData = - replaceData && isPlainObject(response) ? changeObjToUndefined(currentData) : currentData; + // 当原来对象的值都要转为 undefined + const changedCurrentData = changeObjValueToUndefined(currentData) + // 融合两者 data = { ...changedCurrentData, ...response }; } - return responseSchema ? getSchemaData({ schema: responseSchema, data, delimiters, defaultValue: 'data' }) : data; + if (responseSchema) { + return getSchemaData({ schema: responseSchema, data: isNil(data) ? {} : data, delimiters, defaultValue: 'data' }) + } + return data; }; } @@ -103,14 +112,14 @@ export function processSuccess({ onSuccessNotify, message, onSuccess }: SuccessO /** 获取 axios 配置 主要功能是转化 data 和 params */ interface GetAxiosOptions { api?: ApiType; - contextData: Record; - data: Record; - params: Record; + contextData?: Record; + data?: Record; + params?: Record; delimiters?: [string, string]; } -export function getAxiosOptions({ api, contextData, data, params, delimiters }: GetAxiosOptions) { - const axiosApi = castToObj(api, 'url'); +export function getAxiosOptions({ api, contextData = {}, data = {}, params = {}, delimiters }: GetAxiosOptions) { + const axiosApi = castToObj('url', api); const { url, data: dataSchema, @@ -149,20 +158,25 @@ interface ServiceFnOptions { export function serviceFn({ api, - contextData = {}, - defaultData = {}, - defaultParams = {}, + contextData, + defaultData, + defaultParams, delimiters, }: ServiceFnOptions) { - return (data: Record = {}, params: Record = {}, others: Record = {}) => { + return (data?: Record, params?: Record, others?: Record) => { + // 因为 data 和 defaultData 可能为空,所以使用 Object.assign,用解构需要判空 + const dataOption = Object.assign({}, defaultData, data) + const paramOption = Object.assign({}, defaultParams, params) + const contextOption = Object.assign({}, contextData, others) + if (isFunction(api)) { - return api(data, params, { contextData, ...others }); + return Promise.resolve(api(dataOption, paramOption, contextOption)); } return getAxiosOptions({ api, - contextData: { ...contextData, ...others }, - data: { ...defaultData, ...data }, - params: { ...defaultParams, ...params }, + data: dataOption, + params: Object.assign({}, defaultParams, params), + contextData: paramOption, delimiters, }); }; diff --git a/src/shared/src/utils/is.ts b/src/shared/src/utils/is.ts index 79e37a8..7196701 100644 --- a/src/shared/src/utils/is.ts +++ b/src/shared/src/utils/is.ts @@ -12,7 +12,7 @@ export const isPlainObject = (val: unknown): val is Record => checkTyp // 是否为对象 // eslint-disable-next-line @typescript-eslint/ban-types -export const isObject = (val: unknown): val is object => typeof val === 'object'; +export const isObject = (val: unknown): val is object => val instanceof Object; // 是否为 undefined export const isUndefined = (val: unknown): val is undefined => { diff --git a/src/shared/src/utils/util.ts b/src/shared/src/utils/util.ts index e716c15..c74c617 100644 --- a/src/shared/src/utils/util.ts +++ b/src/shared/src/utils/util.ts @@ -44,7 +44,7 @@ export function nextTick(fn: () => void) { // 空函数 export type NoopType = () => void; -export const NOOP: NoopType = () => { }; +export const noop: NoopType = () => { }; export const toPathArr = (path: Key | Key[]) => isArray(path) diff --git a/tests/shared/hooks/useAxios/useAxios.test.tsx b/tests/shared/hooks/useAxios/useAxios.test.tsx new file mode 100644 index 0000000..b506cc2 --- /dev/null +++ b/tests/shared/hooks/useAxios/useAxios.test.tsx @@ -0,0 +1,38 @@ +import React, { FC } from 'react' +import { render, waitFor } from '@testing-library/react'; +import { renderHook } from '@testing-library/react-hooks'; +import { AxiosHooksOptions, useAxios, SuperProvider } from 'super-antd' +import axios from 'axios'; + +const Demo: FC = (props) => { + const { loading, data } = useAxios(props) + if (loading) return
loading
+ if (!data) return
empty
+ return
{ JSON.stringify(data) }
+} + +describe('useAxios', () => { + test('当 api 存在,且 axios 不存在时,应报警告', () => { + const warn = jest.fn() + const originWarn = console.warn + console.warn = warn + renderHook(() => useAxios({ api: '/user' })) + expect(warn).toBeCalled() + console.warn = originWarn + }) + + test('当 api 不存在时,应该 loading 应为 false', () => { + const warpper = render() + expect(warpper.queryByTestId('loading')).not.toBeInTheDocument() + expect(warpper.getByTestId('empty')).toBeInTheDocument() + }) + + test('正常请求', async () => { + const api = () => ({ a: 'a' }) + const warpper = render() + expect(warpper.getByTestId('loading')).toBeInTheDocument() + await waitFor(() => { + expect(warpper.queryByTestId('data')).toHaveTextContent(JSON.stringify({ a: 'a' })) + }) + }) +}); \ No newline at end of file diff --git a/tests/shared/hooks/useAxios/util.test.ts b/tests/shared/hooks/useAxios/util.test.ts new file mode 100644 index 0000000..556bd6b --- /dev/null +++ b/tests/shared/hooks/useAxios/util.test.ts @@ -0,0 +1,151 @@ +import { ApiType, castToObj, changeObjValueToUndefined, processError, processSuccess, processFormatResult, getAxiosOptions, serviceFn } from "super-antd"; + +describe('useAxios util', () => { + test('castToObj', () => { + expect(castToObj('foo', null)).toEqual({}) + expect(castToObj('foo', undefined)).toEqual({}) + expect(castToObj('foo', { a: 'a' })).toEqual({ a: 'a' }) + expect(castToObj('foo', castToObj)).toEqual({ foo: castToObj }) + expect(castToObj('foo', 'bar')).toEqual({ foo: 'bar' }) + expect(castToObj('foo', 123)).toEqual({ foo: 123 }) + }) + + test('changeObjValueToUndefined', () => { + expect(changeObjValueToUndefined()).toEqual({}) + expect(changeObjValueToUndefined({ name: 'foo', bar: 123 })).toEqual({ name: undefined, bar: undefined }) + }) + + describe('processFormatResult', () => { + test('当 api 为函数时,应无需处理,直接返回', () => { + const api: ApiType = () => new Promise((resolve) => resolve({ name: 'foo' })) + const fn = processFormatResult<{ name: string }>({ api }) + const response = { name: 'jack' } + expect(fn(response)).toBe(response) + }) + + test('api.replaceData 为 true 时,应替换 currentData 数据', () => { + const api: ApiType = { url: 'https://foo.com', replaceData: true } + const fn = processFormatResult({ api, currentData: { a: 'a', b: 'b' } }) + expect(fn({ c: 'c' })).toEqual({ a: undefined, b: undefined, c: 'c' }) + }) + + test('api.response 存在,则进行映射', () => { + const api: ApiType = { url: 'https://foo.com', response: { myName: '{{data.name}}' } } + const fn = processFormatResult({ api }) + expect(fn({ name: 'foo' })).toEqual({ myName: 'foo' }) + expect(fn('a')).toEqual({ myName: undefined }) + expect(fn()).toEqual({ myName: undefined }) + expect(fn(null)).toEqual({ myName: undefined }) + expect(fn(['a', 'b'])).toEqual({ myName: undefined }) + }) + }) + + test('processError', () => { + const onErrorNotify = jest.fn() + const onError = jest.fn() + const fn = processError({ onErrorNotify, onError, message: { error: 'message error' } }) + const err = new Error('foo') + const params = 'params' + fn(err, params) + expect(onErrorNotify).toBeCalledWith('message error', err, params) + expect(onError).toBeCalledWith(err, params) + + const noParamsFn = processError({}) + expect(() => noParamsFn(new Error(), {})).not.toThrowError() + }) + + + test('processSuccess', () => { + const onSuccessNotify = jest.fn() + const onSuccess = jest.fn() + const fn = processSuccess({ onSuccessNotify, onSuccess, message: { success: 'request success' } }) + const data = 'data' + const params = 'params' + fn(data, params) + expect(onSuccessNotify).toBeCalledWith('request success', data, params) + expect(onSuccess).toBeCalledWith(data, params) + + const noParamsFn = processSuccess({}) + expect(() => noParamsFn({}, {})).not.toThrowError() + }) + + describe('getAxiosOptions', () => { + test('当 api 为字符串时,应解析为对象', () => { + expect(getAxiosOptions({ api: 'https://foo.com' })).toEqual({ + url: 'https://foo.com', + data: undefined, + params: undefined + }) + }) + + test('当 api 为对象时,应保留原 axios 配置内容', () => { + const api = { url: '/user', baseURL: 'https://foo.com' } + expect(getAxiosOptions({ api })).toEqual({ url: '/user', baseURL: 'https://foo.com', data: undefined, params: undefined }) + }) + + test('dataSchema 存在时,应进行映射', () => { + const api: ApiType = { + url: 'https://foo.com', + data: { + myName: '{{data.name}}', + myAge: '{{data.age}}' + } + } + + expect(getAxiosOptions({ api, data: { name: 'foo' }, contextData: { age: 18 } })).toEqual({ + url: 'https://foo.com', + data: { + myName: 'foo', + myAge: 18 + }, + params: undefined + }) + }) + + test('params 存在时,应进行映射', () => { + const api: ApiType = { + url: 'https://foo.com', + params: { + myName: '{{data.name}}', + myAge: '{{data.age}}' + } + } + + expect(getAxiosOptions({ api, params: { name: 'foo' }, contextData: { age: 18 } })).toEqual({ + url: 'https://foo.com', + params: { + myName: 'foo', + myAge: 18 + }, + data: undefined + }) + }) + + test('当无 schema 时,返回原 data 和 params 数据', () => { + expect(getAxiosOptions({ api: 'https://foo.com', data: { a: 'a' }, params: { b: 'b' } })).toEqual({ + url: 'https://foo.com', + data: { a: 'a' }, + params: { b: 'b' } + }) + }) + }); + + describe('serviceFn', () => { + test('api 为函数类型,则返回 api 函数', async () => { + const api: ApiType = (data, params, contextData) => Promise.resolve({ data, params, contextData }) + const request = serviceFn({ api, defaultData: { a: 'a' }, defaultParams: { m: 'm' }, contextData: { z: 'z' } }) + const res = await request({ b: 'b' }, { n: 'n' }, { y: 'y' }) + expect(res).toEqual({ + data: { a: 'a', b: 'b' }, + params: { m: 'm', n: 'n' }, + contextData: { y: 'y', z: 'z' } + }) + }) + + test('api 为其他类型,则返回 getAxiosOptions', () => { + const requestFn = serviceFn({ api: 'https://foo.com' }) + expect(requestFn()).toEqual({ url: 'https://foo.com' }) + }) + }) +}); + From 516938b459f40d350e8c16854982c5b18a47a172 Mon Sep 17 00:00:00 2001 From: zhangchaojie <1098626505@qq.com> Date: Sun, 30 May 2021 10:55:07 +0800 Subject: [PATCH 11/34] =?UTF-8?q?docs:=20=E6=9B=B4=E6=96=B0=E6=96=87?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 8f733e0..d3d8786 100644 --- a/README.md +++ b/README.md @@ -24,8 +24,23 @@ ## ✨ Feature -- 数据驱动友好:通过在组件层级解决动态渲染、通信、联动等难题,极大增加低代码场景的灵活度 -- 简单、易用:通过对业务场景的提炼,进行了大量组件的组织易封装,做到了比已知的任何 ant design 封装库简单 +- 数据驱动友好:通过在组件层级解决动态渲染、通信、联动等难题; +- 简单、易用:通过对业务场景的提炼,进行大量业务场景的封装; +- 稳定:Typescript 编写 + 高测试覆盖率; + +## Why? + +业界尤其是一线程序员关于低代码有很多负面评价,例如**行业毒瘤**、**拖拽一时爽,重构火葬场**等。 + +这些负面评价当然不是空穴来风,而是由于很多低代码系统在开始做时经验不足,导致整体架构**灵活性、扩展性差**,随着业务发展,低代码平台已无法满足个性化需求,无奈之下,只能从头写一遍。 + +所以低代码平台的核心在于其**灵活性**,那如何尽可能增加其灵活性呢? + +很多低代码系统走了弯路,在低代码编辑器上做各种骚操作,例如各种钩子的注入,让用户在界面上写恶心人的函数等,这样的最终后果就是上层编辑器越来越臃肿,并最终难以适应需求的变化而废弃。 + +`super-antd` 则给出了一条不一样的路,在组件层级解决灵活性的问题,例如 `联动`、`数据映射`、`schema 渲染`、`组件间通信`,这样上层的低代码编辑器只是一层简单的封装和组合,并无复杂的逻辑。 + +而且 `super-antd` 本身其实就是一个普通的 `antd` 组件库,当低代码编辑器真的无法满足需求而需要重构,导出的代码也和程序员真实开发时写的一样,这样就极大的降低重构的风险和成本,这就是为什么要写 `super-antd`。 ## 🎯 Roadmap @@ -59,9 +74,9 @@ 此外至少还有以下系统待开发: -- 基于 `super-antd` 属性面板系统 -- 组件库无关的低代码编辑器 -- 一个开放性的组件市场 +- 基于 `super-antd` 属性面板系统(时间点:组件库发布 0.3 版后) +- 组件库无关的低代码编辑器(时间点:属性面板完成后) +- 一个开放性的组件市场(时间点:低代码编辑器完成后) 虽然要做的东西很多,但整体思路还是很清晰的,期待都实现的一天。 From 31e9a8363a3c2fcaf00346a38ac86984a3a39edf Mon Sep 17 00:00:00 2001 From: zhangchaojie <1098626505@qq.com> Date: Sun, 30 May 2021 11:01:42 +0800 Subject: [PATCH 12/34] =?UTF-8?q?ci:=20=E6=9B=B4=E6=94=B9=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=20gitee=20=E7=AD=96=E7=95=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/gitee.yml | 22 ++++++++++++++++++++++ .github/workflows/workflow.yml | 14 -------------- 2 files changed, 22 insertions(+), 14 deletions(-) create mode 100644 .github/workflows/gitee.yml diff --git a/.github/workflows/gitee.yml b/.github/workflows/gitee.yml new file mode 100644 index 0000000..7465c1a --- /dev/null +++ b/.github/workflows/gitee.yml @@ -0,0 +1,22 @@ +name: Sync to Gitee + +on: [ push, delete, create ] + +jobs: + deploy: + runs-on: ubuntu-18.04 + steps: + - name: Sync to Gitee + uses: wearerequired/git-mirror-action@master + env: + SSH_PRIVATE_KEY: ${{ secrets.GITEE_RSA_PRIVATE_KEY }} + with: + source-repo: git@github.com:dream2023/super-antd.git + destination-repo: git@gitee.com:dream2023/super-antd.git + - name: Build Gitee Pages + uses: yanglbme/gitee-pages-action@main + with: + gitee-username: dream2023 + gitee-password: ${{ secrets.GITEE_PASSWORD }} + gitee-repo: dream2023/super-antd + branch: main \ No newline at end of file diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 10d9dfd..44c08ad 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -22,17 +22,3 @@ jobs: with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./docs-dist - - name: Sync to Gitee - uses: wearerequired/git-mirror-action@master - env: - SSH_PRIVATE_KEY: ${{ secrets.GITEE_RSA_PRIVATE_KEY }} - with: - source-repo: git@github.com:dream2023/super-antd.git - destination-repo: git@gitee.com:dream2023/super-antd.git - - name: Build Gitee Pages - uses: yanglbme/gitee-pages-action@main - with: - gitee-username: dream2023 - gitee-password: ${{ secrets.GITEE_PASSWORD }} - gitee-repo: dream2023/super-antd - branch: main \ No newline at end of file From 19144f6ec4e540f58864bca2b558bd8432d1fc1f Mon Sep 17 00:00:00 2001 From: zhangchaojie <1098626505@qq.com> Date: Sun, 30 May 2021 15:37:11 +0800 Subject: [PATCH 13/34] =?UTF-8?q?test:=20render=20=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- jest.config.js | 2 +- src/render/src/classNameParser.tsx | 4 +- src/render/src/schemaApiParser.tsx | 13 ++++-- src/shared/src/hooks/useAxios/util.ts | 10 ++++- tests/render/classNameParser.test.ts | 20 +++++++++ tests/render/componentPropsParser.test.ts | 13 ++++++ tests/render/schemaApiParser.test.tsx | 51 +++++++++++++++++++++++ tests/utils.ts | 10 +++++ 9 files changed, 117 insertions(+), 8 deletions(-) create mode 100644 tests/render/classNameParser.test.ts create mode 100644 tests/render/componentPropsParser.test.ts create mode 100644 tests/render/schemaApiParser.test.tsx create mode 100644 tests/utils.ts diff --git a/README.md b/README.md index d3d8786..ad196f4 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ - 简单、易用:通过对业务场景的提炼,进行大量业务场景的封装; - 稳定:Typescript 编写 + 高测试覆盖率; -## Why? +## 🤔 Why? 业界尤其是一线程序员关于低代码有很多负面评价,例如**行业毒瘤**、**拖拽一时爽,重构火葬场**等。 diff --git a/jest.config.js b/jest.config.js index ef8a1d5..9ecff8e 100644 --- a/jest.config.js +++ b/jest.config.js @@ -3,7 +3,7 @@ const { join } = require('path'); module.exports = { setupFilesAfterEnv: ['./scripts/setupTests.js'], moduleNameMapper: { - '@/': join(__dirname, './src/'), + '^@/(.*)': '/src/$1', 'super-antd': join(__dirname, './src/index.ts'), }, roots: [ diff --git a/src/render/src/classNameParser.tsx b/src/render/src/classNameParser.tsx index a8e41bb..b15dbb5 100644 --- a/src/render/src/classNameParser.tsx +++ b/src/render/src/classNameParser.tsx @@ -1,10 +1,10 @@ import classnames from 'classnames'; -import { isObject, isString } from '@/shared'; +import { isObject, isPlainObject } from '@/shared'; // 解析器 export const classNameParser = (schema: Record) => { - if (isString(schema.component) && isObject(schema.className)) { + if (isPlainObject(schema) && isObject(schema.className)) { return { ...schema, className: classnames(schema.className) }; } return schema; diff --git a/src/render/src/schemaApiParser.tsx b/src/render/src/schemaApiParser.tsx index 2388a44..76f4d7c 100644 --- a/src/render/src/schemaApiParser.tsx +++ b/src/render/src/schemaApiParser.tsx @@ -2,9 +2,13 @@ import { Skeleton } from 'antd'; import type { FC, Key } from 'react'; import React from 'react'; import { SchemaRender } from 'react-schema-render'; +import warning from 'tiny-warning' -import type { ApiType } from '@/shared'; +import type { ApiType} from '@/shared'; +import { isArray, isPlainObject } from '@/shared'; import { useAxios } from '@/shared'; +import { __DOCS_URL__ } from '@/shared/src/constants'; + /** 从远程加载组件 */ export const SchemaApiToComponent: FC<{ api?: ApiType }> = ({ api }) => { @@ -14,9 +18,12 @@ export const SchemaApiToComponent: FC<{ api?: ApiType }> = ({ api }) => { } if (data) { - return ; - } + if (isArray(data) || isPlainObject(data)) { + return ; + } + warning(false, `[super-antd]: 远程 schema 数据类型不正确,期待为 Object 或者 Array 类型,实际数据为 ${JSON.stringify(data)},具体说明请看 ${__DOCS_URL__}/super-antd/guide/concept/schema` ) + } return null; }; diff --git a/src/shared/src/hooks/useAxios/util.ts b/src/shared/src/hooks/useAxios/util.ts index 9a8ab25..3bba83f 100644 --- a/src/shared/src/hooks/useAxios/util.ts +++ b/src/shared/src/hooks/useAxios/util.ts @@ -170,7 +170,15 @@ export function serviceFn({ const contextOption = Object.assign({}, contextData, others) if (isFunction(api)) { - return Promise.resolve(api(dataOption, paramOption, contextOption)); + const res = api(dataOption, paramOption, contextOption) + if (res instanceof Promise) return res + + // 符合 useRequest 返回值 + return new Promise((resolve) => { + setTimeout(() => { + resolve(res) + }); + }) } return getAxiosOptions({ api, diff --git a/tests/render/classNameParser.test.ts b/tests/render/classNameParser.test.ts new file mode 100644 index 0000000..6bfb688 --- /dev/null +++ b/tests/render/classNameParser.test.ts @@ -0,0 +1,20 @@ +import { classNameParser } from '@/render/src/classNameParser' + +describe('classNameParser', () => { + test('当 className 不存在时,直接返回', () => { + const schema = { + a: 'a' + } + expect(classNameParser(schema)).toBe(schema) + }) + + test('当 className 存在时,使用 classname', () => { + const schema = { + className: [{ + a: true, + b: false, + }, 'foo'] + } + expect(classNameParser(schema)).toEqual({ className: 'a foo' }) + }) +}); \ No newline at end of file diff --git a/tests/render/componentPropsParser.test.ts b/tests/render/componentPropsParser.test.ts new file mode 100644 index 0000000..4e73344 --- /dev/null +++ b/tests/render/componentPropsParser.test.ts @@ -0,0 +1,13 @@ +import { componentPropsParser } from 'super-antd' + +describe('componentPropsParser', () => { + test('未设置默认值的情况,应直接返回 schema', () => { + expect(componentPropsParser({ a: 'a', component: 'div' }, {})).toEqual({ a: 'a', component: 'div' }) + expect(componentPropsParser({ a: 'a' }, { componentProps: { div: { b: 'b' } } })).toEqual({ a: 'a' }) + expect(componentPropsParser({ a: 'a', component: 'span' }, { componentProps: { div: { b: 'b' } } })).toEqual({ a: 'a', component: 'span' }) + }) + + test('当设置了默认值,应进行融合', () => { + expect(componentPropsParser({ a: 'a', b: 'b', component: 'div' }, { componentProps: { div: { a: 'aa', c: 'c' } } })).toEqual({ a: 'a', b: 'b', c: 'c', component: 'div' }) + }) +}) \ No newline at end of file diff --git a/tests/render/schemaApiParser.test.tsx b/tests/render/schemaApiParser.test.tsx new file mode 100644 index 0000000..d5fa58f --- /dev/null +++ b/tests/render/schemaApiParser.test.tsx @@ -0,0 +1,51 @@ +import React from 'react' +import { render, waitFor } from '@testing-library/react' +import { SchemaApiToComponent, SuperProvider, schemaApiParser } from 'super-antd' +import axios from 'axios'; + +describe('schemaApiParser', () => { + describe('SchemaApiToComponent', () => { + test('正常请求,应先 loading,后显示组件', async () => { + const api = () => ({ component: 'div', 'data-testid': 'data', children: 'hello world' }) + const warpper = render() + expect(warpper.container.querySelector('.ant-skeleton')).not.toBeNull() + + await waitFor(() => { + expect(warpper.queryByTestId('data')).toBeInTheDocument() + }) + }) + test('无数据时,应返回为空', () => { + const warpper = render() + expect(warpper.baseElement.innerHTML).toEqual('
') + }) + test('当请求结果类型错误时,应警告', async () => { + const warn = jest.fn() + const originWarn = console.warn + console.warn = warn + + const api = () => '123' + const warpper = render() + + await waitFor(() => { + expect(warpper.container.querySelector('.ant-skeleton')).toBeNull() + }) + + expect(warn).toBeCalled() + console.warn = originWarn + }) + }); + + describe('schemaApiParser', () => { + test('存在 schemaApi,则应该赋值到 children 上', () => { + const schemaApi = () => ({ component: 'div' }) + expect(schemaApiParser({ schemaApi, a: 'a' })).toEqual({ + a: 'a', + children: + }) + }) + + test('不存在 schemaApi,则数据不变', () => { + expect(schemaApiParser({a: 'a'})).toEqual({a: 'a'}) + }) + }); +}); \ No newline at end of file diff --git a/tests/utils.ts b/tests/utils.ts new file mode 100644 index 0000000..df9db41 --- /dev/null +++ b/tests/utils.ts @@ -0,0 +1,10 @@ +import type { NoopType } from "super-antd" + +export function testWarning(cb: NoopType) { + const warn = jest.fn() + const originWarn = console.warn + console.warn = warn + cb() + expect(warn).toBeCalled() + console.warn = originWarn +} \ No newline at end of file From 47a6850df11f78ff7e761cf37ffa89574ef6f157 Mon Sep 17 00:00:00 2001 From: zhangchaojie <1098626505@qq.com> Date: Sun, 30 May 2021 15:56:49 +0800 Subject: [PATCH 14/34] =?UTF-8?q?test:=20btns=20=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/btns/index.tsx | 6 +-- tests/btns/btns.test.tsx | 83 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 5 deletions(-) create mode 100644 tests/btns/btns.test.tsx diff --git a/src/btns/index.tsx b/src/btns/index.tsx index 388cc1b..3c8f335 100644 --- a/src/btns/index.tsx +++ b/src/btns/index.tsx @@ -31,11 +31,7 @@ function changeBtnsToNormal(btns?: BtnsType): ReactElement[] { return castToArray(btns) .filter((item) => item.visible !== false) .map(({ visible, text, ...item }) => { - const res = item; - if (res.disabled) { - res.disabled = 'disabled'; - } - return isValidElement(res) ? res : ; + return isValidElement(item) ? item : ; }); } diff --git a/tests/btns/btns.test.tsx b/tests/btns/btns.test.tsx new file mode 100644 index 0000000..c139646 --- /dev/null +++ b/tests/btns/btns.test.tsx @@ -0,0 +1,83 @@ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { Button } from 'antd'; +import React from 'react'; + +import { SuperBtns } from 'super-antd'; + +describe('SuperBtns', () => { + test('btns 不传时不显示按钮', () => { + const wrapper = render(); + expect(screen.queryByRole('button')).toBeNull(); + expect(wrapper.container).not.toHaveAttribute('class'); + }); + test('btns 为空时不显示按钮', () => { + const wrapper = render(); + expect(screen.queryByRole('button')).toBeNull(); + expect(wrapper.container).not.toHaveAttribute('class'); + }); + + test('btns 为配置数组', () => { + const wrapper = render(); + const btn = screen.getByRole('button'); + expect(btn).toBeInTheDocument(); + expect(btn).toHaveClass('ant-btn ant-btn-primary ant-btn-lg'); + expect(wrapper.container.querySelector('.ant-space')).not.toBeNull(); + }); + + test('btns 为 Button', () => { + const wrapper = render( + + btn2 + , + ]} + />, + ); + const btn = screen.getByRole('button'); + expect(btn).toBeInTheDocument(); + expect(btn).toHaveClass('ant-btn ant-btn-primary ant-btn-lg'); + expect(wrapper.container.querySelector('.ant-space')).not.toBeNull(); + }); + + test('btns 为两者数据混合', () => { + render(btn2]} />); + expect(screen.getAllByRole('button').length).toBe(2); + }); + + test('按钮事件', () => { + const onClick = jest.fn(); + render( + + btn + , + ]} + />, + ); + userEvent.click(screen.getByRole('button')); + expect(onClick).toBeCalled(); + }); + + test('text 属性作为 btn 内容', () => { + render(); + expect(screen.getByText('btn')).toBeInTheDocument(); + }); + + test('visible 为 false 的默认不展示', () => { + render(); + expect(screen.queryByText('btn')).toBeNull(); + }); + + test('btnsAlign 对齐', () => { + const wrapper = render(); + expect(wrapper.container.querySelector('.ant-space')).toHaveStyle('justify-content: center;'); + }); + + test('className 绑定', () => { + const wrapper = render(); + expect(wrapper.container.querySelector('.ant-space')).toHaveClass('test'); + }); +}); From 1db0e10d892d7f48b6cb3156dd451f3737558d6c Mon Sep 17 00:00:00 2001 From: zhangchaojie <1098626505@qq.com> Date: Sun, 30 May 2021 16:13:15 +0800 Subject: [PATCH 15/34] =?UTF-8?q?test:=20=E5=AE=8C=E6=88=90=20provider=20?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/provider/provider.test.tsx | 45 ++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 tests/provider/provider.test.tsx diff --git a/tests/provider/provider.test.tsx b/tests/provider/provider.test.tsx new file mode 100644 index 0000000..15af69a --- /dev/null +++ b/tests/provider/provider.test.tsx @@ -0,0 +1,45 @@ +import React, { useContext } from 'react'; +import { render } from '@testing-library/react' +import { SuperAntdContext, SuperProvider } from 'super-antd' +import { getFilters, setFilters } from '@dream2023/data-mapping'; +import axios from 'axios'; +import mockjs from 'mockjs'; + +describe('SuperProvider', () => { + test('children 可以正常显示', () => { + const warpper = render(
hello world
) + expect(warpper.getByTestId('content')).toBeInTheDocument() + }) + + test('过滤器 filters', () => { + const filters = { + getFoo: () => 'foo' + } + render() + expect(getFilters()).toBe(getFilters()) + }) + + test('axios 相关', () => { + const Demo = () => { + const { axios } = useContext(SuperAntdContext) + return
+ { axios ? 'have' : 'empty' } +
+ } + + const warpper = render() + expect(warpper.getByTestId('axios')).toHaveTextContent('have') + }) + + test('mockjs', () => { + const Demo = () => { + const { mockjs } = useContext(SuperAntdContext) + return
+ { mockjs ? 'have' : 'empty' } +
+ } + + const warpper = render() + expect(warpper.getByTestId('mockjs')).toHaveTextContent('have') + }) +}); \ No newline at end of file From b84a3826d8a60b64d79ef3635b14714b95c9ad51 Mon Sep 17 00:00:00 2001 From: zhangchaojie <1098626505@qq.com> Date: Sun, 30 May 2021 23:50:06 +0800 Subject: [PATCH 16/34] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=20Form=20?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- scripts/setupTests.js | 4 - src/form-item/src/createSuperFormItem.ts | 10 +- src/form-item/src/hoc/withMock.tsx | 1 - src/form/src/Form.tsx | 8 +- src/form/src/utils.ts | 12 +- tests/form/Form.test.tsx | 810 +++++++++++++++++++++++ tests/form/FormDebugger.test.tsx | 23 + tests/form/utils.test.ts | 193 ++++++ tests/utils.ts | 10 - yarn.lock | 8 +- 11 files changed, 1045 insertions(+), 36 deletions(-) create mode 100644 tests/form/Form.test.tsx create mode 100644 tests/form/FormDebugger.test.tsx create mode 100644 tests/form/utils.test.ts delete mode 100644 tests/utils.ts diff --git a/package.json b/package.json index 1eeeb3e..e2c09dc 100644 --- a/package.json +++ b/package.json @@ -97,7 +97,7 @@ "@testing-library/jest-dom": "^5.11.9", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^5.1.0", - "@testing-library/user-event": "^13.0.16", + "@testing-library/user-event": "^13.1.9", "@types/jest": "^26.0.21", "@types/react": "^17.0.2", "@types/react-dom": "^17.0.2", diff --git a/scripts/setupTests.js b/scripts/setupTests.js index 3c0277c..bc2cc19 100644 --- a/scripts/setupTests.js +++ b/scripts/setupTests.js @@ -2,10 +2,6 @@ import '@testing-library/jest-dom'; import { cleanup } from '@testing-library/react'; -jest.mock('react', () => ({ - ...jest.requireActual('react'), - useLayoutEffect: jest.requireActual('react').useEffect, -})); if (typeof window !== 'undefined') { global.window.resizeTo = (width, height) => { global.window.innerWidth = width || global.window.innerWidth; diff --git a/src/form-item/src/createSuperFormItem.ts b/src/form-item/src/createSuperFormItem.ts index 2d8d688..cf79691 100644 --- a/src/form-item/src/createSuperFormItem.ts +++ b/src/form-item/src/createSuperFormItem.ts @@ -19,8 +19,8 @@ export function createSuperFormItem>( config: WithFormItemConfigType & WithMockConfigType, ) { const ComponentWithMock = withMock(Component, config); - const ComponentWithFormItem = withFormItem(ComponentWithMock, config); - const ComponentWithDependency = withDependency>(ComponentWithFormItem); + const ComponentWithFormItem = withFormItem>(ComponentWithMock, config); + const ComponentWithDependency = withDependency>>(ComponentWithFormItem); return ComponentWithDependency; } @@ -34,15 +34,15 @@ export function createSuperFormItemWithOptions>( config: WithFormItemConfigType & WithOptionsConfigType & WithMockConfigType = {}, ) { const ComponentWithMock = withMock(Component, config); - const ComponentWithOptions = withOptions(ComponentWithMock, { + const ComponentWithOptions = withOptions>(ComponentWithMock, { hasLoadingProp: config.hasLoadingProp, needData: config.needData, }); - const ComponentWithFormItem = withFormItem>(ComponentWithOptions, { + const ComponentWithFormItem = withFormItem>>(ComponentWithOptions, { ...config, needData: true, }); - const ComponentWithDependency = withDependency>>(ComponentWithFormItem); + const ComponentWithDependency = withDependency>>>(ComponentWithFormItem); return ComponentWithDependency; } diff --git a/src/form-item/src/hoc/withMock.tsx b/src/form-item/src/hoc/withMock.tsx index a2e348c..31e1bd4 100644 --- a/src/form-item/src/hoc/withMock.tsx +++ b/src/form-item/src/hoc/withMock.tsx @@ -4,7 +4,6 @@ import type { ComponentType, FC} from 'react'; import { useContext } from 'react'; import React from 'react'; -import type { OptionsType } from '@/shared'; import { castToArray } from '@/shared'; import { useFormMock } from '../hooks/useFormMock'; import type { SuperFormContextProps } from '@/form'; diff --git a/src/form/src/Form.tsx b/src/form/src/Form.tsx index a16c809..7154da8 100644 --- a/src/form/src/Form.tsx +++ b/src/form/src/Form.tsx @@ -53,7 +53,7 @@ export function SuperForm = any>(props: SuperFor // 持久化 persistData, - clearPersistDataAfterSubmit = true, + clearPersistDataAfterSubmit, // 提交后的行为 redirect, @@ -242,7 +242,7 @@ export function SuperForm = any>(props: SuperFor Mock: mockjs, initMockRules, onMockCallback: (mockData: Values) => { - const data: any = { ...(formInstance.getFieldsValue() || {}), ...mockData }; + const data: any = { ...formInstance.getFieldsValue(), ...mockData }; formInstance.setFieldsValue(data); } }); @@ -279,7 +279,7 @@ export function SuperForm = any>(props: SuperFor // 提交数据 const handleFinish = usePersistFn(async (values: any) => { - const data = preserveRemoteData ? { ...(remoteInitData || {}), ...values } : values; + const data = preserveRemoteData && isPlainObject(remoteInitData) ? { ...remoteInitData, ...values } : values; if (onFinish) { onFinish(data); @@ -323,7 +323,7 @@ export function SuperForm = any>(props: SuperFor ) : undefined; if (btns?.render) { - return btns.render(formInstance.getFieldsValue(), doms); + return btns.render({ ...initialValuesWithStorage, ...formInstance.getFieldsValue() }, doms); } return doms; }, [hasMockRules, disabled, readonly, btns]); diff --git a/src/form/src/utils.ts b/src/form/src/utils.ts index 3610889..d80a371 100644 --- a/src/form/src/utils.ts +++ b/src/form/src/utils.ts @@ -1,14 +1,13 @@ import type { BtnsType, SuperButtonProps } from '@/btns'; import type { NoopType } from '@/shared'; -import { isNil } from '@/shared'; -import { isBoolean } from '@/shared'; +import { isString } from '@/shared'; import { isPlainObject } from '@/shared'; import type { BtnsProps } from './types'; // 获取按钮文本 export function getBtnText(btn: boolean | string | undefined, defaultText: string) { - return isNil(btn) || isBoolean(btn) ? defaultText : btn; + return isString(btn) ? btn : defaultText; } // 获取按钮 @@ -45,8 +44,8 @@ export function getBtn({ btn, onClick, key, type, htmlType, disabled, defaultTex } interface MockBtnOptions { - mockBtn: string | boolean | SuperButtonProps; - onMock: NoopType; + mockBtn?: string | boolean | SuperButtonProps; + onMock?: NoopType; } interface GetBtnsOptions extends BtnsProps, MockBtnOptions { @@ -59,9 +58,9 @@ export function getBtns(options: GetBtnsOptions): BtnsType { const { disabled, onReset, - mockBtn, onMock, onCancel, + mockBtn = false, resetBtn = true, submitBtn = true, cancelBtn = false, @@ -90,7 +89,6 @@ export function getBtns(options: GetBtnsOptions): BtnsType { defaultText: 'Mock 数据', }), getBtn({ - disabled, btn: cancelBtn, onClick: onCancel, key: 'cancel', diff --git a/tests/form/Form.test.tsx b/tests/form/Form.test.tsx new file mode 100644 index 0000000..7e56b48 --- /dev/null +++ b/tests/form/Form.test.tsx @@ -0,0 +1,810 @@ +import React, { useState } from 'react'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { Button } from 'antd'; + +import { SuperForm, SuperInput, SuperCheckbox, SuperProvider } from 'super-antd' +import axios from 'axios'; +import mockjs from 'mockjs'; + +describe('SuperForm 表单', () => { + describe('渲染', () => { + test('正常渲染', () => { + const wrapper = render(); + expect(wrapper.container.querySelector('.ant-form')).toBeInTheDocument(); + expect(wrapper.queryAllByRole('button', { hidden: false })).toHaveLength(2) + }); + + test('渲染表单项', () => { + const wrapper = render( + + + + + ); + expect(wrapper.container).toBeInTheDocument(); + expect(wrapper.container.querySelectorAll('.ant-form-item').length).toBe( + 3 + ); + expect( + wrapper.container.querySelector('.ant-form-item-control-input') + ).not.toBeNull(); + expect(wrapper.container.querySelector('.ant-checkbox')).not.toBeNull(); + }); + + test('原属性正确渲染', () => { + const fn = jest.fn(); + const wrapper = render( + + + + ); + expect(screen.getByLabelText('姓名')).toHaveValue('jack'); + expect( + wrapper.container.querySelector('.ant-form-vertical') + ).not.toBeNull(); + userEvent.type(screen.getByRole('textbox'), 'a'); + expect(fn).toBeCalled(); + }); + }); + + describe('按钮显示相关', () => { + test('默认显示提交、重置两个按钮', () => { + render(); + expect(screen.queryAllByRole('button').length).toBe(2); + expect(screen.getByText(/提 交/i)).not.toBeNull(); + expect(screen.getByText(/重 置/i)).not.toBeNull(); + }); + + test('设置 false 去掉提交按钮', () => { + render(); + expect(screen.queryByText(/提 交/i)).toBeNull(); + }); + + test('设置空字符串去掉提交按钮', () => { + render(); + expect(screen.queryByText(/提 交/i)).toBeNull(); + }); + + test('设置 false 去掉重置按钮', () => { + render(); + expect(screen.queryByText(/重 置/i)).toBeNull(); + }); + + test('设置空字符串去掉重置按钮', () => { + render(); + expect(screen.queryByText(/重 置/i)).toBeNull(); + }); + + test('设置 true 显示取消按钮', async () => { + render(); + expect(screen.getByRole('button', { name: '取 消' })).toBeInTheDocument(); + }); + + test('设置 字符串 显示取消按钮', async () => { + render(); + expect( + screen.getByRole('button', { name: 'cancel' }) + ).toBeInTheDocument(); + }); + + test('设置对象 显示 mock 按钮', () => { + render( + + + + ); + expect( + screen.getByRole('button', { name: 'Mock 数据' }) + ).toBeInTheDocument(); + }); + + test('表单项设置 mock 从而显示 mock 按钮', () => { + render( + + + + + + ); + expect( + screen.getByRole('button', { name: 'Mock 数据' }) + ).toBeInTheDocument(); + }); + + test('extraBtns 更多按钮', () => { + const moreFn = jest.fn(); + render( + 更多] + }} + > + ); + + userEvent.click(screen.getByRole('button', { name: '更 多' })); + expect(moreFn).toBeCalled(); + }); + + test('仅显示自定义按钮 btns', async () => { + const fn = jest.fn() + const warpper = render( + { + fn(data, btns) + return + } + }} + > + + + ); + + expect(warpper.container.querySelectorAll('.ant-btn').length).toBe(1); + expect(screen.getByText(/custom/i)).not.toBeNull(); + await waitFor(() => { + expect(fn).toBeCalledWith({ name: 'foo' }, expect.any(Object)) + }) + }); + + test('当内置 btns 不显示时,返回 render 函数接受为 null', () => { + const fn = jest.fn() + render( + { + fn(btns) + return null + } + }} + > + ); + + expect(fn).toBeCalledWith(undefined) + }); + }); + + describe('按钮文本', () => { + test('修改提交按钮文本', () => { + render(); + expect(screen.getByText('submit')).not.toBeNull(); + }); + + test('修改重置按钮文本', () => { + render(); + expect(screen.getByText('reset')).not.toBeNull(); + }); + + test('修改取消按钮文本', async () => { + render(); + expect( + screen.getByRole('button', { name: 'cancel' }) + ).toBeInTheDocument(); + }); + }); + + describe('submitBtn 提交按钮事件', () => { + test('触发 onFinish 函数', async () => { + const onFinish = jest.fn(); + render( + + + + ); + userEvent.click(screen.getByText(/提 交/i)); + await waitFor(() => expect(onFinish).toBeCalledWith({ name: 'jack' })); + }); + + test('接口提交', async () => { + const fn = jest.fn(); + + const wrapper = render( + + + + + + ); + + // 未请求时 + expect( + wrapper.container.querySelector('.ant-spin-spinning') + ).toBeNull(); + userEvent.click(screen.getByText(/提 交/i)); + + await waitFor(() => { + expect(fn).toBeCalled(); + expect(screen.queryByText('保存成功')).toBeInTheDocument() + }); + + await waitFor(() => { + // 请求结束 + expect( + wrapper.container.querySelector('.ant-spin-spinning') + ).toBeNull(); + }) + }); + + // test('提交数据后,有报错', async () => { + // const wrapper = render( + // + // Promise.reject({ errors: { name: 'name is error' } })} + // > + // + // + // + // ); + + // userEvent.click(screen.getByText(/提 交/i)); + + // await waitFor(() => { + // expect(wrapper.getByText('name is error')).toBeInTheDocument() + // }); + // }) + + test('提交后重置表单', async () => { + render( + + + + ); + userEvent.type(screen.getByRole('textbox'), 'a'); + expect(screen.getByRole('textbox')).toHaveValue('a'); + userEvent.click(screen.getByText(/提 交/i)); + + await waitFor(() => { + expect(screen.getByRole('textbox')).not.toHaveValue(); + }); + }); + + test('提交后跳转', async () => { + const open = jest.fn() + const originOpen = window.open + window.open = open + + render( + + + ); + userEvent.click(screen.getByText(/提 交/i)); + await waitFor(() => { + expect(open).toBeCalled() + }) + window.open = originOpen + }) + + test('提交数据包含远程数据', async () => { + const onFinish = jest.fn() + const wrapper = render( + + ({ id: 1 })}> + + + + ); + await waitFor(() => { + expect( + wrapper.container.querySelector('.ant-spin-nested-loading') + ).not.toBeNull(); + }) + userEvent.click(screen.getByText(/提 交/i)); + await waitFor(() => { + expect(onFinish).toBeCalledWith({ name: 'a', id: 1 }) + }) + }) + }); + + describe('mock 数据', () => { + test('mock 功能正常', () => { + render( + + + + + + ); + userEvent.click(screen.getByText(/Mock 数据/i)); + expect(screen.getByRole('textbox')).toHaveValue(); + }); + + test('表单项 mock 为 true', () => { + render( + + + + + + ); + userEvent.click(screen.getByText(/Mock 数据/i)); + expect(screen.getByRole('textbox')).toHaveValue(); + }); + + test('表单项 mock 为字符串', () => { + render( + + + + + + ); + userEvent.click(screen.getByText(/Mock 数据/i)); + expect( + (screen.getByRole('textbox') as HTMLInputElement).value.length + ).toBe(5); + }); + + test('表单项 和 表单都存在', () => { + render( + + + + + + + ); + userEvent.click(screen.getByText(/Mock 数据/i)); + expect(screen.getByLabelText('name')).toHaveValue(); + expect(screen.getByLabelText('age')).toHaveValue(); + }); + }); + + describe('按钮事件', () => { + test('点击重置按钮', async () => { + const onReset = jest.fn(); + render( + + + + ); + userEvent.type(screen.getByRole('textbox'), 'a'); + expect(screen.getByRole('textbox')).toHaveValue('a'); + userEvent.click(screen.getByText(/重 置/i)); + + await waitFor(() => { + expect(onReset).toBeCalled(); + expect(screen.getByRole('textbox')).not.toHaveValue(); + }); + }); + + test('点击取消按钮', async () => { + const onCancel = jest.fn(); + render( + + ); + userEvent.click(screen.getByText(/取 消/i)); + await waitFor(() => { + expect(onCancel).toBeCalled(); + }); + }); + + test('extraBtns 按钮点击', async () => { + const onExtraBtnClick = jest.fn(); + render( + + extraBtn + + ] + }} + > + ); + userEvent.click(screen.getByText(/extraBtn/)); + await waitFor(() => { + expect(onExtraBtnClick).toBeCalled(); + }); + }); + + test('btns 按钮点击', async () => { + const onExtraBtnClick = jest.fn(); + render( + [ + + ] + }} + > + ); + userEvent.click(screen.getByText(/extraBtn/)); + await waitFor(() => { + expect(onExtraBtnClick).toBeCalled(); + }); + }); + }); + + describe('col 增强', () => { + test('labelCol & wrapperCol 支持数字', () => { + const wrapper = render( + + + + ); + expect( + wrapper.container.querySelector('.ant-col-4.ant-form-item-label') + ).toBeInTheDocument(); + expect( + wrapper.container.querySelector('.ant-col-20.ant-form-item-control') + ).toBeInTheDocument(); + }); + }); + + describe('持久化数据', () => { + test('基本使用', async () => { + const submitFn = jest.fn(); + const Demo = () => { + const [visible, setVisible] = useState(true); + return ( + <> + + {visible && ( + + + + )} + + ); + }; + + render(); + userEvent.type(screen.getByRole('textbox'), 'a'); + await waitFor(() => { + expect(JSON.parse(localStorage.getItem('form') || '{}')).toEqual({ + username: 'a' + }); + }); + + // 隐藏后重新显示 + userEvent.click(screen.getByRole('button', { name: '切 换' })); + expect(screen.queryByRole('textbox')).toBeNull(); + userEvent.click(screen.getByRole('button', { name: '切 换' })); + await waitFor(() => { + expect(JSON.parse(localStorage.getItem('form') || '{}')).toEqual({ + username: 'a' + }); + }); + }); + + test('提交后清空持久化的数据', async () => { + const submitFn = jest.fn(); + render( + + + + ); + + userEvent.type(screen.getByRole('textbox'), 'a'); + userEvent.click(screen.getByRole('button', { name: '提 交' })); + + await waitFor(() => { + expect(submitFn).toBeCalledWith({ username: 'a' }); + expect(JSON.parse(localStorage.getItem('test-form') || '{}')).toEqual( + {} + ); + }); + }); + + test('persistData && !name', () => { + const errorOrigin = global.console.warn; + const fn = jest.fn(); + global.console.warn = fn; + render(); + expect(fn).toBeCalled(); + global.console.warn = errorOrigin; + }); + }); + + describe('只读和禁用', () => { + test('全表单禁用', () => { + render( + 测试] + }} + mock={{ username: '@string' }} + > + + + + ); + expect(screen.getAllByRole('textbox').length).toBe(2); + screen.getAllByRole('textbox').forEach((item) => { + expect(item).toHaveAttribute('disabled'); + }); + + expect(screen.getByRole('button', { name: /提 交/i })).toHaveAttribute( + 'disabled' + ); + expect(screen.getByRole('button', { name: /重 置/i })).toHaveAttribute( + 'disabled' + ); + expect( + screen.getByRole('button', { name: /Mock 数据/i }) + ).toHaveAttribute('disabled'); + expect( + screen.getByRole('button', { name: /取 消/i }) + ).not.toHaveAttribute('disabled'); + expect( + screen.getByRole('button', { name: /测 试/i }) + ).not.toHaveAttribute('disabled'); + }); + + test('全表单只读', () => { + render( + 测试] + }} + mock={{ username: '@string' }} + > + + + + ); + + expect(screen.queryAllByRole('textbox')).toHaveLength(0) + expect(screen.getAllByText('-')).toHaveLength(2) + + expect(screen.getByRole('button', { name: /提 交/i })).toHaveAttribute( + 'disabled' + ); + expect(screen.getByRole('button', { name: /重 置/i })).toHaveAttribute( + 'disabled' + ); + expect( + screen.getByRole('button', { name: /Mock 数据/i }) + ).toHaveAttribute('disabled'); + expect( + screen.getByRole('button', { name: /取 消/i }) + ).not.toHaveAttribute('disabled'); + expect( + screen.getByRole('button', { name: /测 试/i }) + ).not.toHaveAttribute('disabled'); + }); + }); + + describe('数据获取', () => { + test('获取成功', async () => { + const wrapper = render( + + ({ name: 'foo', age: 10 })} + initialValues={{ name: 'jack' }} + > + + + + + ); + expect( + wrapper.container.querySelector('.ant-spin-nested-loading') + ).not.toBeNull(); + + await waitFor(() => { + expect( + screen.getByRole('textbox', { + name: /姓名/i + }) + ).toHaveValue('foo'); + expect( + screen.getByRole('textbox', { + name: /年龄/i + }) + ).toHaveValue('10'); + }); + }); + + test('获取到的数据不是对象,则应报警告', async () => { + const warnFn = jest.fn(); + console.warn = warnFn; + const originalError = console.warn; + render( + + ([1, 2, 3])}> + + + ); + await waitFor(() => { + expect(warnFn).toBeCalled(); + }) + console.error = originalError; + }) + + // test.only('当获取失败,应有错误提示', async () => { + // render( + // + // Promise.reject({ + // message: 'request error' + // })}> + // + // + // ); + + // await waitFor(() => { + // expect(screen.getByText('request error')).toBeInTheDocument() + // }) + // }) + }); + + describe('debug', () => { + test('默认不显示', () => { + const wrapper = render( + + + + ); + expect( + wrapper.container.querySelector('.super-form-debugger') + ).not.toBeInTheDocument(); + }); + + test('debug', async () => { + const wrapper = render( + + + + ); + expect( + wrapper.container.querySelector('.super-form-debugger') + ).toBeInTheDocument(); + userEvent.type(screen.getByRole('textbox'), 'a'); + await waitFor(() => { + expect(screen.getByText(/"username": "a"/)).not.toBeNull(); + }); + }); + }); + + describe('全表单隐藏标签', () => { + test('hideLabel', () => { + const wrapper = render( + + + + + ); + + expect( + wrapper.container.querySelector('.ant-form-item-label') + ).toBeNull(); + }); + }); + + describe('组件联动', () => { + test('refresh: 正常', async () => { + const fn = jest.fn(); + render( + + + + + + ); + + userEvent.click(screen.getByText(/submit1/i)); + + await waitFor(() => { + expect(fn).toBeCalledTimes(2); + }); + }); + + test('updateTargetData', async () => { + render( + + + + + + + + + + ); + + userEvent.type(screen.getByLabelText('username1'), 'a'); + userEvent.click(screen.getByText(/submit1/i)); + await waitFor(() => { + expect(screen.getByLabelText('username2')).toHaveValue('a'); + }); + }); + }); + + describe('取消自动设置 placeholder', () => { + test('autoPlaceholder', () => { + render( + + + + + ); + + expect(screen.getByLabelText('username')).toHaveAttribute('placeholder', '请输入') + expect(screen.getByLabelText('password')).toHaveAttribute('placeholder', '请输入') + }); + }); + + describe('对齐方式 align', () => { + test('默认居左,无 className', () => { + const wrapper = render(); + expect(wrapper.container.querySelector('super-antd-left')).not.toBeInTheDocument() + }) + test('当设置 align 为 center 时,className 为 super-antd-center', () => { + const wrapper = render(); + expect(wrapper.container.querySelector('super-antd-center')).not.toBeInTheDocument() + }) + }); +}); diff --git a/tests/form/FormDebugger.test.tsx b/tests/form/FormDebugger.test.tsx new file mode 100644 index 0000000..084e801 --- /dev/null +++ b/tests/form/FormDebugger.test.tsx @@ -0,0 +1,23 @@ +import React from 'react' +import { render, waitFor } from '@testing-library/react' +import { Input, Form } from 'antd' +import { SuperFormDebugger } from 'super-antd' +import userEvent from '@testing-library/user-event'; + +test('FormDebugger', async () => { + const wrapper = render(
+ + + + + + + + ) + + userEvent.type(wrapper.getByLabelText('age'), '9'); + await waitFor(() => { + expect(wrapper.getByLabelText('age')).toHaveValue('9') + expect(wrapper.container.querySelector('.super-form-debugger')).toHaveTextContent('{ "name": "foo", "age": "9" }') + }) +}); \ No newline at end of file diff --git a/tests/form/utils.test.ts b/tests/form/utils.test.ts new file mode 100644 index 0000000..d4c3bde --- /dev/null +++ b/tests/form/utils.test.ts @@ -0,0 +1,193 @@ +import { getBtnText, getBtn, getBtns } from 'super-antd' + + +describe('SuperForm utils', () => { + describe('getBtnText', () => { + test('当 btn 为字符串,则返回此字符串', () => { + expect(getBtnText('btn', 'defaultText')).toBe('btn') + }) + + test('当 btn 不为字符串时,返回默认值', () => { + expect(getBtnText(undefined, 'defaultText')).toBe('defaultText') + expect(getBtnText(false, 'defaultText')).toBe('defaultText') + expect(getBtnText(true, 'defaultText')).toBe('defaultText') + }) + }); + + describe('getBtn', () => { + test('btn 为对象时,返回此对象', () => { + expect(getBtn({ + btn: { text: 'hello', htmlType: 'submit' }, + defaultText: 'defaultText', + htmlType: 'reset', + disabled: true + })).toMatchObject({ + text: 'hello', + htmlType: 'submit', + disabled: true, + visible: true + }) + + expect(getBtn({ + btn: { text: 'hello', htmlType: 'submit', visible: false }, + defaultText: 'defaultText', + htmlType: 'reset', + disabled: true + })).toMatchObject({ + text: 'hello', + htmlType: 'submit', + disabled: true, + visible: false + }) + }) + + test('btn 不为对象时,返回默认对象', () => { + expect(getBtn({ + btn: '提交', + defaultText: 'defaultText', + htmlType: 'reset', + disabled: true + })).toMatchObject({ + children: '提交', + htmlType: 'reset', + disabled: true + }) + }) + }); + + describe('getBtns', () => { + test('默认情况应返回 2 个 btn', () => { + expect(getBtns({})).toEqual([ + { + disabled: undefined, + key: 'submit', + htmlType: 'submit', + type: 'primary', + visible: true, + onClick: undefined, + children: '提交' + }, + { + disabled: undefined, + key: 'reset', + htmlType: 'reset', + type: undefined, + visible: true, + onClick: undefined, + children: '重置' + } + ]) + }) + + test('当 disabled 为 true 是,全部 btn 应该都为 disabled', () => { + expect(getBtns({ disabled: true })).toEqual([ + { + disabled: true, + key: 'submit', + htmlType: 'submit', + type: 'primary', + visible: true, + onClick: undefined, + children: '提交' + }, + { + disabled: true, + key: 'reset', + htmlType: 'reset', + type: undefined, + visible: true, + onClick: undefined, + children: '重置' + } + ]) + }) + + test('当 visible 为 false 时,应该被过滤掉', () => { + expect(getBtns({ + submitBtn: false + })).toEqual([ + { + disabled: undefined, + key: 'reset', + htmlType: 'reset', + type: undefined, + visible: true, + onClick: undefined, + children: '重置' + } + ]) + }) + + test('当 extraBtns 存在时,应结合', () => { + expect(getBtns({ + extraBtns: [{ text: 'hello' }] + })).toEqual([ + { + disabled: undefined, + key: 'submit', + htmlType: 'submit', + type: 'primary', + visible: true, + onClick: undefined, + children: '提交' + }, + { + disabled: undefined, + key: 'reset', + htmlType: 'reset', + type: undefined, + visible: true, + onClick: undefined, + children: '重置' + }, + { + text: 'hello' + } + ]) + }) + + test('显示 4 个内置按钮', () => { + expect(getBtns({ + cancelBtn: true, + mockBtn: true + })).toEqual([ + { + disabled: undefined, + key: 'submit', + htmlType: 'submit', + type: 'primary', + visible: true, + onClick: undefined, + children: '提交' + }, + { + disabled: undefined, + key: 'reset', + htmlType: 'reset', + type: undefined, + visible: true, + onClick: undefined, + children: '重置' + }, + { + disabled: undefined, + key: 'mock', + htmlType: undefined, + type: undefined, + visible: true, + onClick: undefined, + children: 'Mock 数据' + }, + { + disabled: undefined, + key: 'cancel', + htmlType: undefined, + type: undefined, + visible: true, + onClick: undefined, + children: '取消' + } + ]) + }) + }); +}); \ No newline at end of file diff --git a/tests/utils.ts b/tests/utils.ts deleted file mode 100644 index df9db41..0000000 --- a/tests/utils.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { NoopType } from "super-antd" - -export function testWarning(cb: NoopType) { - const warn = jest.fn() - const originWarn = console.warn - console.warn = warn - cb() - expect(warn).toBeCalled() - console.warn = originWarn -} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 3a7b8a7..65e7d78 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2617,10 +2617,10 @@ "@babel/runtime" "^7.12.5" "@testing-library/dom" "^7.28.1" -"@testing-library/user-event@^13.0.16": - version "13.0.16" - resolved "https://registry.npm.taobao.org/@testing-library/user-event/download/@testing-library/user-event-13.0.16.tgz#c027a9656f1984cf05f9c94c8f7168c06e85792c" - integrity sha1-wCepZW8ZhM8F+clMj3FowG6FeSw= +"@testing-library/user-event@^13.1.9": + version "13.1.9" + resolved "https://registry.nlark.com/@testing-library/user-event/download/@testing-library/user-event-13.1.9.tgz#29e49a42659ac3c1023565ff56819e0153a82e99" + integrity sha1-KeSaQmWaw8ECNWX/VoGeAVOoLpk= dependencies: "@babel/runtime" "^7.12.5" From 4166ada76ec2817b42af96ca76c136102b9b71b8 Mon Sep 17 00:00:00 2001 From: dream2023 <1098626505@qq.com> Date: Mon, 31 May 2021 20:51:10 +0800 Subject: [PATCH 17/34] =?UTF-8?q?chore:=20form-item=20utils=20=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 33 ++-- src/form-item/src/hoc/withFormItem.tsx | 14 +- src/form-item/src/utils/index.ts | 68 ++++--- src/shared/src/filters.ts | 2 +- tests/form-item/utils.test.ts | 100 ++++++++++ tests/provider/provider.test.tsx | 67 ++++--- .../hooks/useOptions/useOptions.test.tsx | 183 ++++++++++-------- 7 files changed, 301 insertions(+), 166 deletions(-) create mode 100644 tests/form-item/utils.test.ts diff --git a/README.md b/README.md index ad196f4..35d3ab7 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,9 @@ -

super-antd

-[![Codecov](https://img.shields.io/codecov/c/github/dream2023/super-antd?token=BKaeenZ4wi)](https://codecov.io/gh/dream2023/super-antd) -[![GitHub Discussions](https://img.shields.io/github/discussions/dream2023/super-antd?label=吐槽/讨论)](https://github.com/dream2023/super-antd/discussions) -[![文档](https://img.shields.io/static/v1?label=Docs&message=文档&color=blue)](https://dream2023.github.io/super-antd) -[![GitHub Repo stars](https://img.shields.io/github/stars/dream2023/super-antd)](https://github.com/dream2023/super-antd) -[![star](https://gitee.com/dream2023/super-antd/badge/star.svg?theme=dark)](https://gitee.com/dream2023/super-antd) +[![Codecov](https://img.shields.io/codecov/c/github/dream2023/super-antd?token=BKaeenZ4wi)](https://codecov.io/gh/dream2023/super-antd) [![GitHub Discussions](https://img.shields.io/github/discussions/dream2023/super-antd?label=吐槽/讨论)](https://github.com/dream2023/super-antd/discussions) [![文档](https://img.shields.io/static/v1?label=Docs&message=文档&color=blue)](https://dream2023.github.io/super-antd) [![GitHub Repo stars](https://img.shields.io/github/stars/dream2023/super-antd)](https://github.com/dream2023/super-antd) [![star](https://gitee.com/dream2023/super-antd/badge/star.svg?theme=dark)](https://gitee.com/dream2023/super-antd) -[![npm](https://img.shields.io/npm/v/super-antd)](https://www.npmjs.com/package/super-antd) -[![npm](https://img.shields.io/npm/dt/super-antd)](https://www.npmjs.com/package/super-antd) -[![David](https://img.shields.io/david/dream2023/super-antd)](https://github.com/dream2023/super-antd) -[![GitHub top language](https://img.shields.io/github/languages/top/dream2023/super-antd)](https://github.com/dream2023/super-antd) -[![github pages](https://github.com/dream2023/super-antd/actions/workflows/workflow.yml/badge.svg)](https://github.com/dream2023/super-antd/actions/workflows/workflow.yml) +[![npm](https://img.shields.io/npm/v/super-antd)](https://www.npmjs.com/package/super-antd) [![npm](https://img.shields.io/npm/dt/super-antd)](https://www.npmjs.com/package/super-antd) [![David](https://img.shields.io/david/dream2023/super-antd)](https://github.com/dream2023/super-antd) [![GitHub top language](https://img.shields.io/github/languages/top/dream2023/super-antd)](https://github.com/dream2023/super-antd) [![github pages](https://github.com/dream2023/super-antd/actions/workflows/workflow.yml/badge.svg)](https://github.com/dream2023/super-antd/actions/workflows/workflow.yml)
@@ -44,7 +35,7 @@ ## 🎯 Roadmap -- [x] 0.1 alpha 版 +- [x] 1.0 alpha 版 - [x] [数据模板](https://dream2023.github.io/super-antd/guide/concept/template) - [x] [数据联动](https://dream2023.github.io/super-antd/guide/concept/linkage) - [x] [数据请求](https://dream2023.github.io/super-antd/guide/concept/api) @@ -52,13 +43,17 @@ - [x] [表单组件](https://dream2023.github.io/super-antd/components/form) - [x] [表单项组件](https://dream2023.github.io/super-antd/components/form/form-item) - [x] [内置表单组件](https://dream2023.github.io/super-antd/components/form/form-components) -- [ ] 0.1 正式版:测试覆盖率 80% 以上、完成 100% 文档、0️⃣ issue -- [ ] 0.2 版:增加各种扩展的表单组件(富文本组件、地图组件、上传组件、代码编辑器组件...),覆盖 95% 表单组件场景 -- [ ] 0.3 版:增加各种与表单关联的组件(Dialog 弹窗组件、Tabs 选项卡组件...) -- [ ] 0.4 版:Table 表格组件、Table + Form 组件 -- [ ] 0.5 版:Page 组件 -- [ ] 0.6 版:图标类组件 -- [ ] ... +- [ ] 1.0 正式版 + - [ ] 测试覆盖率 80% 以上 + - [ ] 完成 100% 文档 + - [ ] 0️⃣ issue +- [ ] 2.0 版 + - [ ] 模板组件 + - [ ] 富文本组件 + - [ ] 上传图片组件 + - [ ] 上传视频组件 + - [ ] 代码编辑器组件 + - [ ] ... 更详细规划请见 [https://github.com/dream2023/super-antd/projects](https://github.com/dream2023/super-antd/projects)。 diff --git a/src/form-item/src/hoc/withFormItem.tsx b/src/form-item/src/hoc/withFormItem.tsx index fdb6acb..f271ab9 100644 --- a/src/form-item/src/hoc/withFormItem.tsx +++ b/src/form-item/src/hoc/withFormItem.tsx @@ -85,7 +85,7 @@ export function withFormItem

(FormItemComponent: ComponentType< // 获取 colon const computedColon = useCreation(() => { - return getColon(layout, label, colon, hideLabel, formHideLabel); + return getColon({ layout, label, colon, hideLabel, formHideLabel }); }, [label, colon, hideLabel, formHideLabel]); // 名称,从 'info.name' => ['info', 'name'] @@ -95,34 +95,34 @@ export function withFormItem

(FormItemComponent: ComponentType< // 获取 label const componentLabel = useCreation(() => { - const res = getLabel(layout, label, colon, hideLabel, formHideLabel); + const res = getLabel({ layout, label, colon, hideLabel, formHideLabel }); return isString(res) ? compilerStr(res, { data }, delimiters) : res; }, [layout, label, colon, hideLabel, formHideLabel, delimiters, data]); // 联动必填 const linkageRequired = useCreation(() => { // data, required, requiredOn, delimiters - return getLinkageValue({ data, defaultValue: required, linkageFn: requiredOn, delimiters }); // 是否必填 + return getLinkageValue({ data, value: required, linkageFn: requiredOn, delimiters }); // 是否必填 }, [data, required, requiredOn, delimiters]); // 联动只读 const linkageReadonly = useCreation(() => { - const isReadonly = getLinkageValue({ data, defaultValue: readonly, linkageFn: readonlyOn, delimiters }); // 是否只读 + const isReadonly = getLinkageValue({ data, value: readonly, linkageFn: readonlyOn, delimiters }); // 是否只读 // 本身只读或者全局只读都是返回 true return isReadonly || formContext.readonly; }, [data, readonly, readonlyOn, delimiters, formContext.readonly]); // 联动隐藏 const linkageHidden = useCreation(() => { - const isVisible = getLinkageValue({ data, defaultValue: visible, linkageFn: visibleOn, delimiters }); // 是否显示 + const isVisible = getLinkageValue({ data, value: visible, linkageFn: visibleOn, delimiters }); // 是否显示 const isHidden = getLinkageValue({ data, linkageFn: hiddenOn, delimiters }); // 是否隐藏 return getOppositionValue(isHidden, isVisible); }, [data, visible, visibleOn, hiddenOn, delimiters]); // 联动禁用 const linkageDisabled = useCreation(() => { - const isActive = getLinkageValue({ data, defaultValue: active, linkageFn: activeOn, delimiters }); // 是否启用 - const isDisabled = getLinkageValue({ data, defaultValue: disabled, linkageFn: disabledOn, delimiters }); // 是否禁用 + const isActive = getLinkageValue({ data, value: active, linkageFn: activeOn, delimiters }); // 是否启用 + const isDisabled = getLinkageValue({ data, value: disabled, linkageFn: disabledOn, delimiters }); // 是否禁用 return getOppositionValue(isDisabled, isActive) || formContext.disabled; }, [data, active, activeOn, disabled, disabledOn, delimiters]); diff --git a/src/form-item/src/utils/index.ts b/src/form-item/src/utils/index.ts index 57cd00a..cc4a06c 100644 --- a/src/form-item/src/utils/index.ts +++ b/src/form-item/src/utils/index.ts @@ -10,10 +10,8 @@ export type LinkageFunctionType = ((data: Record) => any) | string; interface GetLinkageValueOptions { // 数据 data: Record; - // 返回值是否为布尔值 - isBoolean?: boolean; - // 默认值 - defaultValue?: any; + // 用户传递的值 + value?: any; // 联动函数 linkageFn?: LinkageFunctionType; // 分隔符 @@ -21,20 +19,14 @@ interface GetLinkageValueOptions { } // 转换联动函数 -export function getLinkageValue({ - data, - defaultValue, - linkageFn, - delimiters, - isBoolean = true, -}: GetLinkageValueOptions) { +export function getLinkageValue({ data, value, linkageFn, delimiters }: GetLinkageValueOptions): boolean | undefined { // 为空则返回 - if (isUndefined(defaultValue) && isUndefined(linkageFn)) return undefined; + if (isUndefined(value) && isUndefined(linkageFn)) return undefined; let res; // 如果存在默认值,则使用默认值 - if (!isUndefined(defaultValue)) { - res = defaultValue; + if (!isUndefined(value)) { + res = value; } else if (isString(linkageFn)) { // 如果是字符串,则编译字符串 res = compilerStr(linkageFn, { data }, delimiters); @@ -43,7 +35,7 @@ export function getLinkageValue({ res = linkageFn(data); } - return isBoolean ? !!res : res; + return !!res; } // 获取对立的值 @@ -56,7 +48,7 @@ export function getOppositionValue(val1?: boolean, val2?: boolean) { /** 获取 placeholder 当用户没有自定义 placeholder 时,并且存在 label ,则根据 label自动拼接为 placeholder */ interface GetPlaceholderOptions { - label: ReactNode; + label?: ReactNode; autoPlaceholder?: boolean; placeholderPrefix?: string; messageVariables?: Record; @@ -90,8 +82,8 @@ export function getPlaceholder({ * getName('info.age') => ['info', 'age'] */ export function getName(name?: NamePath): NamePath | undefined { - if (typeof name === 'string') { - const nameArr = name.split('.').filter((item) => item.length); + if (isString(name)) { + const nameArr = name.split('.').filter((item) => item); if (nameArr.length > 1) return nameArr; } @@ -112,13 +104,19 @@ export function getName(name?: NamePath): NamePath | undefined { * @param hideLabel 是否隐藏标签 * @param formHideLabel 是否全表单隐藏 */ -export function getLabel( - layout?: string, - label?: ReactNode, - colon?: boolean, - hideLabel?: boolean, - formHideLabel?: boolean, -): ReactNode { +export function getLabel({ + layout, + label, + colon, + hideLabel, + formHideLabel, +}: { + layout?: string; + label?: ReactNode; + colon?: boolean; + hideLabel?: boolean; + formHideLabel?: boolean; +}): ReactNode { if (formHideLabel) return undefined; return hideLabel || (label === undefined && colon === undefined && layout !== 'vertical') ? ' ' : label; } @@ -128,13 +126,19 @@ export function getLabel( * * 其作用是搭配上面 getLabel 使用的,当 label 为空字符串时,如果有 : 会显得很难看 所以,当 label 为空字符串时,将其设置为 false。 */ -export function getColon( - layout?: string, - label?: ReactNode, - colon?: boolean, - hideLabel?: boolean, - formHideLabel?: boolean, -): boolean | undefined { +export function getColon({ + layout, + label, + colon, + hideLabel, + formHideLabel, +}: { + layout?: string; + label?: ReactNode; + colon?: boolean; + hideLabel?: boolean; + formHideLabel?: boolean; +}): boolean | undefined { return formHideLabel || hideLabel || (label === undefined && colon === undefined && layout !== 'vertical') ? false : colon; diff --git a/src/shared/src/filters.ts b/src/shared/src/filters.ts index 6076ec9..8157e23 100644 --- a/src/shared/src/filters.ts +++ b/src/shared/src/filters.ts @@ -1,6 +1,6 @@ import dayjs from 'dayjs'; -import { isArray, isBoolean, isDate, isNumber, isString } from './utils/is'; +import { isArray, isBoolean, isNumber, isString } from './utils/is'; // 转为 json 字符串 export const json = (data: any, tabSize?: number) => { diff --git a/tests/form-item/utils.test.ts b/tests/form-item/utils.test.ts new file mode 100644 index 0000000..ba2c48e --- /dev/null +++ b/tests/form-item/utils.test.ts @@ -0,0 +1,100 @@ +import { getColon, getLabel, getLinkageValue, getName, getOppositionValue, getPlaceholder } from 'super-antd'; + +describe('form-item utils', () => { + describe('getLinkageValue', () => { + test('当默认值和联动函数都为空,则应返回 undefined', () => { + expect(getLinkageValue({ value: undefined, linkageFn: undefined, data: {} })).toBe(undefined); + }); + + test('当存在值时,应返回值', () => { + expect(getLinkageValue({ value: 'aaa', linkageFn: '{{data.name}}', data: { name: 'foo' } })).toBe(true); + }); + + test('当值不存在,其 linkageFn 为字符串时,返回编译后的值', () => { + expect(getLinkageValue({ linkageFn: '{{data.name}}', data: { name: 'foo' } })).toBe(true); + }); + + test('当值不存在,其 linkageFn 为函数时,返回执行后的值', () => { + expect(getLinkageValue({ linkageFn: (data) => data.name, data: { name: 'foo' } })).toBe(true); + }); + }); + + describe('getOppositionValue', () => { + test('当 value1 存在时,应返回 value1', () => { + expect(getOppositionValue(true, true)).toBe(true); + expect(getOppositionValue(false, true)).toBe(false); + }); + + test('当 value1 不存在时,应该返回 value2 的相反值', () => { + expect(getOppositionValue(undefined, false)).toBe(true); + expect(getOppositionValue(undefined, true)).toBe(false); + }); + }); + + describe('getPlaceholder', () => { + test('when autoPlaceholder is true and label is string, should return placeholder string', () => { + expect(getPlaceholder({ autoPlaceholder: true, label: '姓名', placeholderPrefix: '请输入' })).toBe('请输入姓名'); + expect( + getPlaceholder({ autoPlaceholder: true, messageVariables: { label: '姓名' }, placeholderPrefix: '请输入' }), + ).toBe('请输入姓名'); + }); + + test('when autoPlaceholder is false or label is undefined, should return undefined', () => { + expect(getPlaceholder({ autoPlaceholder: false, label: '姓名', placeholderPrefix: '请输入' })).toBe(undefined); + expect(getPlaceholder({ autoPlaceholder: false, placeholderPrefix: '请输入' })).toBe(undefined); + }); + }); + + describe('getName', () => { + test('当 name 是多级时,应返回分割的数组', () => { + expect(getName('info.name')).toEqual(['info', 'name']); + expect(getName('a.b.c')).toEqual(['a', 'b', 'c']); + expect(getName('a.b.c.')).toEqual(['a', 'b', 'c']); + expect(getName('.a.b.c.')).toEqual(['a', 'b', 'c']); + }); + + test('其他情况,应直接返回', () => { + expect(getName('info')).toBe('info'); + expect(getName('info.')).toBe('info.'); + }); + }); + + describe('getLabel', () => { + test('当全表单隐藏时,应返回 undefined', () => { + expect(getLabel({ formHideLabel: true, label: 'name' })).toBe(undefined); + }); + + test('当自身表单隐藏时,应返回空字符串', () => { + expect(getLabel({ hideLabel: true })).toBe(' '); + }); + + test('当 label 为空时,应返回空字符串', () => { + expect(getLabel({ label: undefined })).toBe(' '); + expect(getLabel({})).toBe(' '); + }); + + test('其他情况下,应返回 label', () => { + expect(getLabel({ label: '姓名' })).toBe('姓名'); + }); + }); + + describe('getColon', () => { + test('全表单隐藏,应该返回 false', () => { + expect(getColon({ formHideLabel: true, colon: true })).toBe(false); + }); + + test('自身表单隐藏,应该返回 false', () => { + expect(getColon({ hideLabel: true, colon: true })).toBe(false); + }); + + test('当为 undefined 是,应该返回 false', () => { + expect(getColon({ colon: undefined })).toBe(false); + }); + + test('其他情况下, 应直接返回', () => { + expect(getColon({ colon: true })).toBe(true); + expect(getColon({ label: 'name' })).toBe(undefined); + expect(getColon({ layout: 'vertical' })).toBe(undefined); + }); + }); +}); diff --git a/tests/provider/provider.test.tsx b/tests/provider/provider.test.tsx index 15af69a..a53d7bd 100644 --- a/tests/provider/provider.test.tsx +++ b/tests/provider/provider.test.tsx @@ -1,45 +1,54 @@ -import React, { useContext } from 'react'; -import { render } from '@testing-library/react' -import { SuperAntdContext, SuperProvider } from 'super-antd' -import { getFilters, setFilters } from '@dream2023/data-mapping'; +import { getFilters } from '@dream2023/data-mapping'; +import { render } from '@testing-library/react'; import axios from 'axios'; import mockjs from 'mockjs'; +import React, { useContext } from 'react'; + +import { SuperAntdContext, SuperProvider } from 'super-antd'; describe('SuperProvider', () => { test('children 可以正常显示', () => { - const warpper = render(

hello world
) - expect(warpper.getByTestId('content')).toBeInTheDocument() - }) + const warpper = render( + +
hello world
+
, + ); + expect(warpper.getByTestId('content')).toBeInTheDocument(); + }); test('过滤器 filters', () => { const filters = { - getFoo: () => 'foo' - } - render() - expect(getFilters()).toBe(getFilters()) - }) + getFoo: () => 'foo', + }; + render(); + expect(getFilters()).toBe(getFilters()); + }); test('axios 相关', () => { const Demo = () => { - const { axios } = useContext(SuperAntdContext) - return
- { axios ? 'have' : 'empty' } -
- } + const { axios } = useContext(SuperAntdContext); + return
{axios ? 'have' : 'empty'}
; + }; - const warpper = render() - expect(warpper.getByTestId('axios')).toHaveTextContent('have') - }) + const warpper = render( + + + , + ); + expect(warpper.getByTestId('axios')).toHaveTextContent('have'); + }); test('mockjs', () => { const Demo = () => { - const { mockjs } = useContext(SuperAntdContext) - return
- { mockjs ? 'have' : 'empty' } -
- } + const { mockjs } = useContext(SuperAntdContext); + return
{mockjs ? 'have' : 'empty'}
; + }; - const warpper = render() - expect(warpper.getByTestId('mockjs')).toHaveTextContent('have') - }) -}); \ No newline at end of file + const warpper = render( + + + , + ); + expect(warpper.getByTestId('mockjs')).toHaveTextContent('have'); + }); +}); diff --git a/tests/shared/hooks/useOptions/useOptions.test.tsx b/tests/shared/hooks/useOptions/useOptions.test.tsx index 0d5dd64..e135817 100644 --- a/tests/shared/hooks/useOptions/useOptions.test.tsx +++ b/tests/shared/hooks/useOptions/useOptions.test.tsx @@ -1,40 +1,49 @@ -import React, { FC, useState } from 'react' +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import { renderHook } from '@testing-library/react-hooks'; -import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'; -import { ApiType, IUseOptions, SuperProvider, useOptions } from 'super-antd' import axios from 'axios'; +import React, { FC, useState } from 'react'; + +import { ApiType, IUseOptions, SuperProvider, useOptions } from 'super-antd'; describe('useOptions', () => { describe('options 为数组', () => { - const setup = (config: IUseOptions) => renderHook(() => useOptions(config)) + const setup = (config: IUseOptions) => renderHook(() => useOptions(config)); test('options 为数组', () => { - const { result } = setup({ options: ['a', 'b'] }) - expect(result.current.loading).toBe(false) - expect(result.current.options).toEqual([{ label: 'a', value: 'a' }, { label: 'b', value: 'b' }]) - }) + const { result } = setup({ options: ['a', 'b'] }); + expect(result.current.loading).toBe(false); + expect(result.current.options).toEqual([ + { label: 'a', value: 'a' }, + { label: 'b', value: 'b' }, + ]); + }); test('optionsProp 存在', () => { const { result } = setup({ - options: [{ name: 'a', id: 1 }, { name: 'b', id: 2 }], - optionsProp: { labelKey: 'name', valueKey: 'id' } - }) - expect(result.current.loading).toBe(false) - expect(result.current.options).toEqual([{ label: 'a', value: 1 }, { label: 'b', value: 2 }]) - }) - }) + options: [ + { name: 'a', id: 1 }, + { name: 'b', id: 2 }, + ], + optionsProp: { labelKey: 'name', valueKey: 'id' }, + }); + expect(result.current.loading).toBe(false); + expect(result.current.options).toEqual([ + { label: 'a', value: 1 }, + { label: 'b', value: 2 }, + ]); + }); + }); describe('options 为 API', () => { - - const Demo: FC<{ api: ApiType, hidden?: boolean, data?: object }> = ({ api, hidden, data}) => { + const Demo: FC<{ api: ApiType; hidden?: boolean; data?: object }> = ({ api, hidden, data }) => { const { options, loading } = useOptions({ options: api, hidden, - data + data, }); return (
-
{loading ? "true" : "false"}
+
{loading ? 'true' : 'false'}
{JSON.stringify(options)}
); @@ -44,95 +53,113 @@ describe('useOptions', () => { const api = async () => { return new Promise((resolve) => { setTimeout(() => { - return resolve(['a', 'b']) - }) - }) + return resolve(['a', 'b']); + }); + }); }; - render( - - ) - expect(screen.getByTestId('loading').textContent).toBe('true') - expect(screen.getByTestId('options').textContent).toBe('[]') - - await waitFor(() => { - expect(screen.getByTestId('loading').textContent).toBe('false') - expect(screen.getByTestId('options').textContent).toBe(JSON.stringify([{label: 'a', value: 'a'}, {label: 'b', value: 'b'}])) - }, { timeout: 1000 }) - }) + render( + + + , + ); + expect(screen.getByTestId('loading').textContent).toBe('true'); + expect(screen.getByTestId('options').textContent).toBe('[]'); + + await waitFor( + () => { + expect(screen.getByTestId('loading').textContent).toBe('false'); + expect(screen.getByTestId('options').textContent).toBe( + JSON.stringify([ + { label: 'a', value: 'a' }, + { label: 'b', value: 'b' }, + ]), + ); + }, + { timeout: 1000 }, + ); + }); test('返回不为数组时,应发出警告', async () => { - const fn = jest.fn() - const originWarn = console.warn - console.warn = fn + const fn = jest.fn(); + const originWarn = console.warn; + console.warn = fn; const api = async () => { return new Promise((resolve) => { setTimeout(() => { - return resolve({}) - }) - }) + return resolve({}); + }); + }); }; - render( - - ) - expect(screen.getByTestId('loading').textContent).toBe('true') - expect(screen.getByTestId('options').textContent).toBe('[]') + render( + + + , + ); + expect(screen.getByTestId('loading').textContent).toBe('true'); + expect(screen.getByTestId('options').textContent).toBe('[]'); await waitFor(() => { - expect(fn).toBeCalled() - expect(screen.getByTestId('loading').textContent).toBe('false') - expect(screen.getByTestId('options').textContent).toBe('[]') - }) + expect(fn).toBeCalled(); + expect(screen.getByTestId('loading').textContent).toBe('false'); + expect(screen.getByTestId('options').textContent).toBe('[]'); + }); - console.warn = originWarn - }) + console.warn = originWarn; + }); test('当 hidden 为 true 时,不请求', async () => { const api = async () => { return new Promise((resolve) => { setTimeout(() => { - return resolve(['a', 'b']) - }) - }) + return resolve(['a', 'b']); + }); + }); }; - render( - ) + render( + + , + ); - expect(screen.getByTestId('loading').textContent).toBe('false') - expect(screen.getByTestId('options').textContent).toBe('[]') - }) + expect(screen.getByTestId('loading').textContent).toBe('false'); + expect(screen.getByTestId('options').textContent).toBe('[]'); + }); test('当依赖项发生时,重新请求', async () => { const api = async () => { return new Promise((resolve) => { setTimeout(() => { - return resolve(['a', 'b']) - }) - }) + return resolve(['a', 'b']); + }); + }); }; const App = () => { - const [data, setData] = useState({}) - return - - - - } - render() + const [data, setData] = useState({}); + return ( + + + + + ); + }; + render(); - expect(screen.getByTestId('loading').textContent).toBe('true') + expect(screen.getByTestId('loading').textContent).toBe('true'); await waitFor(() => { - expect(screen.getByTestId('loading').textContent).toBe('false') - }) + expect(screen.getByTestId('loading').textContent).toBe('false'); + }); - fireEvent.click(screen.getByTestId('setdata')) + fireEvent.click(screen.getByTestId('setdata')); await waitFor(() => { - expect(screen.getByTestId('loading').textContent).toBe('true') - }) - }) - }) -}) \ No newline at end of file + expect(screen.getByTestId('loading').textContent).toBe('true'); + }); + }); + }); +}); From dddab9cc7fa6293d971bc3fc46ef9b0484dec93a Mon Sep 17 00:00:00 2001 From: zhangchaojie <1098626505@qq.com> Date: Tue, 1 Jun 2021 06:05:40 +0800 Subject: [PATCH 18/34] =?UTF-8?q?docs:=20=E6=9B=B4=E6=94=B9=E8=AF=B4?= =?UTF-8?q?=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 ++++++---- docs/guide/index.md | 4 +++- docs/index.md | 2 +- package.json | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 35d3ab7..4a95c74 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,9 @@ -`super-antd` 是一个基于 [ant design](https://ant.design/) 和 [pro-components](https://procomponents.ant.design/) 的数据驱动友好、易用且强大的 UI 库。 +`super-antd` 是一个简单且数据驱动友好的 React 组件库。 + +它基于 [ant design](https://ant.design/) 和 [pro-components](https://procomponents.ant.design/) 。 ## 📖 Document @@ -15,8 +17,8 @@ ## ✨ Feature -- 数据驱动友好:通过在组件层级解决动态渲染、通信、联动等难题; -- 简单、易用:通过对业务场景的提炼,进行大量业务场景的封装; +- 简单:通过对大量业务场景的提炼和抽象,使得其十分简单; +- 数据驱动友好:在组件层级解决了动态渲染、通信、联动等难题; - 稳定:Typescript 编写 + 高测试覆盖率; ## 🤔 Why? @@ -65,7 +67,7 @@ | --- | --- | --- | | react-schema-render | ![react-schema-render](https://img.shields.io/npm/v/react-schema-render?style=flat-square) | 通用型 schema 转 React 组件的工具组件 | | @dream2023/data-mapping | ![@dream2023/data-mapping](https://img.shields.io/npm/v/@dream2023/data-mapping?style=flat-square) | 数据模板映射方案 | -| super-antd | ![super-antd](https://img.shields.io/npm/v/super-antd?style=flat-square) | 数据驱动友好、易用且强大的 UI 库 | +| super-antd | ![super-antd](https://img.shields.io/npm/v/super-antd?style=flat-square) | 简单且数据驱动友好的 React 组件库 | 此外至少还有以下系统待开发: diff --git a/docs/guide/index.md b/docs/guide/index.md index a8b318e..2e01f1e 100644 --- a/docs/guide/index.md +++ b/docs/guide/index.md @@ -13,7 +13,9 @@ nav: ## 什么 super-antd? -`super-antd` 是一个基于 [ant design](https://ant.design/) 和 [pro-components](https://procomponents.ant.design/) 的数据驱动友好、易用且强大的 UI 库。 +`super-antd` 是一个简单且数据驱动友好的 React 组件库。 + +它基于 [ant design](https://ant.design/) 和 [pro-components](https://procomponents.ant.design/) 。 ## 如何做到简单、易用且强大? diff --git a/docs/index.md b/docs/index.md index b9dfd70..7642bc2 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,7 +2,7 @@ title: super-antd | 一个简单、易用且强大的 ant design 增强库。 hero: title: super-antd - desc: super-antd 是一个数据驱动友好、易用且强大的 UI 库。 + desc: super-antd 是一个简单且数据驱动友好的 React 组件库。 actions: - text: (*^▽^*) 快速开始 link: /guide/getting-started diff --git a/package.json b/package.json index e2c09dc..de3b0ba 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": false, "name": "super-antd", - "description": "super-antd 是一个基于 ant design 和 pro-components 的数据驱动友好、易用且强大的 UI 库。", + "description": "super-antd 是一个简单且数据驱动友好的 React 组件库。", "license": "MIT", "version": "0.0.1-alpha", "author": { From 7abed311e02c6f815c8b2af848467df04e7fa885 Mon Sep 17 00:00:00 2001 From: zhangchaojie <1098626505@qq.com> Date: Wed, 2 Jun 2021 21:44:45 +0800 Subject: [PATCH 19/34] =?UTF-8?q?test:=20=E5=AE=8C=E6=88=90=20withMock=20?= =?UTF-8?q?=20=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/form-item/src/hoc/withMock.tsx | 2 +- src/form-item/src/hooks/useFormMock.ts | 34 +++----- src/form/src/Form.tsx | 2 +- tests/form-item/hoc/withMock.test.tsx | 50 ++++++++++++ tests/form-item/hooks/useFormMock.test.tsx | 94 ++++++++++++++++++++++ tests/form/Form.test.tsx | 8 -- 6 files changed, 158 insertions(+), 32 deletions(-) create mode 100644 tests/form-item/hoc/withMock.test.tsx create mode 100644 tests/form-item/hooks/useFormMock.test.tsx diff --git a/src/form-item/src/hoc/withMock.tsx b/src/form-item/src/hoc/withMock.tsx index 31e1bd4..4c6565a 100644 --- a/src/form-item/src/hoc/withMock.tsx +++ b/src/form-item/src/hoc/withMock.tsx @@ -9,7 +9,7 @@ import { useFormMock } from '../hooks/useFormMock'; import type { SuperFormContextProps } from '@/form'; import { SuperFormContext } from '@/form'; -export type WithMockProps = T & { +export type WithMockProps = T & { // 表单项 name name?: NamePath; // 是否隐藏 diff --git a/src/form-item/src/hooks/useFormMock.ts b/src/form-item/src/hooks/useFormMock.ts index 0b01d01..8fdfc74 100644 --- a/src/form-item/src/hooks/useFormMock.ts +++ b/src/form-item/src/hooks/useFormMock.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-param-reassign */ import { useCreation, useUnmount } from 'ahooks'; import type { Key } from 'react'; import { useEffect } from 'react'; @@ -5,11 +6,11 @@ import { useEffect } from 'react'; import type { SuperFormContextProps } from '@/form'; import { isBoolean, isFunction } from '@/shared'; -const getMockRule = (mockRule: any, props: Record) => { +export const getMockRule = (mockRule: any, props: Record) => { return isFunction(mockRule) ? mockRule(props) : mockRule; }; -interface FormMockOptions { +export interface FormMockOptions { name?: string; mock?: any; disabledMock?: boolean; @@ -19,21 +20,12 @@ interface FormMockOptions { } export function useFormMock({ name, mock, disabledMock, defaultMockRule, formContext, props }: FormMockOptions) { - // 自身是否开启 mock - const selfIsMock = useCreation(() => { - // 存在 - return !!mock && !disabledMock; - }, [mock]); - - // 表单是否开启 mock - const formIsMock = useCreation(() => { - return formContext.isMock === true; - }, [formContext.isMock]); - // 是否开启 mock const isMock = useCreation(() => { - return name && (selfIsMock || formIsMock); - }, [selfIsMock, formIsMock]); + if (!name || disabledMock) return false // 如果 name 不存在,则无 key,所以返回 false + if (formContext.isMock) return true // 代表整个表单都开启 Mock + return !!mock // 自身 mock 有值,且无 disabled + }, [name, mock, disabledMock, formContext.isMock]); // 自身规则 const selfMockRule = useCreation(() => { @@ -57,20 +49,18 @@ export function useFormMock({ name, mock, disabledMock, defaultMockRule, formCon }, [Object.values(props), mock, defaultMockRule, formContext]); useEffect(() => { + if (!name) return if (isMock) { - // eslint-disable-next-line no-param-reassign - formContext.mockRules[name!] = mockRule; + formContext.mockRules[name] = mockRule; } else { - // eslint-disable-next-line no-param-reassign - delete formContext.mockRules[name!]; + delete formContext.mockRules[name]; } }, [formContext.mockRules, isMock, mockRule, name]); // 卸载组件时,需要删除 mock useUnmount(() => { - if (isMock) { - // eslint-disable-next-line no-param-reassign - delete formContext.mockRules[name!]; + if (isMock && name) { + delete formContext.mockRules[name]; } }); } diff --git a/src/form/src/Form.tsx b/src/form/src/Form.tsx index 7154da8..352aa66 100644 --- a/src/form/src/Form.tsx +++ b/src/form/src/Form.tsx @@ -30,7 +30,7 @@ import type { } from './types'; import { getBtns } from './utils'; -export interface SuperFormProps +export interface SuperFormProps extends Omit, 'labelCol' | 'wrapperCol' | 'submitter'>, ActionProps, PersistDataProps, diff --git a/tests/form-item/hoc/withMock.test.tsx b/tests/form-item/hoc/withMock.test.tsx new file mode 100644 index 0000000..4e3a8e2 --- /dev/null +++ b/tests/form-item/hoc/withMock.test.tsx @@ -0,0 +1,50 @@ +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event'; +import { useUpdate } from 'ahooks'; +import React, { FC, useContext } from 'react'; +import { SuperForm, SuperFormContext, SuperFormContextProps, withMock, WithMockProps } from 'super-antd' + +const Demo: FC = (props) => { + const formContext = useContext(SuperFormContext); + return
{JSON.stringify(formContext.mockRules)}
+} + +const DemoWithMock = withMock(Demo, { defaultMockRule: '@string' }) + +const App: FC = (props) => { + const update = useUpdate(); + + return + + + +} + +describe('withMock', () => { + test('当 name 为数组时,可以正常 Mock', () => { + render() + userEvent.click(screen.getByTestId('update')) + expect(screen.queryByTestId('mock-rules')).toHaveTextContent(JSON.stringify({ 'info.name': '@string' })) + }) + + test('当 name 为字符串时,可以正常 Mock', () => { + render() + userEvent.click(screen.getByTestId('update')) + expect(screen.queryByTestId('mock-rules')).toHaveTextContent(JSON.stringify({ 'info': '@string' })) + }) + test('当 readonly 为 true 时,则无法 Mock', () => { + render() + userEvent.click(screen.getByTestId('update')) + expect(screen.queryByTestId('mock-rules')).toHaveTextContent(JSON.stringify({})) + }) + test('当 hidden 为 true 时,则无法 Mock', () => { + render(