diff --git a/scripts/__tests__/ComponentTest/ComponentError.test.js b/scripts/__tests__/ComponentTest/ComponentError.test.js new file mode 100755 index 00000000..7a833d04 --- /dev/null +++ b/scripts/__tests__/ComponentTest/ComponentError.test.js @@ -0,0 +1,43 @@ +import * as Horizon from '@cloudsop/horizon/index.ts'; +import { getLogUtils } from '../jest/testUtils'; + +describe('Component Error Test', () => { + const LogUtils = getLogUtils(); + it('createElement不能为null或undefined', () => { + const NullElement = null; + const UndefinedElement = undefined; + + jest.spyOn(console, 'error').mockImplementation(); + expect(() => { + Horizon.render(, document.createElement('div')); + }).toThrow('Component type is invalid, got: null'); + + expect(() => { + Horizon.render(, document.createElement('div')); + }).toThrow('Component type is invalid, got: undefined'); + + const App = () => { + return ; + }; + + let AppChild = () => { + return ( + + ); + }; + + expect(() => { + Horizon.render(, document.createElement('div')); + }).toThrow('Component type is invalid, got: null'); + + AppChild = () => { + return ( + + ); + }; + + expect(() => { + Horizon.render(, document.createElement('div')); + }).toThrow('Component type is invalid, got: undefined'); + }); +}); \ No newline at end of file diff --git a/scripts/__tests__/ComponentTest/Context.test.js b/scripts/__tests__/ComponentTest/Context.test.js new file mode 100644 index 00000000..d64934ef --- /dev/null +++ b/scripts/__tests__/ComponentTest/Context.test.js @@ -0,0 +1,361 @@ +import * as Horizon from '@cloudsop/horizon/index.ts'; +import { getLogUtils } from '../jest/testUtils'; + +describe('Context Test', () => { + const LogUtils = getLogUtils(); + it('Provider及其内部consumer组件都不受制于shouldComponentUpdate函数或者Horizon.memo()', () => { + const LanguageTypes = { + JAVA: 'Java', + JAVASCRIPT: 'JavaScript', + }; + const defaultValue = { type: LanguageTypes.JAVASCRIPT }; + const SystemLanguageContext = Horizon.createContext(defaultValue); + const SystemLanguageConsumer = SystemLanguageContext.Consumer; + const SystemLanguageProvider = (props) => { + LogUtils.log('SystemLanguageProvider'); + return ( + + {props.children} + + ); + }; + + const Consumer = () => { + LogUtils.log('Consumer'); + return ( + + {type => { + LogUtils.log('Consumer DOM mutations'); + return

{type}

; + }} +
+ ); + }; + + class Middle extends Horizon.Component { + shouldComponentUpdate() { + return false; + } + render() { + LogUtils.log('Middle'); + return this.props.children; + } + } + + const App = (props) => { + LogUtils.log('App'); + return ( + + + + + + + + ); + }; + + Horizon.render(, container); + expect(container.querySelector('p').innerHTML).toBe('Java'); + expect(LogUtils.getAndClear()).toEqual([ + 'App', + 'SystemLanguageProvider', + 'Middle', + 'Middle', + 'Consumer', + 'Consumer DOM mutations' + ]); + + // 组件不变,Middle没有更新,消费者也不会执行 + Horizon.render(, container); + expect(container.querySelector('p').innerHTML).toBe('Java'); + expect(LogUtils.getAndClear()).toEqual([ + 'App', + 'SystemLanguageProvider' + ]); + + Horizon.render(, container); + expect(container.querySelector('p').innerHTML).toBe('JavaScript'); + // 组件更新,但是Middle没有更新,会绕过Middle + expect(LogUtils.getAndClear()).toEqual([ + 'App', + 'SystemLanguageProvider', + 'Consumer DOM mutations' + ]); + }); + + it('嵌套consumer provider', () => { + const Num = { + ONE: 1, + TWO: 2, + }; + const NumberContext = Horizon.createContext(0); + const NumberConsumer = NumberContext.Consumer; + const NumberProvider = (props) => { + LogUtils.log(`SystemLanguageProvider: ${props.type}`); + return ( + + {props.children} + + ); + }; + + const Consumer = () => { + LogUtils.log('Consumer'); + return ( + + {type => { + LogUtils.log('Consumer DOM mutations'); + return

{type}

; + }} +
+ ); + }; + + class Middle extends Horizon.Component { + shouldComponentUpdate() { + return false; + } + render() { + LogUtils.log('Middle'); + return this.props.children; + } + } + + const App = (props) => { + LogUtils.log('App'); + return ( + + + + + + + + ); + }; + + // Consumer决定于距离它最近的provider + Horizon.render(, container); + expect(container.querySelector('p').innerHTML).toBe('2'); + expect(LogUtils.getAndClear()).toEqual([ + 'App', + 'SystemLanguageProvider: 1', + 'SystemLanguageProvider: 2', + 'Middle', + 'Consumer', + 'Consumer DOM mutations' + ]); + // 更新 + Horizon.render(, container); + expect(container.querySelector('p').innerHTML).toBe('3'); + expect(LogUtils.getAndClear()).toEqual([ + 'App', + 'SystemLanguageProvider: 2', + 'SystemLanguageProvider: 3', + 'Consumer DOM mutations' + ]); + }); + + it('设置defaultValue', () => { + const Num = { + ONE: 1, + TWO: 2, + }; + const NumberContext = Horizon.createContext(0); + const NewNumberContext = Horizon.createContext(1); + const NumberConsumer = NumberContext.Consumer; + const NumberProvider = props => { + return ( + + {props.children} + + ); + }; + const NewNumberProvider = props => { + return ( + + {props.children} + + ); + }; + + class Middle extends Horizon.Component { + shouldComponentUpdate() { + return false; + } + render() { + return this.props.children; + } + } + + const NewApp = (props) => { + return ( + + + + {type => { + LogUtils.log('Consumer DOM mutations'); + return

{type}

; + }} +
+
+
+ ); + }; + + const App = (props) => { + return ( + + + + {type => { + LogUtils.log('Consumer DOM mutations'); + return

{type}

; + }} +
+
+
+ ); + }; + + Horizon.render(, container); + // 没有匹配到Provider,会使用defaultValue + expect(container.querySelector('p').innerHTML).toBe('0'); + + // 更新,设置value为undefined + Horizon.render(, container); + // 设置value为undefined时,defaultValue不生效 + expect(container.querySelector('p').innerHTML).toBe(''); + }); + + it('不同provider下的多个consumer', () => { + const NumContext = Horizon.createContext(1); + const Consumer = NumContext.Consumer; + + function Provider(props) { + return ( + + {value => ( + + {props.children} + + )} + + ); + } + + class Middle extends Horizon.Component { + shouldComponentUpdate() { + return false; + } + render() { + return this.props.children; + } + } + + const App = props => { + return ( + + + + + + {value =>

{value}

} +
+
+
+ + + {value =>

{value}

} +
+
+
+
+ ); + }; + + Horizon.render(, container); + expect(container.querySelector('p').innerHTML).toBe('4'); + expect(container.querySelector('#p').innerHTML).toBe('2'); + + Horizon.render(, container); + expect(container.querySelector('p').innerHTML).toBe('6'); + expect(container.querySelector('#p').innerHTML).toBe('3'); + }); + + it('consumer里的child更新是不会重新渲染', () => { + const NumContext = Horizon.createContext(1); + const Consumer = NumContext.Consumer; + + let setNum; + const ReturnDom = props => { + const [num, _setNum] = Horizon.useState(0); + setNum = _setNum; + LogUtils.log('ReturnDom'); + return ( +

{`Context: ${props.context}, Num: ${num}`}

+ ); + }; + + const App = props => { + return ( + + + {value => { + LogUtils.log('Consumer'); + return ; + }} + + + ); + }; + + Horizon.render(, container); + expect(container.querySelector('p').innerHTML).toBe('Context: 2, Num: 0'); + expect(LogUtils.getAndClear()).toEqual([ + 'Consumer', + 'ReturnDom' + ]); + setNum(3); + expect(container.querySelector('p').innerHTML).toBe('Context: 2, Num: 3'); + expect(LogUtils.getAndClear()).toEqual(['ReturnDom']); + }); + + + it('consumer可以拿到其他context的值', () => { + const NumContext = Horizon.createContext(1); + const TypeContext = Horizon.createContext('typeA'); + + const NumAndType = () => { + const type = Horizon.useContext(TypeContext); + return ( + + {value => { + LogUtils.log('Consumer'); + return

{`Num: ${value}, Type: ${type}`}

; + }} +
+ ); + }; + + const App = props => { + return ( + + + + + + ); + }; + + Horizon.render(, container); + expect(container.querySelector('p').innerHTML).toBe('Num: 2, Type: typeB'); + + Horizon.render(, container); + expect(container.querySelector('p').innerHTML).toBe('Num: 2, Type: typeR'); + + Horizon.render(, container); + expect(container.querySelector('p').innerHTML).toBe('Num: 8, Type: typeR'); + }); +}); diff --git a/scripts/__tests__/ComponentTest/FragmentComponent.test.js b/scripts/__tests__/ComponentTest/FragmentComponent.test.js new file mode 100755 index 00000000..eee9ceb4 --- /dev/null +++ b/scripts/__tests__/ComponentTest/FragmentComponent.test.js @@ -0,0 +1,474 @@ +import * as Horizon from '@cloudsop/horizon/index.ts'; +import { Text } from '../jest/commonComponents'; +import { getLogUtils } from '../jest/testUtils'; + +describe('Fragment', () => { + const LogUtils = getLogUtils(); + const { + useEffect, + useRef, + act, + } = Horizon; + it('可以渲染空元素', () => { + const element = ( + + ); + + Horizon.render(element, container); + + expect(container.textContent).toBe(''); + }); + it('可以渲染单个元素', () => { + const element = ( + + + + ); + + Horizon.render(element, container); + + expect(LogUtils.getAndClear()).toEqual(['Fragment']); + expect(container.textContent).toBe('Fragment'); + }); + + it('可以渲染混合元素', () => { + const element = ( + + Java and + + ); + + Horizon.render(element, container); + + expect(LogUtils.getAndClear()).toEqual(['JavaScript']); + expect(container.textContent).toBe('Java and JavaScript'); + }); + + it('可以渲染集合元素', () => { + const App = [, ]; + const element = ( + <> + {App} + + ); + + Horizon.render(element, container); + + expect(LogUtils.getAndClear()).toEqual(['Java', 'JavaScript']); + expect(container.textContent).toBe('JavaJavaScript'); + }); + + it('元素被放进不同层级Fragment里时,状态不会保留', () => { + const ChildApp = (props) => { + const flag = useRef(true); + useEffect(() => { + if (flag.current) { + flag.current = false; + } else { + LogUtils.log('useEffect'); + } + }); + + return

{props.logo}

; + }; + + const App = (props) => { + return props.change ? ( + <> + + + + ) : ( + <> + <> + + + + ); + }; + + act(() => { + Horizon.render(, container); + }); + expect(LogUtils.getNotClear()).toEqual([]); + act(() => { + Horizon.render(, container); + }); + // 切换到不同层级Fragment时,副作用状态不会保留 + expect(LogUtils.getNotClear()).toEqual([]); + expect(container.textContent).toBe('2'); + + act(() => { + Horizon.render(, container); + }); + expect(LogUtils.getNotClear()).toEqual([]); + expect(container.textContent).toBe('1'); + }); + + it('元素被放进单层Fragment里,且在Fragment的顶部时,状态会保留', () => { + const ChildApp = (props) => { + const flag = useRef(true); + useEffect(() => { + if (flag.current) { + flag.current = false; + } else { + LogUtils.log('useEffect'); + } + }); + + return

{props.logo}

; + }; + + const App = (props) => { + return props.change ? ( + + ) : ( + <> + + + ); + }; + + act(() => { + Horizon.render(, container); + }); + expect(LogUtils.getNotClear()).toEqual([]); + act(() => { + Horizon.render(, container); + }); + // 状态会保留 + expect(LogUtils.getNotClear()).toEqual(['useEffect']); + expect(container.textContent).toBe('2'); + + act(() => { + Horizon.render(, container); + }); + expect(LogUtils.getNotClear()).toEqual(['useEffect', 'useEffect']); + expect(container.textContent).toBe('1'); + }); + + it('元素被放进单层Fragment里,但不在Fragment的顶部时,状态不会保留', () => { + const ChildApp = (props) => { + const flag = useRef(true); + useEffect(() => { + if (flag.current) { + flag.current = false; + } else { + LogUtils.log('useEffect'); + } + }); + + return

{props.logo}

; + }; + + const App = (props) => { + return props.change ? ( + + ) : ( + <> +
123
+ + + ); + }; + + act(() => { + Horizon.render(, container); + }); + expect(LogUtils.getNotClear()).toEqual([]); + act(() => { + Horizon.render(, container); + }); + // 状态不会保留 + expect(LogUtils.getNotClear()).toEqual([]); + expect(container.textContent).toBe('1232'); + + act(() => { + Horizon.render(, container); + }); + expect(LogUtils.getNotClear()).toEqual([]); + expect(container.textContent).toBe('1'); + }); + + it('元素被放进多层Fragment里时,状态不会保留', () => { + const ChildApp = (props) => { + const flag = useRef(true); + useEffect(() => { + if (flag.current) { + flag.current = false; + } else { + LogUtils.log('useEffect'); + } + }); + + return

{props.logo}

; + }; + + const App = (props) => { + return props.change ? ( + + ) : ( + <> + <> + <> + + + + + ); + }; + + act(() => { + Horizon.render(, container); + }); + expect(LogUtils.getNotClear()).toEqual([]); + act(() => { + Horizon.render(, container); + }); + // 状态不会保留 + expect(LogUtils.getNotClear()).toEqual([]); + expect(container.textContent).toBe('2'); + + act(() => { + Horizon.render(, container); + }); + expect(LogUtils.getNotClear()).toEqual([]); + expect(container.textContent).toBe('1'); + }); + + it('元素被切换放进同级Fragment里时,状态会保留', () => { + const ChildApp = (props) => { + const flag = useRef(true); + useEffect(() => { + if (flag.current) { + flag.current = false; + } else { + LogUtils.log('useEffect'); + } + }); + + return

{props.logo}

; + }; + + const App = (props) => { + return props.change ? ( + <> + <> + <> + + + + + ) : ( + <> + <> + <> + + + + + ); + }; + + act(() => { + Horizon.render(, container); + }); + expect(LogUtils.getNotClear()).toEqual([]); + act(() => { + Horizon.render(, container); + }); + // 状态会保留 + expect(LogUtils.getNotClear()).toEqual(['useEffect']); + expect(container.textContent).toBe('2'); + + act(() => { + Horizon.render(, container); + }); + expect(LogUtils.getNotClear()).toEqual(['useEffect', 'useEffect']); + expect(container.textContent).toBe('1'); + }); + + it('元素被切换放进同级Fragment,且在数组顶层时,状态会保留', () => { + const ChildApp = (props) => { + const flag = useRef(true); + useEffect(() => { + if (flag.current) { + flag.current = false; + } else { + LogUtils.log('useEffect'); + } + }); + + return

{props.logo}

; + }; + + const App = (props) => { + return props.change ? ( + <> + <> + <> + + + + + ) : ( + <> + <> + <> + {[]} + + + + ); + }; + + act(() => { + Horizon.render(, container); + }); + expect(LogUtils.getNotClear()).toEqual([]); + act(() => { + Horizon.render(, container); + }); + // 状态会保留 + expect(LogUtils.getNotClear()).toEqual(['useEffect']); + expect(container.textContent).toBe('2'); + + act(() => { + Horizon.render(, container); + }); + expect(LogUtils.getNotClear()).toEqual(['useEffect', 'useEffect']); + expect(container.textContent).toBe('1'); + }); + + it('数组里的顶层元素被切换放进单级Fragment时,状态会保留', () => { + const ChildApp = (props) => { + const flag = useRef(true); + useEffect(() => { + if (flag.current) { + flag.current = false; + } else { + LogUtils.log('useEffect'); + } + }); + + return

{props.logo}

; + }; + + const App = (props) => { + return props.change ? ( + [] + ) : ( + <> + + + ); + }; + + act(() => { + Horizon.render(, container); + }); + expect(LogUtils.getNotClear()).toEqual([]); + act(() => { + Horizon.render(, container); + }); + // 状态会保留 + expect(LogUtils.getNotClear()).toEqual(['useEffect']); + expect(container.textContent).toBe('2'); + + act(() => { + Horizon.render(, container); + }); + expect(LogUtils.getNotClear()).toEqual(['useEffect', 'useEffect']); + expect(container.textContent).toBe('1'); + }); + + it('Fragment里的顶层数组里的顶层元素被切换放进不同级Fragment时,状态不会保留', () => { + const ChildApp = (props) => { + const flag = useRef(true); + useEffect(() => { + if (flag.current) { + flag.current = false; + } else { + LogUtils.log('useEffect'); + } + }); + + return

{props.logo}

; + }; + + const App = (props) => { + return props.change ? ( + <> + [] + + ) : ( + <> + <> + + + + ); + }; + + act(() => { + Horizon.render(, container); + }); + expect(LogUtils.getNotClear()).toEqual([]); + act(() => { + Horizon.render(, container); + }); + // 状态会保留 + expect(LogUtils.getNotClear()).toEqual([]); + expect(container.textContent).toBe('2'); + + act(() => { + Horizon.render(, container); + }); + expect(LogUtils.getNotClear()).toEqual([]); + expect(container.textContent).toBe('[1]'); + }); + + it('Fragment的key值不同时,状态不会保留', () => { + const ChildApp = (props) => { + const flag = useRef(true); + useEffect(() => { + if (flag.current) { + flag.current = false; + } else { + LogUtils.log('useEffect'); + } + }); + + return

{props.logo}

; + }; + + const App = (props) => { + return props.change ? ( + + + + ) : ( + + + + ); + }; + + act(() => { + Horizon.render(, container); + }); + expect(LogUtils.getNotClear()).toEqual([]); + act(() => { + Horizon.render(, container); + }); + // 状态不会保留 + expect(LogUtils.getNotClear()).toEqual([]); + expect(container.textContent).toBe('2'); + + act(() => { + Horizon.render(, container); + }); + expect(LogUtils.getNotClear()).toEqual([]); + expect(container.textContent).toBe('1'); + }); +}); diff --git a/scripts/__tests__/ComponentTest/FunctionComponent.test.js b/scripts/__tests__/ComponentTest/FunctionComponent.test.js new file mode 100644 index 00000000..004dc15d --- /dev/null +++ b/scripts/__tests__/ComponentTest/FunctionComponent.test.js @@ -0,0 +1,48 @@ +import * as Horizon from '@cloudsop/horizon/index.ts'; +describe('FunctionComponent Test', () => { + it('渲染无状态组件', () => { + const App = (props) => { + return

{props.text}

; + }; + + Horizon.render(, container); + expect(container.querySelector('p').innerHTML).toBe('app'); + }); + + it('更新无状态组件', () => { + const App = (props) => { + return

{props.text}

; + }; + + Horizon.render(, container); + expect(container.querySelector('p').innerHTML).toBe('app'); + + Horizon.render(, container); + expect(container.querySelector('p').innerHTML).toBe('ABC'); + + Horizon.render(, container); + expect(container.querySelector('p').innerHTML).toBe('abc'); + }); + + it('卸载无状态组件', () => { + const App = (props) => { + return

{props.text}

; + }; + + Horizon.render(, container); + expect(container.querySelector('p').innerHTML).toBe('app'); + + Horizon.unmountComponentAtNode(container); + expect(container.querySelector('p')).toBe(null); + }); + + it('渲染空组件返回空子节点', () => { + const App = () => { + return
; + }; + + const realNode = Horizon.render(, container); + expect(realNode).toBe(null); + }); + +}); \ No newline at end of file diff --git a/scripts/__tests__/ComponentTest/HookTest/UseEffect.test.js b/scripts/__tests__/ComponentTest/HookTest/UseEffect.test.js index 38ab9e71..129c2ec3 100644 --- a/scripts/__tests__/ComponentTest/HookTest/UseEffect.test.js +++ b/scripts/__tests__/ComponentTest/HookTest/UseEffect.test.js @@ -1,5 +1,5 @@ import * as Horizon from '@cloudsop/horizon/index.ts'; -import * as LogUtils from '../../jest/logUtils'; +import { getLogUtils } from '../../jest/testUtils'; import { Text } from '../../jest/commonComponents'; describe('useEffect Hook Test', () => { @@ -12,6 +12,7 @@ describe('useEffect Hook Test', () => { act, } = Horizon; + const LogUtils = getLogUtils(); it('简单使用useEffect', () => { const App = () => { const [num, setNum] = useState(0); diff --git a/scripts/__tests__/ComponentTest/HookTest/UseImperativeHandle.test.js b/scripts/__tests__/ComponentTest/HookTest/UseImperativeHandle.test.js index 6924610c..6a21741a 100644 --- a/scripts/__tests__/ComponentTest/HookTest/UseImperativeHandle.test.js +++ b/scripts/__tests__/ComponentTest/HookTest/UseImperativeHandle.test.js @@ -1,6 +1,6 @@ import * as Horizon from '@cloudsop/horizon/index.ts'; -import * as LogUtils from '../../jest/logUtils'; import { Text } from '../../jest/commonComponents'; +import { getLogUtils } from '../../jest/testUtils'; describe('useImperativeHandle Hook Test', () => { const { @@ -10,7 +10,7 @@ describe('useImperativeHandle Hook Test', () => { act, } = Horizon; const { unmountComponentAtNode } = Horizon; - + const LogUtils = getLogUtils(); it('测试useImperativeHandle', () => { let App = (props, ref) => { @@ -48,7 +48,6 @@ describe('useImperativeHandle Hook Test', () => { }); it('useImperativeHandle没有配置dep时自动更新', () => { - let App = (props, ref) => { const [num, setNum] = useState(0); useImperativeHandle(ref, () => ({ num, setNum })); diff --git a/scripts/__tests__/ComponentTest/HookTest/UseLayoutEffect.test.js b/scripts/__tests__/ComponentTest/HookTest/UseLayoutEffect.test.js index 4a087112..b0dd89d4 100644 --- a/scripts/__tests__/ComponentTest/HookTest/UseLayoutEffect.test.js +++ b/scripts/__tests__/ComponentTest/HookTest/UseLayoutEffect.test.js @@ -1,5 +1,5 @@ import * as Horizon from '@cloudsop/horizon/index.ts'; -import * as LogUtils from '../../jest/logUtils'; +import { getLogUtils } from '../../jest/testUtils'; import { Text } from '../../jest/commonComponents'; describe('useLayoutEffect Hook Test', () => { @@ -9,7 +9,7 @@ describe('useLayoutEffect Hook Test', () => { useLayoutEffect, act, } = Horizon; - + const LogUtils = getLogUtils(); it('简单使用useLayoutEffect', () => { const App = () => { const [num, setNum] = useState(0); diff --git a/scripts/__tests__/ComponentTest/HookTest/UseMemo.test.js b/scripts/__tests__/ComponentTest/HookTest/UseMemo.test.js index 1170e6ee..24d381fe 100644 --- a/scripts/__tests__/ComponentTest/HookTest/UseMemo.test.js +++ b/scripts/__tests__/ComponentTest/HookTest/UseMemo.test.js @@ -1,9 +1,10 @@ import * as Horizon from '@cloudsop/horizon/index.ts'; -import * as LogUtils from '../../jest/logUtils'; +import { getLogUtils } from '../../jest/testUtils'; import { Text } from '../../jest/commonComponents'; describe('useMemo Hook Test', () => { const { useMemo, useState } = Horizon; + const LogUtils = getLogUtils(); it('测试useMemo', () => { let setMemo; diff --git a/scripts/__tests__/ComponentTest/HookTest/UseRef.test.js b/scripts/__tests__/ComponentTest/HookTest/UseRef.test.js index a873a0d4..b219e63b 100644 --- a/scripts/__tests__/ComponentTest/HookTest/UseRef.test.js +++ b/scripts/__tests__/ComponentTest/HookTest/UseRef.test.js @@ -1,9 +1,10 @@ import * as Horizon from '@cloudsop/horizon/index.ts'; -import * as LogUtils from '../../jest/logUtils'; +import { getLogUtils } from '../../jest/testUtils'; import { Text } from '../../jest/commonComponents'; describe('useRef Hook Test', () => { const { useState, useRef } = Horizon; + const LogUtils = getLogUtils(); it('测试useRef', () => { const App = () => { diff --git a/scripts/__tests__/ComponentTest/HookTest/UseState.test.js b/scripts/__tests__/ComponentTest/HookTest/UseState.test.js index 01db57c1..75aaf380 100644 --- a/scripts/__tests__/ComponentTest/HookTest/UseState.test.js +++ b/scripts/__tests__/ComponentTest/HookTest/UseState.test.js @@ -1,5 +1,5 @@ import * as Horizon from '@cloudsop/horizon/index.ts'; -import * as LogUtils from '../../jest/logUtils'; +import { getLogUtils } from '../../jest/testUtils'; import { Text } from '../../jest/commonComponents'; describe('useState Hook Test', () => { @@ -10,6 +10,7 @@ describe('useState Hook Test', () => { memo, act, } = Horizon; + const LogUtils = getLogUtils(); it('简单使用useState', () => { const App = () => { @@ -112,7 +113,7 @@ describe('useState Hook Test', () => { it('useState与memo一起使用', () => { let setNum; - const App = memo((props) => { + const App = memo(() => { const [num, _setNum] = useState(0); setNum = _setNum; return ; diff --git a/scripts/__tests__/ComponentTest/LazyComponent.test.js b/scripts/__tests__/ComponentTest/LazyComponent.test.js new file mode 100755 index 00000000..704dca45 --- /dev/null +++ b/scripts/__tests__/ComponentTest/LazyComponent.test.js @@ -0,0 +1,187 @@ +import * as Horizon from '@cloudsop/horizon/index.ts'; +import { Text } from '../jest/commonComponents'; +import { getLogUtils } from '../jest/testUtils'; + +describe('LazyComponent Test', () => { + const LogUtils = getLogUtils(); + const mockImport = jest.fn(async (component) => { + return { default: component }; + }); + + it('Horizon.lazy()', async () => { + class LazyComponent extends Horizon.Component { + static defaultProps = { language: 'Java' }; + + render() { + const text = `${this.props.greeting}: ${this.props.language}`; + return {text}; + } + } + + const Lazy = Horizon.lazy(() => mockImport(LazyComponent)); + + Horizon.render( + }> + + , + container + ); + + expect(LogUtils.getAndClear()).toEqual(['Loading...']); + expect(container.textContent).toBe('Loading...'); + expect(container.querySelector('span')).toBe(null); + + await Promise.resolve(); + Horizon.render( + }> + + , + container + ); + + expect(LogUtils.getAndClear()).toEqual([]); + expect(container.querySelector('span').innerHTML).toBe('Goodbye: Java'); + }); + + it('同步解析', async () => { + const LazyApp = Horizon.lazy(() => ({ + then(cb) { + cb({ default: Text }); + }, + })); + + Horizon.render( + Loading...
}> + + , + container + ); + + expect(LogUtils.getAndClear()).toEqual(['Lazy']); + expect(container.textContent).toBe('Lazy'); + }); + + it('异常捕获边界', async () => { + class ErrorBoundary extends Horizon.Component { + state = {}; + static getDerivedStateFromError(error) { + return { message: error.message }; + } + render() { + return this.state.message + ?

Error: {this.state.message}

+ : this.props.children; + } + } + + const LazyComponent = () => { + const [num, setNum] = Horizon.useState(0); + if (num === 2) { + throw new Error('num is 2'); + } else { + return ( + <> +

{num}

+ + + ); + }; + + + const App = () => { + const handleClick = () => { + LogUtils.log('bubble click event'); + }; + + const handleCaptureClick = () => { + LogUtils.log('capture click event'); + }; + + return ( +
+ }> + + +
+ ); + }; + Horizon.render(, container); + const event = document.createEvent('Event'); + event.initEvent('click', true, true); + buttonRef.current.dispatchEvent(event); + + expect(LogUtils.getAndClear()).toEqual([ + // 从外到内先捕获再冒泡 + 'capture click event', + 'bubble click event' + ]); + }); +}); \ No newline at end of file diff --git a/scripts/__tests__/ComponentTest/SuspenseComponent.test.js b/scripts/__tests__/ComponentTest/SuspenseComponent.test.js new file mode 100755 index 00000000..4409dd9c --- /dev/null +++ b/scripts/__tests__/ComponentTest/SuspenseComponent.test.js @@ -0,0 +1,59 @@ +import * as Horizon from '@cloudsop/horizon/index.ts'; +import { Text } from '../jest/commonComponents'; +import { getLogUtils } from '../jest/testUtils'; + +describe('SuspenseComponent Test', () => { + const LogUtils = getLogUtils(); + const mockImport = jest.fn(async (component) => { + return { default: component }; + }); + + // var EMPTY_OBJECT = {}; + // const mockCreateResource = jest.fn((component) => { + // let result = EMPTY_OBJECT; + // return () =>{ + // component().then(res => { + // LogUtils.log(res); + // result = res; + // }, reason => { + // LogUtils.log(reason); + // }); + // if(result === EMPTY_OBJECT){ + // throw component(); + // } + // return result; + // }; + // }); + + it('挂载lazy组件', async () => { + // 用同步的代码来实现异步操作 + class LazyComponent extends Horizon.Component { + render() { + return ; + } + } + + const Lazy = Horizon.lazy(() => mockImport(LazyComponent)); + + Horizon.render( + }> + + , + container + ); + + expect(LogUtils.getAndClear()).toEqual(['Loading...']); + expect(container.textContent).toBe('Loading...'); + + await Promise.resolve(); + Horizon.render( + }> + + , + container + ); + expect(LogUtils.getAndClear()).toEqual([5]); + expect(container.querySelector('p').innerHTML).toBe('5'); + }); + +}); diff --git a/scripts/__tests__/DomTest/DomInput.test.js b/scripts/__tests__/DomTest/DomInput.test.js index 218cecc6..d5649ff3 100755 --- a/scripts/__tests__/DomTest/DomInput.test.js +++ b/scripts/__tests__/DomTest/DomInput.test.js @@ -1,9 +1,10 @@ /* eslint-disable @typescript-eslint/no-empty-function */ import * as Horizon from '@cloudsop/horizon/index.ts'; -import * as LogUtils from '../jest/logUtils'; +import { getLogUtils } from '../jest/testUtils'; describe('Dom Input', () => { const { act } = Horizon; + const LogUtils = getLogUtils(); describe('type checkbox', () => { it('没有设置checked属性时,控制台不会报错', () => { diff --git a/scripts/__tests__/DomTest/DomSelect.test.js b/scripts/__tests__/DomTest/DomSelect.test.js index 29548fc8..dfd1828e 100755 --- a/scripts/__tests__/DomTest/DomSelect.test.js +++ b/scripts/__tests__/DomTest/DomSelect.test.js @@ -130,7 +130,7 @@ describe('Dom Select', () => { expect(realNode.options[1].selected).toBe(true); }); - it('设置defaultValue后,select不受控', () => { + it('设置defaultValue后,select不受控', () => { const selectNode = ( , container); + expect(realNode.value).toBe('false'); + }); + + it('设置defaultValue为对象', () => { + let textareaValue = { + toString: () => { + return 'Vue'; + } + }; + const textareaNode = ( + , container); + expect(realNode.value).toBe('1234'); + realNode = Horizon.render(, container); + // realNode.value依旧为1234 + expect(realNode.value).toBe('1234'); + }); + +}); \ No newline at end of file diff --git a/scripts/__tests__/DomTest/__snapshots__/DomTextarea.test.js.snap b/scripts/__tests__/DomTest/__snapshots__/DomTextarea.test.js.snap new file mode 100644 index 00000000..c2975807 --- /dev/null +++ b/scripts/__tests__/DomTest/__snapshots__/DomTextarea.test.js.snap @@ -0,0 +1,5 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Dom Textarea should not incur unnecessary DOM mutations 1`] = `