inula/scripts/__tests__/ComponentTest/HookTest/UseEffect.test.js

756 lines
23 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import * as Horizon from '@cloudsop/horizon/index.ts';
import { getLogUtils } from '../../jest/testUtils';
import { Text } from '../../jest/commonComponents';
describe('useEffect Hook Test', () => {
const {
useEffect,
useLayoutEffect,
useState,
memo,
forwardRef,
act,
} = Horizon;
const LogUtils = getLogUtils();
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)} />
</>
);
};
Horizon.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('act方法', () => {
const App = () => {
return <Text text={'op'} />;
};
act(() => {
Horizon.render(<App />, container, () => {
LogUtils.log('num effect');
});
// 第一次渲染为同步所以同步执行的可以写在act里做判断
expect(LogUtils.getAndClear()).toEqual(['op', 'num effect']);
expect(container.textContent).toBe('op');
});
act(() => {
Horizon.render(null, container, () => {
LogUtils.log('num effect89');
});
// 第二次渲染为异步所以同步执行的不可以写在act里做判断act里拿到的为空数组
expect(LogUtils.getAndClear()).toEqual([]);
});
expect(LogUtils.getAndClear()).toEqual(['num effect89']);
expect(container.textContent).toBe('');
});
it('兄弟节点被删除useEffect依然正常', () => {
const App = () => {
return <Text text="App" />;
};
const NewApp = () => {
useEffect(() => {
LogUtils.log(`NewApp effect`);
}, []);
return <Text text="NewApp" />;
};
const na = <NewApp />;
// <App />必须设置key值否则在diff的时候na会被视为不同组件
Horizon.render([<App key="app" />, na], container);
expect(LogUtils.getAndClear()).toEqual([
'App',
'NewApp'
]);
expect(container.textContent).toBe('AppNewApp');
expect(LogUtils.getAndClear()).toEqual([]);
// 在执行新的render前会执行完上一次render的useEffect所以LogUtils会加入'NewApp effect'。
Horizon.render([na], container);
expect(LogUtils.getAndClear()).toEqual(['NewApp effect', 'NewApp']);
expect(container.textContent).toBe('NewApp');
expect(LogUtils.getAndClear()).toEqual([]);
});
it('兄弟节点更新useEffect依然正常', () => {
const App = () => {
const [num, setNum] = useState(0);
useLayoutEffect(() => {
if (num === 0) {
setNum(1);
}
LogUtils.log('App Layout effect ' + num);
});
return <Text text="App" />;
};
const NewApp = () => {
useEffect(() => {
LogUtils.log(`NewApp effect`);
}, []);
return <Text text="NewApp" />;
};
// <App />必须设置key值否则在diff的时候na会被视为不同组件
Horizon.render([<App key="app" />, <NewApp />], container);
expect(LogUtils.getAndClear()).toEqual([
'App',
'NewApp',
'App Layout effect 0',
// 在App更新前会执行完NewApp的useEffect
'NewApp effect',
'App',
'App Layout effect 1',
]);
expect(container.textContent).toBe('AppNewApp');
});
it('兄弟节点执行新的挂载动作useEffect依然正常', () => {
const newContainer = document.createElement('div');
const App = () => {
useLayoutEffect(() => {
LogUtils.log('App Layout effect');
Horizon.render(<Text text="NewContainer" />, newContainer);
});
return <Text text="App" />;
};
const NewApp = () => {
useEffect(() => {
LogUtils.log(`NewApp effect`);
}, []);
return <Text text="NewApp" />;
};
// <App />必须设置key值否则在diff的时候na会被视为不同组件
Horizon.render([<App key="app" />, <NewApp />], container);
expect(LogUtils.getAndClear()).toEqual([
'App',
'NewApp',
'App Layout effect',
// 在执行useLayoutEffectApp的render前会执行完NewApp的useEffect
'NewApp effect',
'NewContainer',
]);
expect(container.textContent).toBe('AppNewApp');
});
it('执行新render的useEffect前会先执行旧render的useEffect', () => {
const App = (props) => {
useEffect(() => {
LogUtils.log(`First effect [${props.num}]`);
});
return <Text text={'num: ' + props.num} />;
};
act(() => {
Horizon.render(<App num={0} />, container, () => LogUtils.log('callback effect'));
expect(LogUtils.getAndClear()).toEqual(['num: 0', 'callback effect']);
expect(container.textContent).toEqual('num: 0');
});
expect(LogUtils.getAndClear()).toEqual(['First effect [0]']);
act(() => {
Horizon.render(<App num={1} />, container, () => LogUtils.log('callback effect'));
});
// 此时异步执行act执行完后会执行新render的useEffect
expect(LogUtils.getAndClear()).toEqual([
'num: 1',
'callback effect',
'First effect [1]'
]);
expect(container.textContent).toEqual('num: 1');
});
it('混合使用useEffect', () => {
const App = (props) => {
useEffect(() => {
LogUtils.log(`First effect [${props.num}]`);
});
useEffect(() => {
LogUtils.log(`Second effect [${props.num}]`);
});
return <Text text={'num: ' + props.num} />;
};
act(() => {
Horizon.render(<App num={0} />, container, () => LogUtils.log('callback effect'));
expect(LogUtils.getAndClear()).toEqual(['num: 0', 'callback effect']);
expect(container.textContent).toEqual('num: 0');
});
expect(LogUtils.getAndClear()).toEqual(['First effect [0]', 'Second effect [0]']);
act(() => {
Horizon.render(<App num={1} />, container, () => LogUtils.log('callback effect'));
});
// 第二次render时异步执行act保证所有效果都已更新所以先常规记录日志
// 然后记录useEffect的日志
expect(LogUtils.getAndClear()).toEqual([
'num: 1',
'callback effect',
'First effect [1]',
'Second effect [1]'
]);
expect(container.textContent).toEqual('num: 1');
});
it('创建销毁useEffect', () => {
const App = (props) => {
useEffect(() => {
LogUtils.log(`num effect [${props.num}]`);
return () => {
LogUtils.log('num effect destroy');
};
}, [props.num]);
useEffect(() => {
LogUtils.log(`word effect [${props.word}]`);
return () => {
LogUtils.log('word effect destroy');
};
}, [props.word]);
useLayoutEffect(() => {
LogUtils.log(`num Layouteffect [${props.num}]`);
return () => {
LogUtils.log('num Layouteffect destroy');
};
}, [props.num]);
useLayoutEffect(() => {
LogUtils.log(`word Layouteffect [${props.word}]`);
return () => {
LogUtils.log('word Layouteffect destroy');
};
}, [props.word]);
return <Text text={'num: ' + props.num + ',word: ' + props.word} />;
};
act(() => {
Horizon.render(<App num={0} word={'App'} />, container, () => LogUtils.log('callback effect'));
expect(LogUtils.getAndClear()).toEqual([
'num: 0,word: App',
'num Layouteffect [0]',
'word Layouteffect [App]',
'callback effect'
]);
});
expect(LogUtils.getAndClear()).toEqual([
'num effect [0]',
'word effect [App]',
]);
act(() => {
// 此时word改变num不变
Horizon.render(<App num={0} word={'Horizon'} />, container, () => LogUtils.log('callback effect'));
});
expect(LogUtils.getAndClear()).toEqual([
'num: 0,word: Horizon',
'word Layouteffect destroy',
'word Layouteffect [Horizon]',
'callback effect',
// 最后执行异步的
'word effect destroy',
'word effect [Horizon]',
]);
act(() => {
// 此时num和word的所有effect都销毁
Horizon.render(null, container, () => LogUtils.log('callback effect'));
});
expect(LogUtils.getAndClear()).toEqual([
'num Layouteffect destroy',
'word Layouteffect destroy',
'callback effect',
// 最后执行异步useEffect
'num effect destroy',
'word effect destroy',
]);
});
it('销毁不含依赖数组的useEffect', () => {
const App = (props) => {
useEffect(() => {
LogUtils.log(`num effect [${props.num}]`);
return () => {
LogUtils.log('num effect destroy');
};
});
return <Text text={'num: ' + props.num} />;
};
act(() => {
Horizon.render(<App num={0} />, container, () => LogUtils.log('callback effect'));
expect(LogUtils.getAndClear()).toEqual([
'num: 0',
'callback effect'
]);
expect(container.textContent).toEqual('num: 0');
});
expect(LogUtils.getAndClear()).toEqual([
'num effect [0]',
]);
act(() => {
Horizon.render(<App num={1} />, container, () => LogUtils.log('callback effect'));
});
expect(LogUtils.getAndClear()).toEqual([
'num: 1',
'callback effect',
// 最后执行异步
'num effect destroy',
'num effect [1]',
]);
expect(container.textContent).toEqual('num: 1');
expect(LogUtils.getAndClear()).toEqual([]);
act(() => {
Horizon.render(null, container, () => LogUtils.log('callback effect'));
});
expect(LogUtils.getAndClear()).toEqual([
'callback effect',
'num effect destroy'
]);
expect(container.textContent).toEqual('');
expect(LogUtils.getAndClear()).toEqual([]);
});
it('销毁依赖空数组的useEffect', () => {
const App = (props) => {
useEffect(() => {
LogUtils.log(`num effect [${props.num}]`);
return () => {
LogUtils.log('num effect destroy');
};
}, []);
return <Text text={'num: ' + props.num} />;
};
act(() => {
Horizon.render(<App num={0} />, container, () => LogUtils.log('callback effect'));
expect(LogUtils.getAndClear()).toEqual([
'num: 0',
'callback effect'
]);
expect(container.textContent).toEqual('num: 0');
});
expect(LogUtils.getAndClear()).toEqual([
'num effect [0]',
]);
act(() => {
Horizon.render(<App num={1} />, container, () => LogUtils.log('callback effect'));
});
expect(LogUtils.getAndClear()).toEqual([
'num: 1',
'callback effect'
// 依赖空数组没有执行useEffect
]);
expect(container.textContent).toEqual('num: 1');
expect(LogUtils.getAndClear()).toEqual([]);
act(() => {
Horizon.render(null, container, () => LogUtils.log('callback effect'));
});
expect(LogUtils.getAndClear()).toEqual([
'callback effect',
'num effect destroy'
]);
expect(container.textContent).toEqual('');
expect(LogUtils.getAndClear()).toEqual([]);
});
it('useEffect里使用useState(1', () => {
let setNum;
const App = () => {
const [num, _setNum] = Horizon.useState(0);
useEffect(() => {
LogUtils.log(`num effect [${num}]`);
setNum = () => _setNum(1);
}, [num]);
useLayoutEffect(() => {
LogUtils.log(`num Layouteffect [${num}]`);
return () => {
LogUtils.log('num Layouteffect destroy');
};
}, []);
return <Text text={'num: ' + num} />;
};
act(() => {
Horizon.render(<App num={0} />, container, () => LogUtils.log('callback effect'));
expect(LogUtils.getAndClear()).toEqual([
'num: 0',
'num Layouteffect [0]',
'callback effect'
]);
});
expect(LogUtils.getAndClear()).toEqual([
'num effect [0]',
]);
act(() => {
setNum();
});
expect(LogUtils.getAndClear()).toEqual([
'num: 1',
'num effect [1]'
]);
expect(LogUtils.getAndClear()).toEqual([]);
});
it('useEffect里使用useState(2', () => {
let setNum;
const App = () => {
const [num, _setNum] = useState(0);
setNum = _setNum;
useEffect(() => {
LogUtils.log(`App effect`);
setNum(1);
}, []);
return <Text text={'Num: ' + num} />;
};
Horizon.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);
});
// 虽然执行了setNum(2)但执行到setNum(1)所以最终num为1
expect(LogUtils.getAndClear()).toEqual([
'App effect',
'Num: 1',
]);
expect(container.textContent).toEqual('Num: 1');
});
it('useEffect与memo一起使用(1', () => {
let setNum;
const App = memo(() => {
const [num, _setNum] = useState(0);
setNum = _setNum;
useEffect(() => {
LogUtils.log(`num effect [${num}]`);
return () => {
LogUtils.log(`num effect destroy ${num}`);
};
});
return <Text text={num} />;
});
act(() => {
Horizon.render(<App />, container, () => LogUtils.log('callback effect'));
expect(LogUtils.getAndClear()).toEqual([
0,
'callback effect'
]);
expect(container.textContent).toEqual('0');
});
expect(LogUtils.getAndClear()).toEqual(['num effect [0]']);
// 不会重新渲染
act(() => {
Horizon.render(<App />, container, () => LogUtils.log('callback effect'));
});
expect(LogUtils.getAndClear()).toEqual(['callback effect']);
expect(container.textContent).toEqual('0');
expect(LogUtils.getAndClear()).toEqual([]);
// 会重新渲染
act(() => {
setNum(1);
});
expect(LogUtils.getAndClear()).toEqual([
1,
'num effect destroy 0',
'num effect [1]'
]);
expect(container.textContent).toEqual('1');
expect(LogUtils.getAndClear()).toEqual([]);
act(() => {
Horizon.render(null, container, () => LogUtils.log('callback effect'));
});
expect(LogUtils.getAndClear()).toEqual([
'callback effect',
'num effect destroy 1'
]);
expect(container.textContent).toEqual('');
expect(LogUtils.getAndClear()).toEqual([]);
});
it('useEffect与memo一起使用(2', () => {
const compare = (prevProps, nextProps) => prevProps.num === nextProps.num;
const App = memo((props) => {
useEffect(() => {
LogUtils.log(`num effect [${props.num}]`);
return () => {
LogUtils.log(`num effect destroy ${props.num}`);
};
});
return <Text text={props.num} />;
}, compare);
act(() => {
Horizon.render(<App num={0} />, container, () => LogUtils.log('callback effect'));
expect(LogUtils.getAndClear()).toEqual([
0,
'callback effect'
]);
expect(container.textContent).toEqual('0');
});
expect(LogUtils.getAndClear()).toEqual(['num effect [0]']);
// 不会重新渲染
act(() => {
Horizon.render(<App num={0} />, container, () => LogUtils.log('callback effect'));
});
expect(LogUtils.getAndClear()).toEqual(['callback effect']);
expect(container.textContent).toEqual('0');
expect(LogUtils.getAndClear()).toEqual([]);
// 会重新渲染
act(() => {
Horizon.render(<App num={1} />, container, () => LogUtils.log('callback effect'));
});
expect(LogUtils.getAndClear()).toEqual([
1,
'callback effect',
// 执行异步,先清除旧的,再执行新的
'num effect destroy 0',
'num effect [1]'
]);
expect(container.textContent).toEqual('1');
expect(LogUtils.getAndClear()).toEqual([]);
act(() => {
Horizon.render(null, container, () => LogUtils.log('callback effect'));
});
expect(LogUtils.getAndClear()).toEqual(['callback effect', 'num effect destroy 1']);
expect(container.textContent).toEqual('');
expect(LogUtils.getAndClear()).toEqual([]);
});
it('useEffect处理错误', () => {
const App = (props) => {
useEffect(() => {
LogUtils.log('throw Error');
throw new Error('mistake');
// eslint-disable-next-line no-unreachable
LogUtils.log(`Mount with [${props.num}]`);
return () => {
LogUtils.log(`Unmount with [${props.num}]`);
};
});
return <Text text={'Number: ' + props.num} />;
};
act(() => {
Horizon.render(<App num={0} />, container, () =>
LogUtils.log('App callback effect'),
);
expect(LogUtils.getAndClear()).toEqual(['Number: 0', 'App callback effect']);
expect(container.textContent).toEqual('Number: 0');
});
// 处理错误不会向下执行LogUtils.log(`Mount with [${props.num}]`);
expect(LogUtils.getAndClear()).toEqual(['throw Error']);
act(() => {
Horizon.render(null, container, () =>
LogUtils.log('App callback effect'),
);
});
expect(LogUtils.getAndClear()).toEqual([
'App callback effect',
// 不会处理卸载部分 LogUtils.log(`Unmount with [${props.num}]`);
]);
expect(container.textContent).toEqual('');
expect(LogUtils.getAndClear()).toEqual([]);
});
it('卸载useEffect', () => {
const App = (props) => {
useEffect(() => {
LogUtils.log(`num effect [${props.num}]`);
return () => {
LogUtils.log(`num effect destroy ${props.num}`);
};
}, []);
if (props.num < 0) {
useEffect(() => {
LogUtils.log(`New num effect [${props.num}]`);
return () => {
LogUtils.log(`New num effect destroy ${props.num}`);
};
}, []);
}
return <Text text={`Number: ${props.num}`} />;
};
act(() => {
Horizon.render(<App num={0} />, container, () => LogUtils.log('num effect'));
expect(LogUtils.getAndClear()).toEqual(['Number: 0', 'num effect']);
expect(container.textContent).toBe('Number: 0');
});
expect(LogUtils.getAndClear()).toEqual(['num effect [0]']);
act(() => {
Horizon.render(null, container, () => LogUtils.log('num effect'));
});
expect(LogUtils.getAndClear()).toEqual(['num effect', 'num effect destroy 0']);
expect(container.textContent).toBe('');
expect(LogUtils.getAndClear()).toEqual([]);
});
it('同步刷新不会导致effect执行', () => {
let setNum;
const App = () => {
const [num, _setNum] = useState(0);
setNum = _setNum;
useEffect(() => {
LogUtils.log(`num effect [${num}]`);
_setNum(1);
}, []);
return <Text text={`Number: ${num}`} />;
};
Horizon.render(<App />, container, () => LogUtils.log('num effect'));
expect(LogUtils.getAndClear()).toEqual(['Number: 0', 'num effect']);
expect(container.textContent).toBe('Number: 0');
act(() => {
// 模拟同步刷新
(function () {
setNum(2);
})();
});
expect(LogUtils.getAndClear()).toEqual(['num effect [0]', 'Number: 1']);
expect(container.textContent).toBe('Number: 1');
expect(LogUtils.getAndClear()).toEqual([]);
});
it('当组件的更新方法在卸载函数中,组件更新不会告警', () => {
const App = () => {
LogUtils.log('useEffect');
const [num, setNum] = useState(0);
useEffect(() => {
LogUtils.log('effect');
return () => {
setNum(1);
LogUtils.log('effect destroy');
};
}, []);
return num;
};
act(() => {
Horizon.render(<App />, container, () => LogUtils.log('num effect'));
expect(LogUtils.getAndClear()).toEqual(['useEffect', 'num effect']);
});
expect(LogUtils.getAndClear()).toEqual(['effect']);
act(() => {
Horizon.render(null, container);
});
// 不会处理setNum(1)
expect(LogUtils.getAndClear()).toEqual(['effect destroy']);
});
it('当组件的更新方法在卸载函数中,组件的子组件更新不会告警', () => {
const App = () => {
LogUtils.log('App');
const appRef = Horizon.createRef(null);
useEffect(() => {
LogUtils.log('App effect');
return () => {
appRef.current(1);
LogUtils.log('App effect destroy');
};
}, []);
return <AppChild ref={appRef} />;
};
let AppChild = (props, ref) => {
LogUtils.log('AppChild');
const [num, setNum] = useState(0);
useEffect(() => {
LogUtils.log('Child effect');
ref.current = setNum;
}, []);
return num;
};
AppChild = forwardRef(AppChild);
act(() => {
Horizon.render(<App />, container, () => LogUtils.log('num effect'));
expect(LogUtils.getAndClear()).toEqual([
'App',
'AppChild',
'num effect'
]);
});
expect(LogUtils.getAndClear()).toEqual(['Child effect', 'App effect']);
act(() => {
Horizon.render(null, container);
});
// 销毁时执行appRef.current(1)不会报错
expect(LogUtils.getAndClear()).toEqual(['App effect destroy']);
});
it('当组件的更新方法在卸载函数中,组件的父组件更新不会告警', () => {
const App = () => {
LogUtils.log('App');
const [num, setNum] = useState(0);
return <AppChild num={num} setNum={setNum} />;
};
let AppChild = (props) => {
LogUtils.log('AppChild');
useEffect(() => {
LogUtils.log('Child effect');
return () => {
LogUtils.log('Child effect destroy');
props.setNum(1);
};
}, []);
return props.num;
};
act(() => {
Horizon.render(<App />, container, () => LogUtils.log('num effect'));
expect(LogUtils.getAndClear()).toEqual([
'App',
'AppChild',
'num effect'
]);
});
expect(LogUtils.getAndClear()).toEqual(['Child effect']);
act(() => {
Horizon.render(null, container);
});
// 销毁时执行 props.setNum(1);不会报错
expect(LogUtils.getAndClear()).toEqual(['Child effect destroy']);
});
});