Browse Source

refactoring, push e2e

Daniel Zlotin 8 years ago
parent
commit
b2ce627b33

+ 3
- 2
ios/RNN.h View File

@@ -6,10 +6,11 @@
6 6
 
7 7
 @interface RNN : NSObject
8 8
 
9
+@property (readonly) BOOL isReadyToReceiveCommands;
10
+@property (readonly) RCTBridge* bridge;
11
+
9 12
 +(instancetype)instance;
10 13
 
11 14
 -(void)bootstrap:(NSURL*)jsCodeLocation launchOptions:(NSDictionary*)launchOptions;
12 15
 
13
--(RCTBridge*)bridge;
14
-
15 16
 @end

+ 4
- 9
ios/RNN.m View File

@@ -7,12 +7,11 @@
7 7
 
8 8
 @interface RNN()
9 9
 @property RNNEventEmitter* eventEmitter;
10
+@property (readwrite) RCTBridge* bridge;
11
+@property (readwrite) BOOL isReadyToReceiveCommands;
10 12
 @end
11 13
 
12 14
 @implementation RNN
13
-{
14
-	RCTBridge* bridge;
15
-}
16 15
 
17 16
 +(instancetype)instance
18 17
 {
@@ -39,7 +38,7 @@
39 38
 	
40 39
 	[self registerForJsEvents];
41 40
 	// this will load the JS bundle
42
-	bridge = [[RCTBridge alloc] initWithBundleURL:jsCodeLocation moduleProvider:nil launchOptions:launchOptions];
41
+	self.bridge = [[RCTBridge alloc] initWithBundleURL:jsCodeLocation moduleProvider:nil launchOptions:launchOptions];
43 42
 }
44 43
 
45 44
 -(void)registerForJsEvents
@@ -54,6 +53,7 @@
54 53
 
55 54
 -(void)onJavaScriptLoaded
56 55
 {
56
+	self.isReadyToReceiveCommands = true;
57 57
 	[self.eventEmitter sendOnAppLaunched];
58 58
 }
59 59
 
@@ -62,9 +62,4 @@
62 62
 	UIApplication.sharedApplication.delegate.window.rootViewController = nil;
63 63
 }
64 64
 
65
--(RCTBridge *)bridge
66
-{
67
-	return bridge;
68
-}
69
-
70 65
 @end

+ 8
- 0
ios/RNNBridgeModule.m View File

@@ -14,9 +14,17 @@ RCT_EXPORT_MODULE();
14 14
 
15 15
 RCT_EXPORT_METHOD(setRoot:(NSDictionary*)layout)
16 16
 {
17
+	[self assertReady];
17 18
 	UIApplication.sharedApplication.delegate.window.rootViewController = [[RNNControllerFactory new] createRootViewController:layout];
18 19
 	[UIApplication.sharedApplication.delegate.window makeKeyAndVisible];
19 20
 }
20 21
 
22
+-(void)assertReady
23
+{
24
+	if (!RNN.instance.isReadyToReceiveCommands) {
25
+		@throw [NSException exceptionWithName:@"BridgeNotLoadedError" reason:@"Bridge not yet loaded! Send commands after Navigation.events().onAppLaunched() has been called." userInfo:nil];
26
+	}
27
+}
28
+
21 29
 @end
22 30
 

+ 1
- 1
ios/RNNControllerFactory.m View File

@@ -90,7 +90,7 @@
90 90
 	
91 91
 	RCTRootView *reactView = [[RCTRootView alloc] initWithBridge:RNN.instance.bridge
92 92
 													  moduleName:containerName
93
-											   initialProperties:@{@"containerId": node.nodeId}];
93
+											   initialProperties:@{@"id": node.nodeId}];
94 94
 	
95 95
 	UIViewController* controller = [UIViewController new];
96 96
 	controller.view = reactView;

+ 5
- 0
playground/e2e/app.test.js View File

@@ -13,6 +13,11 @@ describe('app', () => {
13 13
     expect(elementByLabel('Hello from a function!')).toBeVisible();
14 14
   });
15 15
 
16
+  xit('push screen', () => {
17
+    elementByLabel('Push').tap();
18
+    expect(elementByLabel('Pushed screen')).toBeVisible();
19
+  });
20
+
16 21
   xit('switch to tabs with side menus', () => {
17 22
     elementByLabel('Switch to tab based app with side menus').tap();
18 23
     elementByLabel('Switch to tab based app with side menus').swipeRight();

+ 0
- 1
playground/src/app.js View File

@@ -13,4 +13,3 @@ export function start() {
13 13
     });
14 14
   });
15 15
 }
16
-

+ 27
- 6
playground/src/containers/WelcomeScreen.js View File

@@ -6,6 +6,7 @@ import Navigation from 'react-native-navigation';
6 6
 class WelcomeScreen extends Component {
7 7
   constructor(props) {
8 8
     super(props);
9
+    this.onClickPush = this.onClickPush.bind(this);
9 10
   }
10 11
 
11 12
   render() {
@@ -13,7 +14,9 @@ class WelcomeScreen extends Component {
13 14
       <View style={styles.root}>
14 15
         <Text style={styles.h1}>{`React Native Navigation!`}</Text>
15 16
         <Button title="Switch to tab based app" onPress={this.onClickSwitchToTabs} />
16
-        <Button title="Switch to tab based app with side menus" onPress={this.onClickSwitchToTabsWithSideMenus} />
17
+        <Button title="Switch to app with side menus" onPress={this.onClickSwitchToSideMenus} />
18
+        <Button title="Push" onPress={this.onClickPush} />
19
+        <Text style={styles.footer}>{`this.props.id = ${this.props.id}`}</Text>
17 20
       </View>
18 21
     );
19 22
   }
@@ -42,7 +45,7 @@ class WelcomeScreen extends Component {
42 45
     });
43 46
   }
44 47
 
45
-  onClickSwitchToTabsWithSideMenus() {
48
+  onClickSwitchToSideMenus() {
46 49
     Navigation.setRoot({
47 50
       tabs: [
48 51
         {
@@ -52,7 +55,7 @@ class WelcomeScreen extends Component {
52 55
         },
53 56
         {
54 57
           container: {
55
-            name: 'com.example.WelcomeScreen'
58
+            name: 'com.example.SimpleScreen'
56 59
           }
57 60
         },
58 61
         {
@@ -63,14 +66,27 @@ class WelcomeScreen extends Component {
63 66
       ],
64 67
       sideMenu: {
65 68
         left: {
66
-          name: 'com.example.SimpleScreen'
69
+          container: {
70
+            name: 'com.example.SimpleScreen'
71
+          }
67 72
         },
68 73
         right: {
69
-          name: 'com.example.SimpleScreen'
74
+          container: {
75
+            name: 'com.example.SimpleScreen'
76
+          }
70 77
         }
71 78
       }
72 79
     });
73 80
   }
81
+
82
+  onClickPush() {
83
+    Navigation.on(this.props.id).push({
84
+      name: 'com.example.SimpleScreen',
85
+      passProps: {
86
+        text: 'Pushed screen'
87
+      }
88
+    });
89
+  }
74 90
 }
75 91
 
76 92
 export default WelcomeScreen;
@@ -85,6 +101,11 @@ const styles = {
85 101
   h1: {
86 102
     fontSize: 24,
87 103
     textAlign: 'center',
88
-    margin: 10
104
+    margin: 30
105
+  },
106
+  footer: {
107
+    fontSize: 10,
108
+    color: '#888',
109
+    marginTop: 80
89 110
   }
90 111
 };

+ 1
- 1
src/Navigation.js View File

@@ -20,7 +20,7 @@ class Navigation {
20 20
   }
21 21
 
22 22
   setRoot(params) {
23
-    this.commands.setRoot(params);
23
+    return this.commands.setRoot(params);
24 24
   }
25 25
 
26 26
   events() {

+ 4
- 2
src/Navigation.test.js View File

@@ -20,9 +20,11 @@ describe('Navigation', () => {
20 20
     expect(Navigation.containerRegistry.registerContainer).toHaveBeenCalledWith('name', fn);
21 21
   });
22 22
 
23
-  it('setRoot delegates to Commands', () => {
23
+  it('setRoot delegates to Commands', async () => {
24
+    Navigation.commands.setRoot.mockReturnValue(Promise.resolve('result'));
24 25
     const params = {};
25
-    Navigation.setRoot(params);
26
+    const result = await Navigation.setRoot(params);
27
+    expect(result).toEqual('result');
26 28
     expect(Navigation.commands.setRoot).toHaveBeenCalledTimes(1);
27 29
     expect(Navigation.commands.setRoot).toHaveBeenCalledWith(params);
28 30
   });

+ 1
- 0
src/adapters/NativeCommandsSender.js View File

@@ -7,5 +7,6 @@ export default class NativeCommandsSender {
7 7
 
8 8
   setRoot(layoutTree) {
9 9
     this.nativeCommandsModule.setRoot(layoutTree);
10
+    return Promise.resolve(layoutTree);
10 11
   }
11 12
 }

+ 5
- 0
src/adapters/NativeCommandsSender.test.js View File

@@ -14,4 +14,9 @@ describe('NativeCommandsSender', () => {
14 14
     uut.setRoot();
15 15
     expect(mockNativeModule.setRoot).toHaveBeenCalledTimes(1);
16 16
   });
17
+
18
+  it('returns promise with resolved layout', async () => {
19
+    const result = await uut.setRoot({});
20
+    expect(result).toBeDefined();
21
+  });
17 22
 });

+ 1
- 1
src/commands/Commands.js View File

@@ -14,6 +14,6 @@ export default class Commands {
14 14
     const input = _.cloneDeep(simpleApi);
15 15
     const layout = this.layoutTreeParser.parseFromSimpleJSON(input);
16 16
     this.layoutTreeCrawler.crawl(layout);
17
-    this.nativeCommandsSender.setRoot(layout);
17
+    return this.nativeCommandsSender.setRoot(layout);
18 18
   }
19 19
 }

+ 6
- 0
src/commands/Commands.test.js View File

@@ -54,5 +54,11 @@ describe('Commands', () => {
54 54
       uut.setRoot(SimpleLayouts.singleScreenWithAditionalParams);
55 55
       expect(store.getPropsForContainerId('Container+UNIQUE_ID')).toEqual(SimpleLayouts.passProps);
56 56
     });
57
+
58
+    it('returns a promise with the resolved layout', async () => {
59
+      mockCommandsSender.setRoot.mockReturnValue(Promise.resolve('the resolved layout'));
60
+      const result = await uut.setRoot({ container: { name: 'com.example.MyScreen' } });
61
+      expect(result).toEqual('the resolved layout');
62
+    });
57 63
   });
58 64
 });

+ 1
- 1
src/containers/ContainerRegistry.test.js View File

@@ -28,7 +28,7 @@ describe('ContainerRegistry', () => {
28 28
   it('resulting in a normal component', () => {
29 29
     uut.registerContainer('example.MyContainer.name', () => MyContainer);
30 30
     const Container = AppRegistry.registerComponent.mock.calls[0][1]();
31
-    const tree = renderer.create(<Container containerId="123" />);
31
+    const tree = renderer.create(<Container id="123" />);
32 32
     expect(tree.toJSON().children).toEqual(['Hello, World!']);
33 33
   });
34 34
 });

+ 6
- 6
src/containers/ContainerWrapper.js View File

@@ -5,18 +5,18 @@ export default class ContainerWrapper {
5 5
     return class extends Component {
6 6
       constructor(props) {
7 7
         super(props);
8
-        if (!props.containerId) {
9
-          throw new Error(`Container ${containerName} does not have a containerId!`);
8
+        if (!props.id) {
9
+          throw new Error(`Container ${containerName} does not have an id!`);
10 10
         }
11 11
         this.state = {
12
-          containerId: props.containerId,
13
-          allProps: { ...props, ...propStore.getPropsForContainerId(props.containerId) }
12
+          id: props.id,
13
+          allProps: { ...props, ...propStore.getPropsForContainerId(props.id) }
14 14
         };
15 15
       }
16 16
 
17 17
       componentWillReceiveProps(nextProps) {
18 18
         this.setState({
19
-          allProps: { ...nextProps, ...propStore.getPropsForContainerId(this.state.containerId) }
19
+          allProps: { ...nextProps, ...propStore.getPropsForContainerId(this.state.id) }
20 20
         });
21 21
       }
22 22
 
@@ -24,7 +24,7 @@ export default class ContainerWrapper {
24 24
         return (
25 25
           <OriginalContainer
26 26
             {...this.state.allProps}
27
-            containerId={this.state.containerId}
27
+            id={this.state.id}
28 28
           />
29 29
         );
30 30
       }

+ 11
- 11
src/containers/ContainerWrapper.test.js View File

@@ -31,7 +31,7 @@ describe('ContainerWrapper', () => {
31 31
     render() {
32 32
       const Child = this.ChildClass;
33 33
       return (
34
-        <Child containerId="container1" {...this.state.propsFromState} />
34
+        <Child id="container1" {...this.state.propsFromState} />
35 35
       );
36 36
     }
37 37
   }
@@ -42,24 +42,24 @@ describe('ContainerWrapper', () => {
42 42
     store = new Store();
43 43
   });
44 44
 
45
-  it('must have containerId as prop', () => {
45
+  it('must have id as prop', () => {
46 46
     const NavigationContainer = ContainerWrapper.wrap(containerName, MyContainer, store);
47 47
     expect(() => {
48 48
       renderer.create(<NavigationContainer />);
49
-    }).toThrow(new Error('Container example.MyContainer does not have a containerId!'));
49
+    }).toThrow(new Error('Container example.MyContainer does not have an id!'));
50 50
   });
51 51
 
52 52
   it('wraps the container and saves to store', () => {
53 53
     const NavigationContainer = ContainerWrapper.wrap(containerName, MyContainer, store);
54 54
     expect(NavigationContainer).not.toBeInstanceOf(MyContainer);
55
-    const tree = renderer.create(<NavigationContainer containerId={'container1'} />);
55
+    const tree = renderer.create(<NavigationContainer id={'container1'} />);
56 56
     expect(tree.toJSON().children).toEqual(['Hello, World!']);
57 57
     expect(myContainerRef).toBeInstanceOf(MyContainer);
58 58
   });
59 59
 
60 60
   it('injects props from wrapper into original container', () => {
61 61
     const NavigationContainer = ContainerWrapper.wrap(containerName, MyContainer, store);
62
-    renderer.create(<NavigationContainer containerId={'container1'} myProp={'yo'} />);
62
+    renderer.create(<NavigationContainer id={'container1'} myProp={'yo'} />);
63 63
     expect(myContainerRef.props.myProp).toEqual('yo');
64 64
   });
65 65
 
@@ -74,8 +74,8 @@ describe('ContainerWrapper', () => {
74 74
   it('pulls props from the store and injects them into the inner container', () => {
75 75
     store.setPropsForContainerId('container123', { numberProp: 1, stringProp: 'hello', objectProp: { a: 2 } });
76 76
     const NavigationContainer = ContainerWrapper.wrap(containerName, MyContainer, store);
77
-    renderer.create(<NavigationContainer containerId={'container123'} />);
78
-    expect(myContainerRef.props).toEqual({ containerId: 'container123', numberProp: 1, stringProp: 'hello', objectProp: { a: 2 } });
77
+    renderer.create(<NavigationContainer id={'container123'} />);
78
+    expect(myContainerRef.props).toEqual({ id: 'container123', numberProp: 1, stringProp: 'hello', objectProp: { a: 2 } });
79 79
   });
80 80
 
81 81
   it('updates props from store into inner container', () => {
@@ -89,11 +89,11 @@ describe('ContainerWrapper', () => {
89 89
     expect(myContainerRef.props.myProp).toEqual('hello');
90 90
   });
91 91
 
92
-  it('protects containerId from change', () => {
92
+  it('protects id from change', () => {
93 93
     const NavigationContainer = ContainerWrapper.wrap(containerName, MyContainer, store);
94 94
     renderer.create(<TestParent ChildClass={NavigationContainer} />);
95
-    expect(myContainerRef.props.containerId).toEqual('container1');
96
-    testParentRef.setState({ propsFromState: { containerId: 'ERROR' } });
97
-    expect(myContainerRef.props.containerId).toEqual('container1');
95
+    expect(myContainerRef.props.id).toEqual('container1');
96
+    testParentRef.setState({ propsFromState: { id: 'ERROR' } });
97
+    expect(myContainerRef.props.id).toEqual('container1');
98 98
   });
99 99
 });

+ 6
- 6
yarn.lock View File

@@ -1948,8 +1948,8 @@ escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1
1948 1948
   resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
1949 1949
 
1950 1950
 escodegen-wallaby@^1.6.7:
1951
-  version "1.6.8"
1952
-  resolved "https://registry.yarnpkg.com/escodegen-wallaby/-/escodegen-wallaby-1.6.8.tgz#903afc307b1a0df4a79797e523157fade70b1165"
1951
+  version "1.6.10"
1952
+  resolved "https://registry.yarnpkg.com/escodegen-wallaby/-/escodegen-wallaby-1.6.10.tgz#abffe3301b20df346229b51cf1e827873aff1e06"
1953 1953
   dependencies:
1954 1954
     esprima "^2.7.1"
1955 1955
     estraverse "^1.9.1"
@@ -2797,7 +2797,7 @@ http-signature@~1.1.0:
2797 2797
     jsprim "^1.2.2"
2798 2798
     sshpk "^1.7.0"
2799 2799
 
2800
-iconv-lite@0.4.11:
2800
+iconv-lite@0.4.11, iconv-lite@^0.4.5:
2801 2801
   version "0.4.11"
2802 2802
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.11.tgz#2ecb42fd294744922209a2e7c404dac8793d8ade"
2803 2803
 
@@ -2805,7 +2805,7 @@ iconv-lite@0.4.13:
2805 2805
   version "0.4.13"
2806 2806
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2"
2807 2807
 
2808
-iconv-lite@^0.4.13, iconv-lite@^0.4.5, iconv-lite@~0.4.13:
2808
+iconv-lite@^0.4.13, iconv-lite@~0.4.13:
2809 2809
   version "0.4.15"
2810 2810
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb"
2811 2811
 
@@ -3414,8 +3414,8 @@ joi@^6.6.1:
3414 3414
     topo "1.x.x"
3415 3415
 
3416 3416
 js-tokens@^3.0.0:
3417
-  version "3.0.0"
3418
-  resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.0.tgz#a2f2a969caae142fb3cd56228358c89366957bd1"
3417
+  version "3.0.1"
3418
+  resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7"
3419 3419
 
3420 3420
 js-yaml@^3.5.1, js-yaml@^3.7.0:
3421 3421
   version "3.7.0"