Match-id-991d5f810c56de98d317600d544e7e30fd874144

This commit is contained in:
* 2022-03-15 17:42:45 +08:00 committed by *
parent 4d2cd8276a
commit 2100ebcb57
14 changed files with 638 additions and 655 deletions

View File

@ -58,15 +58,15 @@ module.exports = {
// globalTeardown: undefined,
// A set of global variables that need to be available in all test environments
// globals: {
// 'isDev': process.env.NODE_ENV === 'development',
// 'MessageChannel': function MessageChannel() {
// this.port1 = {};
// this.port2 = {
// postMessage() {}
// };
// }
// },
globals: {
//'isDev': process.env.NODE_ENV === 'development',
'MessageChannel': function MessageChannel() {
this.port1 = {};
this.port2 = {
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.
// maxWorkers: "50%",

View File

@ -0,0 +1,36 @@
/* eslint-disable no-undef */
import * as React from '../../../../libs/horizon/src/external/Horizon';
import * as HorizonDOM from '../../../../libs/horizon/src/dom/DOMExternal';
describe('useCallback Hook Test', () => {
const { useState, useCallback } = React;
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');
});
});

View File

@ -0,0 +1,52 @@
/* eslint-disable no-undef */
import * as React from '../../../../libs/horizon/src/external/Horizon';
import * as HorizonDOM from '../../../../libs/horizon/src/dom/DOMExternal';
import { act } from '../../jest/customMatcher';
describe('useContext Hook Test', () => {
const { useState, useContext } = React;
const { unmountComponentAtNode } = HorizonDOM;
it('简单使用useContext', () => {
const LanguageTypes = {
JAVA: 'Java',
JAVASCRIPT: 'JavaScript',
};
const defaultValue = { type: LanguageTypes.JAVASCRIPT };
const SystemLanguageContext = React.createContext(defaultValue);
const SystemLanguageProvider = ({ type, children }) => {
return (
<SystemLanguageContext.Provider value={{ type }}>
{children}
</SystemLanguageContext.Provider>
);
};
const TestFunction = () => {
const context = useContext(SystemLanguageContext);
return <p id="p">{context.type}</p>;
}
let setValue;
const App = () => {
const [value, _setValue] = useState(LanguageTypes.JAVA);
setValue = _setValue;
return (
<div className="App">
<SystemLanguageProvider type={value}>
<TestFunction />
</SystemLanguageProvider>
</div>
)
}
HorizonDOM.render(<TestFunction />, container);
// 测试当Provider未提供时获取到的默认值'JavaScript'。
expect(container.querySelector('p').innerHTML).toBe('JavaScript');
unmountComponentAtNode(container);
HorizonDOM.render(<App />, container);
// 测试当Provider提供时可以获取到Provider的值'Java'。
expect(container.querySelector('p').innerHTML).toBe('Java');
// 测试当Provider改变时可以获取到最新Provider的值。
act(() => setValue(LanguageTypes.JAVASCRIPT));
expect(container.querySelector('p').innerHTML).toBe('JavaScript');
});
});

View File

@ -1,32 +1,35 @@
import * as React from '../../../libs/horizon/src/external/Horizon';
import * as HorizonDOM from '../../../libs/horizon/src/dom/DOMExternal';
import * as LogUtils from '../jest/logUtils';
import { act } from '../jest/customMatcher';
/* eslint-disable no-undef */
import * as React from '../../../../libs/horizon/src/external/Horizon';
import * as HorizonDOM from '../../../../libs/horizon/src/dom/DOMExternal';
import * as LogUtils from '../../jest/logUtils';
import { act } from '../../jest/customMatcher';
import Text from '../../jest/Text';
describe('useEffect Hook Test', () => {
const { useEffect, useLayoutEffect, useState, memo, forwardRef } = React;
const { unmountComponentAtNode } = HorizonDOM;
let container = null;
beforeEach(() => {
LogUtils.clear();
// 创建一个 DOM 元素作为渲染目标
container = document.createElement('div');
document.body.appendChild(container);
});
afterEach(() => {
// 退出时进行清理
unmountComponentAtNode(container);
container.remove();
container = null;
LogUtils.clear();
it('简单使用useEffect', () => {
const App = () => {
const [num, setNum] = useState(0);
useEffect(() => {
document.getElementById('p').style.display = num === 0 ? 'none' : 'inline';
});
return (
<>
<p style={{ display: 'block' }} id="p">{num}</p>
<button onClick={() => setNum(num + 1)} />
</>
)
}
HorizonDOM.render(<App />, container);
expect(document.getElementById('p').style.display).toBe('block');
// 点击按钮触发num加1
container.querySelector('button').click();
expect(document.getElementById('p').style.display).toBe('none');
container.querySelector('button').click();
expect(container.querySelector('p').style.display).toBe('inline');
});
const Text = (props) => {
LogUtils.log(props.text);
return <p>{props.text}</p>;
}
it('act方法', () => {
const App = () => {
return <Text text={'op'} />;
@ -406,8 +409,8 @@ describe('useEffect Hook Test', () => {
return <Text text={'Num: ' + num} />;
}
HorizonDOM.render(<App />, container, () => LogUtils.log('Sync effect'));
expect(LogUtils.getAndClear()).toEqual(['Num: 0', 'Sync effect']);
HorizonDOM.render(<App />, container, () => LogUtils.log('App callback effect'));
expect(LogUtils.getAndClear()).toEqual(['Num: 0', 'App callback effect']);
expect(container.textContent).toEqual('Num: 0');
act(() => {
setNum(2);
@ -542,9 +545,9 @@ describe('useEffect Hook Test', () => {
}
act(() => {
HorizonDOM.render(<App num={0} />, container, () =>
LogUtils.log('Sync effect'),
LogUtils.log('App callback effect'),
);
expect(LogUtils.getAndClear()).toEqual(['Number: 0', 'Sync effect']);
expect(LogUtils.getAndClear()).toEqual(['Number: 0', 'App callback effect']);
expect(container.textContent).toEqual('Number: 0');
});
// 处理错误不会向下执行LogUtils.log(`Mount with [${props.num}]`);
@ -552,11 +555,11 @@ describe('useEffect Hook Test', () => {
act(() => {
HorizonDOM.render(null, container, () =>
LogUtils.log('Sync effect'),
LogUtils.log('App callback effect'),
);
});
expect(LogUtils.getAndClear()).toEqual([
'Sync effect',
'App callback effect',
// 不会处理卸载部分 LogUtils.log(`Unmount with [${props.num}]`);
]);
expect(container.textContent).toEqual('');
@ -652,7 +655,7 @@ describe('useEffect Hook Test', () => {
});
it('当组件的更新方法在卸载函数中,组件的子组件更新不会告警', () => {
const App = () => {
const App = () => {
LogUtils.log('App');
const appRef = React.createRef(null);
useEffect(() => {
@ -684,7 +687,7 @@ const App = () => {
'num effect'
]);
});
expect(LogUtils.getAndClear()).toEqual(['Child effect','App effect']);
expect(LogUtils.getAndClear()).toEqual(['Child effect', 'App effect']);
act(() => {
HorizonDOM.render(null, container);

View File

@ -0,0 +1,89 @@
/* eslint-disable no-undef */
import * as React from '../../../../libs/horizon/src/external/Horizon';
import * as HorizonDOM from '../../../../libs/horizon/src/dom/DOMExternal';
import * as LogUtils from '../../jest/logUtils';
import Text from '../../jest/Text';
describe('useImperativeHandle Hook Test', () => {
const {
useState,
useImperativeHandle,
forwardRef
} = React;
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);
});
it('useImperativeHandle没有配置dep时自动更新', () => {
let App = (props, ref) => {
const [num, setNum] = useState(0);
useImperativeHandle(ref, () => ({ num, setNum }));
return <Text text={num} />;
}
let App1 = (props, ref) => {
const [num1, setNum1] = useState(0);
useImperativeHandle(ref, () => ({ num1, setNum1 }), []);
return <Text text={num1} />;
}
App = forwardRef(App);
App1 = forwardRef(App1);
const counter = React.createRef(null);
const counter1 = React.createRef(null);
HorizonDOM.render(<App ref={counter} />, container);
expect(LogUtils.getAndClear()).toEqual([0]);
expect(counter.current.num).toBe(0);
act(() => {
counter.current.setNum(1);
});
expect(LogUtils.getAndClear()).toEqual([1]);
// useImperativeHandle没有配置的dep,所以会自动更新
expect(counter.current.num).toBe(1);
// 清空container
unmountComponentAtNode(container);
HorizonDOM.render(<App1 ref={counter1} />, container);
expect(LogUtils.getAndClear()).toEqual([0]);
expect(counter1.current.num1).toBe(0);
act(() => {
counter1.current.setNum1(1);
});
expect(LogUtils.getAndClear()).toEqual([1]);
// useImperativeHandle的dep为[],所以不会变
expect(counter1.current.num1).toBe(0);
});
});

View File

@ -0,0 +1,116 @@
/* eslint-disable no-undef */
import * as React from '../../../../libs/horizon/src/external/Horizon';
import * as HorizonDOM from '../../../../libs/horizon/src/dom/DOMExternal';
import * as LogUtils from '../../jest/logUtils';
import { act } from '../../jest/customMatcher';
import Text from '../../jest/Text';
describe('useLayoutEffect Hook Test', () => {
const {
useState,
useEffect,
useLayoutEffect
} = React;
it('简单使用useLayoutEffect', () => {
const App = () => {
const [num, setNum] = useState(0);
useLayoutEffect(() => {
document.getElementById('p').style.display = num === 0 ? 'none' : 'inline';
});
return (
<>
<p style={{ display: 'block' }} id="p">{num}</p>
<button onClick={() => setNum(num + 1)} />
</>
)
}
HorizonDOM.render(<App />, container);
expect(document.getElementById('p').style.display).toBe('none');
container.querySelector('button').click();
expect(container.querySelector('p').style.display).toBe('inline');
});
it('useLayoutEffect的触发时序', () => {
const App = (props) => {
useLayoutEffect(() => {
LogUtils.log('LayoutEffect');
});
return <Text text={props.num} />;
}
HorizonDOM.render(<App num={1} />, container, () => LogUtils.log('Sync effect'));
expect(LogUtils.getAndClear()).toEqual([
1,
// 同步在渲染之后
'LayoutEffect',
'Sync effect'
]);
expect(container.querySelector('p').innerHTML).toBe('1');
// 更新
HorizonDOM.render(<App num={2} />, container, () => LogUtils.log('Sync effect'));
expect(LogUtils.getAndClear()).toEqual([
2,
'LayoutEffect',
'Sync effect'
]);
expect(container.querySelector('p').innerHTML).toBe('2');
});
it('创建销毁useLayoutEffect', () => {
const App = (props) => {
useEffect(() => {
LogUtils.log(`num effect [${props.num}]`);
return () => {
LogUtils.log('num effect destroy');
};
}, [props.num]);
useLayoutEffect(() => {
LogUtils.log(`num Layouteffect [${props.num}]`);
return () => {
LogUtils.log(`num [${props.num}] Layouteffect destroy`);
};
}, [props.num]);
return <Text text={'num: ' + props.num} />;
}
act(() => {
HorizonDOM.render(<App num={0} />, container, () => LogUtils.log('callback effect'));
expect(LogUtils.getAndClear()).toEqual([
'num: 0',
'num Layouteffect [0]',
'callback effect'
]);
expect(container.textContent).toBe('num: 0');
})
// 更新
act(() => {
HorizonDOM.render(<App num={1} />, container, () => LogUtils.log('callback effect'));
})
expect(LogUtils.getAndClear()).toEqual([
// 异步effect
'num effect [0]',
'num: 1',
// 旧Layouteffect销毁
'num [0] Layouteffect destroy',
// 新Layouteffect建立
'num Layouteffect [1]',
'callback effect',
// 异步旧的effect销毁
'num effect destroy',
// 异步新的effect建立
'num effect [1]'
]);
act(() => {
HorizonDOM.render(null, container, () => LogUtils.log('callback effect'));
})
expect(LogUtils.getAndClear()).toEqual([
// 同步Layouteffect销毁
'num [1] Layouteffect destroy',
'callback effect',
// 最后执行异步effect销毁
'num effect destroy',
]);
});
});

View File

@ -0,0 +1,107 @@
/* eslint-disable no-undef */
import * as React from '../../../../libs/horizon/src/external/Horizon';
import * as HorizonDOM from '../../../../libs/horizon/src/dom/DOMExternal';
import * as LogUtils from '../../jest/logUtils';
import Text from '../../jest/Text';
describe('useMemo Hook Test', () => {
const { useMemo, useState } = React;
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('触发useMemo', () => {
const App = (props) => {
const num = useMemo(() => {
LogUtils.log(props._num);
return props._num + 1;
}, [props._num]);
return <Text text={num} />;
}
HorizonDOM.render(<App _num={0} />, container);
expect(LogUtils.getAndClear()).toEqual([
0,
1
]);
expect(container.textContent).toBe('1');
HorizonDOM.render(<App _num={1} />, container);
expect(LogUtils.getAndClear()).toEqual([
1,
2
]);
expect(container.textContent).toBe('2');
HorizonDOM.render(<App _num={1} />, container);
// 不会触发useMemo
expect(LogUtils.getAndClear()).toEqual([2]);
expect(container.textContent).toBe('2');
HorizonDOM.render(<App _num={2} />, container);
expect(LogUtils.getAndClear()).toEqual([
2,
3
]);
expect(container.textContent).toBe('3');
});
it('输入不变重新渲染也会触发useMemo', () => {
const App = (props) => {
const num = useMemo(props._num);
return <Text text={num} />;
}
const num1 = () => {
LogUtils.log('num 1');
return 1;
}
const num2 = () => {
LogUtils.log('num 2');
return 2;
}
HorizonDOM.render(<App _num={num1} />, container);
expect(LogUtils.getAndClear()).toEqual(['num 1', 1]);
HorizonDOM.render(<App _num={num1} />, container);
expect(LogUtils.getAndClear()).toEqual(['num 1', 1]);
HorizonDOM.render(<App _num={num1} />, container);
expect(LogUtils.getAndClear()).toEqual(['num 1', 1]);
HorizonDOM.render(<App _num={num2} />, container);
expect(LogUtils.getAndClear()).toEqual(['num 2', 2]);
});
});

View File

@ -0,0 +1,61 @@
/* eslint-disable no-undef */
import * as React from '../../../../libs/horizon/src/external/Horizon';
import * as HorizonDOM from '../../../../libs/horizon/src/dom/DOMExternal';
describe('useReducer Hook Test', () => {
const { useReducer } = React;
it('简单使用useReducer', () => {
const intlCar = { logo: '', price: 0 };
let dispatch;
const App = () => {
const carReducer = (state, action) => {
switch (action.logo) {
case 'ford':
return {
...intlCar,
logo: 'ford',
price: 76
};
case 'bmw':
return {
...intlCar,
logo: 'bmw',
price: 100
};
case 'benz':
return {
...intlCar,
logo: 'benz',
price: 80
};
default:
return {
...intlCar,
logo: 'audi',
price: 88
};
}
}
const [car, carDispatch] = useReducer(carReducer, intlCar);
dispatch = carDispatch;
return (
<div>
<p>{car.logo}</p>
<p id={'senP'}>{car.price}</p>
</div>
)
}
HorizonDOM.render(<App />, container);
expect(container.querySelector('p').innerHTML).toBe('');
expect(container.querySelector('#senP').innerHTML).toBe('0');
// 触发bmw
dispatch({ logo: 'bmw' });
expect(container.querySelector('p').innerHTML).toBe('bmw');
expect(container.querySelector('#senP').innerHTML).toBe('100');
// 触发carReducer里的switch的default项
dispatch({ logo: 'wrong logo' });
expect(container.querySelector('p').innerHTML).toBe('audi');
expect(container.querySelector('#senP').innerHTML).toBe('88');
});
});

View File

@ -0,0 +1,58 @@
/* eslint-disable no-undef */
import * as React from '../../../../libs/horizon/src/external/Horizon';
import * as HorizonDOM from '../../../../libs/horizon/src/dom/DOMExternal';
import * as LogUtils from '../../jest/logUtils';
import Text from '../../jest/Text';
describe('useRef Hook Test', () => {
const { useState, useRef } = React;
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('更新current值时不会re-render', () => {
const App = () => {
const ref = useRef(1);
return (
<>
<button onClick={() => {
ref.current += 1;
}}>
button
</button>
<Text text={ref.current} />;
</>
)
}
HorizonDOM.render(<App />, container);
expect(LogUtils.getAndClear()).toEqual([1]);
expect(container.querySelector('p').innerHTML).toBe('1');
// 点击按钮触发ref.current加1
container.querySelector('button').click();
// ref.current改变不会触发重新渲染
expect(LogUtils.getAndClear()).toEqual([]);
expect(container.querySelector('p').innerHTML).toBe('1');
});
});

View File

@ -1,31 +1,35 @@
import * as React from '../../../libs/horizon/src/external/Horizon';
import * as HorizonDOM from '../../../libs/horizon/src/dom/DOMExternal';
import * as LogUtils from '../jest/logUtils';
import { act } from '../jest/customMatcher';
/* eslint-disable no-undef */
import * as React from '../../../../libs/horizon/src/external/Horizon';
import * as HorizonDOM from '../../../../libs/horizon/src/dom/DOMExternal';
import * as LogUtils from '../../jest/logUtils';
import { act } from '../../jest/customMatcher';
import Text from '../../jest/Text';
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);
});
const {
useState,
forwardRef,
useImperativeHandle,
memo
} = React;
afterEach(() => {
// 退出时进行清理
unmountComponentAtNode(container);
container.remove();
container = null;
LogUtils.clear();
it('简单使用useState', () => {
const App = () => {
const [num, setNum] = useState(0);
return (
<>
<p>{num}</p>
<button onClick={() => setNum(num + 1)} />
</>
)
}
HorizonDOM.render(<App />, container);
expect(container.querySelector('p').innerHTML).toBe('0');
// 点击按钮触发num加1
container.querySelector('button').click();
expect(container.querySelector('p').innerHTML).toBe('1');
});
const Text = (props) => {
LogUtils.log(props.text);
return <p>{props.text}</p>;
}
it('多个useState', () => {
const App = () => {
const [num, setNum] = useState(0);
@ -129,41 +133,38 @@ describe('useState Hook Test', () => {
});
it('卸载useState', () => {
let updateA;
let updateB;
let updateC;
// let updateA;
let setNum;
let setCount;
const App = (props) => {
const [A, _updateA] = useState(0);
const [B, _updateB] = useState(0);
updateA = _updateA;
updateB = _updateB;
const [num, setNum_1] = useState(0);
setNum = setNum_1;
let C;
if (props.loadC) {
const [_C, _updateC] = useState(0);
C = _C;
updateC = _updateC;
let count;
if (props.hasCount) {
const [count_1, setCount_1] = useState(0);
count = count_1;
setCount = setCount_1;
} else {
C = '[not loaded]';
count = 'null';
}
return <Text text={`A: ${A}, B: ${B}, C: ${C}`} />;
return <Text text={`Number: ${num}, Count: ${count}`} />;
}
HorizonDOM.render(<App loadC={true} />, container);
expect(LogUtils.getAndClear()).toEqual(['A: 0, B: 0, C: 0']);
expect(container.textContent).toBe('A: 0, B: 0, C: 0');
HorizonDOM.render(<App hasCount={true} />, container);
expect(LogUtils.getAndClear()).toEqual(['Number: 0, Count: 0']);
expect(container.textContent).toBe('Number: 0, Count: 0');
act(() => {
updateA(2);
updateB(3);
updateC(4);
setNum(1);
setCount(2);
});
expect(LogUtils.getAndClear()).toEqual(['A: 2, B: 3, C: 4']);
expect(container.textContent).toBe('A: 2, B: 3, C: 4');
expect(LogUtils.getAndClear()).toEqual(['Number: 1, Count: 2']);
expect(container.textContent).toBe('Number: 1, Count: 2');
expect(() => {
HorizonDOM.render(<App loadC={false} />, container);
HorizonDOM.render(<App hasCount={false} />, container);
}).toThrow(
'Hooks are less than expected, please check whether the hook is written in the condition.',
);

View File

@ -1,249 +0,0 @@
import * as React from '../../../libs/horizon/src/external/Horizon';
import * as HorizonDOM from '../../../libs/horizon/src/dom/DOMExternal';
import * as LogUtils from '../jest/logUtils';
import { act } from '../jest/customMatcher';
describe('Hook Test', () => {
const { useMemo, useRef, useState, useImperativeHandle, forwardRef, useLayoutEffect, useEffect } = React;
const { unmountComponentAtNode } = HorizonDOM;
let container = null;
beforeEach(() => {
LogUtils.clear();
// 创建一个 DOM 元素作为渲染目标
container = document.createElement('div');
document.body.appendChild(container);
});
afterEach(() => {
// 退出时进行清理
unmountComponentAtNode(container);
container.remove();
container = null;
LogUtils.clear();
});
const Text = (props) => {
LogUtils.log(props.text);
return <p>{props.text}</p>;
}
describe('useLayoutEffect Test', () => {
it('useLayoutEffect的触发时序', () => {
const App = (props) => {
useLayoutEffect(() => {
LogUtils.log('LayoutEffect');
});
return <Text text={props.num} />;
}
HorizonDOM.render(<App num={1} />, container, () => LogUtils.log('Sync effect'));
expect(LogUtils.getAndClear()).toEqual([
1,
// 同步在渲染之后
'LayoutEffect',
'Sync effect'
]);
expect(container.querySelector('p').innerHTML).toBe('1');
// 更新
HorizonDOM.render(<App num={2} />, container, () => LogUtils.log('Sync effect'));
expect(LogUtils.getAndClear()).toEqual([
2,
'LayoutEffect',
'Sync effect'
]);
expect(container.querySelector('p').innerHTML).toBe('2');
});
it('创建销毁useLayoutEffect', () => {
const App = (props) => {
useEffect(() => {
LogUtils.log(`num effect [${props.num}]`);
return () => {
LogUtils.log('num effect destroy');
};
}, [props.num]);
useLayoutEffect(() => {
LogUtils.log(`num Layouteffect [${props.num}]`);
return () => {
LogUtils.log(`num [${props.num}] Layouteffect destroy`);
};
}, [props.num]);
return <Text text={'num: ' + props.num} />;
}
act(() => {
HorizonDOM.render(<App num={0} />, container, () => LogUtils.log('callback effect'));
expect(LogUtils.getAndClear()).toEqual([
'num: 0',
'num Layouteffect [0]',
'callback effect'
]);
expect(container.textContent).toBe('num: 0');
})
// 更新
act(() => {
HorizonDOM.render(<App num={1} />, container, () => LogUtils.log('callback effect'));
})
expect(LogUtils.getAndClear()).toEqual([
// 异步effect
'num effect [0]',
'num: 1',
// 旧Layouteffect销毁
'num [0] Layouteffect destroy',
// 新Layouteffect建立
'num Layouteffect [1]',
'callback effect',
// 异步旧的effect销毁
'num effect destroy',
// 异步新的effect建立
'num effect [1]'
]);
act(() => {
HorizonDOM.render(null, container, () => LogUtils.log('callback effect'));
})
expect(LogUtils.getAndClear()).toEqual([
// 同步Layouteffect销毁
'num [1] Layouteffect destroy',
'callback effect',
// 最后执行异步effect销毁
'num effect destroy',
]);
});
})
describe('useMemo Test', () => {
it('触发useMemo', () => {
const App = (props) => {
const num = useMemo(() => {
LogUtils.log(props._num);
return props._num + 1;
}, [props._num]);
return <Text text={num} />;
}
HorizonDOM.render(<App _num={0} />, container);
expect(LogUtils.getAndClear()).toEqual([
0,
1
]);
expect(container.textContent).toBe('1');
HorizonDOM.render(<App _num={1} />, container);
expect(LogUtils.getAndClear()).toEqual([
1,
2
]);
expect(container.textContent).toBe('2');
HorizonDOM.render(<App _num={1} />, container);
// 不会触发useMemo
expect(LogUtils.getAndClear()).toEqual([2]);
expect(container.textContent).toBe('2');
HorizonDOM.render(<App _num={2} />, container);
expect(LogUtils.getAndClear()).toEqual([
2,
3
]);
expect(container.textContent).toBe('3');
});
it('输入不变重新渲染也会触发useMemo', () => {
const App = (props) => {
const num = useMemo(props._num);
return <Text text={num} />;
}
const num1 = () => {
LogUtils.log('num 1');
return 1;
}
const num2 = () => {
LogUtils.log('num 2');
return 2;
}
HorizonDOM.render(<App _num={num1} />, container);
expect(LogUtils.getAndClear()).toEqual(['num 1', 1]);
HorizonDOM.render(<App _num={num1} />, container);
expect(LogUtils.getAndClear()).toEqual(['num 1', 1]);
HorizonDOM.render(<App _num={num1} />, container);
expect(LogUtils.getAndClear()).toEqual(['num 1', 1]);
HorizonDOM.render(<App _num={num2} />, container);
expect(LogUtils.getAndClear()).toEqual(['num 2', 2]);
});
})
describe('useRef Test', () => {
it('更新current值时不会re-render', () => {
const App = () => {
const ref = useRef(1);
return (
<>
<button onClick={() => {
ref.current += 1;
}}>
button
</button>
<Text text={ref.current} />;
</>
)
}
HorizonDOM.render(<App />, container);
expect(LogUtils.getAndClear()).toEqual([1]);
expect(container.querySelector('p').innerHTML).toBe('1');
// 点击按钮触发ref.current加1
container.querySelector('button').click();
// ref.current改变不会触发重新渲染
expect(LogUtils.getAndClear()).toEqual([]);
expect(container.querySelector('p').innerHTML).toBe('1');
});
})
describe('useImperativeHandle Test', () => {
it('useImperativeHandle没有配置dep时自动更新', () => {
let App = (props, ref) => {
const [num, setNum] = useState(0);
useImperativeHandle(ref, () => ({ num, setNum }));
return <Text text={num} />;
}
let App1 = (props, ref) => {
const [num1, setNum1] = useState(0);
useImperativeHandle(ref, () => ({ num1, setNum1 }), []);
return <Text text={num1} />;
}
App = forwardRef(App);
App1 = forwardRef(App1);
const counter = React.createRef(null);
const counter1 = React.createRef(null);
HorizonDOM.render(<App ref={counter} />, container);
expect(LogUtils.getAndClear()).toEqual([0]);
expect(counter.current.num).toBe(0);
act(() => {
counter.current.setNum(1);
});
expect(LogUtils.getAndClear()).toEqual([1]);
// useImperativeHandle没有配置的dep,所以会自动更新
expect(counter.current.num).toBe(1);
// 清空container
unmountComponentAtNode(container);
HorizonDOM.render(<App1 ref={counter1} />, container);
expect(LogUtils.getAndClear()).toEqual([0]);
expect(counter1.current.num1).toBe(0);
act(() => {
counter1.current.setNum1(1);
});
expect(LogUtils.getAndClear()).toEqual([1]);
// useImperativeHandle的dep为[],所以不会变
expect(counter1.current.num1).toBe(0);
});
})
})

View File

@ -1,312 +0,0 @@
import * as React from '../../../libs/horizon/src/external/Horizon';
import * as HorizonDOM from '../../../libs/horizon/src/dom/DOMExternal';
import { act } from '../jest/customMatcher';
describe('Hook Test', () => {
const {
useState,
useReducer,
useEffect,
useLayoutEffect,
useContext,
useMemo,
useCallback,
useRef,
useImperativeHandle,
forwardRef
} = React;
const { unmountComponentAtNode } = HorizonDOM;
let container = null;
beforeEach(() => {
// 创建一个 DOM 元素作为渲染目标
container = document.createElement('div');
document.body.appendChild(container);
});
afterEach(() => {
// 退出时进行清理
unmountComponentAtNode(container);
container.remove();
container = null;
});
it('简单使用useState', () => {
const App = () => {
const [num, setNum] = useState(0);
return (
<>
<p>{num}</p>
<button onClick={() => setNum(num + 1)} />
</>
)
}
HorizonDOM.render(<App />, container);
expect(container.querySelector('p').innerHTML).toBe('0');
// 点击按钮触发num加1
container.querySelector('button').click();
expect(container.querySelector('p').innerHTML).toBe('1');
});
it('简单使用useEffect', () => {
const App = () => {
const [num, setNum] = useState(0);
useEffect(() => {
document.getElementById('p').style.display = num === 0 ? 'none' : 'inline';
});
return (
<>
<p style={{ display: 'block' }} id="p">{num}</p>
<button onClick={() => setNum(num + 1)} />
</>
)
}
HorizonDOM.render(<App />, container);
expect(document.getElementById('p').style.display).toBe('block');
// 点击按钮触发num加1
container.querySelector('button').click();
expect(document.getElementById('p').style.display).toBe('none');
container.querySelector('button').click();
expect(container.querySelector('p').style.display).toBe('inline');
});
it('简单使用useLayoutEffect', () => {
const App = () => {
const [num, setNum] = useState(0);
useLayoutEffect(() => {
document.getElementById('p').style.display = num === 0 ? 'none' : 'inline';
});
return (
<>
<p style={{ display: 'block' }} id="p">{num}</p>
<button onClick={() => setNum(num + 1)} />
</>
)
}
HorizonDOM.render(<App />, container);
expect(document.getElementById('p').style.display).toBe('none');
container.querySelector('button').click();
expect(container.querySelector('p').style.display).toBe('inline');
});
it('简单使用useContext', () => {
const LanguageTypes = {
JAVA: 'Java',
JAVASCRIPT: 'JavaScript',
};
const defaultValue = { type: LanguageTypes.JAVASCRIPT };
const SystemLanguageContext = React.createContext(defaultValue);
const SystemLanguageProvider = ({ type, children }) => {
return (
<SystemLanguageContext.Provider value={{ type }}>
{children}
</SystemLanguageContext.Provider>
);
};
const TestFunction = () => {
const context = useContext(SystemLanguageContext);
return <p id="p">{context.type}</p>;
}
let setValue;
const App = () => {
const [value, _setValue] = useState(LanguageTypes.JAVA);
setValue = _setValue;
return (
<div className="App">
<SystemLanguageProvider type={value}>
<TestFunction />
</SystemLanguageProvider>
</div>
)
}
HorizonDOM.render(<TestFunction />, container);
// 测试当Provider未提供时获取到的默认值'JavaScript'。
expect(container.querySelector('p').innerHTML).toBe('JavaScript');
unmountComponentAtNode(container);
HorizonDOM.render(<App />, container);
// 测试当Provider提供时可以获取到Provider的值'Java'。
expect(container.querySelector('p').innerHTML).toBe('Java');
// 测试当Provider改变时可以获取到最新Provider的值。
act(() => setValue(LanguageTypes.JAVASCRIPT));
expect(container.querySelector('p').innerHTML).toBe('JavaScript');
});
it('简单使用useReducer', () => {
const intlCar = { logo: '', price: 0 };
let dispatch;
const App = () => {
const carReducer = (state, action) => {
switch (action.logo) {
case 'ford':
return {
...intlCar,
logo: 'ford',
price: 76
};
case 'bmw':
return {
...intlCar,
logo: 'bmw',
price: 100
};
case 'benz':
return {
...intlCar,
logo: 'benz',
price: 80
};
default:
return {
...intlCar,
logo: 'audi',
price: 88
};
}
}
const [car, carDispatch] = useReducer(carReducer, intlCar);
dispatch = carDispatch;
return (
<div>
<p>{car.logo}</p>
<p id={'senP'}>{car.price}</p>
</div>
)
}
HorizonDOM.render(<App />, container);
expect(container.querySelector('p').innerHTML).toBe('');
expect(container.querySelector('#senP').innerHTML).toBe('0');
// 触发bmw
dispatch({ logo: 'bmw' });
expect(container.querySelector('p').innerHTML).toBe('bmw');
expect(container.querySelector('#senP').innerHTML).toBe('100');
// 触发carReducer里的switch的default项
dispatch({ logo: 'wrong logo' });
expect(container.querySelector('p').innerHTML).toBe('audi');
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,9 @@
import * as React from '../../../libs/horizon/src/external/Horizon';
import * as LogUtils from '../jest/logUtils';
const Text = (props) => {
LogUtils.log(props.text);
return <p>{props.text}</p>;
};
export default Text;

View File

@ -1,10 +1,22 @@
import { unmountComponentAtNode } from '../../../libs/horizon/src/dom/DOMExternal';
import * as LogUtils from '../jest/logUtils';
global.isDev = process.env.NODE_ENV === 'development';
global.MessageChannel = function MessageChannel() {
this.port1 = {};
this.port2 = {
postMessage() { }
};
};
global.container = null;
global.beforeEach(() => {
LogUtils.clear();
// 创建一个 DOM 元素作为渲染目标
global.container = document.createElement('div');
document.body.appendChild(global.container);
});
global.afterEach(() => {
unmountComponentAtNode(global.container);
global.container.remove();
global.container = null;
LogUtils.clear();
});
// 使Jest感知自定义匹配器
expect.extend({