Browse Source

refactoring, push e2e

Daniel Zlotin 8 years ago
parent
commit
b2ce627b33

+ 3
- 2
ios/RNN.h View File

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

+ 4
- 9
ios/RNN.m View File

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

+ 8
- 0
ios/RNNBridgeModule.m View File

14
 
14
 
15
 RCT_EXPORT_METHOD(setRoot:(NSDictionary*)layout)
15
 RCT_EXPORT_METHOD(setRoot:(NSDictionary*)layout)
16
 {
16
 {
17
+	[self assertReady];
17
 	UIApplication.sharedApplication.delegate.window.rootViewController = [[RNNControllerFactory new] createRootViewController:layout];
18
 	UIApplication.sharedApplication.delegate.window.rootViewController = [[RNNControllerFactory new] createRootViewController:layout];
18
 	[UIApplication.sharedApplication.delegate.window makeKeyAndVisible];
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
 @end
29
 @end
22
 
30
 

+ 1
- 1
ios/RNNControllerFactory.m View File

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

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

13
     expect(elementByLabel('Hello from a function!')).toBeVisible();
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
   xit('switch to tabs with side menus', () => {
21
   xit('switch to tabs with side menus', () => {
17
     elementByLabel('Switch to tab based app with side menus').tap();
22
     elementByLabel('Switch to tab based app with side menus').tap();
18
     elementByLabel('Switch to tab based app with side menus').swipeRight();
23
     elementByLabel('Switch to tab based app with side menus').swipeRight();

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

13
     });
13
     });
14
   });
14
   });
15
 }
15
 }
16
-

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

6
 class WelcomeScreen extends Component {
6
 class WelcomeScreen extends Component {
7
   constructor(props) {
7
   constructor(props) {
8
     super(props);
8
     super(props);
9
+    this.onClickPush = this.onClickPush.bind(this);
9
   }
10
   }
10
 
11
 
11
   render() {
12
   render() {
13
       <View style={styles.root}>
14
       <View style={styles.root}>
14
         <Text style={styles.h1}>{`React Native Navigation!`}</Text>
15
         <Text style={styles.h1}>{`React Native Navigation!`}</Text>
15
         <Button title="Switch to tab based app" onPress={this.onClickSwitchToTabs} />
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
       </View>
20
       </View>
18
     );
21
     );
19
   }
22
   }
42
     });
45
     });
43
   }
46
   }
44
 
47
 
45
-  onClickSwitchToTabsWithSideMenus() {
48
+  onClickSwitchToSideMenus() {
46
     Navigation.setRoot({
49
     Navigation.setRoot({
47
       tabs: [
50
       tabs: [
48
         {
51
         {
52
         },
55
         },
53
         {
56
         {
54
           container: {
57
           container: {
55
-            name: 'com.example.WelcomeScreen'
58
+            name: 'com.example.SimpleScreen'
56
           }
59
           }
57
         },
60
         },
58
         {
61
         {
63
       ],
66
       ],
64
       sideMenu: {
67
       sideMenu: {
65
         left: {
68
         left: {
66
-          name: 'com.example.SimpleScreen'
69
+          container: {
70
+            name: 'com.example.SimpleScreen'
71
+          }
67
         },
72
         },
68
         right: {
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
 export default WelcomeScreen;
92
 export default WelcomeScreen;
85
   h1: {
101
   h1: {
86
     fontSize: 24,
102
     fontSize: 24,
87
     textAlign: 'center',
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
   }
20
   }
21
 
21
 
22
   setRoot(params) {
22
   setRoot(params) {
23
-    this.commands.setRoot(params);
23
+    return this.commands.setRoot(params);
24
   }
24
   }
25
 
25
 
26
   events() {
26
   events() {

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

20
     expect(Navigation.containerRegistry.registerContainer).toHaveBeenCalledWith('name', fn);
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
     const params = {};
25
     const params = {};
25
-    Navigation.setRoot(params);
26
+    const result = await Navigation.setRoot(params);
27
+    expect(result).toEqual('result');
26
     expect(Navigation.commands.setRoot).toHaveBeenCalledTimes(1);
28
     expect(Navigation.commands.setRoot).toHaveBeenCalledTimes(1);
27
     expect(Navigation.commands.setRoot).toHaveBeenCalledWith(params);
29
     expect(Navigation.commands.setRoot).toHaveBeenCalledWith(params);
28
   });
30
   });

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

7
 
7
 
8
   setRoot(layoutTree) {
8
   setRoot(layoutTree) {
9
     this.nativeCommandsModule.setRoot(layoutTree);
9
     this.nativeCommandsModule.setRoot(layoutTree);
10
+    return Promise.resolve(layoutTree);
10
   }
11
   }
11
 }
12
 }

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

14
     uut.setRoot();
14
     uut.setRoot();
15
     expect(mockNativeModule.setRoot).toHaveBeenCalledTimes(1);
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
     const input = _.cloneDeep(simpleApi);
14
     const input = _.cloneDeep(simpleApi);
15
     const layout = this.layoutTreeParser.parseFromSimpleJSON(input);
15
     const layout = this.layoutTreeParser.parseFromSimpleJSON(input);
16
     this.layoutTreeCrawler.crawl(layout);
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
       uut.setRoot(SimpleLayouts.singleScreenWithAditionalParams);
54
       uut.setRoot(SimpleLayouts.singleScreenWithAditionalParams);
55
       expect(store.getPropsForContainerId('Container+UNIQUE_ID')).toEqual(SimpleLayouts.passProps);
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
   it('resulting in a normal component', () => {
28
   it('resulting in a normal component', () => {
29
     uut.registerContainer('example.MyContainer.name', () => MyContainer);
29
     uut.registerContainer('example.MyContainer.name', () => MyContainer);
30
     const Container = AppRegistry.registerComponent.mock.calls[0][1]();
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
     expect(tree.toJSON().children).toEqual(['Hello, World!']);
32
     expect(tree.toJSON().children).toEqual(['Hello, World!']);
33
   });
33
   });
34
 });
34
 });

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

5
     return class extends Component {
5
     return class extends Component {
6
       constructor(props) {
6
       constructor(props) {
7
         super(props);
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
         this.state = {
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
       componentWillReceiveProps(nextProps) {
17
       componentWillReceiveProps(nextProps) {
18
         this.setState({
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
         return (
24
         return (
25
           <OriginalContainer
25
           <OriginalContainer
26
             {...this.state.allProps}
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
     render() {
31
     render() {
32
       const Child = this.ChildClass;
32
       const Child = this.ChildClass;
33
       return (
33
       return (
34
-        <Child containerId="container1" {...this.state.propsFromState} />
34
+        <Child id="container1" {...this.state.propsFromState} />
35
       );
35
       );
36
     }
36
     }
37
   }
37
   }
42
     store = new Store();
42
     store = new Store();
43
   });
43
   });
44
 
44
 
45
-  it('must have containerId as prop', () => {
45
+  it('must have id as prop', () => {
46
     const NavigationContainer = ContainerWrapper.wrap(containerName, MyContainer, store);
46
     const NavigationContainer = ContainerWrapper.wrap(containerName, MyContainer, store);
47
     expect(() => {
47
     expect(() => {
48
       renderer.create(<NavigationContainer />);
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
   it('wraps the container and saves to store', () => {
52
   it('wraps the container and saves to store', () => {
53
     const NavigationContainer = ContainerWrapper.wrap(containerName, MyContainer, store);
53
     const NavigationContainer = ContainerWrapper.wrap(containerName, MyContainer, store);
54
     expect(NavigationContainer).not.toBeInstanceOf(MyContainer);
54
     expect(NavigationContainer).not.toBeInstanceOf(MyContainer);
55
-    const tree = renderer.create(<NavigationContainer containerId={'container1'} />);
55
+    const tree = renderer.create(<NavigationContainer id={'container1'} />);
56
     expect(tree.toJSON().children).toEqual(['Hello, World!']);
56
     expect(tree.toJSON().children).toEqual(['Hello, World!']);
57
     expect(myContainerRef).toBeInstanceOf(MyContainer);
57
     expect(myContainerRef).toBeInstanceOf(MyContainer);
58
   });
58
   });
59
 
59
 
60
   it('injects props from wrapper into original container', () => {
60
   it('injects props from wrapper into original container', () => {
61
     const NavigationContainer = ContainerWrapper.wrap(containerName, MyContainer, store);
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
     expect(myContainerRef.props.myProp).toEqual('yo');
63
     expect(myContainerRef.props.myProp).toEqual('yo');
64
   });
64
   });
65
 
65
 
74
   it('pulls props from the store and injects them into the inner container', () => {
74
   it('pulls props from the store and injects them into the inner container', () => {
75
     store.setPropsForContainerId('container123', { numberProp: 1, stringProp: 'hello', objectProp: { a: 2 } });
75
     store.setPropsForContainerId('container123', { numberProp: 1, stringProp: 'hello', objectProp: { a: 2 } });
76
     const NavigationContainer = ContainerWrapper.wrap(containerName, MyContainer, store);
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
   it('updates props from store into inner container', () => {
81
   it('updates props from store into inner container', () => {
89
     expect(myContainerRef.props.myProp).toEqual('hello');
89
     expect(myContainerRef.props.myProp).toEqual('hello');
90
   });
90
   });
91
 
91
 
92
-  it('protects containerId from change', () => {
92
+  it('protects id from change', () => {
93
     const NavigationContainer = ContainerWrapper.wrap(containerName, MyContainer, store);
93
     const NavigationContainer = ContainerWrapper.wrap(containerName, MyContainer, store);
94
     renderer.create(<TestParent ChildClass={NavigationContainer} />);
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
   resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
1948
   resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
1949
 
1949
 
1950
 escodegen-wallaby@^1.6.7:
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
   dependencies:
1953
   dependencies:
1954
     esprima "^2.7.1"
1954
     esprima "^2.7.1"
1955
     estraverse "^1.9.1"
1955
     estraverse "^1.9.1"
2797
     jsprim "^1.2.2"
2797
     jsprim "^1.2.2"
2798
     sshpk "^1.7.0"
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
   version "0.4.11"
2801
   version "0.4.11"
2802
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.11.tgz#2ecb42fd294744922209a2e7c404dac8793d8ade"
2802
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.11.tgz#2ecb42fd294744922209a2e7c404dac8793d8ade"
2803
 
2803
 
2805
   version "0.4.13"
2805
   version "0.4.13"
2806
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2"
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
   version "0.4.15"
2809
   version "0.4.15"
2810
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb"
2810
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb"
2811
 
2811
 
3414
     topo "1.x.x"
3414
     topo "1.x.x"
3415
 
3415
 
3416
 js-tokens@^3.0.0:
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
 js-yaml@^3.5.1, js-yaml@^3.7.0:
3420
 js-yaml@^3.5.1, js-yaml@^3.7.0:
3421
   version "3.7.0"
3421
   version "3.7.0"