import * as React from 'react';
import { Text } from 'react-native';
import * as renderer from 'react-test-renderer';
import { ComponentWrapper } from './ComponentWrapper';
import { Store } from './Store';
declare module 'react-test-renderer' {
interface ReactTestInstance {
[P: string]: any;
}
}
describe('ComponentWrapper', () => {
let store;
const componentName = 'example.MyComponent';
let childRef;
class MyComponent extends React.Component {
render() {
return {'Hello, World!'};
}
}
class TestParent extends React.Component {
private ChildClass;
constructor(props) {
super(props);
this.ChildClass = props.ChildClass;
this.state = { propsFromState: {} };
}
render() {
return (
childRef = r}
componentId='component1'
{...this.state.propsFromState}
/>
);
}
}
beforeEach(() => {
store = new Store();
});
it('must have componentId as prop', () => {
const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store);
const orig = console.error;
console.error = (a) => a;
expect(() => {
renderer.create();
}).toThrowError('Component example.MyComponent does not have a componentId!');
console.error = orig;
});
it('wraps the component', () => {
const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store);
expect(NavigationComponent).not.toBeInstanceOf(MyComponent);
const tree = renderer.create();
expect(tree.toJSON()!.children).toEqual(['Hello, World!']);
expect(tree.getInstance()!.originalComponentRef).toBeInstanceOf(MyComponent);
});
it('injects props from wrapper into original component', () => {
const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store);
const tree = renderer.create();
expect(tree.getInstance()!.originalComponentRef.props.myProp).toEqual('yo');
});
it('updates props from wrapper into original component', () => {
const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store);
const tree = renderer.create();
expect(childRef.props.foo).toEqual(undefined);
tree.getInstance()!.setState({ propsFromState: { foo: 'yo' } });
expect(childRef.props.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 = ComponentWrapper.wrap(componentName, MyComponent, store);
const tree = renderer.create();
const originalComponentProps = tree.getInstance()!.originalComponentRef.props;
expect(originalComponentProps).toEqual({ componentId: 'component123', numberProp: 1, stringProp: 'hello', objectProp: { a: 2 } });
});
it('updates props from store into inner component', () => {
const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store);
const tree = renderer.create();
store.setPropsForId('component1', { myProp: 'hello' });
expect(childRef.originalComponentRef.props.foo).toEqual(undefined);
expect(childRef.originalComponentRef.props.myProp).toEqual(undefined);
tree.getInstance()!.setState({ propsFromState: { foo: 'yo' } });
expect(childRef.originalComponentRef.props.foo).toEqual('yo');
expect(childRef.originalComponentRef.props.myProp).toEqual('hello');
});
it('protects id from change', () => {
const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store);
const tree = renderer.create();
expect(childRef.originalComponentRef.props.componentId).toEqual('component1');
tree.getInstance()!.setState({ propsFromState: { id: 'ERROR' } });
expect(childRef.originalComponentRef.props.componentId).toEqual('component1');
});
it('assignes key by id', () => {
const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store);
const tree = renderer.create();
expect(tree.getInstance()!.originalComponentRef.props.componentId).toEqual('component1');
expect(tree.getInstance()!.originalComponentRef._reactInternalInstance.key).toEqual('component1');
});
it('saves self ref into store', () => {
const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store);
const tree = renderer.create();
expect(store.getRefForId('component1')).toBeDefined();
expect(store.getRefForId('component1')).toBe(tree.getInstance());
});
it('cleans ref from store on unMount', () => {
const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store);
const tree = renderer.create();
expect(store.getRefForId('component1')).toBeDefined();
tree.unmount();
expect(store.getRefForId('component1')).toBeUndefined();
});
it('holds ref to OriginalComponent', () => {
const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store);
const tree = renderer.create();
expect(tree.getInstance()!.originalComponentRef).toBeDefined();
expect(tree.getInstance()!.originalComponentRef).toBeInstanceOf(MyComponent);
});
it('cleans ref to internal component on unount', () => {
const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store);
const tree = renderer.create();
const instance = tree.getInstance()!;
expect(instance.originalComponentRef).toBeInstanceOf(React.Component);
tree.unmount();
expect(instance.originalComponentRef).toBeFalsy();
});
describe('component lifecycle', () => {
const componentDidAppearCallback = jest.fn();
const componentDidDisappearCallback = jest.fn();
const onNavigationButtonPressedCallback = jest.fn();
const onSearchBarCallback = jest.fn();
class MyLifecycleComponent extends MyComponent {
componentDidAppear() {
componentDidAppearCallback();
}
componentDidDisappear() {
componentDidDisappearCallback();
}
onNavigationButtonPressed() {
onNavigationButtonPressedCallback();
}
onSearchBarUpdated() {
onSearchBarCallback();
}
}
it('componentDidAppear, componentDidDisappear and onNavigationButtonPressed are optional', () => {
const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store);
const tree = renderer.create();
expect(() => tree.getInstance()!.componentDidAppear()).not.toThrow();
expect(() => tree.getInstance()!.componentDidDisappear()).not.toThrow();
expect(() => tree.getInstance()!.onNavigationButtonPressed()).not.toThrow();
expect(() => tree.getInstance()!.onSearchBarUpdated()).not.toThrow();
});
it('calls componentDidAppear on OriginalComponent', () => {
const NavigationComponent = ComponentWrapper.wrap(componentName, MyLifecycleComponent, store);
const tree = renderer.create();
expect(componentDidAppearCallback).toHaveBeenCalledTimes(0);
tree.getInstance()!.componentDidAppear();
expect(componentDidAppearCallback).toHaveBeenCalledTimes(1);
});
it('calls componentDidDisappear on OriginalComponent', () => {
const NavigationComponent = ComponentWrapper.wrap(componentName, MyLifecycleComponent, store);
const tree = renderer.create();
expect(componentDidDisappearCallback).toHaveBeenCalledTimes(0);
tree.getInstance()!.componentDidDisappear();
expect(componentDidDisappearCallback).toHaveBeenCalledTimes(1);
});
it('calls onNavigationButtonPressed on OriginalComponent', () => {
const NavigationComponent = ComponentWrapper.wrap(componentName, MyLifecycleComponent, store);
const tree = renderer.create();
expect(onNavigationButtonPressedCallback).toHaveBeenCalledTimes(0);
tree.getInstance()!.onNavigationButtonPressed();
expect(onNavigationButtonPressedCallback).toHaveBeenCalledTimes(1);
});
it('calls onSearchBarUpdated on OriginalComponent', () => {
const NavigationComponent = ComponentWrapper.wrap(componentName, MyLifecycleComponent, store);
const tree = renderer.create();
expect(onSearchBarCallback).toHaveBeenCalledTimes(0);
tree.getInstance()!.onSearchBarUpdated();
expect(onSearchBarCallback).toHaveBeenCalledTimes(1);
});
});
});