From 7dce0e69e3c334acf1320c6a240c2a2ac3a3f9e8 Mon Sep 17 00:00:00 2001
From: * <8>
Date: Tue, 8 Mar 2022 09:57:37 +0800
Subject: [PATCH 1/5] Match-id-dcf83e9762d769d5a52954b612df113c9bf42253
---
scripts/__tests__/jest/customMatcher.js | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/scripts/__tests__/jest/customMatcher.js b/scripts/__tests__/jest/customMatcher.js
index c6b80208..f921e916 100644
--- a/scripts/__tests__/jest/customMatcher.js
+++ b/scripts/__tests__/jest/customMatcher.js
@@ -1,5 +1,5 @@
-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';
function runAssertion(fn) {
try {
@@ -21,7 +21,8 @@ function toMatchValue(LogUtils, expectedValues) {
}
const act = (fun) => {
- syncUpdates(fun);
+ fun();
+ callRenderQueueImmediate();
runAsyncEffects();
}
From 5a06b9b663a5eb2f5de9326e84f6c9f978039078 Mon Sep 17 00:00:00 2001
From: * <8>
Date: Sun, 13 Mar 2022 13:11:36 +0800
Subject: [PATCH 2/5] Match-id-1988199472c662613fee402f6bd87513d3016c2b
---
.../HooksExceptEffectAndState.test.js | 249 +++++++++++
.../__tests__/ComponentTest/UseEffect.test.js | 393 +++++++++++++++---
.../__tests__/ComponentTest/UseState.test.js | 42 ++
scripts/__tests__/jest/customMatcher.js | 6 +-
4 files changed, 636 insertions(+), 54 deletions(-)
create mode 100644 scripts/__tests__/ComponentTest/HooksExceptEffectAndState.test.js
diff --git a/scripts/__tests__/ComponentTest/HooksExceptEffectAndState.test.js b/scripts/__tests__/ComponentTest/HooksExceptEffectAndState.test.js
new file mode 100644
index 00000000..34bb86d0
--- /dev/null
+++ b/scripts/__tests__/ComponentTest/HooksExceptEffectAndState.test.js
@@ -0,0 +1,249 @@
+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
{props.text}
;
+ }
+
+ describe('useLayoutEffect Test', () => {
+ 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',
+ ]);
+ });
+ })
+
+ describe('useMemo Test', () => {
+ 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]);
+ });
+ })
+
+ describe('useRef Test', () => {
+ it('更新current值时不会re-render', () => {
+ const App = () => {
+ const ref = useRef(1);
+ return (
+ <>
+
+ ;
+ >
+ )
+
+ }
+ 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');
+ });
+ })
+
+ describe('useImperativeHandle Test', () => {
+ 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/UseEffect.test.js b/scripts/__tests__/ComponentTest/UseEffect.test.js
index 0eccd5f9..1a0631fe 100644
--- a/scripts/__tests__/ComponentTest/UseEffect.test.js
+++ b/scripts/__tests__/ComponentTest/UseEffect.test.js
@@ -4,7 +4,7 @@ import * as LogUtils from '../jest/logUtils';
import { act } from '../jest/customMatcher';
describe('useEffect Hook Test', () => {
- const { useEffect, useLayoutEffect, useState, memo } = React;
+ const { useEffect, useLayoutEffect, useState, memo, forwardRef, useImperativeHandle } = React;
const { unmountComponentAtNode } = HorizonDOM;
let container = null;
beforeEach(() => {
@@ -27,6 +27,30 @@ describe('useEffect Hook Test', () => {
return {props.text}
;
}
+ 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 ;
@@ -123,13 +147,19 @@ describe('useEffect Hook Test', () => {
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]']);
+ 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', () => {
@@ -150,10 +180,16 @@ describe('useEffect Hook Test', () => {
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]']);
+ // 第二次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', () => {
@@ -202,14 +238,13 @@ describe('useEffect Hook Test', () => {
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([
+ 'num: 0,word: React',
+ 'word Layouteffect destroy',
+ 'word Layouteffect [React]',
+ 'callback effect',
+ // 最后执行异步的
'word effect destroy',
'word effect [React]',
]);
@@ -217,13 +252,12 @@ describe('useEffect Hook Test', () => {
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 Layouteffect destroy',
+ 'word Layouteffect destroy',
+ 'callback effect',
+ // 最后执行异步useEffect
'num effect destroy',
'word effect destroy',
]);
@@ -254,27 +288,26 @@ describe('useEffect Hook Test', () => {
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: 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'
- ]);
- expect(container.textContent).toEqual('');
});
expect(LogUtils.getAndClear()).toEqual([
+ 'callback effect',
'num effect destroy'
]);
+ expect(container.textContent).toEqual('');
+ expect(LogUtils.getAndClear()).toEqual([]);
});
it('销毁依赖空数组的useEffect', () => {
@@ -302,25 +335,24 @@ describe('useEffect Hook Test', () => {
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([
+ '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'
- ]);
- expect(container.textContent).toEqual('');
});
expect(LogUtils.getAndClear()).toEqual([
+ 'callback effect',
'num effect destroy'
]);
+ expect(container.textContent).toEqual('');
+ expect(LogUtils.getAndClear()).toEqual([]);
});
it('useEffect里使用useState(1', () => {
@@ -354,11 +386,12 @@ describe('useEffect Hook Test', () => {
act(() => {
setNum();
- expect(LogUtils.getAndClear()).toEqual([
- 'num: 1'
- ]);
});
- expect(LogUtils.getAndClear()).toEqual(['num effect [1]']);
+ expect(LogUtils.getAndClear()).toEqual([
+ 'num: 1',
+ 'num effect [1]'
+ ]);
+ expect(LogUtils.getAndClear()).toEqual([]);
});
it('useEffect里使用useState(2', () => {
@@ -389,7 +422,7 @@ describe('useEffect Hook Test', () => {
expect(container.textContent).toEqual('Num: 1');
});
- it('useEffect与memo一起使用', () => {
+ it('useEffect与memo一起使用(1', () => {
let setNum;
const App = memo(() => {
const [num, _setNum] = useState(0);
@@ -415,28 +448,284 @@ describe('useEffect Hook Test', () => {
// 不会重新渲染
act(() => {
HorizonDOM.render(, container, () => LogUtils.log('callback effect'));
- expect(LogUtils.getAndClear()).toEqual(['callback effect']);
- expect(container.textContent).toEqual('0');
});
+ 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([
+ 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']);
- expect(container.textContent).toEqual('');
});
- expect(LogUtils.getAndClear()).toEqual(['num effect destroy 1']);
+ 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('Sync effect'),
+ );
+ expect(LogUtils.getAndClear()).toEqual(['Number: 0', 'Sync 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('Sync effect'),
+ );
+ });
+ expect(LogUtils.getAndClear()).toEqual([
+ 'Sync 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/UseState.test.js b/scripts/__tests__/ComponentTest/UseState.test.js
index ffd1cda0..02c3996f 100644
--- a/scripts/__tests__/ComponentTest/UseState.test.js
+++ b/scripts/__tests__/ComponentTest/UseState.test.js
@@ -1,6 +1,7 @@
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('useState Hook Test', () => {
const { useState, forwardRef, useImperativeHandle, memo } = React;
@@ -126,4 +127,45 @@ describe('useState Hook Test', () => {
expect(LogUtils.getAndClear()).toEqual([1]);
expect(container.querySelector('p').innerHTML).toBe('1');
});
+
+ it('卸载useState', () => {
+ let updateA;
+ let updateB;
+ let updateC;
+
+ const App = (props) => {
+ const [A, _updateA] = useState(0);
+ const [B, _updateB] = useState(0);
+ updateA = _updateA;
+ updateB = _updateB;
+
+ let C;
+ if (props.loadC) {
+ const [_C, _updateC] = useState(0);
+ C = _C;
+ updateC = _updateC;
+ } else {
+ C = '[not loaded]';
+ }
+
+ return ;
+ }
+
+ HorizonDOM.render(, container);
+ expect(LogUtils.getAndClear()).toEqual(['A: 0, B: 0, C: 0']);
+ expect(container.textContent).toBe('A: 0, B: 0, C: 0');
+ act(() => {
+ updateA(2);
+ updateB(3);
+ updateC(4);
+ });
+ expect(LogUtils.getAndClear()).toEqual(['A: 2, B: 3, C: 4']);
+ expect(container.textContent).toBe('A: 2, B: 3, C: 4');
+
+ 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__/jest/customMatcher.js b/scripts/__tests__/jest/customMatcher.js
index f921e916..c373fbd5 100644
--- a/scripts/__tests__/jest/customMatcher.js
+++ b/scripts/__tests__/jest/customMatcher.js
@@ -1,5 +1,7 @@
-import { runAsyncEffects } from '../../../libs/horizon/src/renderer/submit/HookEffectHandler';
+import { runAsyncEffects, callUseEffects } from '../../../libs/horizon/src/renderer/submit/HookEffectHandler';
import { callRenderQueueImmediate } from '../../../libs/horizon/src/renderer/taskExecutor/RenderQueue';
+import { asyncUpdates } from '../../../libs/horizon/src/renderer/TreeBuilder';
+import { runAsync } from '../../../libs/horizon/src/renderer/taskExecutor/TaskExecutor';
function runAssertion(fn) {
try {
@@ -21,7 +23,7 @@ function toMatchValue(LogUtils, expectedValues) {
}
const act = (fun) => {
- fun();
+ asyncUpdates(fun);
callRenderQueueImmediate();
runAsyncEffects();
}
From 4d2cd8276ac95fa3de4c3172d19e59e8fe47e68b Mon Sep 17 00:00:00 2001
From: * <8>
Date: Sun, 13 Mar 2022 13:13:06 +0800
Subject: [PATCH 3/5] Match-id-38f76aafad7b269e2beed396a0ef1c14487adfc3
---
scripts/__tests__/ComponentTest/UseEffect.test.js | 2 +-
scripts/__tests__/jest/customMatcher.js | 3 +--
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/scripts/__tests__/ComponentTest/UseEffect.test.js b/scripts/__tests__/ComponentTest/UseEffect.test.js
index 1a0631fe..89513844 100644
--- a/scripts/__tests__/ComponentTest/UseEffect.test.js
+++ b/scripts/__tests__/ComponentTest/UseEffect.test.js
@@ -4,7 +4,7 @@ import * as LogUtils from '../jest/logUtils';
import { act } from '../jest/customMatcher';
describe('useEffect Hook Test', () => {
- const { useEffect, useLayoutEffect, useState, memo, forwardRef, useImperativeHandle } = React;
+ const { useEffect, useLayoutEffect, useState, memo, forwardRef } = React;
const { unmountComponentAtNode } = HorizonDOM;
let container = null;
beforeEach(() => {
diff --git a/scripts/__tests__/jest/customMatcher.js b/scripts/__tests__/jest/customMatcher.js
index c373fbd5..1f6a96fb 100644
--- a/scripts/__tests__/jest/customMatcher.js
+++ b/scripts/__tests__/jest/customMatcher.js
@@ -1,7 +1,6 @@
-import { runAsyncEffects, callUseEffects } from '../../../libs/horizon/src/renderer/submit/HookEffectHandler';
+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';
-import { runAsync } from '../../../libs/horizon/src/renderer/taskExecutor/TaskExecutor';
function runAssertion(fn) {
try {
From 2100ebcb5731eb99670a13d247cc42302c15e737 Mon Sep 17 00:00:00 2001
From: * <8>
Date: Tue, 15 Mar 2022 17:42:45 +0800
Subject: [PATCH 4/5] Match-id-991d5f810c56de98d317600d544e7e30fd874144
---
jest.config.js | 18 +-
.../HookTest/UseCallback.test.js | 36 ++
.../ComponentTest/HookTest/UseContext.test.js | 52 +++
.../{ => HookTest}/UseEffect.test.js | 69 ++--
.../HookTest/UseImperativeHandle.test.js | 89 +++++
.../HookTest/UseLayoutEffect.test.js | 116 +++++++
.../ComponentTest/HookTest/UseMemo.test.js | 107 ++++++
.../ComponentTest/HookTest/UseReducer.test.js | 61 ++++
.../ComponentTest/HookTest/UseRef.test.js | 58 ++++
.../{ => HookTest}/UseState.test.js | 93 +++---
.../HooksExceptEffectAndState.test.js | 249 --------------
.../ComponentTest/SimpleUseHook.test.js | 312 ------------------
scripts/__tests__/jest/Text.js | 9 +
scripts/__tests__/jest/jestSetting.js | 24 +-
14 files changed, 638 insertions(+), 655 deletions(-)
create mode 100644 scripts/__tests__/ComponentTest/HookTest/UseCallback.test.js
create mode 100644 scripts/__tests__/ComponentTest/HookTest/UseContext.test.js
rename scripts/__tests__/ComponentTest/{ => HookTest}/UseEffect.test.js (93%)
create mode 100644 scripts/__tests__/ComponentTest/HookTest/UseImperativeHandle.test.js
create mode 100644 scripts/__tests__/ComponentTest/HookTest/UseLayoutEffect.test.js
create mode 100644 scripts/__tests__/ComponentTest/HookTest/UseMemo.test.js
create mode 100644 scripts/__tests__/ComponentTest/HookTest/UseReducer.test.js
create mode 100644 scripts/__tests__/ComponentTest/HookTest/UseRef.test.js
rename scripts/__tests__/ComponentTest/{ => HookTest}/UseState.test.js (68%)
delete mode 100644 scripts/__tests__/ComponentTest/HooksExceptEffectAndState.test.js
delete mode 100644 scripts/__tests__/ComponentTest/SimpleUseHook.test.js
create mode 100644 scripts/__tests__/jest/Text.js
diff --git a/jest.config.js b/jest.config.js
index 333fc68e..1c30d15d 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -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%",
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/UseEffect.test.js b/scripts/__tests__/ComponentTest/HookTest/UseEffect.test.js
similarity index 93%
rename from scripts/__tests__/ComponentTest/UseEffect.test.js
rename to scripts/__tests__/ComponentTest/HookTest/UseEffect.test.js
index 89513844..89459ab4 100644
--- a/scripts/__tests__/ComponentTest/UseEffect.test.js
+++ b/scripts/__tests__/ComponentTest/HookTest/UseEffect.test.js
@@ -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 (
+ <>
+ {num}
+