Match-id-fa76e4980e36055d166fbde807cfd760cfa46a3d

This commit is contained in:
* 2022-02-28 20:47:45 +08:00 committed by *
parent c2f6ad0265
commit 8d516d8999
8 changed files with 354 additions and 33 deletions

View File

@ -2,7 +2,7 @@ module.exports = {
presets: [ presets: [
'@babel/react', '@babel/react',
'@babel/preset-typescript', '@babel/preset-typescript',
'@babel/preset-env' ['@babel/preset-env', { targets: { node: 'current' } }]
], ],
plugins: [ plugins: [
['@babel/plugin-proposal-class-properties', { loose: true }], ['@babel/plugin-proposal-class-properties', { loose: true }],

View File

@ -12,7 +12,7 @@ module.exports = {
// browser: false, // browser: false,
// The directory where Jest should store its cached dependency information // The directory where Jest should store its cached dependency information
// cacheDirectory: "C:\\Users\\j30009756\\AppData\\Local\\Temp\\jest", // cacheDirectory: "",
// Automatically clear mock calls and instances between every test // Automatically clear mock calls and instances between every test
// clearMocks: false, // clearMocks: false,
@ -58,15 +58,15 @@ module.exports = {
// globalTeardown: undefined, // globalTeardown: undefined,
// A set of global variables that need to be available in all test environments // A set of global variables that need to be available in all test environments
globals: { // globals: {
'isDev': process.env.NODE_ENV === 'development', // 'isDev': process.env.NODE_ENV === 'development',
'MessageChannel': function MessageChannel() { // 'MessageChannel': function MessageChannel() {
this.port1 = {}; // this.port1 = {};
this.port2 = { // this.port2 = {
postMessage() {} // postMessage() {}
}; // };
} // }
}, // },
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
// maxWorkers: "50%", // maxWorkers: "50%",
@ -78,12 +78,8 @@ module.exports = {
// An array of file extensions your modules use // An array of file extensions your modules use
// moduleFileExtensions: [ // moduleFileExtensions: [
// "js", // 'js',
// "json", // 'ts'
// "jsx",
// "ts",
// "tsx",
// "node"
// ], // ],
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
@ -120,21 +116,21 @@ module.exports = {
// restoreMocks: false, // restoreMocks: false,
// The root directory that Jest should scan for tests and modules within // The root directory that Jest should scan for tests and modules within
// rootDir: undefined, rootDir: process.cwd(),
// A list of paths to directories that Jest should use to search for files in // A list of paths to directories that Jest should use to search for files in
// roots: [ // roots: [
// "<rootDir>" // '<rootDir>/scripts'
// ], // ],
// Allows you to use a custom runner instead of Jest's default test runner // Allows you to use a custom runner instead of Jest's default test runner
// runner: "jest-runner", // runner: "jest-runner",
// The paths to modules that run some code to configure or set up the testing environment before each test // The paths to modules that run some code to configure or set up the testing environment before each test
//setupFiles: [require.resolve('./scripts/jest/setupHostConfig.js')], setupFilesAfterEnv: [require.resolve('./scripts/__tests__/jest/setupEnvironment.js')],
// A list of paths to modules that run some code to configure or set up the testing framework before each test // A list of paths to modules that run some code to configure or set up the testing framework before each test
// setupFilesAfterEnv: [], setupFiles: [require.resolve('./scripts/__tests__/jest/setupTests.js')],
// A list of paths to snapshot serializer modules Jest should use for snapshot testing // A list of paths to snapshot serializer modules Jest should use for snapshot testing
// snapshotSerializers: [], // snapshotSerializers: [],
@ -150,9 +146,7 @@ module.exports = {
// The glob patterns Jest uses to detect test files // The glob patterns Jest uses to detect test files
testMatch: [ testMatch: [
'<rootDir>/scripts/__tests__/**/*.test.js', '<rootDir>/scripts/__tests__/**/*.test.js'
'<rootDir>/scripts/__tests__/*.test.js',
'<rootDir>/scripts/__tests__/*.test.jsx',
], ],
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
@ -173,7 +167,7 @@ module.exports = {
// testURL: "http://localhost", // testURL: "http://localhost",
// Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout" // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
// timers: "real", timers: 'fake',
// A map from regular expressions to paths to transformers // A map from regular expressions to paths to transformers
// transform: undefined, // transform: undefined,

View File

@ -4,7 +4,7 @@ import { act } from 'react-dom/test-utils';
describe('Hook Test', () => { describe('Hook Test', () => {
const { useState, useReducer, useEffect, useLayoutEffect, useContext } = React; const { useState, useReducer, useEffect, useLayoutEffect, useContext, useMemo, useCallback, useRef, useImperativeHandle, forwardRef } = React;
const { unmountComponentAtNode } = HorizonDOM; const { unmountComponentAtNode } = HorizonDOM;
let container = null; let container = null;
beforeEach(() => { beforeEach(() => {
@ -175,4 +175,127 @@ describe('Hook Test', () => {
expect(container.querySelector('p').innerHTML).toBe('audi'); expect(container.querySelector('p').innerHTML).toBe('audi');
expect(container.querySelector('#senP').innerHTML).toBe('88'); expect(container.querySelector('#senP').innerHTML).toBe('88');
}); });
it('测试useMemo', () => {
let setMemo;
const App = () => {
const [num, setNum] = useState(0);
const [memoDependent, _setMemo] = useState('App');
setMemo = _setMemo;
const text = useMemo(() => {
setNum(num + 1);
return memoDependent;
}, [memoDependent])
return (
<>
<p>{text}</p>
<p id="p">{num}</p>
</>
);
}
HorizonDOM.render(<App words="App" />, container);
expect(container.querySelector('p').innerHTML).toBe('App');
expect(container.querySelector('#p').innerHTML).toBe('1');
// 修改useMemo的依赖项num会加一text会改变。
setMemo('Apps')
expect(container.querySelector('p').innerHTML).toBe('Apps');
expect(container.querySelector('#p').innerHTML).toBe('2');
// useMemo的依赖项不变num不会加一text不会改变。
setMemo('Apps')
expect(container.querySelector('p').innerHTML).toBe('Apps');
expect(container.querySelector('#p').innerHTML).toBe('2');
// 修改useMemo的依赖项num会加一text会改变。
setMemo('App')
expect(container.querySelector('p').innerHTML).toBe('App');
expect(container.querySelector('#p').innerHTML).toBe('3');
});
it('测试useCallback', () => {
const App = (props) => {
const [num, setNum] = useState(0);
const NumUseCallback = useCallback(() => {
setNum(num + props.text)
}, [props]);
return (
<>
<p>{num}</p>
<button onClick={NumUseCallback} />
</>
)
}
HorizonDOM.render(<App text={1} />, container);
expect(container.querySelector('p').innerHTML).toBe('0');
// 点击按钮触发num加1
container.querySelector('button').click();
expect(container.querySelector('p').innerHTML).toBe('1');
// 再次点击依赖项没变num不增加
container.querySelector('button').click();
expect(container.querySelector('p').innerHTML).toBe('1');
HorizonDOM.render(<App text={2} />, container);
expect(container.querySelector('p').innerHTML).toBe('1');
// 依赖项有变化点击按钮num增加
container.querySelector('button').click();
expect(container.querySelector('p').innerHTML).toBe('3');
});
it('测试useRef', () => {
const App = () => {
const [num, setNum] = useState(1);
const ref = useRef();
if (!ref.current) {
ref.current = num;
}
return (
<>
<p>{num}</p>
<p id="sp">{ref.current}</p>
<button onClick={() => setNum(num + 1)} />
</>
)
}
HorizonDOM.render(<App />, container);
expect(container.querySelector('p').innerHTML).toBe('1');
expect(container.querySelector('#sp').innerHTML).toBe('1');
// 点击按钮触发num加1,ref不变
container.querySelector('button').click();
expect(container.querySelector('p').innerHTML).toBe('2');
expect(container.querySelector('#sp').innerHTML).toBe('1');
});
it('测试useImperativeHandle', () => {
let App = (props, ref) => {
const [num, setNum] = useState(0);
useImperativeHandle(ref, () => ({num, setNum}), []);
return <p>{num}</p>;
}
let App1 = (props, ref) => {
const [num1, setNum1] = useState(0);
useImperativeHandle(ref, () => ({num1, setNum1}), [num1]);
return <p>{num1}</p>;
}
App = forwardRef(App);
App1 = forwardRef(App1);
const counter = React.createRef(null);
const counter1 = React.createRef(null);
HorizonDOM.render(<App ref={counter} />, container);
expect(counter.current.num).toBe(0);
act(() => {
counter.current.setNum(1);
});
// useImperativeHandle的dep为[],所以不会变
expect(counter.current.num).toBe(0);
// 清空container
unmountComponentAtNode(container);
HorizonDOM.render(<App1 ref={counter1} />, container);
expect(counter1.current.num1).toBe(0);
act(() => {
counter1.current.setNum1(1);
});
// useImperativeHandle的dep为[num1],所以会变
expect(counter1.current.num1).toBe(1);
});
}); });

View File

@ -0,0 +1,136 @@
import * as React from '../../../libs/horizon/src/external/Horizon';
import * as HorizonDOM from '../../../libs/horizon/src/dom/DOMExternal';
//import { act } from 'react-dom/test-utils';
import * as Scheduler from 'scheduler';
describe('useState Hook Test', () => {
const { useState, forwardRef, useImperativeHandle, memo } = React;
const { unmountComponentAtNode } = HorizonDOM;
let container = null;
beforeEach(() => {
// 创建一个 DOM 元素作为渲染目标
container = document.createElement('div');
document.body.appendChild(container);
});
afterEach(() => {
// 退出时进行清理
unmountComponentAtNode(container);
container.remove();
container = null;
Scheduler.reset();
});
const Text = (props) => {
Scheduler.unstable_yieldValue(props.text);
return <p>{props.text}</p>;
}
it('多个useState', () => {
const App = () => {
const [num, setNum] = useState(0);
const [count, setCount] = useState(0);
return (
<p
onClick={() => {
setNum(num + 1);
setCount(count + 2);
}}
>
{num}{count}
</p>
);
}
HorizonDOM.render(<App />, container);
expect(container.querySelector('p').innerHTML).toBe('00');
container.querySelector('p').click();
expect(container.querySelector('p').innerHTML).toBe('12');
container.querySelector('p').click();
expect(container.querySelector('p').innerHTML).toBe('24');
});
it('同一个useState声明的状态会被覆盖处理', () => {
const App = () => {
const [num, setNum] = useState(0);
return (
<p
onClick={() => {
setNum(num + 1);
setNum(num + 2);
}}
>
{num}
</p>
);
}
HorizonDOM.render(<App />, container);
expect(container.querySelector('p').innerHTML).toBe('0');
container.querySelector('p').click();
expect(container.querySelector('p').innerHTML).toBe('2');
container.querySelector('p').click();
expect(container.querySelector('p').innerHTML).toBe('4');
});
it('useState设置相同的值时不会重新渲染', () => {
let setNum;
const App = () => {
const [num, _setNum] = useState(0);
setNum = _setNum;
return <Text text={num} />;
}
HorizonDOM.render(<App />, container);
expect(container.querySelector('p').innerHTML).toBe('0');
expect(Scheduler).toHaveYielded([0]);
// useState修改state 时,设置相同的值,函数组件不会重新渲染
setNum(0);
expect(Scheduler).toHaveYielded([]);
expect(container.querySelector('p').innerHTML).toBe('0');
});
it('useState的惰性初始化', () => {
//let data = null;
const App = forwardRef((props, ref) => {
const [num, setNum] = useState(() => {
// if (data === null) {
// data = ['initNum'];
// } else {
// data.push('initNum');
// }
Scheduler.unstable_yieldValue(props.initNum);
return props.initNum
});
useImperativeHandle(ref, () => ({ setNum }))
return <p>{num}</p>;
})
const ref = React.createRef(null);
HorizonDOM.render(<App initNum={1} ref={ref} />, container);
expect(Scheduler).toHaveYielded([1]);
expect(container.querySelector('p').innerHTML).toBe('1');
// 设置num为3
ref.current.setNum(3);
// 初始化函数只在初始渲染时被调用,所以Scheduler里的dataArray清空后没有新增。
expect(Scheduler).toHaveYielded([]);
expect(container.querySelector('p').innerHTML).toBe('3');
});
it('useState与memo一起使用', () => {
let setNum;
const App = memo((props) => {
const [num, _setNum] = useState(0);
setNum = _setNum;
return <Text text={num} />;
})
HorizonDOM.render(<App />, container);
expect(Scheduler).toHaveYielded([0]);
expect(container.querySelector('p').innerHTML).toBe('0');
// 不会重新渲染
HorizonDOM.render(<App />, container);
expect(Scheduler).toHaveYielded([]);
expect(container.querySelector('p').innerHTML).toBe('0');
// 会重新渲染
setNum(1)
expect(Scheduler).toHaveYielded([1]);
expect(container.querySelector('p').innerHTML).toBe('1');
});
});

View File

@ -0,0 +1,22 @@
function captureAssertion(fn) {
try {
fn();
} catch (error) {
return {
pass: false,
message: () => error.message,
};
}
return {pass: true};
}
function toHaveYielded(Scheduler, expectedYields) {
return captureAssertion(() => {
const actualYields = Scheduler.unstable_clearYields();
expect(actualYields).toEqual(expectedYields);
});
}
module.exports = {
toHaveYielded,
};

View File

@ -0,0 +1,11 @@
global.isDev = process.env.NODE_ENV === 'development';
global.MessageChannel = function MessageChannel() {
this.port1 = {};
this.port2 = {
postMessage() { }
};
};
expect.extend({
...require('./customMatcher'),
});

View File

@ -0,0 +1 @@
jest.mock('scheduler', () => require('./testUtil.js'));

View File

@ -0,0 +1,34 @@
if (process.env.NODE_ENV !== 'production') {
(function () {
let dataArray = null;
let isFlushing = false;
const unstable_yieldValue = (value) => {
if (dataArray === null) {
dataArray = [value];
} else {
dataArray.push(value);
}
};
const unstable_clearYields = () => {
if (dataArray === null) {
return [];
}
const values = dataArray;
dataArray = null;
return values;
};
const reset = () => {
if (isFlushing) {
throw new Error('Cannot reset while already flushing work.');
}
dataArray = null;
}
exports.reset = reset;
exports.unstable_yieldValue = unstable_yieldValue;
exports.unstable_clearYields = unstable_clearYields;
})();
}