diff --git a/jest.config.js b/jest.config.js
index 333fc68e..665708c9 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -58,15 +58,7 @@ 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: {},
// 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%",
@@ -127,7 +119,7 @@ module.exports = {
// runner: "jest-runner",
// The paths to modules that run some code to configure or set up the testing environment before each test
- //setupFiles: [],
+ setupFiles: [require.resolve('./scripts/__tests__/jest/jestEnvironment.js')],
// A list of paths to modules that run some code to configure or set up the testing framework before each test
setupFilesAfterEnv: [require.resolve('./scripts/__tests__/jest/jestSetting.js')],
diff --git a/scripts/__tests__/ComponentTest/HookTest/UseCallback.test.js b/scripts/__tests__/ComponentTest/HookTest/UseCallback.test.js
new file mode 100644
index 00000000..071c4512
--- /dev/null
+++ b/scripts/__tests__/ComponentTest/HookTest/UseCallback.test.js
@@ -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 (
+ <>
+
{num}
+
+ >
+ )
+ }
+ HorizonDOM.render( , 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( , container);
+ expect(container.querySelector('p').innerHTML).toBe('1');
+ // 依赖项有变化,点击按钮num增加
+ container.querySelector('button').click();
+ expect(container.querySelector('p').innerHTML).toBe('3');
+ });
+});
diff --git a/scripts/__tests__/ComponentTest/HookTest/UseContext.test.js b/scripts/__tests__/ComponentTest/HookTest/UseContext.test.js
new file mode 100644
index 00000000..89482aff
--- /dev/null
+++ b/scripts/__tests__/ComponentTest/HookTest/UseContext.test.js
@@ -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 (
+
+ {children}
+
+ );
+ };
+ const TestFunction = () => {
+ const context = useContext(SystemLanguageContext);
+ return {context.type}
;
+ }
+ let setValue;
+ const App = () => {
+ const [value, _setValue] = useState(LanguageTypes.JAVA);
+ setValue = _setValue;
+ return (
+
+
+
+
+
+ )
+ }
+ HorizonDOM.render( , container);
+ // 测试当Provider未提供时,获取到的默认值'JavaScript'。
+ expect(container.querySelector('p').innerHTML).toBe('JavaScript');
+ unmountComponentAtNode(container);
+ HorizonDOM.render( , container);
+ // 测试当Provider提供时,可以获取到Provider的值'Java'。
+ expect(container.querySelector('p').innerHTML).toBe('Java');
+ // 测试当Provider改变时,可以获取到最新Provider的值。
+ act(() => setValue(LanguageTypes.JAVASCRIPT));
+ expect(container.querySelector('p').innerHTML).toBe('JavaScript');
+ });
+});
diff --git a/scripts/__tests__/ComponentTest/HookTest/UseEffect.test.js b/scripts/__tests__/ComponentTest/HookTest/UseEffect.test.js
new file mode 100644
index 00000000..e4a52d65
--- /dev/null
+++ b/scripts/__tests__/ComponentTest/HookTest/UseEffect.test.js
@@ -0,0 +1,740 @@
+/* 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;
+
+ it('简单使用useEffect', () => {
+ const App = () => {
+ const [num, setNum] = useState(0);
+ useEffect(() => {
+ document.getElementById('p').style.display = num === 0 ? 'none' : 'inline';
+ });
+ return (
+ <>
+ {num}
+ setNum(num + 1)} />
+ >
+ )
+ }
+ HorizonDOM.render( , 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 ;
+ }
+
+ act(() => {
+ HorizonDOM.render( , container, () => {
+ LogUtils.log('num effect');
+ });
+ // 第一次渲染为同步,所以同步执行的可以写在act里做判断
+ expect(LogUtils.getAndClear()).toEqual(['op', 'num effect']);
+ expect(container.textContent).toBe('op');
+ });
+ act(() => {
+ HorizonDOM.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 ;
+ }
+ const NewApp = () => {
+ useEffect(() => {
+ LogUtils.log(`NewApp effect`);
+ }, []);
+ return ;
+ }
+ const na = ;
+ // 必须设置key值,否则在diff的时候na会被视为不同组件
+ HorizonDOM.render([ , na], container);
+ expect(LogUtils.getAndClear()).toEqual([
+ 'App',
+ 'NewApp'
+ ]);
+ expect(container.textContent).toBe('AppNewApp');
+ expect(LogUtils.getAndClear()).toEqual([]);
+ // 在执行新的render前,会执行完上一次render的useEffect,所以LogUtils会加入'NewApp effect'。
+ HorizonDOM.render([na], container);
+ expect(LogUtils.getAndClear()).toEqual(['NewApp effect']);
+ 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 ;
+ }
+ const NewApp = () => {
+ useEffect(() => {
+ LogUtils.log(`NewApp effect`);
+ }, []);
+ return ;
+ }
+ // 必须设置key值,否则在diff的时候na会被视为不同组件
+ HorizonDOM.render([ , ], 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');
+ HorizonDOM.render( , newContainer);
+ });
+ return ;
+ }
+ const NewApp = () => {
+ useEffect(() => {
+ LogUtils.log(`NewApp effect`);
+ }, []);
+ return ;
+ }
+ // 必须设置key值,否则在diff的时候na会被视为不同组件
+ HorizonDOM.render([ , ], 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 ;
+ }
+ act(() => {
+ HorizonDOM.render( , 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(() => {
+ HorizonDOM.render( , 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 ;
+ }
+ act(() => {
+ HorizonDOM.render( , 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(() => {
+ HorizonDOM.render( , 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 ;
+ }
+
+ act(() => {
+ HorizonDOM.render( , 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不变
+ HorizonDOM.render( , container, () => LogUtils.log('callback effect'));
+ });
+ expect(LogUtils.getAndClear()).toEqual([
+ 'num: 0,word: React',
+ 'word Layouteffect destroy',
+ 'word Layouteffect [React]',
+ 'callback effect',
+ // 最后执行异步的
+ 'word effect destroy',
+ 'word effect [React]',
+ ]);
+
+ act(() => {
+ // 此时num和word的所有effect都销毁
+ HorizonDOM.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 ;
+ }
+
+ act(() => {
+ HorizonDOM.render( , 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(() => {
+ HorizonDOM.render( , 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(() => {
+ HorizonDOM.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 ;
+ }
+
+ act(() => {
+ HorizonDOM.render( , 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(() => {
+ HorizonDOM.render( , 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(() => {
+ HorizonDOM.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] = React.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 ;
+ }
+
+ act(() => {
+ HorizonDOM.render( , 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 ;
+ }
+
+ HorizonDOM.render( , 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 ;
+ })
+ act(() => {
+ HorizonDOM.render( , 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(() => {
+ HorizonDOM.render( , 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(() => {
+ HorizonDOM.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 ;
+ }, compare)
+ act(() => {
+ HorizonDOM.render( , 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(() => {
+ HorizonDOM.render( , container, () => LogUtils.log('callback effect'));
+ });
+ expect(LogUtils.getAndClear()).toEqual(['callback effect']);
+ expect(container.textContent).toEqual('0');
+ expect(LogUtils.getAndClear()).toEqual([]);
+
+ // 会重新渲染
+ act(() => {
+ HorizonDOM.render( , 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(() => {
+ HorizonDOM.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 ;
+ }
+ act(() => {
+ HorizonDOM.render( , 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(() => {
+ HorizonDOM.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 ;
+ }
+
+ act(() => {
+ HorizonDOM.render( , 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(() => {
+ HorizonDOM.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 ;
+ }
+
+ HorizonDOM.render( , 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(() => {
+ HorizonDOM.render( , container, () => LogUtils.log('num effect'));
+ expect(LogUtils.getAndClear()).toEqual(['useEffect', 'num effect']);
+ });
+ expect(LogUtils.getAndClear()).toEqual(['effect']);
+
+ act(() => {
+ HorizonDOM.render(null, container);
+ });
+ // 不会处理setNum(1)
+ expect(LogUtils.getAndClear()).toEqual(['effect destroy']);
+ });
+
+ it('当组件的更新方法在卸载函数中,组件的子组件更新不会告警', () => {
+ const App = () => {
+ LogUtils.log('App');
+ const appRef = React.createRef(null);
+ useEffect(() => {
+ LogUtils.log('App effect');
+ return () => {
+ appRef.current(1);
+ LogUtils.log('App effect destroy');
+ };
+ }, []);
+ return ;
+ }
+
+ 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(() => {
+ HorizonDOM.render( , container, () => LogUtils.log('num effect'));
+ expect(LogUtils.getAndClear()).toEqual([
+ 'App',
+ 'AppChild',
+ 'num effect'
+ ]);
+ });
+ expect(LogUtils.getAndClear()).toEqual(['Child effect', 'App effect']);
+
+ act(() => {
+ HorizonDOM.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 ;
+ }
+
+ let AppChild = (props) => {
+ LogUtils.log('AppChild')
+ useEffect(() => {
+ LogUtils.log('Child effect');
+ return () => {
+ LogUtils.log('Child effect destroy');
+ props.setNum(1);
+ };
+ }, []);
+ return props.num;
+ }
+
+ act(() => {
+ HorizonDOM.render( , container, () => LogUtils.log('num effect'));
+ expect(LogUtils.getAndClear()).toEqual([
+ 'App',
+ 'AppChild',
+ 'num effect'
+ ]);
+ });
+ expect(LogUtils.getAndClear()).toEqual(['Child effect']);
+
+ act(() => {
+ HorizonDOM.render(null, container);
+ });
+ // 销毁时执行 props.setNum(1);不会报错
+ expect(LogUtils.getAndClear()).toEqual(['Child effect destroy']);
+ });
+})
diff --git a/scripts/__tests__/ComponentTest/HookTest/UseImperativeHandle.test.js b/scripts/__tests__/ComponentTest/HookTest/UseImperativeHandle.test.js
new file mode 100644
index 00000000..10ce8623
--- /dev/null
+++ b/scripts/__tests__/ComponentTest/HookTest/UseImperativeHandle.test.js
@@ -0,0 +1,91 @@
+/* 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('useImperativeHandle Hook Test', () => {
+ const {
+ useState,
+ useImperativeHandle,
+ forwardRef
+ } = React;
+ const { unmountComponentAtNode } = HorizonDOM;
+
+ it('测试useImperativeHandle', () => {
+
+ let App = (props, ref) => {
+ const [num, setNum] = useState(0);
+ useImperativeHandle(ref, () => ({ num, setNum }), []);
+ return {num}
;
+ }
+ let App1 = (props, ref) => {
+ const [num1, setNum1] = useState(0);
+ useImperativeHandle(ref, () => ({ num1, setNum1 }), [num1]);
+ return {num1}
;
+ }
+
+ App = forwardRef(App);
+ App1 = forwardRef(App1);
+ const counter = React.createRef(null);
+ const counter1 = React.createRef(null);
+ HorizonDOM.render( , 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( , 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 ;
+ }
+ let App1 = (props, ref) => {
+ const [num1, setNum1] = useState(0);
+ useImperativeHandle(ref, () => ({ num1, setNum1 }), []);
+ return ;
+ }
+
+ App = forwardRef(App);
+ App1 = forwardRef(App1);
+ const counter = React.createRef(null);
+ const counter1 = React.createRef(null);
+ HorizonDOM.render( , 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( , 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);
+ });
+});
diff --git a/scripts/__tests__/ComponentTest/HookTest/UseLayoutEffect.test.js b/scripts/__tests__/ComponentTest/HookTest/UseLayoutEffect.test.js
new file mode 100644
index 00000000..ec7eda11
--- /dev/null
+++ b/scripts/__tests__/ComponentTest/HookTest/UseLayoutEffect.test.js
@@ -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 (
+ <>
+ {num}
+ setNum(num + 1)} />
+ >
+ )
+ }
+ HorizonDOM.render( , 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 ;
+ }
+ HorizonDOM.render( , container, () => LogUtils.log('Sync effect'));
+ expect(LogUtils.getAndClear()).toEqual([
+ 1,
+ // 同步在渲染之后
+ 'LayoutEffect',
+ 'Sync effect'
+ ]);
+ expect(container.querySelector('p').innerHTML).toBe('1');
+ // 更新
+ HorizonDOM.render( , 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 ;
+ }
+
+ act(() => {
+ HorizonDOM.render( , 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( , 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',
+ ]);
+ });
+});
diff --git a/scripts/__tests__/ComponentTest/HookTest/UseMemo.test.js b/scripts/__tests__/ComponentTest/HookTest/UseMemo.test.js
new file mode 100644
index 00000000..294ae787
--- /dev/null
+++ b/scripts/__tests__/ComponentTest/HookTest/UseMemo.test.js
@@ -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 (
+ <>
+ {text}
+ {num}
+ >
+ );
+ }
+ HorizonDOM.render( , 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 ;
+ }
+ HorizonDOM.render( , container);
+ expect(LogUtils.getAndClear()).toEqual([
+ 0,
+ 1
+ ]);
+ expect(container.textContent).toBe('1');
+
+ HorizonDOM.render( , container);
+ expect(LogUtils.getAndClear()).toEqual([
+ 1,
+ 2
+ ]);
+ expect(container.textContent).toBe('2');
+
+ HorizonDOM.render( , container);
+ // 不会触发useMemo
+ expect(LogUtils.getAndClear()).toEqual([2]);
+ expect(container.textContent).toBe('2');
+
+ HorizonDOM.render( , container);
+ expect(LogUtils.getAndClear()).toEqual([
+ 2,
+ 3
+ ]);
+ expect(container.textContent).toBe('3');
+ });
+
+ it('输入不变,重新渲染也会触发useMemo', () => {
+ const App = (props) => {
+ const num = useMemo(props._num);
+ return ;
+ }
+
+ const num1 = () => {
+ LogUtils.log('num 1');
+ return 1;
+ }
+
+ const num2 = () => {
+ LogUtils.log('num 2');
+ return 2;
+ }
+
+ HorizonDOM.render( , container);
+ expect(LogUtils.getAndClear()).toEqual(['num 1', 1]);
+
+ HorizonDOM.render( , container);
+ expect(LogUtils.getAndClear()).toEqual(['num 1', 1]);
+
+ HorizonDOM.render( , container);
+ expect(LogUtils.getAndClear()).toEqual(['num 1', 1]);
+
+ HorizonDOM.render( , container);
+ expect(LogUtils.getAndClear()).toEqual(['num 2', 2]);
+ });
+});
diff --git a/scripts/__tests__/ComponentTest/HookTest/UseReducer.test.js b/scripts/__tests__/ComponentTest/HookTest/UseReducer.test.js
new file mode 100644
index 00000000..29c228c1
--- /dev/null
+++ b/scripts/__tests__/ComponentTest/HookTest/UseReducer.test.js
@@ -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 (
+
+
{car.logo}
+
{car.price}
+
+ )
+ }
+ HorizonDOM.render( , 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');
+ });
+});
diff --git a/scripts/__tests__/ComponentTest/HookTest/UseRef.test.js b/scripts/__tests__/ComponentTest/HookTest/UseRef.test.js
new file mode 100644
index 00000000..39e663bb
--- /dev/null
+++ b/scripts/__tests__/ComponentTest/HookTest/UseRef.test.js
@@ -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 (
+ <>
+ {num}
+ {ref.current}
+ setNum(num + 1)} />
+ >
+ )
+ }
+ HorizonDOM.render( , 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 (
+ <>
+ {
+ ref.current += 1;
+ }}>
+ button
+
+ ;
+ >
+ )
+
+ }
+ HorizonDOM.render( , 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');
+ });
+});
diff --git a/scripts/__tests__/ComponentTest/UseState.test.js b/scripts/__tests__/ComponentTest/HookTest/UseState.test.js
similarity index 63%
rename from scripts/__tests__/ComponentTest/UseState.test.js
rename to scripts/__tests__/ComponentTest/HookTest/UseState.test.js
index ffd1cda0..adccf421 100644
--- a/scripts/__tests__/ComponentTest/UseState.test.js
+++ b/scripts/__tests__/ComponentTest/HookTest/UseState.test.js
@@ -1,30 +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';
+/* 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 (
+ <>
+ {num}
+ setNum(num + 1)} />
+ >
+ )
+ }
+ HorizonDOM.render( , 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 {props.text}
;
- }
-
it('多个useState', () => {
const App = () => {
const [num, setNum] = useState(0);
@@ -126,4 +131,42 @@ describe('useState Hook Test', () => {
expect(LogUtils.getAndClear()).toEqual([1]);
expect(container.querySelector('p').innerHTML).toBe('1');
});
+
+ it('卸载useState', () => {
+ // let updateA;
+ let setNum;
+ let setCount;
+
+ const App = (props) => {
+ const [num, setNum_1] = useState(0);
+ setNum = setNum_1;
+
+ let count;
+ if (props.hasCount) {
+ const [count_1, setCount_1] = useState(0);
+ count = count_1;
+ setCount = setCount_1;
+ } else {
+ count = 'null';
+ }
+
+ return ;
+ }
+
+ HorizonDOM.render( , container);
+ expect(LogUtils.getAndClear()).toEqual(['Number: 0, Count: 0']);
+ expect(container.textContent).toBe('Number: 0, Count: 0');
+ act(() => {
+ setNum(1);
+ setCount(2);
+ });
+ expect(LogUtils.getAndClear()).toEqual(['Number: 1, Count: 2']);
+ expect(container.textContent).toBe('Number: 1, Count: 2');
+
+ expect(() => {
+ HorizonDOM.render( , container);
+ }).toThrow(
+ 'Hooks are less than expected, please check whether the hook is written in the condition.',
+ );
+ });
});
diff --git a/scripts/__tests__/ComponentTest/SimpleUseHook.test.js b/scripts/__tests__/ComponentTest/SimpleUseHook.test.js
deleted file mode 100644
index 0d99f8ed..00000000
--- a/scripts/__tests__/ComponentTest/SimpleUseHook.test.js
+++ /dev/null
@@ -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 (
- <>
- {num}
- setNum(num + 1)} />
- >
- )
- }
- HorizonDOM.render( , 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 (
- <>
- {num}
- setNum(num + 1)} />
- >
- )
- }
- HorizonDOM.render( , 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 (
- <>
- {num}
- setNum(num + 1)} />
- >
- )
- }
- HorizonDOM.render( , 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 (
-
- {children}
-
- );
- };
- const TestFunction = () => {
- const context = useContext(SystemLanguageContext);
- return {context.type}
;
- }
- let setValue;
- const App = () => {
- const [value, _setValue] = useState(LanguageTypes.JAVA);
- setValue = _setValue;
- return (
-
-
-
-
-
- )
- }
- HorizonDOM.render( , container);
- // 测试当Provider未提供时,获取到的默认值'JavaScript'。
- expect(container.querySelector('p').innerHTML).toBe('JavaScript');
- unmountComponentAtNode(container);
- HorizonDOM.render( , 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 (
-
-
{car.logo}
-
{car.price}
-
- )
- }
- HorizonDOM.render( , 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 (
- <>
- {text}
- {num}
- >
- );
- }
- HorizonDOM.render( , 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 (
- <>
- {num}
-
- >
- )
- }
- HorizonDOM.render( , 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( , 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 (
- <>
- {num}
- {ref.current}
- setNum(num + 1)} />
- >
- )
- }
- HorizonDOM.render( , 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 {num}
;
- }
- let App1 = (props, ref) => {
- const [num1, setNum1] = useState(0);
- useImperativeHandle(ref, () => ({num1, setNum1}), [num1]);
- return {num1}
;
- }
-
- App = forwardRef(App);
- App1 = forwardRef(App1);
- const counter = React.createRef(null);
- const counter1 = React.createRef(null);
- HorizonDOM.render( , 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( , container);
- expect(counter1.current.num1).toBe(0);
- act(() => {
- counter1.current.setNum1(1);
- });
- // useImperativeHandle的dep为[num1],所以会变
- expect(counter1.current.num1).toBe(1);
- });
-});
diff --git a/scripts/__tests__/ComponentTest/UseEffect.test.js b/scripts/__tests__/ComponentTest/UseEffect.test.js
deleted file mode 100644
index 0eccd5f9..00000000
--- a/scripts/__tests__/ComponentTest/UseEffect.test.js
+++ /dev/null
@@ -1,442 +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('useEffect Hook Test', () => {
- const { useEffect, useLayoutEffect, useState, memo } = 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 {props.text}
;
- }
-
- it('兄弟节点被删除,useEffect依然正常', () => {
- const App = () => {
- return ;
- }
- const NewApp = () => {
- useEffect(() => {
- LogUtils.log(`NewApp effect`);
- }, []);
- return ;
- }
- const na = ;
- // 必须设置key值,否则在diff的时候na会被视为不同组件
- HorizonDOM.render([ , na], container);
- expect(LogUtils.getAndClear()).toEqual([
- 'App',
- 'NewApp'
- ]);
- expect(container.textContent).toBe('AppNewApp');
- expect(LogUtils.getAndClear()).toEqual([]);
- // 在执行新的render前,会执行完上一次render的useEffect,所以LogUtils会加入'NewApp effect'。
- HorizonDOM.render([na], container);
- expect(LogUtils.getAndClear()).toEqual(['NewApp effect']);
- 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 ;
- }
- const NewApp = () => {
- useEffect(() => {
- LogUtils.log(`NewApp effect`);
- }, []);
- return ;
- }
- // 必须设置key值,否则在diff的时候na会被视为不同组件
- HorizonDOM.render([ , ], 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');
- HorizonDOM.render( , newContainer);
- });
- return ;
- }
- const NewApp = () => {
- useEffect(() => {
- LogUtils.log(`NewApp effect`);
- }, []);
- return ;
- }
- // 必须设置key值,否则在diff的时候na会被视为不同组件
- HorizonDOM.render([ , ], 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 ;
- }
- act(() => {
- HorizonDOM.render( , container, () => LogUtils.log('callback effect'));
- expect(LogUtils.getAndClear()).toEqual(['num: 0', 'callback effect']);
- expect(container.textContent).toEqual('num: 0');
- HorizonDOM.render( , container, () => LogUtils.log('callback effect'));
- // 执行新的render前,会执行旧render的useEffect,所以会添加'First effect [0]'
- expect(LogUtils.getAndClear()).toEqual(['First effect [0]', 'num: 1', 'callback effect']);
- expect(container.textContent).toEqual('num: 1');
- })
- // 最后在act执行完后会执行新render的useEffect
- expect(LogUtils.getAndClear()).toEqual(['First effect [1]']);
- });
-
- it('混合使用useEffect', () => {
- const App = (props) => {
- useEffect(() => {
- LogUtils.log(`First effect [${props.num}]`);
- });
- useEffect(() => {
- LogUtils.log(`Second effect [${props.num}]`);
- });
- return ;
- }
- act(() => {
- HorizonDOM.render( , 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(() => {
- HorizonDOM.render( , container, () => LogUtils.log('callback effect'));
- expect(LogUtils.getAndClear()).toEqual(['num: 1', 'callback effect']);
- expect(container.textContent).toEqual('num: 1');
- })
- expect(LogUtils.getAndClear()).toEqual(['First effect [1]', 'Second effect [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 ;
- }
-
- act(() => {
- HorizonDOM.render( , 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不变
- HorizonDOM.render( , container, () => LogUtils.log('callback effect'));
- expect(LogUtils.getAndClear()).toEqual([
- 'num: 0,word: React',
- 'word Layouteffect destroy',
- 'word Layouteffect [React]',
- 'callback effect'
- ]);
- });
- expect(LogUtils.getAndClear()).toEqual([
- 'word effect destroy',
- 'word effect [React]',
- ]);
-
- act(() => {
- // 此时num和word的所有effect都销毁
- HorizonDOM.render(null, container, () => LogUtils.log('callback effect'));
- expect(LogUtils.getAndClear()).toEqual([
- 'num Layouteffect destroy',
- 'word Layouteffect destroy',
- 'callback effect'
- ]);
- });
- expect(LogUtils.getAndClear()).toEqual([
- '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 ;
- }
-
- act(() => {
- HorizonDOM.render( , 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(() => {
- HorizonDOM.render( , container, () => LogUtils.log('callback effect'));
- expect(LogUtils.getAndClear()).toEqual([
- 'num: 1',
- 'callback effect'
- ]);
- expect(container.textContent).toEqual('num: 1');
- });
- expect(LogUtils.getAndClear()).toEqual([
- 'num effect destroy',
- 'num effect [1]',
- ]);
-
- act(() => {
- HorizonDOM.render(null, container, () => LogUtils.log('callback effect'));
- expect(LogUtils.getAndClear()).toEqual([
- 'callback effect'
- ]);
- expect(container.textContent).toEqual('');
- });
- expect(LogUtils.getAndClear()).toEqual([
- 'num effect destroy'
- ]);
- });
-
- it('销毁依赖空数组的useEffect', () => {
- const App = (props) => {
- useEffect(() => {
- LogUtils.log(`num effect [${props.num}]`);
- return () => {
- LogUtils.log('num effect destroy');
- };
- }, []);
- return ;
- }
-
- act(() => {
- HorizonDOM.render( , 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(() => {
- HorizonDOM.render( , container, () => LogUtils.log('callback effect'));
- expect(LogUtils.getAndClear()).toEqual([
- 'num: 1',
- 'callback effect'
- ]);
- expect(container.textContent).toEqual('num: 1');
- });
- // 没有执行useEffect
- expect(LogUtils.getAndClear()).toEqual([]);
-
- act(() => {
- HorizonDOM.render(null, container, () => LogUtils.log('callback effect'));
- expect(LogUtils.getAndClear()).toEqual([
- 'callback effect'
- ]);
- expect(container.textContent).toEqual('');
- });
- expect(LogUtils.getAndClear()).toEqual([
- 'num effect destroy'
- ]);
- });
-
- it('useEffect里使用useState(1', () => {
- let setNum;
- const App = () => {
- const [num, _setNum] = React.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 ;
- }
-
- act(() => {
- HorizonDOM.render( , 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'
- ]);
- });
- expect(LogUtils.getAndClear()).toEqual(['num effect [1]']);
- });
-
- it('useEffect里使用useState(2', () => {
- let setNum;
- const App = () => {
- const [num, _setNum] = useState(0);
- setNum = _setNum;
- useEffect(() => {
- LogUtils.log(`App effect`);
- setNum(1);
- }, []);
- return ;
- }
-
- HorizonDOM.render( , container, () => LogUtils.log('Sync effect'));
- expect(LogUtils.getAndClear()).toEqual(['Num: 0', 'Sync 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一起使用', () => {
- 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 ;
- })
- act(() => {
- HorizonDOM.render( , 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(() => {
- HorizonDOM.render( , 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]);
- expect(container.textContent).toEqual('1');
- });
- expect(LogUtils.getAndClear()).toEqual([
- 'num effect destroy 0',
- 'num effect [1]'
- ]);
-
- act(() => {
- HorizonDOM.render(null, container, () => LogUtils.log('callback effect'));
- expect(LogUtils.getAndClear()).toEqual(['callback effect']);
- expect(container.textContent).toEqual('');
- });
- expect(LogUtils.getAndClear()).toEqual(['num effect destroy 1']);
- });
-
-})
diff --git a/scripts/__tests__/jest/Text.js b/scripts/__tests__/jest/Text.js
new file mode 100644
index 00000000..24d63cf8
--- /dev/null
+++ b/scripts/__tests__/jest/Text.js
@@ -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 {props.text}
;
+};
+
+export default Text;
diff --git a/scripts/__tests__/jest/customMatcher.js b/scripts/__tests__/jest/customMatcher.js
index c6b80208..1f6a96fb 100644
--- a/scripts/__tests__/jest/customMatcher.js
+++ b/scripts/__tests__/jest/customMatcher.js
@@ -1,5 +1,6 @@
-import { runAsyncEffects } from '../../../libs/horizon/src/renderer/submit/HookEffectHandler'
-import { syncUpdates } from '../../../libs/horizon/src/renderer/TreeBuilder'
+import { runAsyncEffects } from '../../../libs/horizon/src/renderer/submit/HookEffectHandler';
+import { callRenderQueueImmediate } from '../../../libs/horizon/src/renderer/taskExecutor/RenderQueue';
+import { asyncUpdates } from '../../../libs/horizon/src/renderer/TreeBuilder';
function runAssertion(fn) {
try {
@@ -21,7 +22,8 @@ function toMatchValue(LogUtils, expectedValues) {
}
const act = (fun) => {
- syncUpdates(fun);
+ asyncUpdates(fun);
+ callRenderQueueImmediate();
runAsyncEffects();
}
diff --git a/scripts/__tests__/jest/jestEnvironment.js b/scripts/__tests__/jest/jestEnvironment.js
new file mode 100644
index 00000000..59b5a6ee
--- /dev/null
+++ b/scripts/__tests__/jest/jestEnvironment.js
@@ -0,0 +1,6 @@
+global.MessageChannel = function MessageChannel() {
+ this.port1 = {};
+ this.port2 = {
+ postMessage() { }
+ };
+};
diff --git a/scripts/__tests__/jest/jestSetting.js b/scripts/__tests__/jest/jestSetting.js
index 010b9922..deda8dd9 100644
--- a/scripts/__tests__/jest/jestSetting.js
+++ b/scripts/__tests__/jest/jestSetting.js
@@ -1,10 +1,21 @@
+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({