Browse Source

screen lifecycle

Daniel Zlotin 7 years ago
parent
commit
293fb6adcd

+ 4
- 0
ios/RNN.m View File

@@ -15,6 +15,8 @@
15 15
 
16 16
 @implementation RNN
17 17
 
18
+# pragma mark public
19
+
18 20
 +(instancetype)instance
19 21
 {
20 22
 	static RNN *sharedInstance = nil;
@@ -43,6 +45,8 @@
43 45
 	self.bridge = [[RCTBridge alloc] initWithBundleURL:jsCodeLocation moduleProvider:nil launchOptions:launchOptions];
44 46
 }
45 47
 
48
+# pragma mark private
49
+
46 50
 -(void)registerForJsEvents
47 51
 {
48 52
 	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onJavaScriptLoaded) name:RCTJavaScriptDidLoadNotification object:self.bridge];

+ 3
- 3
ios/RNNBridgeModule.m View File

@@ -15,15 +15,15 @@ RCT_EXPORT_MODULE();
15 15
 RCT_EXPORT_METHOD(setRoot:(NSDictionary*)layout)
16 16
 {
17 17
 	[self assertReady];
18
-	UIApplication.sharedApplication.delegate.window.rootViewController = [[RNNControllerFactory new] createRootViewController:layout];
18
+	UIApplication.sharedApplication.delegate.window.rootViewController = [[RNNControllerFactory new] createLayout:layout];
19 19
 	[UIApplication.sharedApplication.delegate.window makeKeyAndVisible];
20 20
 }
21 21
 
22 22
 RCT_EXPORT_METHOD(push:(NSString*)containerId layout:(NSDictionary*)layout)
23 23
 {
24 24
 	[self assertReady];
25
-	//TODO make this not shitty
26
-	UIViewController* newVc = [[RNNControllerFactory new] createRootViewController:layout];
25
+	//TODO implement correctly
26
+	UIViewController* newVc = [[RNNControllerFactory new] createLayout:layout];
27 27
 	id vc = [UIApplication.sharedApplication.delegate.window.rootViewController childViewControllers][0];
28 28
 	[[vc navigationController]pushViewController:newVc animated:true];
29 29
 }

+ 1
- 1
ios/RNNControllerFactory.h View File

@@ -4,6 +4,6 @@
4 4
 
5 5
 @interface RNNControllerFactory : NSObject
6 6
 
7
--(UIViewController*)createRootViewController:(NSDictionary*)layout;
7
+-(UIViewController*)createLayout:(NSDictionary*)layout;
8 8
 
9 9
 @end

+ 5
- 1
ios/RNNControllerFactory.m View File

@@ -5,11 +5,15 @@
5 5
 
6 6
 @implementation RNNControllerFactory
7 7
 
8
--(UIViewController *)createRootViewController:(NSDictionary *)layout
8
+# pragma mark public
9
+
10
+-(UIViewController *)createLayout:(NSDictionary *)layout
9 11
 {
10 12
 	return [self fromTree:layout];
11 13
 }
12 14
 
15
+# pragma mark private
16
+
13 17
 -(UIViewController*)fromTree:(NSDictionary*)json
14 18
 {
15 19
 	RNNLayoutNode* node = [RNNLayoutNode create:json];

+ 7
- 7
ios/RNNEventEmitter.m View File

@@ -10,6 +10,11 @@ static NSString* const onAppLaunched	= @"RNN.appLaunched";
10 10
 static NSString* const containerStart	= @"RNN.containerStart";
11 11
 static NSString* const containerStop	= @"RNN.containerStop";
12 12
 
13
+-(NSArray<NSString *> *)supportedEvents
14
+{
15
+	return @[onAppLaunched, containerStart, containerStop];
16
+}
17
+
13 18
 # pragma mark public
14 19
 
15 20
 -(void)sendOnAppLaunched
@@ -19,21 +24,16 @@ static NSString* const containerStop	= @"RNN.containerStop";
19 24
 
20 25
 -(void)sendContainerStart:(NSString *)containerId
21 26
 {
22
-	[self send:containerStart body:@{@"id": containerId}];
27
+	[self send:containerStart body:containerId];
23 28
 }
24 29
 
25 30
 -(void)sendContainerStop:(NSString *)containerId
26 31
 {
27
-	[self send:containerStop body:@{@"id": containerId}];
32
+	[self send:containerStop body:containerId];
28 33
 }
29 34
 
30 35
 # pragma mark private
31 36
 
32
--(NSArray<NSString *> *)supportedEvents
33
-{
34
-	return @[onAppLaunched, containerStart, containerStop];
35
-}
36
-
37 37
 -(void)send:(NSString *)eventName body:(id)body
38 38
 {
39 39
 	[[RNN.instance.bridge moduleForClass:[RNNEventEmitter class]] sendEventWithName:eventName body:body];

+ 1
- 1
playground/e2e/app.test.js View File

@@ -24,7 +24,7 @@ describe('app', () => {
24 24
     expect(elementByLabel('This is a side menu screen')).toBeVisible();
25 25
   });
26 26
 
27
-  xit('screen lifecycle', () => {
27
+  it('screen lifecycle', () => {
28 28
     elementByLabel('Switch to lifecycle screen').tap();
29 29
     expect(elementByLabel('onStart!')).toBeVisible();
30 30
   });

+ 4
- 3
playground/src/containers/WelcomeScreen.js View File

@@ -7,7 +7,6 @@ class WelcomeScreen extends Component {
7 7
   constructor(props) {
8 8
     super(props);
9 9
     this.onClickPush = this.onClickPush.bind(this);
10
-    this.onClickLifecycleScreen = this.onClickLifecycleScreen.bind(this);
11 10
   }
12 11
 
13 12
   render() {
@@ -91,8 +90,10 @@ class WelcomeScreen extends Component {
91 90
   }
92 91
 
93 92
   onClickLifecycleScreen() {
94
-    Navigation.on(this.props.id).push({
95
-      name: 'com.example.LifecycleScreen'
93
+    Navigation.setRoot({
94
+      container: {
95
+        name: 'com.example.LifecycleScreen'
96
+      }
96 97
     });
97 98
   }
98 99
 }

+ 4
- 0
src/adapters/NativeEventsReceiver.js View File

@@ -9,6 +9,10 @@ export default class NativeEventsReceiver {
9 9
     this.emitter.addListener('RNN.containerStart', callback);
10 10
   }
11 11
 
12
+  containerStop(callback) {
13
+    this.emitter.addListener('RNN.containerStop', callback);
14
+  }
15
+
12 16
   appLaunched(callback) {
13 17
     this.emitter.addListener('RNN.appLaunched', callback);
14 18
   }

+ 9
- 1
src/adapters/NativeEventsReceiver.test.js View File

@@ -19,11 +19,19 @@ describe('NativeEventsReceiver', () => {
19 19
     expect(eventEmitterMock.addListener).toHaveBeenCalledWith('RNN.appLaunched', callback);
20 20
   });
21 21
 
22
-  it('register for internal events', () => {
22
+  it('containerStart', () => {
23 23
     const callback = jest.fn();
24 24
     uut.containerStart(callback);
25 25
     expect(callback).not.toHaveBeenCalled();
26 26
     expect(eventEmitterMock.addListener).toHaveBeenCalledTimes(1);
27 27
     expect(eventEmitterMock.addListener).toHaveBeenCalledWith('RNN.containerStart', callback);
28 28
   });
29
+
30
+  it('containerStop', () => {
31
+    const callback = jest.fn();
32
+    uut.containerStop(callback);
33
+    expect(callback).not.toHaveBeenCalled();
34
+    expect(eventEmitterMock.addListener).toHaveBeenCalledTimes(1);
35
+    expect(eventEmitterMock.addListener).toHaveBeenCalledWith('RNN.containerStop', callback);
36
+  });
29 37
 });

+ 13
- 0
src/containers/ContainerWrapper.js View File

@@ -31,6 +31,18 @@ export default class ContainerWrapper {
31 31
         store.cleanId(this.state.id);
32 32
       }
33 33
 
34
+      onStart() {
35
+        if (this.originalContainerRef.onStart) {
36
+          this.originalContainerRef.onStart();
37
+        }
38
+      }
39
+
40
+      onStop() {
41
+        if (this.originalContainerRef.onStop) {
42
+          this.originalContainerRef.onStop();
43
+        }
44
+      }
45
+
34 46
       componentWillReceiveProps(nextProps) {
35 47
         this.setState({
36 48
           allProps: _.merge({}, nextProps, store.getPropsForContainerId(this.state.id))
@@ -40,6 +52,7 @@ export default class ContainerWrapper {
40 52
       render() {
41 53
         return (
42 54
           <OriginalContainer
55
+            ref={(r) => this.originalContainerRef = r}
43 56
             {...this.state.allProps}
44 57
             id={this.state.id}
45 58
             key={this.state.id}

+ 30
- 3
src/containers/ContainerWrapper.test.js View File

@@ -120,7 +120,22 @@ describe('ContainerWrapper', () => {
120 120
     expect(store.getRefForId('container1')).toBeUndefined();
121 121
   });
122 122
 
123
-  xdescribe('container lifecycle', () => {
123
+  it('holds ref to OriginalContainer', () => {
124
+    const NavigationContainer = ContainerWrapper.wrap(containerName, MyContainer, store);
125
+    const tree = renderer.create(<NavigationContainer id={'container1'} />);
126
+    expect(tree.getInstance().originalContainerRef).toBe(myContainerRef);
127
+  });
128
+
129
+  it('cleans ref to internal container on unount', () => {
130
+    const NavigationContainer = ContainerWrapper.wrap(containerName, MyContainer, store);
131
+    const tree = renderer.create(<NavigationContainer id={'container1'} />);
132
+    const instance = tree.getInstance();
133
+    expect(instance.originalContainerRef).not.toBeFalsy();
134
+    tree.unmount();
135
+    expect(instance.originalContainerRef).toBeFalsy();
136
+  });
137
+
138
+  describe('container lifecycle', () => {
124 139
     const onStartCallback = jest.fn();
125 140
     const onStopCallback = jest.fn();
126 141
 
@@ -134,8 +149,20 @@ describe('ContainerWrapper', () => {
134 149
       }
135 150
     }
136 151
 
137
-    it('', () => {
138
-      //
152
+    it('calls onStart on OriginalContainer', () => {
153
+      const NavigationContainer = ContainerWrapper.wrap(containerName, MyLifecycleContainer, store);
154
+      const tree = renderer.create(<NavigationContainer id={'container1'} />);
155
+      expect(onStartCallback).toHaveBeenCalledTimes(0);
156
+      tree.getInstance().onStart();
157
+      expect(onStartCallback).toHaveBeenCalledTimes(1);
158
+    });
159
+
160
+    it('calls onSop on OriginalContainer', () => {
161
+      const NavigationContainer = ContainerWrapper.wrap(containerName, MyLifecycleContainer, store);
162
+      const tree = renderer.create(<NavigationContainer id={'container1'} />);
163
+      expect(onStopCallback).toHaveBeenCalledTimes(0);
164
+      tree.getInstance().onStop();
165
+      expect(onStopCallback).toHaveBeenCalledTimes(1);
139 166
     });
140 167
   });
141 168
 });

+ 21
- 0
src/containers/Lifecycle.js View File

@@ -0,0 +1,21 @@
1
+export default class Lifecycle {
2
+  constructor(store) {
3
+    this.store = store;
4
+    this.containerStart = this.containerStart.bind(this);
5
+    this.containerStop = this.containerStop.bind(this);
6
+  }
7
+
8
+  containerStart(id) {
9
+    const ref = this.store.getRefForId(id);
10
+    if (ref && ref.onStart) {
11
+      ref.onStart();
12
+    }
13
+  }
14
+
15
+  containerStop(id) {
16
+    const ref = this.store.getRefForId(id);
17
+    if (ref && ref.onStop) {
18
+      ref.onStop();
19
+    }
20
+  }
21
+}

+ 56
- 0
src/containers/Lifecycle.test.js View File

@@ -0,0 +1,56 @@
1
+import Lifecycle from './Lifecycle';
2
+import Store from '../containers/Store';
3
+
4
+describe('Lifecycle', () => {
5
+  let uut;
6
+  let mockRef;
7
+
8
+  beforeEach(() => {
9
+    mockRef = {
10
+      onStart: jest.fn(),
11
+      onStop: jest.fn()
12
+    };
13
+    store = new Store();
14
+    store.setRefForId('myUniqueId', mockRef);
15
+
16
+    uut = new Lifecycle(store);
17
+  });
18
+
19
+  describe('containerStart', () => {
20
+    it('calls onStart on container ref from store', () => {
21
+      uut.containerStart('myUniqueId');
22
+      expect(mockRef.onStart).toHaveBeenCalledTimes(1);
23
+    });
24
+
25
+    it('skips undefined refs', () => {
26
+      uut.containerStart('myUniqueId2');
27
+      expect(mockRef.onStart).not.toHaveBeenCalled();
28
+    });
29
+
30
+    it('skips unimplemented onStart', () => {
31
+      mockRef = {};
32
+      expect(mockRef.onStart).toBeUndefined();
33
+      store.setRefForId('myUniqueId', mockRef);
34
+      uut.containerStart('myUniqueId');
35
+    });
36
+  });
37
+
38
+  describe('containerStop', () => {
39
+    it('calls onStop on container ref from store', () => {
40
+      uut.containerStop('myUniqueId');
41
+      expect(mockRef.onStop).toHaveBeenCalledTimes(1);
42
+    });
43
+
44
+    it('skips undefined refs', () => {
45
+      uut.containerStop('myUniqueId2');
46
+      expect(mockRef.onStop).not.toHaveBeenCalled();
47
+    });
48
+
49
+    it('skips unimplemented onStop', () => {
50
+      mockRef = {};
51
+      expect(mockRef.onStop).toBeUndefined();
52
+      store.setRefForId('myUniqueId', mockRef);
53
+      uut.containerStop('myUniqueId');
54
+    });
55
+  });
56
+});

+ 4
- 14
src/events/PrivateEventsListener.js View File

@@ -1,24 +1,14 @@
1 1
 import _ from 'lodash';
2
+import Lifecycle from '../containers/Lifecycle';
2 3
 
3 4
 export default class PrivateEventsListener {
4 5
   constructor(nativeEventsReceiver, store) {
5 6
     this.nativeEventsReceiver = nativeEventsReceiver;
6
-    this.store = store;
7
+    this.lifecycle = new Lifecycle(store);
7 8
   }
8 9
 
9 10
   listenAndHandlePrivateEvents() {
10
-    this.nativeEventsReceiver.containerStart(this._handleContainerStart);
11
-  }
12
-
13
-  _handleContainerStart(params) {
14
-    // const id = params.id;
15
-    // try {
16
-    //   const ref = this.store.getRefForId(id);
17
-    //   if (ref && ref.onStart) {
18
-    //     ref.onStart();
19
-    //   }
20
-    // } catch (e) {
21
-    //   console.warn(e);
22
-    // }
11
+    this.nativeEventsReceiver.containerStart(this.lifecycle.containerStart);
12
+    this.nativeEventsReceiver.containerStop(this.lifecycle.containerStop);
23 13
   }
24 14
 }

+ 18
- 2
src/events/PrivateEventsListener.test.js View File

@@ -1,21 +1,37 @@
1 1
 import _ from 'lodash';
2 2
 import PrivateEventsListener from './PrivateEventsListener';
3 3
 import NativeEventsReceiver from '../adapters/NativeEventsReceiver.mock';
4
+import Store from '../containers/Store';
4 5
 
5 6
 describe('PrivateEventsListener', () => {
6 7
   let uut;
7 8
   let nativeEventsReceiver;
9
+  let store;
8 10
 
9 11
   beforeEach(() => {
10 12
     nativeEventsReceiver = new NativeEventsReceiver();
11
-    uut = new PrivateEventsListener(nativeEventsReceiver);
13
+    store = new Store();
14
+    uut = new PrivateEventsListener(nativeEventsReceiver, store);
12 15
   });
13 16
 
14
-  it('registers for private events against nativeEventsReceiver', () => {
17
+  it('register and handle containerStart', () => {
18
+    const mockRef = {
19
+      onStart: jest.fn()
20
+    };
21
+    store.setRefForId('myContainerId', mockRef);
15 22
     uut.listenAndHandlePrivateEvents();
16 23
     expect(nativeEventsReceiver.containerStart).toHaveBeenCalledTimes(1);
17 24
     const callbackFunction = nativeEventsReceiver.containerStart.mock.calls[0][0];
18 25
     expect(callbackFunction).toBeInstanceOf(Function);
26
+
27
+    expect(mockRef.onStart).not.toHaveBeenCalled();
19 28
     callbackFunction('myContainerId');
29
+
30
+    expect(mockRef.onStart).toHaveBeenCalledTimes(1);
31
+  });
32
+
33
+  it('register and listen containerStop', () => {
34
+    uut.listenAndHandlePrivateEvents();
35
+    expect(nativeEventsReceiver.containerStop).toHaveBeenCalledTimes(1);
20 36
   });
21 37
 });