import { oneLineTrim } from 'common-tags'; import { render } from '@/ui/vdom/renderer'; import { Component } from '@/ui/vdom/component'; import { VNode } from '@/ui/vdom/vnode'; import html from '@/ui/vdom/template'; interface Props { mounted?: jest.Mock; updated?: jest.Mock; beforeDestroy?: jest.Mock; refDOM?: jest.Mock; } interface State { hide: boolean; conditional: boolean; } class TestComponent extends Component { constructor(props: Props) { super(props); this.state = { hide: false, conditional: true, }; } show() { this.setState({ hide: false }); } hide() { this.setState({ hide: true }); } conditionalRender() { this.setState({ conditional: false }); } mounted() { if (this.props.mounted) { this.props.mounted(); } } updated() { if (this.props.updated) { this.props.updated(); } } beforeDestroy() { if (this.props.beforeDestroy) { this.props.beforeDestroy(); } } render() { const style = { display: this.state.hide ? 'none' : 'block', }; return html`
{ if (this.props.refDOM) { this.props.refDOM(el); } }} >
child
${this.state.conditional ? [1, 2, 3].map((num) => html`${num}`) : null}
${this.state.conditional ? [1, 2, 3].map((num) => html`${num}`) : null}
`; } } let container: HTMLElement, destroy: () => void; describe('html', () => { it('should be rendered properly', () => { const wrapper = document.createElement('div'); render(wrapper, html`
test
` as VNode); expect(wrapper).toContainHTML('
test
'); }); it('list children should be rendered properly', () => { const wrapper = document.createElement('div'); const expected = oneLineTrim`
1 2 3
`; render( wrapper, html`
${[1, 2, 3].map((text) => html`${text}`)}
` as VNode ); expect(wrapper).toContainHTML(expected); }); it('nested vnode should be rendered properly', () => { const wrapper = document.createElement('div'); const expected = oneLineTrim`
`; render( wrapper, html`
` as VNode ); expect(wrapper).toContainHTML(expected); }); it('should be rendered with style object properly', () => { const wrapper = document.createElement('div'); const style = { display: 'inline-block', backgroundColor: '#ccc' }; const expected = oneLineTrim`
test
`; render(wrapper, html`
test
` as VNode); expect(wrapper).toContainHTML(expected); }); it('should be rendered with pixel added automatically', () => { const wrapper = document.createElement('div'); const style = { position: 'absolute', top: 10, left: 10 }; const expected = oneLineTrim`
test
`; render(wrapper, html`
test
` as VNode); expect(wrapper).toContainHTML(expected); }); }); describe('Class Component', () => { function clickShowBtn() { container.querySelector('button')!.click(); } function clickHideBtn() { container.querySelectorAll('button')[1].click(); } function clickConditionalBtn() { container.querySelectorAll('button')[2].click(); } function renderComponent(spies?: Record) { container = document.createElement('div'); destroy = render(container, html`<${TestComponent} ...${spies} />` as VNode); } it('should be rendered properly', () => { renderComponent(); const expected = oneLineTrim`
child
1 2 3
1 2 3
`; expect(container).toContainHTML(expected); }); it('should be updated by event', () => { renderComponent(); clickHideBtn(); let expected = oneLineTrim`
child
1 2 3
1 2 3
`; expect(container).toContainHTML(expected); clickShowBtn(); expected = oneLineTrim`
child
1 2 3
1 2 3
`; expect(container).toContainHTML(expected); }); it('should call ref function with DOM after rendering the component', () => { const spy = jest.fn(); renderComponent({ refDOM: spy }); expect(spy).toHaveBeenCalledWith(container.querySelector('.my-comp')); }); it('should call ref function with component after rendering the component', () => { const spy = jest.fn(); renderComponent({ ref: spy }); expect(spy).toHaveBeenCalledTimes(1); }); it('should call mounted life cycle method ', () => { const spy = jest.fn(); renderComponent({ mounted: spy }); expect(spy).toHaveBeenCalledTimes(1); }); it('should call mounted life cycle method once', () => { const spy = jest.fn(); renderComponent({ mounted: spy }); clickHideBtn(); expect(spy).toHaveBeenCalledTimes(1); }); it('should call updated life cycle method after component is updated', () => { const spy = jest.fn(); renderComponent({ updated: spy }); clickHideBtn(); expect(spy).toHaveBeenCalledTimes(1); }); it('should call beforeDestroy life cycle method after component is destroyed', () => { const spy = jest.fn(); renderComponent({ beforeDestroy: spy }); destroy(); expect(spy).toHaveBeenCalledTimes(1); expect(container).toContainHTML(''); }); it('should render conditional children components', () => { renderComponent(); let expected = oneLineTrim`
child
1 2 3
1 2 3
`; expect(container).toContainHTML(expected); clickConditionalBtn(); expected = oneLineTrim`
child
`; expect(container).toContainHTML(expected); }); });