Sfoglia il codice sorgente

rename to Component

Daniel Zlotin 7 anni fa
parent
commit
d0f5ec244b

+ 33
- 36
lib/src/Navigation.js Vedi File

@@ -1,17 +1,14 @@
1 1
 const NativeCommandsSender = require('./adapters/NativeCommandsSender');
2 2
 const NativeEventsReceiver = require('./adapters/NativeEventsReceiver');
3 3
 const UniqueIdProvider = require('./adapters/UniqueIdProvider');
4
-const Store = require('./containers/Store');
5
-const ContainerRegistry = require('./containers/ContainerRegistry');
4
+const Store = require('./components/Store');
5
+const ComponentRegistry = require('./components/ComponentRegistry');
6 6
 const Commands = require('./commands/Commands');
7 7
 const LayoutTreeParser = require('./commands/LayoutTreeParser');
8 8
 const LayoutTreeCrawler = require('./commands/LayoutTreeCrawler');
9 9
 const PrivateEventsListener = require('./events/PrivateEventsListener');
10 10
 const PublicEventsRegistry = require('./events/PublicEventsRegistry');
11 11
 const Element = require('./adapters/Element');
12
-// const Root = require('./params/containers/Root');
13
-// const Container = require('./params/containers/Container');
14
-// const Options = require('./params/options/Options');
15 12
 
16 13
 /** @constructor */
17 14
 class Navigation {
@@ -19,7 +16,7 @@ class Navigation {
19 16
     this.store = new Store();
20 17
     this.nativeEventsReceiver = new NativeEventsReceiver();
21 18
     this.uniqueIdProvider = new UniqueIdProvider();
22
-    this.containerRegistry = new ContainerRegistry(this.store);
19
+    this.componentRegistry = new ComponentRegistry(this.store);
23 20
     this.layoutTreeParser = new LayoutTreeParser();
24 21
     this.layoutTreeCrawler = new LayoutTreeCrawler(this.uniqueIdProvider, this.store);
25 22
     this.nativeCommandsSender = new NativeCommandsSender();
@@ -31,16 +28,16 @@ class Navigation {
31 28
   }
32 29
 
33 30
   /**
34
-   * Every screen component in your app must be registered with a unique name. The component itself is a traditional React component extending React.Component.
35
-   * @param {string} containerName Unique container name
36
-   * @param {function} getContainerFunc generator function, typically `() => require('./myContainer')`
31
+   * Every navigation component in your app must be registered with a unique name. The component itself is a traditional React component extending React.Component.
32
+   * @param {string} componentName Unique component name
33
+   * @param {function} getComponentClassFunc generator function, typically `() => require('./myComponent')`
37 34
    */
38
-  registerContainer(containerName, getContainerFunc) {
39
-    this.containerRegistry.registerContainer(containerName, getContainerFunc);
35
+  registerComponent(componentName, getComponentClassFunc) {
36
+    this.componentRegistry.registerComponent(componentName, getComponentClassFunc);
40 37
   }
41 38
 
42 39
   /**
43
-   * Reset the navigation stack to a new screen (the stack root is changed).
40
+   * Reset the navigation stack to a new component (the stack root is changed).
44 41
    * @param {Root} root
45 42
    */
46 43
   setRoot(params) {
@@ -56,12 +53,12 @@ class Navigation {
56 53
   }
57 54
 
58 55
   /**
59
-   * Change a containers navigation options
60
-   * @param {string} containerId The container's id.
56
+   * Change a components navigation options
57
+   * @param {string} componentId The component's id.
61 58
    * @param {options:Options} options
62 59
    */
63
-  setOptions(containerId, options) {
64
-    this.commands.setOptions(containerId, options);
60
+  setOptions(componentId, options) {
61
+    this.commands.setOptions(componentId, options);
65 62
   }
66 63
 
67 64
   /**
@@ -73,11 +70,11 @@ class Navigation {
73 70
   }
74 71
 
75 72
   /**
76
-   * Dismiss a modal by containerId. The dismissed modal can be anywhere in the stack.
77
-   * @param {string} containerId The container's id.
73
+   * Dismiss a modal by componentId. The dismissed modal can be anywhere in the stack.
74
+   * @param {string} componentId The component's id.
78 75
    */
79
-  dismissModal(containerId) {
80
-    return this.commands.dismissModal(containerId);
76
+  dismissModal(componentId) {
77
+    return this.commands.dismissModal(componentId);
81 78
   }
82 79
 
83 80
   /**
@@ -89,36 +86,36 @@ class Navigation {
89 86
 
90 87
   /**
91 88
    * Push a new screen into this screen's navigation stack.
92
-   * @param {string} containerId The container's id.
93
-   * @param {Container} container
89
+   * @param {string} componentId The component's id.
90
+   * @param {Container} component
94 91
    */
95
-  push(containerId, container) {
96
-    return this.commands.push(containerId, container);
92
+  push(componentId, component) {
93
+    return this.commands.push(componentId, component);
97 94
   }
98 95
 
99 96
   /**
100
-   * Pop a container from the stack, regardless of it's position.
101
-   * @param {string} containerId The container's id.
97
+   * Pop a component from the stack, regardless of it's position.
98
+   * @param {string} componentId The component's id.
102 99
    * @param {*} params
103 100
    */
104
-  pop(containerId, params) {
105
-    return this.commands.pop(containerId, params);
101
+  pop(componentId, params) {
102
+    return this.commands.pop(componentId, params);
106 103
   }
107 104
 
108 105
   /**
109
-   * Pop the stack to a given container
110
-   * @param {string} containerId The container's id.
106
+   * Pop the stack to a given component
107
+   * @param {string} componentId The component's id.
111 108
    */
112
-  popTo(containerId) {
113
-    return this.commands.popTo(containerId);
109
+  popTo(componentId) {
110
+    return this.commands.popTo(componentId);
114 111
   }
115 112
 
116 113
   /**
117
-   * Pop the container's stack to root.
118
-   * @param {*} containerId
114
+   * Pop the component's stack to root.
115
+   * @param {*} componentId
119 116
    */
120
-  popToRoot(containerId) {
121
-    return this.commands.popToRoot(containerId);
117
+  popToRoot(componentId) {
118
+    return this.commands.popToRoot(componentId);
122 119
   }
123 120
 
124 121
   /**

+ 17
- 0
lib/src/components/ComponentRegistry.js Vedi File

@@ -0,0 +1,17 @@
1
+const { AppRegistry } = require('react-native');
2
+const ComponentWrapper = require('./ComponentWrapper');
3
+
4
+class ComponentRegistry {
5
+  constructor(store) {
6
+    this.store = store;
7
+  }
8
+
9
+  registerComponent(componentName, getComponentClassFunc) {
10
+    const OriginalComponentClass = getComponentClassFunc();
11
+    const NavigationComponent = ComponentWrapper.wrap(componentName, OriginalComponentClass, this.store);
12
+    this.store.setOriginalComponentClassForName(componentName, OriginalComponentClass);
13
+    AppRegistry.registerComponent(componentName, () => NavigationComponent);
14
+  }
15
+}
16
+
17
+module.exports = ComponentRegistry;

lib/src/containers/ContainerRegistry.test.js → lib/src/components/ComponentRegistry.test.js Vedi File


+ 75
- 0
lib/src/components/ComponentWrapper.js Vedi File

@@ -0,0 +1,75 @@
1
+const _ = require('lodash');
2
+
3
+const React = require('react');
4
+const { Component } = require('react');
5
+
6
+class ComponentWrapper {
7
+  static wrap(componentName, OriginalComponent, store) {
8
+    return class extends Component {
9
+      constructor(props) {
10
+        super(props);
11
+        this._saveComponentRef = this._saveComponentRef.bind(this);
12
+        this._assertComponentId(props);
13
+        this.state = {
14
+          componentId: props.componentId,
15
+          allProps: _.merge({}, props, store.getPropsForComponentId(props.componentId))
16
+        };
17
+      }
18
+
19
+      _assertComponentId(props) {
20
+        if (!props.componentId) {
21
+          throw new Error(`Component ${componentName} does not have a componentId!`);
22
+        }
23
+      }
24
+
25
+      _saveComponentRef(r) {
26
+        this.originalComponentRef = r;
27
+      }
28
+
29
+      componentWillMount() {
30
+        store.setRefForComponentId(this.state.componentId, this);
31
+      }
32
+
33
+      componentWillUnmount() {
34
+        store.cleanId(this.state.componentId);
35
+      }
36
+
37
+      didAppear() {
38
+        if (this.originalComponentRef.didAppear) {
39
+          this.originalComponentRef.didAppear();
40
+        }
41
+      }
42
+
43
+      didDisappear() {
44
+        if (this.originalComponentRef.didDisappear) {
45
+          this.originalComponentRef.didDisappear();
46
+        }
47
+      }
48
+
49
+      onNavigationButtonPressed(buttonId) {
50
+        if (this.originalComponentRef.onNavigationButtonPressed) {
51
+          this.originalComponentRef.onNavigationButtonPressed(buttonId);
52
+        }
53
+      }
54
+
55
+      componentWillReceiveProps(nextProps) {
56
+        this.setState({
57
+          allProps: _.merge({}, nextProps, store.getPropsForComponentId(this.state.componentId))
58
+        });
59
+      }
60
+
61
+      render() {
62
+        return (
63
+          <OriginalComponent
64
+            ref={this._saveComponentRef}
65
+            {...this.state.allProps}
66
+            componentId={this.state.componentId}
67
+            key={this.state.componentId}
68
+          />
69
+        );
70
+      }
71
+    };
72
+  }
73
+}
74
+
75
+module.exports = ComponentWrapper;

+ 191
- 0
lib/src/components/ComponentWrapper.test.js Vedi File

@@ -0,0 +1,191 @@
1
+const React = require('react');
2
+const { Component } = require('react');
3
+
4
+const { Text } = require('react-native');
5
+const renderer = require('react-test-renderer');
6
+const ComponentWrapper = require('./ComponentWrapper');
7
+const Store = require('./Store');
8
+
9
+describe('ComponentWrapper', () => {
10
+  let store;
11
+  const componentName = 'example.MyComponent';
12
+  let childRef;
13
+
14
+  class MyComponent extends Component {
15
+    render() {
16
+      return <Text>{'Hello, World!'}</Text>;
17
+    }
18
+  }
19
+
20
+  class TestParent extends Component {
21
+    constructor(props) {
22
+      super(props);
23
+      this.ChildClass = props.ChildClass;
24
+      this.state = { propsFromState: {} };
25
+    }
26
+
27
+    render() {
28
+      const Child = this.ChildClass;
29
+      return (
30
+        <Child
31
+          ref={(r) => childRef = r}
32
+          componentId="component1"
33
+          {...this.state.propsFromState}
34
+        />
35
+      );
36
+    }
37
+  }
38
+
39
+  beforeEach(() => {
40
+    store = new Store();
41
+  });
42
+
43
+  it('must have componentId as prop', () => {
44
+    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store);
45
+    const orig = console.error;
46
+    console.error = (a) => a;
47
+    expect(() => {
48
+      renderer.create(<NavigationComponent />);
49
+    }).toThrow(new Error('Component example.MyComponent does not have a componentId!'));
50
+    console.error = orig;
51
+  });
52
+
53
+  it('wraps the component', () => {
54
+    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store);
55
+    expect(NavigationComponent).not.toBeInstanceOf(MyComponent);
56
+    const tree = renderer.create(<NavigationComponent componentId={'component1'} />);
57
+    expect(tree.toJSON().children).toEqual(['Hello, World!']);
58
+    expect(tree.getInstance().originalComponentRef).toBeInstanceOf(MyComponent);
59
+  });
60
+
61
+  it('injects props from wrapper into original component', () => {
62
+    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store);
63
+    const tree = renderer.create(<NavigationComponent componentId={'component1'} myProp={'yo'} />);
64
+    expect(tree.getInstance().originalComponentRef.props.myProp).toEqual('yo');
65
+  });
66
+
67
+  it('updates props from wrapper into original component', () => {
68
+    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store);
69
+    const tree = renderer.create(<TestParent ChildClass={NavigationComponent} />);
70
+    expect(childRef.props.foo).toEqual(undefined);
71
+    tree.getInstance().setState({ propsFromState: { foo: 'yo' } });
72
+    expect(childRef.props.foo).toEqual('yo');
73
+  });
74
+
75
+  it('pulls props from the store and injects them into the inner component', () => {
76
+    store.setPropsForComponentId('component123', { numberProp: 1, stringProp: 'hello', objectProp: { a: 2 } });
77
+    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store);
78
+    const tree = renderer.create(<NavigationComponent componentId={'component123'} />);
79
+    const originalComponentProps = tree.getInstance().originalComponentRef.props;
80
+    expect(originalComponentProps).toEqual({ componentId: 'component123', numberProp: 1, stringProp: 'hello', objectProp: { a: 2 } });
81
+  });
82
+
83
+  it('updates props from store into inner component', () => {
84
+    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store);
85
+    const tree = renderer.create(<TestParent ChildClass={NavigationComponent} />);
86
+    store.setPropsForComponentId('component1', { myProp: 'hello' });
87
+    expect(childRef.originalComponentRef.props.foo).toEqual(undefined);
88
+    expect(childRef.originalComponentRef.props.myProp).toEqual(undefined);
89
+    tree.getInstance().setState({ propsFromState: { foo: 'yo' } });
90
+    expect(childRef.originalComponentRef.props.foo).toEqual('yo');
91
+    expect(childRef.originalComponentRef.props.myProp).toEqual('hello');
92
+  });
93
+
94
+  it('protects id from change', () => {
95
+    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store);
96
+    const tree = renderer.create(<TestParent ChildClass={NavigationComponent} />);
97
+    expect(childRef.originalComponentRef.props.componentId).toEqual('component1');
98
+    tree.getInstance().setState({ propsFromState: { id: 'ERROR' } });
99
+    expect(childRef.originalComponentRef.props.componentId).toEqual('component1');
100
+  });
101
+
102
+  it('assignes key by id', () => {
103
+    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store);
104
+    const tree = renderer.create(<NavigationComponent componentId={'component1'} />);
105
+    expect(tree.getInstance().originalComponentRef.props.componentId).toEqual('component1');
106
+    expect(tree.getInstance().originalComponentRef._reactInternalInstance.key).toEqual('component1');
107
+  });
108
+
109
+  it('saves self ref into store', () => {
110
+    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store);
111
+    const tree = renderer.create(<NavigationComponent componentId={'component1'} />);
112
+    expect(store.getRefForComponentId('component1')).toBeDefined();
113
+    expect(store.getRefForComponentId('component1')).toBe(tree.getInstance());
114
+  });
115
+
116
+  it('cleans ref from store on unMount', () => {
117
+    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store);
118
+    const tree = renderer.create(<NavigationComponent componentId={'component1'} />);
119
+    expect(store.getRefForComponentId('component1')).toBeDefined();
120
+    tree.unmount();
121
+    expect(store.getRefForComponentId('component1')).toBeUndefined();
122
+  });
123
+
124
+  it('holds ref to OriginalComponent', () => {
125
+    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store);
126
+    const tree = renderer.create(<NavigationComponent componentId={'component1'} />);
127
+    expect(tree.getInstance().originalComponentRef).toBeDefined();
128
+    expect(tree.getInstance().originalComponentRef).toBeInstanceOf(MyComponent);
129
+  });
130
+
131
+  it('cleans ref to internal component on unount', () => {
132
+    const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store);
133
+    const tree = renderer.create(<NavigationComponent componentId={'component1'} />);
134
+    const instance = tree.getInstance();
135
+    expect(instance.originalComponentRef).toBeInstanceOf(Component);
136
+    tree.unmount();
137
+    expect(instance.originalComponentRef).toBeFalsy();
138
+  });
139
+
140
+  describe('component lifecycle', () => {
141
+    const didAppearCallback = jest.fn();
142
+    const didDisappearCallback = jest.fn();
143
+    const onNavigationButtonPressedCallback = jest.fn();
144
+
145
+    class MyLifecycleComponent extends MyComponent {
146
+      didAppear() {
147
+        didAppearCallback();
148
+      }
149
+
150
+      didDisappear() {
151
+        didDisappearCallback();
152
+      }
153
+
154
+      onNavigationButtonPressed() {
155
+        onNavigationButtonPressedCallback();
156
+      }
157
+    }
158
+
159
+    it('didAppear, didDisappear and onNavigationButtonPressed are optional', () => {
160
+      const NavigationComponent = ComponentWrapper.wrap(componentName, MyComponent, store);
161
+      const tree = renderer.create(<NavigationComponent componentId={'component1'} />);
162
+      expect(() => tree.getInstance().didAppear()).not.toThrow();
163
+      expect(() => tree.getInstance().didDisappear()).not.toThrow();
164
+      expect(() => tree.getInstance().onNavigationButtonPressed()).not.toThrow();
165
+    });
166
+
167
+    it('calls didAppear on OriginalComponent', () => {
168
+      const NavigationComponent = ComponentWrapper.wrap(componentName, MyLifecycleComponent, store);
169
+      const tree = renderer.create(<NavigationComponent componentId={'component1'} />);
170
+      expect(didAppearCallback).toHaveBeenCalledTimes(0);
171
+      tree.getInstance().didAppear();
172
+      expect(didAppearCallback).toHaveBeenCalledTimes(1);
173
+    });
174
+
175
+    it('calls didDisappear on OriginalComponent', () => {
176
+      const NavigationComponent = ComponentWrapper.wrap(componentName, MyLifecycleComponent, store);
177
+      const tree = renderer.create(<NavigationComponent componentId={'component1'} />);
178
+      expect(didDisappearCallback).toHaveBeenCalledTimes(0);
179
+      tree.getInstance().didDisappear();
180
+      expect(didDisappearCallback).toHaveBeenCalledTimes(1);
181
+    });
182
+
183
+    it('calls onNavigationButtonPressed on OriginalComponent', () => {
184
+      const NavigationComponent = ComponentWrapper.wrap(componentName, MyLifecycleComponent, store);
185
+      const tree = renderer.create(<NavigationComponent componentId={'component1'} />);
186
+      expect(onNavigationButtonPressedCallback).toHaveBeenCalledTimes(0);
187
+      tree.getInstance().onNavigationButtonPressed();
188
+      expect(onNavigationButtonPressedCallback).toHaveBeenCalledTimes(1);
189
+    });
190
+  });
191
+});

lib/src/containers/Lifecycle.js → lib/src/components/Lifecycle.js Vedi File

@@ -1,27 +1,27 @@
1 1
 class Lifecycle {
2 2
   constructor(store) {
3 3
     this.store = store;
4
-    this.containerDidAppear = this.containerDidAppear.bind(this);
5
-    this.containerDidDisappear = this.containerDidDisappear.bind(this);
4
+    this.componentDidAppear = this.componentDidAppear.bind(this);
5
+    this.componentDidDisappear = this.componentDidDisappear.bind(this);
6 6
     this.onNavigationButtonPressed = this.onNavigationButtonPressed.bind(this);
7 7
   }
8 8
 
9
-  containerDidAppear(id) {
10
-    const ref = this.store.getRefForContainerId(id);
9
+  componentDidAppear(id) {
10
+    const ref = this.store.getRefForComponentId(id);
11 11
     if (ref && ref.didAppear) {
12 12
       ref.didAppear();
13 13
     }
14 14
   }
15 15
 
16
-  containerDidDisappear(id) {
17
-    const ref = this.store.getRefForContainerId(id);
16
+  componentDidDisappear(id) {
17
+    const ref = this.store.getRefForComponentId(id);
18 18
     if (ref && ref.didDisappear) {
19 19
       ref.didDisappear();
20 20
     }
21 21
   }
22 22
 
23 23
   onNavigationButtonPressed(params) {
24
-    const ref = this.store.getRefForContainerId(params.containerId);
24
+    const ref = this.store.getRefForComponentId(params.componentId);
25 25
     if (ref && ref.onNavigationButtonPressed) {
26 26
       ref.onNavigationButtonPressed(params.buttonId);
27 27
     }

lib/src/containers/Lifecycle.test.js → lib/src/components/Lifecycle.test.js Vedi File

@@ -1,5 +1,5 @@
1 1
 const Lifecycle = require('./Lifecycle');
2
-const Store = require('../containers/Store');
2
+const Store = require('../components/Store');
3 3
 
4 4
 describe('Lifecycle', () => {
5 5
   let store;
@@ -13,53 +13,53 @@ describe('Lifecycle', () => {
13 13
       onNavigationButtonPressed: jest.fn()
14 14
     };
15 15
     store = new Store();
16
-    store.setRefForContainerId('myUniqueId', mockRef);
16
+    store.setRefForComponentId('myUniqueId', mockRef);
17 17
 
18 18
     uut = new Lifecycle(store);
19 19
   });
20 20
 
21
-  describe('containerDidAppear', () => {
22
-    it('calls didAppear on container ref from store', () => {
23
-      uut.containerDidAppear('myUniqueId');
21
+  describe('componentDidAppear', () => {
22
+    it('calls didAppear on component ref from store', () => {
23
+      uut.componentDidAppear('myUniqueId');
24 24
       expect(mockRef.didAppear).toHaveBeenCalledTimes(1);
25 25
     });
26 26
 
27 27
     it('skips undefined refs', () => {
28
-      uut.containerDidAppear('myUniqueId2');
28
+      uut.componentDidAppear('myUniqueId2');
29 29
       expect(mockRef.didAppear).not.toHaveBeenCalled();
30 30
     });
31 31
 
32 32
     it('skips unimplemented didAppear', () => {
33 33
       mockRef = {};
34 34
       expect(mockRef.didAppear).toBeUndefined();
35
-      store.setRefForContainerId('myUniqueId', mockRef);
36
-      uut.containerDidAppear('myUniqueId');
35
+      store.setRefForComponentId('myUniqueId', mockRef);
36
+      uut.componentDidAppear('myUniqueId');
37 37
     });
38 38
   });
39 39
 
40
-  describe('containerDidDisappear', () => {
41
-    it('calls didDisappear on container ref from store', () => {
42
-      uut.containerDidDisappear('myUniqueId');
40
+  describe('componentDidDisappear', () => {
41
+    it('calls didDisappear on component ref from store', () => {
42
+      uut.componentDidDisappear('myUniqueId');
43 43
       expect(mockRef.didDisappear).toHaveBeenCalledTimes(1);
44 44
     });
45 45
 
46 46
     it('skips undefined refs', () => {
47
-      uut.containerDidDisappear('myUniqueId2');
47
+      uut.componentDidDisappear('myUniqueId2');
48 48
       expect(mockRef.didDisappear).not.toHaveBeenCalled();
49 49
     });
50 50
 
51 51
     it('skips unimplemented didDisappear', () => {
52 52
       mockRef = {};
53 53
       expect(mockRef.didDisappear).toBeUndefined();
54
-      store.setRefForContainerId('myUniqueId', mockRef);
55
-      uut.containerDidDisappear('myUniqueId');
54
+      store.setRefForComponentId('myUniqueId', mockRef);
55
+      uut.componentDidDisappear('myUniqueId');
56 56
     });
57 57
   });
58 58
 
59 59
   describe('onNavigationButtonPressed', () => {
60
-    it('calls onNavigationButtonPressed on container ref from store', () => {
60
+    it('calls onNavigationButtonPressed on component ref from store', () => {
61 61
       uut.onNavigationButtonPressed({
62
-        containerId: 'myUniqueId',
62
+        componentId: 'myUniqueId',
63 63
         buttonId: 'myButtonId'
64 64
       });
65 65
       expect(mockRef.onNavigationButtonPressed).toHaveBeenCalledTimes(1);
@@ -73,8 +73,8 @@ describe('Lifecycle', () => {
73 73
     it('skips unimplemented onNavigationButtonPressed', () => {
74 74
       mockRef = {};
75 75
       expect(mockRef.onNavigationButtonPressed).toBeUndefined();
76
-      store.setRefForContainerId('myUniqueId', mockRef);
77
-      uut.containerDidAppear('myUniqueId');
76
+      store.setRefForComponentId('myUniqueId', mockRef);
77
+      uut.componentDidAppear('myUniqueId');
78 78
     });
79 79
   });
80 80
 });

+ 40
- 0
lib/src/components/Store.js Vedi File

@@ -0,0 +1,40 @@
1
+const _ = require('lodash');
2
+
3
+class Store {
4
+  constructor() {
5
+    this.propsByComponentId = {};
6
+    this.componentsByName = {};
7
+    this.refsById = {};
8
+  }
9
+
10
+  setPropsForComponentId(componentId, props) {
11
+    _.set(this.propsByComponentId, componentId, props);
12
+  }
13
+
14
+  getPropsForComponentId(componentId) {
15
+    return _.get(this.propsByComponentId, componentId, {});
16
+  }
17
+
18
+  setOriginalComponentClassForName(componentName, ComponentClass) {
19
+    _.set(this.componentsByName, componentName, ComponentClass);
20
+  }
21
+
22
+  getOriginalComponentClassForName(componentName) {
23
+    return _.get(this.componentsByName, componentName);
24
+  }
25
+
26
+  setRefForComponentId(id, ref) {
27
+    _.set(this.refsById, id, ref);
28
+  }
29
+
30
+  getRefForComponentId(id) {
31
+    return _.get(this.refsById, id);
32
+  }
33
+
34
+  cleanId(id) {
35
+    _.unset(this.refsById, id);
36
+    _.unset(this.propsByComponentId, id);
37
+  }
38
+}
39
+
40
+module.exports = Store;

+ 50
- 0
lib/src/components/Store.test.js Vedi File

@@ -0,0 +1,50 @@
1
+const Store = require('./Store');
2
+
3
+describe('Store', () => {
4
+  let uut;
5
+
6
+  beforeEach(() => {
7
+    uut = new Store();
8
+  });
9
+
10
+  it('initial state', () => {
11
+    expect(uut.getPropsForComponentId('component1')).toEqual({});
12
+  });
13
+
14
+  it('holds props by componentId', () => {
15
+    uut.setPropsForComponentId('component1', { a: 1, b: 2 });
16
+    expect(uut.getPropsForComponentId('component1')).toEqual({ a: 1, b: 2 });
17
+  });
18
+
19
+  it('defensive for invalid componentId and props', () => {
20
+    uut.setPropsForComponentId('component1', undefined);
21
+    uut.setPropsForComponentId(undefined, undefined);
22
+    expect(uut.getPropsForComponentId('component1')).toEqual({});
23
+  });
24
+
25
+  it('holds original components classes by componentName', () => {
26
+    const MyComponent = class {
27
+      //
28
+    };
29
+    uut.setOriginalComponentClassForName('example.mycomponent', MyComponent);
30
+    expect(uut.getOriginalComponentClassForName('example.mycomponent')).toEqual(MyComponent);
31
+  });
32
+
33
+  it('holds component refs by id', () => {
34
+    const ref = {};
35
+    uut.setRefForComponentId('refUniqueId', ref);
36
+    expect(uut.getRefForComponentId('other')).toBeUndefined();
37
+    expect(uut.getRefForComponentId('refUniqueId')).toBe(ref);
38
+  });
39
+
40
+  it('clean by id', () => {
41
+    uut.setRefForComponentId('refUniqueId', {});
42
+    uut.setPropsForComponentId('refUniqueId', { foo: 'bar' });
43
+
44
+    uut.cleanId('refUniqueId');
45
+
46
+    expect(uut.getRefForComponentId('refUniqueId')).toBeUndefined();
47
+    expect(uut.getPropsForComponentId('refUniqueId')).toEqual({});
48
+  });
49
+});
50
+

+ 0
- 17
lib/src/containers/ContainerRegistry.js Vedi File

@@ -1,17 +0,0 @@
1
-const { AppRegistry } = require('react-native');
2
-const ContainerWrapper = require('./ContainerWrapper');
3
-
4
-class ContainerRegistry {
5
-  constructor(store) {
6
-    this.store = store;
7
-  }
8
-
9
-  registerContainer(containerName, getContainerFunc) {
10
-    const OriginalContainer = getContainerFunc();
11
-    const NavigationContainer = ContainerWrapper.wrap(containerName, OriginalContainer, this.store);
12
-    this.store.setOriginalContainerClassForName(containerName, OriginalContainer);
13
-    AppRegistry.registerComponent(containerName, () => NavigationContainer);
14
-  }
15
-}
16
-
17
-module.exports = ContainerRegistry;

+ 0
- 75
lib/src/containers/ContainerWrapper.js Vedi File

@@ -1,75 +0,0 @@
1
-const _ = require('lodash');
2
-
3
-const React = require('react');
4
-const { Component } = require('react');
5
-
6
-class ContainerWrapper {
7
-  static wrap(containerName, OriginalContainer, store) {
8
-    return class extends Component {
9
-      constructor(props) {
10
-        super(props);
11
-        this._saveContainerRef = this._saveContainerRef.bind(this);
12
-        this._assertContainerId(props);
13
-        this.state = {
14
-          containerId: props.containerId,
15
-          allProps: _.merge({}, props, store.getPropsForContainerId(props.containerId))
16
-        };
17
-      }
18
-
19
-      _assertContainerId(props) {
20
-        if (!props.containerId) {
21
-          throw new Error(`Container ${containerName} does not have a containerId!`);
22
-        }
23
-      }
24
-
25
-      _saveContainerRef(r) {
26
-        this.originalContainerRef = r;
27
-      }
28
-
29
-      componentWillMount() {
30
-        store.setRefForContainerId(this.state.containerId, this);
31
-      }
32
-
33
-      componentWillUnmount() {
34
-        store.cleanId(this.state.containerId);
35
-      }
36
-
37
-      didAppear() {
38
-        if (this.originalContainerRef.didAppear) {
39
-          this.originalContainerRef.didAppear();
40
-        }
41
-      }
42
-
43
-      didDisappear() {
44
-        if (this.originalContainerRef.didDisappear) {
45
-          this.originalContainerRef.didDisappear();
46
-        }
47
-      }
48
-
49
-      onNavigationButtonPressed(buttonId) {
50
-        if (this.originalContainerRef.onNavigationButtonPressed) {
51
-          this.originalContainerRef.onNavigationButtonPressed(buttonId);
52
-        }
53
-      }
54
-
55
-      componentWillReceiveProps(nextProps) {
56
-        this.setState({
57
-          allProps: _.merge({}, nextProps, store.getPropsForContainerId(this.state.containerId))
58
-        });
59
-      }
60
-
61
-      render() {
62
-        return (
63
-          <OriginalContainer
64
-            ref={this._saveContainerRef}
65
-            {...this.state.allProps}
66
-            containerId={this.state.containerId}
67
-            key={this.state.containerId}
68
-          />
69
-        );
70
-      }
71
-    };
72
-  }
73
-}
74
-
75
-module.exports = ContainerWrapper;

+ 0
- 191
lib/src/containers/ContainerWrapper.test.js Vedi File

@@ -1,191 +0,0 @@
1
-const React = require('react');
2
-const { Component } = require('react');
3
-
4
-const { Text } = require('react-native');
5
-const renderer = require('react-test-renderer');
6
-const ContainerWrapper = require('./ContainerWrapper');
7
-const Store = require('./Store');
8
-
9
-describe('ContainerWrapper', () => {
10
-  let store;
11
-  const containerName = 'example.MyContainer';
12
-  let childRef;
13
-
14
-  class MyContainer extends Component {
15
-    render() {
16
-      return <Text>{'Hello, World!'}</Text>;
17
-    }
18
-  }
19
-
20
-  class TestParent extends Component {
21
-    constructor(props) {
22
-      super(props);
23
-      this.ChildClass = props.ChildClass;
24
-      this.state = { propsFromState: {} };
25
-    }
26
-
27
-    render() {
28
-      const Child = this.ChildClass;
29
-      return (
30
-        <Child
31
-          ref={(r) => childRef = r}
32
-          containerId="container1"
33
-          {...this.state.propsFromState}
34
-        />
35
-      );
36
-    }
37
-  }
38
-
39
-  beforeEach(() => {
40
-    store = new Store();
41
-  });
42
-
43
-  it('must have containerId as prop', () => {
44
-    const NavigationContainer = ContainerWrapper.wrap(containerName, MyContainer, store);
45
-    const orig = console.error;
46
-    console.error = (a) => a;
47
-    expect(() => {
48
-      renderer.create(<NavigationContainer />);
49
-    }).toThrow(new Error('Container example.MyContainer does not have a containerId!'));
50
-    console.error = orig;
51
-  });
52
-
53
-  it('wraps the container', () => {
54
-    const NavigationContainer = ContainerWrapper.wrap(containerName, MyContainer, store);
55
-    expect(NavigationContainer).not.toBeInstanceOf(MyContainer);
56
-    const tree = renderer.create(<NavigationContainer containerId={'container1'} />);
57
-    expect(tree.toJSON().children).toEqual(['Hello, World!']);
58
-    expect(tree.getInstance().originalContainerRef).toBeInstanceOf(MyContainer);
59
-  });
60
-
61
-  it('injects props from wrapper into original container', () => {
62
-    const NavigationContainer = ContainerWrapper.wrap(containerName, MyContainer, store);
63
-    const tree = renderer.create(<NavigationContainer containerId={'container1'} myProp={'yo'} />);
64
-    expect(tree.getInstance().originalContainerRef.props.myProp).toEqual('yo');
65
-  });
66
-
67
-  it('updates props from wrapper into original container', () => {
68
-    const NavigationContainer = ContainerWrapper.wrap(containerName, MyContainer, store);
69
-    const tree = renderer.create(<TestParent ChildClass={NavigationContainer} />);
70
-    expect(childRef.props.foo).toEqual(undefined);
71
-    tree.getInstance().setState({ propsFromState: { foo: 'yo' } });
72
-    expect(childRef.props.foo).toEqual('yo');
73
-  });
74
-
75
-  it('pulls props from the store and injects them into the inner container', () => {
76
-    store.setPropsForContainerId('container123', { numberProp: 1, stringProp: 'hello', objectProp: { a: 2 } });
77
-    const NavigationContainer = ContainerWrapper.wrap(containerName, MyContainer, store);
78
-    const tree = renderer.create(<NavigationContainer containerId={'container123'} />);
79
-    const originalContainerProps = tree.getInstance().originalContainerRef.props;
80
-    expect(originalContainerProps).toEqual({ containerId: 'container123', numberProp: 1, stringProp: 'hello', objectProp: { a: 2 } });
81
-  });
82
-
83
-  it('updates props from store into inner container', () => {
84
-    const NavigationContainer = ContainerWrapper.wrap(containerName, MyContainer, store);
85
-    const tree = renderer.create(<TestParent ChildClass={NavigationContainer} />);
86
-    store.setPropsForContainerId('container1', { myProp: 'hello' });
87
-    expect(childRef.originalContainerRef.props.foo).toEqual(undefined);
88
-    expect(childRef.originalContainerRef.props.myProp).toEqual(undefined);
89
-    tree.getInstance().setState({ propsFromState: { foo: 'yo' } });
90
-    expect(childRef.originalContainerRef.props.foo).toEqual('yo');
91
-    expect(childRef.originalContainerRef.props.myProp).toEqual('hello');
92
-  });
93
-
94
-  it('protects id from change', () => {
95
-    const NavigationContainer = ContainerWrapper.wrap(containerName, MyContainer, store);
96
-    const tree = renderer.create(<TestParent ChildClass={NavigationContainer} />);
97
-    expect(childRef.originalContainerRef.props.containerId).toEqual('container1');
98
-    tree.getInstance().setState({ propsFromState: { id: 'ERROR' } });
99
-    expect(childRef.originalContainerRef.props.containerId).toEqual('container1');
100
-  });
101
-
102
-  it('assignes key by id', () => {
103
-    const NavigationContainer = ContainerWrapper.wrap(containerName, MyContainer, store);
104
-    const tree = renderer.create(<NavigationContainer containerId={'container1'} />);
105
-    expect(tree.getInstance().originalContainerRef.props.containerId).toEqual('container1');
106
-    expect(tree.getInstance().originalContainerRef._reactInternalInstance.key).toEqual('container1');
107
-  });
108
-
109
-  it('saves self ref into store', () => {
110
-    const NavigationContainer = ContainerWrapper.wrap(containerName, MyContainer, store);
111
-    const tree = renderer.create(<NavigationContainer containerId={'container1'} />);
112
-    expect(store.getRefForContainerId('container1')).toBeDefined();
113
-    expect(store.getRefForContainerId('container1')).toBe(tree.getInstance());
114
-  });
115
-
116
-  it('cleans ref from store on unMount', () => {
117
-    const NavigationContainer = ContainerWrapper.wrap(containerName, MyContainer, store);
118
-    const tree = renderer.create(<NavigationContainer containerId={'container1'} />);
119
-    expect(store.getRefForContainerId('container1')).toBeDefined();
120
-    tree.unmount();
121
-    expect(store.getRefForContainerId('container1')).toBeUndefined();
122
-  });
123
-
124
-  it('holds ref to OriginalContainer', () => {
125
-    const NavigationContainer = ContainerWrapper.wrap(containerName, MyContainer, store);
126
-    const tree = renderer.create(<NavigationContainer containerId={'container1'} />);
127
-    expect(tree.getInstance().originalContainerRef).toBeDefined();
128
-    expect(tree.getInstance().originalContainerRef).toBeInstanceOf(MyContainer);
129
-  });
130
-
131
-  it('cleans ref to internal container on unount', () => {
132
-    const NavigationContainer = ContainerWrapper.wrap(containerName, MyContainer, store);
133
-    const tree = renderer.create(<NavigationContainer containerId={'container1'} />);
134
-    const instance = tree.getInstance();
135
-    expect(instance.originalContainerRef).toBeInstanceOf(Component);
136
-    tree.unmount();
137
-    expect(instance.originalContainerRef).toBeFalsy();
138
-  });
139
-
140
-  describe('container lifecycle', () => {
141
-    const didAppearCallback = jest.fn();
142
-    const didDisappearCallback = jest.fn();
143
-    const onNavigationButtonPressedCallback = jest.fn();
144
-
145
-    class MyLifecycleContainer extends MyContainer {
146
-      didAppear() {
147
-        didAppearCallback();
148
-      }
149
-
150
-      didDisappear() {
151
-        didDisappearCallback();
152
-      }
153
-
154
-      onNavigationButtonPressed() {
155
-        onNavigationButtonPressedCallback();
156
-      }
157
-    }
158
-
159
-    it('didAppear, didDisappear and onNavigationButtonPressed are optional', () => {
160
-      const NavigationContainer = ContainerWrapper.wrap(containerName, MyContainer, store);
161
-      const tree = renderer.create(<NavigationContainer containerId={'container1'} />);
162
-      expect(() => tree.getInstance().didAppear()).not.toThrow();
163
-      expect(() => tree.getInstance().didDisappear()).not.toThrow();
164
-      expect(() => tree.getInstance().onNavigationButtonPressed()).not.toThrow();
165
-    });
166
-
167
-    it('calls didAppear on OriginalContainer', () => {
168
-      const NavigationContainer = ContainerWrapper.wrap(containerName, MyLifecycleContainer, store);
169
-      const tree = renderer.create(<NavigationContainer containerId={'container1'} />);
170
-      expect(didAppearCallback).toHaveBeenCalledTimes(0);
171
-      tree.getInstance().didAppear();
172
-      expect(didAppearCallback).toHaveBeenCalledTimes(1);
173
-    });
174
-
175
-    it('calls didDisappear on OriginalContainer', () => {
176
-      const NavigationContainer = ContainerWrapper.wrap(containerName, MyLifecycleContainer, store);
177
-      const tree = renderer.create(<NavigationContainer containerId={'container1'} />);
178
-      expect(didDisappearCallback).toHaveBeenCalledTimes(0);
179
-      tree.getInstance().didDisappear();
180
-      expect(didDisappearCallback).toHaveBeenCalledTimes(1);
181
-    });
182
-
183
-    it('calls onNavigationButtonPressed on OriginalContainer', () => {
184
-      const NavigationContainer = ContainerWrapper.wrap(containerName, MyLifecycleContainer, store);
185
-      const tree = renderer.create(<NavigationContainer containerId={'container1'} />);
186
-      expect(onNavigationButtonPressedCallback).toHaveBeenCalledTimes(0);
187
-      tree.getInstance().onNavigationButtonPressed();
188
-      expect(onNavigationButtonPressedCallback).toHaveBeenCalledTimes(1);
189
-    });
190
-  });
191
-});

+ 0
- 40
lib/src/containers/Store.js Vedi File

@@ -1,40 +0,0 @@
1
-const _ = require('lodash');
2
-
3
-class Store {
4
-  constructor() {
5
-    this.propsByContainerId = {};
6
-    this.containersByName = {};
7
-    this.refsById = {};
8
-  }
9
-
10
-  setPropsForContainerId(containerId, props) {
11
-    _.set(this.propsByContainerId, containerId, props);
12
-  }
13
-
14
-  getPropsForContainerId(containerId) {
15
-    return _.get(this.propsByContainerId, containerId, {});
16
-  }
17
-
18
-  setOriginalContainerClassForName(containerName, ContainerClass) {
19
-    _.set(this.containersByName, containerName, ContainerClass);
20
-  }
21
-
22
-  getOriginalContainerClassForName(containerName) {
23
-    return _.get(this.containersByName, containerName);
24
-  }
25
-
26
-  setRefForContainerId(id, ref) {
27
-    _.set(this.refsById, id, ref);
28
-  }
29
-
30
-  getRefForContainerId(id) {
31
-    return _.get(this.refsById, id);
32
-  }
33
-
34
-  cleanId(id) {
35
-    _.unset(this.refsById, id);
36
-    _.unset(this.propsByContainerId, id);
37
-  }
38
-}
39
-
40
-module.exports = Store;

+ 0
- 50
lib/src/containers/Store.test.js Vedi File

@@ -1,50 +0,0 @@
1
-const Store = require('./Store');
2
-
3
-describe('Store', () => {
4
-  let uut;
5
-
6
-  beforeEach(() => {
7
-    uut = new Store();
8
-  });
9
-
10
-  it('initial state', () => {
11
-    expect(uut.getPropsForContainerId('container1')).toEqual({});
12
-  });
13
-
14
-  it('holds props by containerId', () => {
15
-    uut.setPropsForContainerId('container1', { a: 1, b: 2 });
16
-    expect(uut.getPropsForContainerId('container1')).toEqual({ a: 1, b: 2 });
17
-  });
18
-
19
-  it('defensive for invalid containerId and props', () => {
20
-    uut.setPropsForContainerId('container1', undefined);
21
-    uut.setPropsForContainerId(undefined, undefined);
22
-    expect(uut.getPropsForContainerId('container1')).toEqual({});
23
-  });
24
-
25
-  it('holds original containers classes by containerName', () => {
26
-    const MyComponent = class {
27
-      //
28
-    };
29
-    uut.setOriginalContainerClassForName('example.mycontainer', MyComponent);
30
-    expect(uut.getOriginalContainerClassForName('example.mycontainer')).toEqual(MyComponent);
31
-  });
32
-
33
-  it('holds container refs by id', () => {
34
-    const ref = {};
35
-    uut.setRefForContainerId('refUniqueId', ref);
36
-    expect(uut.getRefForContainerId('other')).toBeUndefined();
37
-    expect(uut.getRefForContainerId('refUniqueId')).toBe(ref);
38
-  });
39
-
40
-  it('clean by id', () => {
41
-    uut.setRefForContainerId('refUniqueId', {});
42
-    uut.setPropsForContainerId('refUniqueId', { foo: 'bar' });
43
-
44
-    uut.cleanId('refUniqueId');
45
-
46
-    expect(uut.getRefForContainerId('refUniqueId')).toBeUndefined();
47
-    expect(uut.getPropsForContainerId('refUniqueId')).toEqual({});
48
-  });
49
-});
50
-