Browse Source

Avoid calling component generators on registerComponent (#4286)

* Avoid calling component generators on registerComponent

* fix unit tests

* remove console.log

* fix coverage

* fix coverage
Yogev Ben David 6 years ago
parent
commit
708d594877
No account linked to committer's email address

+ 4
- 4
docs/api/Store.md View File

16
 
16
 
17
 ---
17
 ---
18
 
18
 
19
-## setOriginalComponentClassForName
19
+## setComponentClassForName
20
 
20
 
21
-`setOriginalComponentClassForName(componentName: string, ComponentClass: any): void`
21
+`setComponentClassForName(componentName: string, ComponentClass: any): void`
22
 
22
 
23
 [source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/components/Store.ts#L15)
23
 [source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/components/Store.ts#L15)
24
 
24
 
25
 ---
25
 ---
26
 
26
 
27
-## getOriginalComponentClassForName
27
+## getComponentClassForName
28
 
28
 
29
-`getOriginalComponentClassForName(componentName: string): any`
29
+`getComponentClassForName(componentName: string): any`
30
 
30
 
31
 [source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/components/Store.ts#L19)
31
 [source](https://github.com/wix/react-native-navigation/blob/v2/lib/src/components/Store.ts#L19)
32
 
32
 

+ 1
- 1
integration/redux/Redux.test.js View File

41
         );
41
         );
42
       }
42
       }
43
     };
43
     };
44
-    const CompFromNavigation = Navigation.registerComponent('ComponentName', () => HOC);
44
+    const CompFromNavigation = Navigation.registerComponent('ComponentName', () => HOC)();
45
 
45
 
46
     const tree = renderer.create(<CompFromNavigation componentId='componentId' renderCountIncrement={renderCountIncrement}/>);
46
     const tree = renderer.create(<CompFromNavigation componentId='componentId' renderCountIncrement={renderCountIncrement}/>);
47
     expect(tree.toJSON().children).toEqual(['no name']);
47
     expect(tree.toJSON().children).toEqual(['no name']);

+ 1
- 1
integration/remx/remx.test.js View File

48
   it('support for static members in connected components', () => {
48
   it('support for static members in connected components', () => {
49
     expect(MyConnectedComponent.options).toEqual({ title: 'MyComponent' });
49
     expect(MyConnectedComponent.options).toEqual({ title: 'MyComponent' });
50
 
50
 
51
-    const registeredComponentClass = Navigation.registerComponent('MyComponentName', () => MyConnectedComponent);
51
+    const registeredComponentClass = Navigation.registerComponent('MyComponentName', () => MyConnectedComponent)();
52
     expect(registeredComponentClass.options).toEqual({ title: 'MyComponent' });
52
     expect(registeredComponentClass.options).toEqual({ title: 'MyComponent' });
53
   });
53
   });
54
 });
54
 });

+ 6
- 4
lib/src/Navigation.ts View File

11
 import { Element } from './adapters/Element';
11
 import { Element } from './adapters/Element';
12
 import { CommandsObserver } from './events/CommandsObserver';
12
 import { CommandsObserver } from './events/CommandsObserver';
13
 import { Constants } from './adapters/Constants';
13
 import { Constants } from './adapters/Constants';
14
-import { ComponentType } from 'react';
15
 import { ComponentEventsObserver } from './events/ComponentEventsObserver';
14
 import { ComponentEventsObserver } from './events/ComponentEventsObserver';
16
 import { TouchablePreview } from './adapters/TouchablePreview';
15
 import { TouchablePreview } from './adapters/TouchablePreview';
17
 import { LayoutRoot, Layout } from './interfaces/Layout';
16
 import { LayoutRoot, Layout } from './interfaces/Layout';
18
 import { Options } from './interfaces/Options';
17
 import { Options } from './interfaces/Options';
18
+import { ComponentWrapper } from './components/ComponentWrapper';
19
 
19
 
20
 export class Navigation {
20
 export class Navigation {
21
   public readonly Element: React.ComponentType<{ elementId: any; resizeMode?: any; }>;
21
   public readonly Element: React.ComponentType<{ elementId: any; resizeMode?: any; }>;
31
   private readonly eventsRegistry: EventsRegistry;
31
   private readonly eventsRegistry: EventsRegistry;
32
   private readonly commandsObserver: CommandsObserver;
32
   private readonly commandsObserver: CommandsObserver;
33
   private readonly componentEventsObserver: ComponentEventsObserver;
33
   private readonly componentEventsObserver: ComponentEventsObserver;
34
+  private readonly componentWrapper: typeof ComponentWrapper;
34
 
35
 
35
   constructor() {
36
   constructor() {
36
     this.Element = Element;
37
     this.Element = Element;
37
     this.TouchablePreview = TouchablePreview;
38
     this.TouchablePreview = TouchablePreview;
38
     this.store = new Store();
39
     this.store = new Store();
40
+    this.componentWrapper = ComponentWrapper;
39
     this.nativeEventsReceiver = new NativeEventsReceiver();
41
     this.nativeEventsReceiver = new NativeEventsReceiver();
40
     this.uniqueIdProvider = new UniqueIdProvider();
42
     this.uniqueIdProvider = new UniqueIdProvider();
41
     this.componentEventsObserver = new ComponentEventsObserver(this.nativeEventsReceiver);
43
     this.componentEventsObserver = new ComponentEventsObserver(this.nativeEventsReceiver);
42
-    this.componentRegistry = new ComponentRegistry(this.store, this.componentEventsObserver);
44
+    this.componentRegistry = new ComponentRegistry(this.store, this.componentEventsObserver, this.componentWrapper);
43
     this.layoutTreeParser = new LayoutTreeParser();
45
     this.layoutTreeParser = new LayoutTreeParser();
44
     this.layoutTreeCrawler = new LayoutTreeCrawler(this.uniqueIdProvider, this.store);
46
     this.layoutTreeCrawler = new LayoutTreeCrawler(this.uniqueIdProvider, this.store);
45
     this.nativeCommandsSender = new NativeCommandsSender();
47
     this.nativeCommandsSender = new NativeCommandsSender();
54
    * Every navigation component in your app must be registered with a unique name.
56
    * Every navigation component in your app must be registered with a unique name.
55
    * The component itself is a traditional React component extending React.Component.
57
    * The component itself is a traditional React component extending React.Component.
56
    */
58
    */
57
-  public registerComponent(componentName: string, getComponentClassFunc: ComponentProvider): ComponentType<any> {
59
+  public registerComponent(componentName: string, getComponentClassFunc: ComponentProvider): ComponentProvider {
58
     return this.componentRegistry.registerComponent(componentName, getComponentClassFunc);
60
     return this.componentRegistry.registerComponent(componentName, getComponentClassFunc);
59
   }
61
   }
60
 
62
 
67
     getComponentClassFunc: ComponentProvider,
69
     getComponentClassFunc: ComponentProvider,
68
     ReduxProvider: any,
70
     ReduxProvider: any,
69
     reduxStore: any
71
     reduxStore: any
70
-  ): ComponentType<any> {
72
+  ): ComponentProvider {
71
     return this.componentRegistry.registerComponent(componentName, getComponentClassFunc, ReduxProvider, reduxStore);
73
     return this.componentRegistry.registerComponent(componentName, getComponentClassFunc, ReduxProvider, reduxStore);
72
   }
74
   }
73
 
75
 

+ 9
- 0
lib/src/commands/Commands.test.ts View File

254
         children: []
254
         children: []
255
       });
255
       });
256
     });
256
     });
257
+
258
+    it('calls component generator once', async () => {
259
+      const generator = jest.fn(() => {
260
+        return {};
261
+      });
262
+      store.setComponentClassForName('theComponentName', generator);
263
+      await uut.push('theComponentId', { component: { name: 'theComponentName' } });
264
+      expect(generator).toHaveBeenCalledTimes(1);
265
+    });
257
   });
266
   });
258
 
267
 
259
   describe('pop', () => {
268
   describe('pop', () => {

+ 39
- 6
lib/src/commands/LayoutTreeCrawler.test.ts View File

62
     };
62
     };
63
 
63
 
64
     const node: any = { type: LayoutType.Component, data: { name: 'theComponentName' } };
64
     const node: any = { type: LayoutType.Component, data: { name: 'theComponentName' } };
65
-    store.setOriginalComponentClassForName('theComponentName', MyComponent);
65
+    store.setComponentClassForName('theComponentName', () => MyComponent);
66
     uut.crawl(node);
66
     uut.crawl(node);
67
     expect(node.data.options).toEqual(theStyle);
67
     expect(node.data.options).toEqual(theStyle);
68
   });
68
   });
69
 
69
 
70
+  it('Components: crawl does not cache options', () => {
71
+    const optionsWithTitle = (title) => {
72
+      return {
73
+        topBar: {
74
+          title: {
75
+            text: title
76
+          }
77
+        }
78
+      }
79
+    };
80
+
81
+    const MyComponent = class {
82
+      static options(props) {
83
+        return {
84
+          topBar: {
85
+            title: {
86
+              text: props.title
87
+            }
88
+          }
89
+        };
90
+      }
91
+    };
92
+
93
+    const node: any = { type: LayoutType.Component, data: { name: 'theComponentName', passProps: { title: 'title' } } };
94
+    store.setComponentClassForName('theComponentName', () => MyComponent);
95
+    uut.crawl(node);
96
+    expect(node.data.options).toEqual(optionsWithTitle('title'));
97
+
98
+    const node2: any = { type: LayoutType.Component, data: { name: 'theComponentName' } };
99
+    uut.crawl(node2);
100
+    expect(node2.data.options).toEqual(optionsWithTitle(undefined));
101
+  });
102
+
70
   it('Components: passes passProps to the static options function to be used by the user', () => {
103
   it('Components: passes passProps to the static options function to be used by the user', () => {
71
     const MyComponent = class {
104
     const MyComponent = class {
72
       static options(passProps) {
105
       static options(passProps) {
75
     };
108
     };
76
 
109
 
77
     const node: any = { type: LayoutType.Component, data: { name: 'theComponentName', passProps: { bar: { baz: { value: 'hello' } } } } };
110
     const node: any = { type: LayoutType.Component, data: { name: 'theComponentName', passProps: { bar: { baz: { value: 'hello' } } } } };
78
-    store.setOriginalComponentClassForName('theComponentName', MyComponent);
111
+    store.setComponentClassForName('theComponentName', () => MyComponent);
79
     uut.crawl(node);
112
     uut.crawl(node);
80
     expect(node.data.options).toEqual({ foo: 'hello' });
113
     expect(node.data.options).toEqual({ foo: 'hello' });
81
   });
114
   });
88
     };
121
     };
89
 
122
 
90
     const node: any = { type: LayoutType.Component, data: { name: 'theComponentName' } };
123
     const node: any = { type: LayoutType.Component, data: { name: 'theComponentName' } };
91
-    store.setOriginalComponentClassForName('theComponentName', MyComponent);
124
+    store.setComponentClassForName('theComponentName', () => MyComponent);
92
     uut.crawl(node);
125
     uut.crawl(node);
93
     expect(node.data.options).toEqual({ foo: {} });
126
     expect(node.data.options).toEqual({ foo: {} });
94
   });
127
   });
116
     };
149
     };
117
 
150
 
118
     const node = { type: LayoutType.Component, data: { name: 'theComponentName', options: passedOptions } };
151
     const node = { type: LayoutType.Component, data: { name: 'theComponentName', options: passedOptions } };
119
-    store.setOriginalComponentClassForName('theComponentName', MyComponent);
152
+    store.setComponentClassForName('theComponentName', () => MyComponent);
120
 
153
 
121
     uut.crawl(node);
154
     uut.crawl(node);
122
 
155
 
139
     };
172
     };
140
 
173
 
141
     const node: any = { type: LayoutType.Component, data: { name: 'theComponentName' } };
174
     const node: any = { type: LayoutType.Component, data: { name: 'theComponentName' } };
142
-    store.setOriginalComponentClassForName('theComponentName', MyComponent);
175
+    store.setComponentClassForName('theComponentName', () => MyComponent);
143
     uut.crawl(node);
176
     uut.crawl(node);
144
     expect(node.data.options).not.toBe(theStyle);
177
     expect(node.data.options).not.toBe(theStyle);
145
   });
178
   });
153
     const MyComponent = class { };
186
     const MyComponent = class { };
154
 
187
 
155
     const node: any = { type: LayoutType.Component, data: { name: 'theComponentName' } };
188
     const node: any = { type: LayoutType.Component, data: { name: 'theComponentName' } };
156
-    store.setOriginalComponentClassForName('theComponentName', MyComponent);
189
+    store.setComponentClassForName('theComponentName', () => MyComponent);
157
     uut.crawl(node);
190
     uut.crawl(node);
158
     expect(node.data.options).toEqual({});
191
     expect(node.data.options).toEqual({});
159
   });
192
   });

+ 1
- 1
lib/src/commands/LayoutTreeCrawler.ts View File

52
   }
52
   }
53
 
53
 
54
   _applyStaticOptions(node) {
54
   _applyStaticOptions(node) {
55
-    const clazz = this.store.getOriginalComponentClassForName(node.data.name) || {};
55
+    const clazz = this.store.getComponentClassForName(node.data.name) ? this.store.getComponentClassForName(node.data.name)() : {};
56
     const staticOptions = _.isFunction(clazz.options) ? clazz.options(node.data.passProps || {}) : (_.cloneDeep(clazz.options) || {});
56
     const staticOptions = _.isFunction(clazz.options) ? clazz.options(node.data.passProps || {}) : (_.cloneDeep(clazz.options) || {});
57
     const passedOptions = node.data.options || {};
57
     const passedOptions = node.data.options || {};
58
     node.data.options = _.merge({}, staticOptions, passedOptions);
58
     node.data.options = _.merge({}, staticOptions, passedOptions);

+ 30
- 12
lib/src/components/ComponentRegistry.test.tsx View File

8
   let uut;
8
   let uut;
9
   let store;
9
   let store;
10
   let mockRegistry: any;
10
   let mockRegistry: any;
11
+  let mockWrapper: any;
11
 
12
 
12
-  class MyComponent extends React.Component {
13
+
14
+  class WrappedComponent extends React.Component {
13
     render() {
15
     render() {
14
       return (
16
       return (
15
         <Text>
17
         <Text>
23
   beforeEach(() => {
25
   beforeEach(() => {
24
     store = new Store();
26
     store = new Store();
25
     mockRegistry = AppRegistry.registerComponent = jest.fn(AppRegistry.registerComponent);
27
     mockRegistry = AppRegistry.registerComponent = jest.fn(AppRegistry.registerComponent);
26
-    uut = new ComponentRegistry(store, {} as any);
28
+    mockWrapper = jest.mock('./ComponentWrapper');
29
+    mockWrapper.wrap = () => WrappedComponent;
30
+    uut = new ComponentRegistry(store, {} as any, mockWrapper);
27
   });
31
   });
28
 
32
 
29
-  it('registers component component by componentName into AppRegistry', () => {
33
+  it('registers component by componentName into AppRegistry', () => {
30
     expect(mockRegistry).not.toHaveBeenCalled();
34
     expect(mockRegistry).not.toHaveBeenCalled();
31
-    const result = uut.registerComponent('example.MyComponent.name', () => MyComponent);
35
+    const result = uut.registerComponent('example.MyComponent.name', () => {});
32
     expect(mockRegistry).toHaveBeenCalledTimes(1);
36
     expect(mockRegistry).toHaveBeenCalledTimes(1);
33
     expect(mockRegistry.mock.calls[0][0]).toEqual('example.MyComponent.name');
37
     expect(mockRegistry.mock.calls[0][0]).toEqual('example.MyComponent.name');
34
-    expect(mockRegistry.mock.calls[0][1]()).toEqual(result);
38
+    expect(mockRegistry.mock.calls[0][1]()).toEqual(result());
35
   });
39
   });
36
 
40
 
37
-  it('saves the original component into the store', () => {
38
-    expect(store.getOriginalComponentClassForName('example.MyComponent.name')).toBeUndefined();
39
-    uut.registerComponent('example.MyComponent.name', () => MyComponent);
40
-    const Class = store.getOriginalComponentClassForName('example.MyComponent.name');
41
+  it('saves the wrapper component generator the store', () => {
42
+    expect(store.getComponentClassForName('example.MyComponent.name')).toBeUndefined();
43
+    uut.registerComponent('example.MyComponent.name', () => {});
44
+    const Class = store.getComponentClassForName('example.MyComponent.name');
41
     expect(Class).not.toBeUndefined();
45
     expect(Class).not.toBeUndefined();
42
-    expect(Class).toEqual(MyComponent);
43
-    expect(Object.getPrototypeOf(Class)).toEqual(React.Component);
46
+    expect(Class()).toEqual(WrappedComponent);
47
+    expect(Object.getPrototypeOf(Class())).toEqual(React.Component);
44
   });
48
   });
45
 
49
 
46
   it('resulting in a normal component', () => {
50
   it('resulting in a normal component', () => {
47
-    uut.registerComponent('example.MyComponent.name', () => MyComponent);
51
+    uut.registerComponent('example.MyComponent.name', () => {});
48
     const Component = mockRegistry.mock.calls[0][1]();
52
     const Component = mockRegistry.mock.calls[0][1]();
49
     const tree = renderer.create(<Component componentId='123' />);
53
     const tree = renderer.create(<Component componentId='123' />);
50
     expect(tree.toJSON()!.children).toEqual(['Hello, World!']);
54
     expect(tree.toJSON()!.children).toEqual(['Hello, World!']);
51
   });
55
   });
56
+
57
+  it('should not invoke generator', () => {
58
+    const generator = jest.fn(() => {});
59
+    uut.registerComponent('example.MyComponent.name', generator);
60
+    expect(generator).toHaveBeenCalledTimes(0);
61
+  });
62
+
63
+  it('saves wrapped component to store', () => {
64
+    jest.spyOn(store, 'setComponentClassForName');
65
+    const generator = jest.fn(() => {});
66
+    const componentName = 'example.MyComponent.name';
67
+    uut.registerComponent(componentName, generator);
68
+    expect(store.getComponentClassForName(componentName)()).toEqual(WrappedComponent);
69
+  });
52
 });
70
 });

+ 7
- 7
lib/src/components/ComponentRegistry.ts View File

1
 import { AppRegistry, ComponentProvider } from 'react-native';
1
 import { AppRegistry, ComponentProvider } from 'react-native';
2
 import { ComponentWrapper } from './ComponentWrapper';
2
 import { ComponentWrapper } from './ComponentWrapper';
3
-import { ComponentType } from 'react';
4
 import { Store } from './Store';
3
 import { Store } from './Store';
5
 import { ComponentEventsObserver } from '../events/ComponentEventsObserver';
4
 import { ComponentEventsObserver } from '../events/ComponentEventsObserver';
6
 
5
 
7
 export class ComponentRegistry {
6
 export class ComponentRegistry {
8
-  constructor(private readonly store: Store, private readonly componentEventsObserver: ComponentEventsObserver) { }
7
+  constructor(private readonly store: Store, private readonly componentEventsObserver: ComponentEventsObserver, private readonly ComponentWrapperClass: typeof ComponentWrapper) { }
9
 
8
 
10
-  registerComponent(componentName: string, getComponentClassFunc: ComponentProvider, ReduxProvider?: any, reduxStore?: any): ComponentType<any> {
11
-    const OriginalComponentClass = getComponentClassFunc();
12
-    const NavigationComponent = ComponentWrapper.wrap(componentName, OriginalComponentClass, this.store, this.componentEventsObserver, ReduxProvider, reduxStore);
13
-    this.store.setOriginalComponentClassForName(componentName, OriginalComponentClass);
14
-    AppRegistry.registerComponent(componentName, () => NavigationComponent);
9
+  registerComponent(componentName: string, getComponentClassFunc: ComponentProvider, ReduxProvider?: any, reduxStore?: any): ComponentProvider {
10
+    const NavigationComponent = () => {
11
+      return this.ComponentWrapperClass.wrap(componentName, getComponentClassFunc, this.store, this.componentEventsObserver, ReduxProvider, reduxStore)
12
+    };
13
+    this.store.setComponentClassForName(componentName, NavigationComponent);
14
+    AppRegistry.registerComponent(componentName, NavigationComponent);
15
     return NavigationComponent;
15
     return NavigationComponent;
16
   }
16
   }
17
 }
17
 }

+ 13
- 13
lib/src/components/ComponentWrapper.test.tsx View File

53
   });
53
   });
54
 
54
 
55
   it('must have componentId as prop', () => {
55
   it('must have componentId as prop', () => {
56
-    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store, componentEventsObserver);
56
+    const NavigationComponent = ComponentWrapper.wrap(componentName, () => MyComponent, store, componentEventsObserver);
57
     const orig = console.error;
57
     const orig = console.error;
58
     console.error = (a) => a;
58
     console.error = (a) => a;
59
     expect(() => {
59
     expect(() => {
63
   });
63
   });
64
 
64
 
65
   it('wraps the component', () => {
65
   it('wraps the component', () => {
66
-    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store, componentEventsObserver);
66
+    const NavigationComponent = ComponentWrapper.wrap(componentName, () => MyComponent, store, componentEventsObserver);
67
     expect(NavigationComponent).not.toBeInstanceOf(MyComponent);
67
     expect(NavigationComponent).not.toBeInstanceOf(MyComponent);
68
     const tree = renderer.create(<NavigationComponent componentId={'component1'} />);
68
     const tree = renderer.create(<NavigationComponent componentId={'component1'} />);
69
     expect(tree.toJSON()!.children).toEqual(['Hello, World!']);
69
     expect(tree.toJSON()!.children).toEqual(['Hello, World!']);
71
 
71
 
72
   it('injects props from wrapper into original component', () => {
72
   it('injects props from wrapper into original component', () => {
73
     const renderCount = jest.fn();
73
     const renderCount = jest.fn();
74
-    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store, componentEventsObserver);
74
+    const NavigationComponent = ComponentWrapper.wrap(componentName, () => MyComponent, store, componentEventsObserver);
75
     const tree = renderer.create(<NavigationComponent componentId={'component1'} text={'yo'} renderCount={renderCount} />);
75
     const tree = renderer.create(<NavigationComponent componentId={'component1'} text={'yo'} renderCount={renderCount} />);
76
     expect(tree.toJSON()!.children).toEqual(['yo']);
76
     expect(tree.toJSON()!.children).toEqual(['yo']);
77
     expect(renderCount).toHaveBeenCalledTimes(1);
77
     expect(renderCount).toHaveBeenCalledTimes(1);
78
   });
78
   });
79
 
79
 
80
   it('updates props from wrapper into original component on state change', () => {
80
   it('updates props from wrapper into original component on state change', () => {
81
-    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store, componentEventsObserver);
81
+    const NavigationComponent = ComponentWrapper.wrap(componentName, () => MyComponent, store, componentEventsObserver);
82
     const tree = renderer.create(<TestParent ChildClass={NavigationComponent} />);
82
     const tree = renderer.create(<TestParent ChildClass={NavigationComponent} />);
83
     expect(myComponentProps.foo).toEqual(undefined);
83
     expect(myComponentProps.foo).toEqual(undefined);
84
     (tree.getInstance() as any).setState({ propsFromState: { foo: 'yo' } });
84
     (tree.getInstance() as any).setState({ propsFromState: { foo: 'yo' } });
87
 
87
 
88
   it('pulls props from the store and injects them into the inner component', () => {
88
   it('pulls props from the store and injects them into the inner component', () => {
89
     store.setPropsForId('component123', { numberProp: 1, stringProp: 'hello', objectProp: { a: 2 } });
89
     store.setPropsForId('component123', { numberProp: 1, stringProp: 'hello', objectProp: { a: 2 } });
90
-    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store, componentEventsObserver);
90
+    const NavigationComponent = ComponentWrapper.wrap(componentName, () => MyComponent, store, componentEventsObserver);
91
     renderer.create(<NavigationComponent componentId={'component123'} />);
91
     renderer.create(<NavigationComponent componentId={'component123'} />);
92
     expect(myComponentProps).toEqual({ componentId: 'component123', numberProp: 1, stringProp: 'hello', objectProp: { a: 2 } });
92
     expect(myComponentProps).toEqual({ componentId: 'component123', numberProp: 1, stringProp: 'hello', objectProp: { a: 2 } });
93
   });
93
   });
94
 
94
 
95
   it('updates props from store into inner component', () => {
95
   it('updates props from store into inner component', () => {
96
-    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store, componentEventsObserver);
96
+    const NavigationComponent = ComponentWrapper.wrap(componentName, () => MyComponent, store, componentEventsObserver);
97
     const tree = renderer.create(<TestParent ChildClass={NavigationComponent} />);
97
     const tree = renderer.create(<TestParent ChildClass={NavigationComponent} />);
98
     store.setPropsForId('component1', { myProp: 'hello' });
98
     store.setPropsForId('component1', { myProp: 'hello' });
99
     expect(myComponentProps.foo).toEqual(undefined);
99
     expect(myComponentProps.foo).toEqual(undefined);
104
   });
104
   });
105
 
105
 
106
   it('protects id from change', () => {
106
   it('protects id from change', () => {
107
-    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store, componentEventsObserver);
107
+    const NavigationComponent = ComponentWrapper.wrap(componentName, () => MyComponent, store, componentEventsObserver);
108
     const tree = renderer.create(<TestParent ChildClass={NavigationComponent} />);
108
     const tree = renderer.create(<TestParent ChildClass={NavigationComponent} />);
109
     expect(myComponentProps.componentId).toEqual('component1');
109
     expect(myComponentProps.componentId).toEqual('component1');
110
     (tree.getInstance() as any).setState({ propsFromState: { id: 'ERROR' } });
110
     (tree.getInstance() as any).setState({ propsFromState: { id: 'ERROR' } });
112
   });
112
   });
113
 
113
 
114
   it('assignes key by id', () => {
114
   it('assignes key by id', () => {
115
-    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store, componentEventsObserver);
115
+    const NavigationComponent = ComponentWrapper.wrap(componentName, () => MyComponent, store, componentEventsObserver);
116
     const tree = renderer.create(<NavigationComponent componentId={'component1'} />);
116
     const tree = renderer.create(<NavigationComponent componentId={'component1'} />);
117
     expect(myComponentProps.componentId).toEqual('component1');
117
     expect(myComponentProps.componentId).toEqual('component1');
118
     expect((tree.getInstance() as any)._reactInternalInstance.child.key).toEqual('component1');
118
     expect((tree.getInstance() as any)._reactInternalInstance.child.key).toEqual('component1');
120
 
120
 
121
   it('cleans props from store on unMount', () => {
121
   it('cleans props from store on unMount', () => {
122
     store.setPropsForId('component123', { foo: 'bar' });
122
     store.setPropsForId('component123', { foo: 'bar' });
123
-    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store, componentEventsObserver);
123
+    const NavigationComponent = ComponentWrapper.wrap(componentName, () => MyComponent, store, componentEventsObserver);
124
     const tree = renderer.create(<NavigationComponent componentId={'component123'} />);
124
     const tree = renderer.create(<NavigationComponent componentId={'component123'} />);
125
     expect(store.getPropsForId('component123')).toEqual({ foo: 'bar' });
125
     expect(store.getPropsForId('component123')).toEqual({ foo: 'bar' });
126
     tree.unmount();
126
     tree.unmount();
127
     expect(store.getPropsForId('component123')).toEqual({});
127
     expect(store.getPropsForId('component123')).toEqual({});
128
   });
128
   });
129
 
129
 
130
-  it(`merges static members from wrapped component`, () => {
131
-    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store, componentEventsObserver) as any;
130
+  it(`merges static members from wrapped component when generated`, () => {
131
+    const NavigationComponent = ComponentWrapper.wrap(componentName, () => MyComponent, store, componentEventsObserver) as any;
132
     expect(NavigationComponent.options).toEqual({ title: 'MyComponentTitle' });
132
     expect(NavigationComponent.options).toEqual({ title: 'MyComponentTitle' });
133
   });
133
   });
134
 
134
 
135
   it(`calls unmounted on componentEventsObserver`, () => {
135
   it(`calls unmounted on componentEventsObserver`, () => {
136
-    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store, componentEventsObserver);
136
+    const NavigationComponent = ComponentWrapper.wrap(componentName, () => MyComponent, store, componentEventsObserver);
137
     const tree = renderer.create(<NavigationComponent componentId={'component123'} />);
137
     const tree = renderer.create(<NavigationComponent componentId={'component123'} />);
138
     verify(mockedComponentEventsObserver.unmounted('component123')).never();
138
     verify(mockedComponentEventsObserver.unmounted('component123')).never();
139
     tree.unmount();
139
     tree.unmount();
162
     const reduxStore = require('redux').createStore((state = initialState) => state);
162
     const reduxStore = require('redux').createStore((state = initialState) => state);
163
 
163
 
164
     it(`wraps the component with a react-redux provider with passed store`, () => {
164
     it(`wraps the component with a react-redux provider with passed store`, () => {
165
-      const NavigationComponent = ComponentWrapper.wrap(componentName, ConnectedComp, store, componentEventsObserver, ReduxProvider, reduxStore);
165
+      const NavigationComponent = ComponentWrapper.wrap(componentName, () => ConnectedComp, store, componentEventsObserver, ReduxProvider, reduxStore);
166
       const tree = renderer.create(<NavigationComponent componentId={'theCompId'} />);
166
       const tree = renderer.create(<NavigationComponent componentId={'theCompId'} />);
167
       expect(tree.toJSON()!.children).toEqual(['it just works']);
167
       expect(tree.toJSON()!.children).toEqual(['it just works']);
168
       expect((NavigationComponent as any).options).toEqual({ foo: 123 });
168
       expect((NavigationComponent as any).options).toEqual({ foo: 123 });

+ 5
- 3
lib/src/components/ComponentWrapper.tsx View File

1
 import * as React from 'react';
1
 import * as React from 'react';
2
+import { ComponentProvider } from 'react-native';
2
 import * as  _ from 'lodash';
3
 import * as  _ from 'lodash';
3
 import * as ReactLifecyclesCompat from 'react-lifecycles-compat';
4
 import * as ReactLifecyclesCompat from 'react-lifecycles-compat';
4
 import { Store } from './Store';
5
 import { Store } from './Store';
10
 export class ComponentWrapper {
11
 export class ComponentWrapper {
11
   static wrap(
12
   static wrap(
12
     componentName: string,
13
     componentName: string,
13
-    OriginalComponentClass: React.ComponentType<any>,
14
+    OriginalComponentGenerator: ComponentProvider,
14
     store: Store,
15
     store: Store,
15
     componentEventsObserver: ComponentEventsObserver,
16
     componentEventsObserver: ComponentEventsObserver,
16
     ReduxProvider?: any,
17
     ReduxProvider?: any,
17
     reduxStore?: any
18
     reduxStore?: any
18
   ): React.ComponentClass<any> {
19
   ): React.ComponentClass<any> {
20
+    const GeneratedComponentClass = OriginalComponentGenerator();
19
     class WrappedComponent extends React.Component<HocProps, HocState> {
21
     class WrappedComponent extends React.Component<HocProps, HocState> {
20
       static getDerivedStateFromProps(nextProps: any, prevState: HocState) {
22
       static getDerivedStateFromProps(nextProps: any, prevState: HocState) {
21
         return {
23
         return {
39
 
41
 
40
       render() {
42
       render() {
41
         return (
43
         return (
42
-          <OriginalComponentClass
44
+          <GeneratedComponentClass
43
             {...this.state.allProps}
45
             {...this.state.allProps}
44
             componentId={this.state.componentId}
46
             componentId={this.state.componentId}
45
             key={this.state.componentId}
47
             key={this.state.componentId}
55
     }
57
     }
56
 
58
 
57
     ReactLifecyclesCompat.polyfill(WrappedComponent);
59
     ReactLifecyclesCompat.polyfill(WrappedComponent);
58
-    require('hoist-non-react-statics')(WrappedComponent, OriginalComponentClass);
60
+    require('hoist-non-react-statics')(WrappedComponent, GeneratedComponentClass);
59
 
61
 
60
     if (reduxStore && ReduxProvider) {
62
     if (reduxStore && ReduxProvider) {
61
       return ComponentWrapper.wrapWithRedux(WrappedComponent, ReduxProvider, reduxStore);
63
       return ComponentWrapper.wrapWithRedux(WrappedComponent, ReduxProvider, reduxStore);

+ 2
- 2
lib/src/components/Store.test.ts View File

26
     const MyComponent = class {
26
     const MyComponent = class {
27
       //
27
       //
28
     };
28
     };
29
-    uut.setOriginalComponentClassForName('example.mycomponent', MyComponent);
30
-    expect(uut.getOriginalComponentClassForName('example.mycomponent')).toEqual(MyComponent);
29
+    uut.setComponentClassForName('example.mycomponent', MyComponent);
30
+    expect(uut.getComponentClassForName('example.mycomponent')).toEqual(MyComponent);
31
   });
31
   });
32
 
32
 
33
   it('clean by component id', () => {
33
   it('clean by component id', () => {

+ 3
- 3
lib/src/components/Store.ts View File

12
     return _.get(this.propsById, componentId, {});
12
     return _.get(this.propsById, componentId, {});
13
   }
13
   }
14
 
14
 
15
-  setOriginalComponentClassForName(componentName: string, ComponentClass) {
15
+  setComponentClassForName(componentName: string, ComponentClass) {
16
     _.set(this.componentsByName, componentName, ComponentClass);
16
     _.set(this.componentsByName, componentName, ComponentClass);
17
   }
17
   }
18
 
18
 
19
-  getOriginalComponentClassForName(componentName: string) {
19
+  getComponentClassForName(componentName: string) {
20
     return _.get(this.componentsByName, componentName);
20
     return _.get(this.componentsByName, componentName);
21
   }
21
   }
22
-
22
+  
23
   cleanId(id: string) {
23
   cleanId(id: string) {
24
     _.unset(this.propsById, id);
24
     _.unset(this.propsById, id);
25
   }
25
   }