Match-id-cf6e19b9b095d96cdd036104c08c90fc2bc856ef

This commit is contained in:
* 2022-03-06 11:47:34 +08:00 committed by *
parent a1c73a6e2f
commit 46922ff307
7 changed files with 463 additions and 12 deletions

View File

@ -130,7 +130,7 @@ module.exports = {
//setupFiles: [], //setupFiles: [],
// A list of paths to modules that run some code to configure or set up the testing framework before each test // A list of paths to modules that run some code to configure or set up the testing framework before each test
setupFilesAfterEnv: [require.resolve('./scripts/__tests__/jest/deployConfiguration.js')], setupFilesAfterEnv: [require.resolve('./scripts/__tests__/jest/jestSetting.js')],
// A list of paths to snapshot serializer modules Jest should use for snapshot testing // A list of paths to snapshot serializer modules Jest should use for snapshot testing
// snapshotSerializers: [], // snapshotSerializers: [],

View File

@ -1,6 +1,6 @@
import * as React from '../../../libs/horizon/src/external/Horizon'; import * as React from '../../../libs/horizon/src/external/Horizon';
import * as HorizonDOM from '../../../libs/horizon/src/dom/DOMExternal'; import * as HorizonDOM from '../../../libs/horizon/src/dom/DOMExternal';
import { act } from 'react-dom/test-utils'; import { act } from '../jest/customMatcher';
describe('Hook Test', () => { describe('Hook Test', () => {

View File

@ -0,0 +1,442 @@
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 <p>{props.text}</p>;
}
it('兄弟节点被删除useEffect依然正常', () => {
const App = () => {
return <Text text="App" />;
}
const NewApp = () => {
useEffect(() => {
LogUtils.log(`NewApp effect`);
}, []);
return <Text text="NewApp" />;
}
const na = <NewApp />;
// <App />必须设置key值否则在diff的时候na会被视为不同组件
HorizonDOM.render([<App key="app" />, na], container);
expect(LogUtils.getAndClear()).toEqual([
'App',
'NewApp'
]);
expect(container.textContent).toBe('AppNewApp');
expect(LogUtils.getAndClear()).toEqual([]);
// 在执行新的render前会执行完上一次render的useEffect所以LogUtils会加入'NewApp effect'。
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 <Text text="App" />;
}
const NewApp = () => {
useEffect(() => {
LogUtils.log(`NewApp effect`);
}, []);
return <Text text="NewApp" />;
}
// <App />必须设置key值否则在diff的时候na会被视为不同组件
HorizonDOM.render([<App key="app" />, <NewApp />], container);
expect(LogUtils.getAndClear()).toEqual([
'App',
'NewApp',
'App Layout effect 0',
// 在App更新前会执行完NewApp的useEffect
'NewApp effect',
'App',
'App Layout effect 1',
]);
expect(container.textContent).toBe('AppNewApp');
});
it('兄弟节点执行新的挂载动作useEffect依然正常', () => {
const newContainer = document.createElement('div');
const App = () => {
useLayoutEffect(() => {
LogUtils.log('App Layout effect');
HorizonDOM.render(<Text text="NewContainer" />, newContainer);
});
return <Text text="App" />;
}
const NewApp = () => {
useEffect(() => {
LogUtils.log(`NewApp effect`);
}, []);
return <Text text="NewApp" />;
}
// <App />必须设置key值否则在diff的时候na会被视为不同组件
HorizonDOM.render([<App key="app" />, <NewApp />], container);
expect(LogUtils.getAndClear()).toEqual([
'App',
'NewApp',
'App Layout effect',
// 在执行useLayoutEffectApp的render前会执行完NewApp的useEffect
'NewApp effect',
'NewContainer',
]);
expect(container.textContent).toBe('AppNewApp');
});
it('执行新render的useEffect前会先执行旧render的useEffect', () => {
const App = (props) => {
useEffect(() => {
LogUtils.log(`First effect [${props.num}]`);
});
return <Text text={'num: ' + props.num} />;
}
act(() => {
HorizonDOM.render(<App num={0} />, container, () => LogUtils.log('callback effect'));
expect(LogUtils.getAndClear()).toEqual(['num: 0', 'callback effect']);
expect(container.textContent).toEqual('num: 0');
HorizonDOM.render(<App num={1} />, 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 <Text text={'num: ' + props.num} />;
}
act(() => {
HorizonDOM.render(<App num={0} />, container, () => LogUtils.log('callback effect'));
expect(LogUtils.getAndClear()).toEqual(['num: 0', 'callback effect']);
expect(container.textContent).toEqual('num: 0');
})
expect(LogUtils.getAndClear()).toEqual(['First effect [0]', 'Second effect [0]']);
act(() => {
HorizonDOM.render(<App num={1} />, 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 <Text text={'num: ' + props.num + ',word: ' + props.word} />;
}
act(() => {
HorizonDOM.render(<App num={0} word={'App'} />, container, () => LogUtils.log('callback effect'));
expect(LogUtils.getAndClear()).toEqual([
'num: 0,word: App',
'num Layouteffect [0]',
'word Layouteffect [App]',
'callback effect'
]);
})
expect(LogUtils.getAndClear()).toEqual([
'num effect [0]',
'word effect [App]',
]);
act(() => {
// 此时word改变num不变
HorizonDOM.render(<App num={0} word={'React'} />, 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 <Text text={'num: ' + props.num} />;
}
act(() => {
HorizonDOM.render(<App num={0} />, container, () => LogUtils.log('callback effect'));
expect(LogUtils.getAndClear()).toEqual([
'num: 0',
'callback effect'
]);
expect(container.textContent).toEqual('num: 0');
})
expect(LogUtils.getAndClear()).toEqual([
'num effect [0]',
]);
act(() => {
HorizonDOM.render(<App num={1} />, 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 <Text text={'num: ' + props.num} />;
}
act(() => {
HorizonDOM.render(<App num={0} />, container, () => LogUtils.log('callback effect'));
expect(LogUtils.getAndClear()).toEqual([
'num: 0',
'callback effect'
]);
expect(container.textContent).toEqual('num: 0');
})
expect(LogUtils.getAndClear()).toEqual([
'num effect [0]',
]);
act(() => {
HorizonDOM.render(<App num={1} />, 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 <Text text={'num: ' + num} />;
}
act(() => {
HorizonDOM.render(<App num={0} />, container, () => LogUtils.log('callback effect'));
expect(LogUtils.getAndClear()).toEqual([
'num: 0',
'num Layouteffect [0]',
'callback effect'
]);
})
expect(LogUtils.getAndClear()).toEqual([
'num effect [0]',
]);
act(() => {
setNum();
expect(LogUtils.getAndClear()).toEqual([
'num: 1'
]);
});
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 <Text text={'Num: ' + num} />;
}
HorizonDOM.render(<App />, 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 <Text text={num} />;
})
act(() => {
HorizonDOM.render(<App />, container, () => LogUtils.log('callback effect'));
expect(LogUtils.getAndClear()).toEqual([
0,
'callback effect'
]);
expect(container.textContent).toEqual('0');
});
expect(LogUtils.getAndClear()).toEqual(['num effect [0]']);
// 不会重新渲染
act(() => {
HorizonDOM.render(<App />, container, () => LogUtils.log('callback effect'));
expect(LogUtils.getAndClear()).toEqual(['callback effect']);
expect(container.textContent).toEqual('0');
});
expect(LogUtils.getAndClear()).toEqual([]);
// 会重新渲染
act(() => {
setNum(1);
expect(LogUtils.getAndClear()).toEqual([1]);
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']);
});
})

View File

@ -79,10 +79,10 @@ describe('useState Hook Test', () => {
} }
HorizonDOM.render(<App />, container); HorizonDOM.render(<App />, container);
expect(container.querySelector('p').innerHTML).toBe('0'); expect(container.querySelector('p').innerHTML).toBe('0');
expect(LogUtils).toMatchValue([0]); expect(LogUtils.getAndClear()).toEqual([0]);
// useState修改state 时,设置相同的值,函数组件不会重新渲染 // useState修改state 时,设置相同的值,函数组件不会重新渲染
setNum(0); setNum(0);
expect(LogUtils).toMatchValue([]); expect(LogUtils.getAndClear()).toEqual([]);
expect(container.querySelector('p').innerHTML).toBe('0'); expect(container.querySelector('p').innerHTML).toBe('0');
}); });
@ -98,12 +98,12 @@ describe('useState Hook Test', () => {
}) })
const ref = React.createRef(null); const ref = React.createRef(null);
HorizonDOM.render(<App initNum={1} ref={ref} />, container); HorizonDOM.render(<App initNum={1} ref={ref} />, container);
expect(LogUtils).toMatchValue([1]); expect(LogUtils.getAndClear()).toEqual([1]);
expect(container.querySelector('p').innerHTML).toBe('1'); expect(container.querySelector('p').innerHTML).toBe('1');
// 设置num为3 // 设置num为3
ref.current.setNum(3); ref.current.setNum(3);
// 初始化函数只在初始渲染时被调用,所以Scheduler里的dataArray清空后没有新增。 // 初始化函数只在初始渲染时被调用,所以Scheduler里的dataArray清空后没有新增。
expect(LogUtils).toMatchValue([]); expect(LogUtils.getAndClear()).toEqual([]);
expect(container.querySelector('p').innerHTML).toBe('3'); expect(container.querySelector('p').innerHTML).toBe('3');
}); });
@ -115,15 +115,15 @@ describe('useState Hook Test', () => {
return <Text text={num} />; return <Text text={num} />;
}) })
HorizonDOM.render(<App />, container); HorizonDOM.render(<App />, container);
expect(LogUtils).toMatchValue([0]); expect(LogUtils.getAndClear()).toEqual([0]);
expect(container.querySelector('p').innerHTML).toBe('0'); expect(container.querySelector('p').innerHTML).toBe('0');
// 不会重新渲染 // 不会重新渲染
HorizonDOM.render(<App />, container); HorizonDOM.render(<App />, container);
expect(LogUtils).toMatchValue([]); expect(LogUtils.getAndClear()).toEqual([]);
expect(container.querySelector('p').innerHTML).toBe('0'); expect(container.querySelector('p').innerHTML).toBe('0');
// 会重新渲染 // 会重新渲染
setNum(1) setNum(1)
expect(LogUtils).toMatchValue([1]); expect(LogUtils.getAndClear()).toEqual([1]);
expect(container.querySelector('p').innerHTML).toBe('1'); expect(container.querySelector('p').innerHTML).toBe('1');
}); });
}); });

View File

@ -1,3 +1,6 @@
import { runAsyncEffects } from '../../../libs/horizon/src/renderer/submit/HookEffectHandler'
import { syncUpdates } from '../../../libs/horizon/src/renderer/TreeBuilder'
function runAssertion(fn) { function runAssertion(fn) {
try { try {
fn(); fn();
@ -17,6 +20,12 @@ function toMatchValue(LogUtils, expectedValues) {
}); });
} }
const act = (fun) => {
syncUpdates(fun);
runAsyncEffects();
}
module.exports = { module.exports = {
toMatchValue, toMatchValue,
act
}; };

View File

@ -18,7 +18,7 @@ const getAndClear = () => {
}; };
const clear = () => { const clear = () => {
dataArray = null; dataArray = dataArray ? null : dataArray;
} }
exports.clear = clear; exports.clear = clear;