import * as React from 'react'; import { View, Text } from 'react-native'; import * as renderer from 'react-test-renderer'; import { ComponentWrapper } from './ComponentWrapper'; import { Store } from './Store'; import { mock, verify, instance } from 'ts-mockito'; import { ComponentEventsObserver } from '../events/ComponentEventsObserver'; describe('ComponentWrapper', () => { const componentName = 'example.MyComponent'; let store: Store; let myComponentProps: any; let mockedComponentEventsObserver: ComponentEventsObserver; let componentEventsObserver: ComponentEventsObserver; let uut: ComponentWrapper; class MyComponent extends React.Component { static options = { title: 'MyComponentTitle' }; render() { myComponentProps = this.props; if (this.props.renderCount) { this.props.renderCount(); } return {this.props.text || 'Hello, World!'}; } } class TestParent extends React.Component { private ChildClass: any; constructor(props: any) { super(props); this.ChildClass = props.ChildClass; this.state = { propsFromState: {} }; } render() { return ( ); } } beforeEach(() => { store = new Store(); mockedComponentEventsObserver = mock(ComponentEventsObserver); componentEventsObserver = instance(mockedComponentEventsObserver); uut = new ComponentWrapper(); }); it('must have componentId as prop', () => { const NavigationComponent = uut.wrap(componentName, () => MyComponent, store, componentEventsObserver); const orig = console.error; console.error = (a: any) => a; expect(() => { renderer.create(); }).toThrowError('Component example.MyComponent does not have a componentId!'); console.error = orig; }); it('wraps the component', () => { const NavigationComponent = uut.wrap(componentName, () => MyComponent, store, componentEventsObserver); expect(NavigationComponent).not.toBeInstanceOf(MyComponent); const tree = renderer.create(); expect(tree.toJSON()!.children).toEqual(['Hello, World!']); }); it('injects props from wrapper into original component', () => { const renderCount = jest.fn(); const NavigationComponent = uut.wrap(componentName, () => MyComponent, store, componentEventsObserver); const tree = renderer.create(); expect(tree.toJSON()!.children).toEqual(['yo']); expect(renderCount).toHaveBeenCalledTimes(1); }); it('updates props from wrapper into original component on state change', () => { const NavigationComponent = uut.wrap(componentName, () => MyComponent, store, componentEventsObserver); const tree = renderer.create(); expect(myComponentProps.foo).toEqual(undefined); (tree.getInstance() as any).setState({ propsFromState: { foo: 'yo' } }); expect(myComponentProps.foo).toEqual('yo'); }); it('pulls props from the store and injects them into the inner component', () => { store.setPropsForId('component123', { numberProp: 1, stringProp: 'hello', objectProp: { a: 2 } }); const NavigationComponent = uut.wrap(componentName, () => MyComponent, store, componentEventsObserver); renderer.create(); expect(myComponentProps).toEqual({ componentId: 'component123', numberProp: 1, stringProp: 'hello', objectProp: { a: 2 } }); }); it('updates props from store into inner component', () => { const NavigationComponent = uut.wrap(componentName, () => MyComponent, store, componentEventsObserver); const tree = renderer.create(); store.setPropsForId('component1', { myProp: 'hello' }); expect(myComponentProps.foo).toEqual(undefined); expect(myComponentProps.myProp).toEqual(undefined); (tree.getInstance() as any).setState({ propsFromState: { foo: 'yo' } }); expect(myComponentProps.foo).toEqual('yo'); expect(myComponentProps.myProp).toEqual('hello'); }); it('protects id from change', () => { const NavigationComponent = uut.wrap(componentName, () => MyComponent, store, componentEventsObserver); const tree = renderer.create(); expect(myComponentProps.componentId).toEqual('component1'); (tree.getInstance() as any).setState({ propsFromState: { id: 'ERROR' } }); expect(myComponentProps.componentId).toEqual('component1'); }); xit('assigns key by componentId', () => { const NavigationComponent = uut.wrap(componentName, () => MyComponent, store, componentEventsObserver); const tree = renderer.create(); expect(myComponentProps.componentId).toEqual('component1'); console.log(Object.keys(tree.root.findByType(NavigationComponent).instance._reactInternalFiber)); console.log(tree.root.findByType(NavigationComponent).instance._reactInternalFiber.child.child.child.return.return.key); expect((tree.getInstance() as any)._reactInternalInstance.child.child.Fibernode.key).toEqual('component1'); }); it('cleans props from store on unMount', () => { store.setPropsForId('component123', { foo: 'bar' }); const NavigationComponent = uut.wrap(componentName, () => MyComponent, store, componentEventsObserver); const tree = renderer.create(); expect(store.getPropsForId('component123')).toEqual({ foo: 'bar' }); tree.unmount(); expect(store.getPropsForId('component123')).toEqual({}); }); it('merges static members from wrapped component when generated', () => { const NavigationComponent = uut.wrap(componentName, () => MyComponent, store, componentEventsObserver) as any; expect(NavigationComponent.options).toEqual({ title: 'MyComponentTitle' }); }); it('calls unmounted on componentEventsObserver', () => { const NavigationComponent = uut.wrap(componentName, () => MyComponent, store, componentEventsObserver); const tree = renderer.create(); verify(mockedComponentEventsObserver.unmounted('component123')).never(); tree.unmount(); verify(mockedComponentEventsObserver.unmounted('component123')).once(); }); it('renders HOC components correctly', () => { const generator = () => (props) => ( ); uut = new ComponentWrapper(); const NavigationComponent = uut.wrap(componentName, generator, store, componentEventsObserver); const tree = renderer.create(); expect(tree.root.findByType(View)).toBeDefined() expect(tree.root.findByType(MyComponent).props).toEqual({componentId: 'component123'}); }); describe(`register with redux store`, () => { class MyReduxComp extends React.Component { static get options() { return { foo: 123 }; } render() { return ( {this.props.txt} ); } } interface RootState { txt: string; } function mapStateToProps(state: RootState) { return { txt: state.txt }; } const ConnectedComp = require('react-redux').connect(mapStateToProps)(MyReduxComp); const ReduxProvider = require('react-redux').Provider; const initialState: RootState = { txt: 'it just works' }; const reduxStore = require('redux').createStore((state = initialState) => state); it(`wraps the component with a react-redux provider with passed store`, () => { const NavigationComponent = uut.wrap(componentName, () => ConnectedComp, store, componentEventsObserver, undefined, ReduxProvider, reduxStore); const tree = renderer.create(); expect(tree.toJSON()!.children).toEqual(['it just works']); expect((NavigationComponent as any).options).toEqual({ foo: 123 }); }); }); });