Browse Source

Ensure appLaunched event is emitted only when app is resumed

Under certain conditions, app launched event was emitted when the Android Activity isn't in resumed state. In this case, setting root isn't possible since ReactContext doesn't have an instance of the Activity (it can even be destroyed).

This PR ensures the event is emitted only when the Activity is resumed and root can be set.
Guy Carmeli 5 years ago
parent
commit
21584fd4a5
No account linked to committer's email address

+ 13
- 7
lib/android/app/src/main/java/com/reactnativenavigation/parse/LayoutFactory.java View File

@@ -38,21 +38,27 @@ import static com.reactnativenavigation.parse.Options.parse;
38 38
 
39 39
 public class LayoutFactory {
40 40
 
41
-	private final Activity activity;
42
-    private final ChildControllersRegistry childRegistry;
41
+	private Activity activity;
42
+    private ChildControllersRegistry childRegistry;
43 43
 	private final ReactInstanceManager reactInstanceManager;
44 44
     private EventEmitter eventEmitter;
45 45
     private Map<String, ExternalComponentCreator> externalComponentCreators;
46 46
     private Options defaultOptions;
47
-    private final TypefaceLoader typefaceManager;
47
+    private TypefaceLoader typefaceManager;
48 48
 
49
-    public LayoutFactory(Activity activity, ChildControllersRegistry childRegistry, final ReactInstanceManager reactInstanceManager, EventEmitter eventEmitter, Map<String, ExternalComponentCreator> externalComponentCreators, Options defaultOptions) {
50
-		this.activity = activity;
51
-        this.childRegistry = childRegistry;
49
+    public void setDefaultOptions(Options defaultOptions) {
50
+        this.defaultOptions = defaultOptions;
51
+    }
52
+
53
+    public LayoutFactory(final ReactInstanceManager reactInstanceManager) {
52 54
         this.reactInstanceManager = reactInstanceManager;
55
+    }
56
+
57
+    public void init(Activity activity, EventEmitter eventEmitter, ChildControllersRegistry childRegistry, Map<String, ExternalComponentCreator> externalComponentCreators) {
58
+        this.activity = activity;
53 59
         this.eventEmitter = eventEmitter;
60
+        this.childRegistry = childRegistry;
54 61
         this.externalComponentCreators = externalComponentCreators;
55
-        this.defaultOptions = defaultOptions;
56 62
         typefaceManager = new TypefaceLoader(activity);
57 63
     }
58 64
 

+ 2
- 2
lib/android/app/src/main/java/com/reactnativenavigation/parse/parsers/JSONParser.java View File

@@ -9,7 +9,7 @@ import org.json.JSONException;
9 9
 import org.json.JSONObject;
10 10
 
11 11
 public class JSONParser {
12
-    public static JSONObject parse(ReadableMap map) {
12
+    public JSONObject parse(ReadableMap map) {
13 13
         try {
14 14
             ReadableMapKeySetIterator it = map.keySetIterator();
15 15
             JSONObject result = new JSONObject();
@@ -41,7 +41,7 @@ public class JSONParser {
41 41
         }
42 42
     }
43 43
 
44
-    public static JSONArray parse(ReadableArray arr) {
44
+    public JSONArray parse(ReadableArray arr) {
45 45
         JSONArray result = new JSONArray();
46 46
         for (int i = 0; i < arr.size(); i++) {
47 47
             switch (arr.getType(i)) {

+ 20
- 0
lib/android/app/src/main/java/com/reactnativenavigation/react/LifecycleEventListenerAdapter.java View File

@@ -0,0 +1,20 @@
1
+package com.reactnativenavigation.react;
2
+
3
+import com.facebook.react.bridge.LifecycleEventListener;
4
+
5
+public class LifecycleEventListenerAdapter implements LifecycleEventListener {
6
+    @Override
7
+    public void onHostResume() {
8
+
9
+    }
10
+
11
+    @Override
12
+    public void onHostPause() {
13
+
14
+    }
15
+
16
+    @Override
17
+    public void onHostDestroy() {
18
+
19
+    }
20
+}

+ 103
- 95
lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationModule.java View File

@@ -25,161 +25,169 @@ import com.reactnativenavigation.utils.TypefaceLoader;
25 25
 import com.reactnativenavigation.utils.UiThread;
26 26
 import com.reactnativenavigation.utils.UiUtils;
27 27
 import com.reactnativenavigation.viewcontrollers.ViewController;
28
-import com.reactnativenavigation.viewcontrollers.externalcomponent.ExternalComponentCreator;
29 28
 import com.reactnativenavigation.viewcontrollers.navigator.Navigator;
30 29
 
31 30
 import java.util.ArrayList;
32
-import java.util.Map;
33 31
 
34 32
 public class NavigationModule extends ReactContextBaseJavaModule {
35
-	private static final String NAME = "RNNBridgeModule";
33
+    private static final String NAME = "RNNBridgeModule";
36 34
 
37 35
     private final Now now = new Now();
38
-	private final ReactInstanceManager reactInstanceManager;
36
+    private final ReactInstanceManager reactInstanceManager;
37
+    private final JSONParser jsonParser;
38
+    private final LayoutFactory layoutFactory;
39 39
     private EventEmitter eventEmitter;
40 40
 
41 41
     @SuppressWarnings("WeakerAccess")
42
-    public NavigationModule(ReactApplicationContext reactContext, ReactInstanceManager reactInstanceManager) {
43
-		super(reactContext);
44
-		this.reactInstanceManager = reactInstanceManager;
45
-		reactInstanceManager.addReactInstanceEventListener(context -> eventEmitter = new EventEmitter(context));
42
+    public NavigationModule(ReactApplicationContext reactContext, ReactInstanceManager reactInstanceManager, LayoutFactory layoutFactory) {
43
+        this(reactContext, reactInstanceManager, new JSONParser(), layoutFactory);
46 44
     }
47 45
 
48
-	@Override
49
-	public String getName() {
50
-		return NAME;
51
-	}
46
+    public NavigationModule(ReactApplicationContext reactContext, ReactInstanceManager reactInstanceManager, JSONParser jsonParser, LayoutFactory layoutFactory) {
47
+        super(reactContext);
48
+        this.reactInstanceManager = reactInstanceManager;
49
+        this.jsonParser = jsonParser;
50
+        this.layoutFactory = layoutFactory;
51
+        reactContext.addLifecycleEventListener(new LifecycleEventListenerAdapter() {
52
+            @Override
53
+            public void onHostResume() {
54
+                eventEmitter = new EventEmitter(reactContext);
55
+                navigator().setEventEmitter(eventEmitter);
56
+                layoutFactory.init(
57
+                        activity(),
58
+                        eventEmitter,
59
+                        navigator().getChildRegistry(),
60
+                        ((NavigationApplication) activity().getApplication()).getExternalComponents()
61
+                );
62
+            }
63
+        });
64
+    }
65
+
66
+    @NonNull
67
+    @Override
68
+    public String getName() {
69
+        return NAME;
70
+    }
52 71
 
53
-	@ReactMethod
72
+    @ReactMethod
54 73
     public void getConstants(Promise promise) {
55 74
         ReactApplicationContext ctx = getReactApplicationContext();
56 75
         WritableMap constants = Arguments.createMap();
57
-        constants.putString(Constants.BACK_BUTTON_JS_KEY,    Constants.BACK_BUTTON_ID);
58
-        constants.putDouble(Constants.BOTTOM_TABS_HEIGHT_KEY,    Constants.BOTTOM_TABS_HEIGHT);
76
+        constants.putString(Constants.BACK_BUTTON_JS_KEY, Constants.BACK_BUTTON_ID);
77
+        constants.putDouble(Constants.BOTTOM_TABS_HEIGHT_KEY, Constants.BOTTOM_TABS_HEIGHT);
59 78
         constants.putDouble(Constants.STATUS_BAR_HEIGHT_KEY, UiUtils.pxToDp(ctx, UiUtils.getStatusBarHeight(ctx)));
60
-        constants.putDouble(Constants.TOP_BAR_HEIGHT_KEY,    UiUtils.pxToDp(ctx, UiUtils.getTopBarHeight(ctx)));
79
+        constants.putDouble(Constants.TOP_BAR_HEIGHT_KEY, UiUtils.pxToDp(ctx, UiUtils.getTopBarHeight(ctx)));
61 80
         promise.resolve(constants);
62 81
     }
63 82
 
64
-	@ReactMethod
65
-	public void setRoot(String commandId, ReadableMap rawLayoutTree, Promise promise) {
66
-        final LayoutNode layoutTree = LayoutNodeParser.parse(JSONParser.parse(rawLayoutTree).optJSONObject("root"));
67
-		handle(() -> {
68
-            navigator().setEventEmitter(eventEmitter);
69
-            final ViewController viewController = newLayoutFactory().create(layoutTree);
83
+    @ReactMethod
84
+    public void setRoot(String commandId, ReadableMap rawLayoutTree, Promise promise) {
85
+        final LayoutNode layoutTree = LayoutNodeParser.parse(jsonParser.parse(rawLayoutTree).optJSONObject("root"));
86
+        handle(() -> {
87
+            final ViewController viewController = layoutFactory.create(layoutTree);
70 88
             navigator().setRoot(viewController, new NativeCommandListener(commandId, promise, eventEmitter, now), reactInstanceManager);
71 89
         });
72
-	}
90
+    }
73 91
 
74
-	@ReactMethod
75
-	public void setDefaultOptions(ReadableMap options) {
76
-        handle(() -> navigator().setDefaultOptions(parse(options)));
92
+    @ReactMethod
93
+    public void setDefaultOptions(ReadableMap options) {
94
+        handle(() -> {
95
+            Options defaultOptions = parse(options);
96
+            layoutFactory.setDefaultOptions(defaultOptions);
97
+            navigator().setDefaultOptions(defaultOptions);
98
+        });
77 99
     }
78 100
 
79
-	@ReactMethod
80
-	public void mergeOptions(String onComponentId, @Nullable ReadableMap options) {
81
-		handle(() -> navigator().mergeOptions(onComponentId, parse(options)));
82
-	}
101
+    @ReactMethod
102
+    public void mergeOptions(String onComponentId, @Nullable ReadableMap options) {
103
+        handle(() -> navigator().mergeOptions(onComponentId, parse(options)));
104
+    }
83 105
 
84
-	@ReactMethod
85
-	public void push(String commandId, String onComponentId, ReadableMap rawLayoutTree, Promise promise) {
86
-        final LayoutNode layoutTree = LayoutNodeParser.parse(JSONParser.parse(rawLayoutTree));
87
-		handle(() -> {
88
-            final ViewController viewController = newLayoutFactory().create(layoutTree);
106
+    @ReactMethod
107
+    public void push(String commandId, String onComponentId, ReadableMap rawLayoutTree, Promise promise) {
108
+        final LayoutNode layoutTree = LayoutNodeParser.parse(jsonParser.parse(rawLayoutTree));
109
+        handle(() -> {
110
+            final ViewController viewController = layoutFactory.create(layoutTree);
89 111
             navigator().push(onComponentId, viewController, new NativeCommandListener(commandId, promise, eventEmitter, now));
90 112
         });
91
-	}
113
+    }
92 114
 
93 115
     @ReactMethod
94 116
     public void setStackRoot(String commandId, String onComponentId, ReadableArray children, Promise promise) {
95 117
         handle(() -> {
96 118
             ArrayList<ViewController> _children = new ArrayList();
97 119
             for (int i = 0; i < children.size(); i++) {
98
-                final LayoutNode layoutTree = LayoutNodeParser.parse(JSONParser.parse(children.getMap(i)));
99
-                _children.add(newLayoutFactory().create(layoutTree));
120
+                final LayoutNode layoutTree = LayoutNodeParser.parse(jsonParser.parse(children.getMap(i)));
121
+                _children.add(layoutFactory.create(layoutTree));
100 122
             }
101 123
             navigator().setStackRoot(onComponentId, _children, new NativeCommandListener(commandId, promise, eventEmitter, now));
102 124
         });
103 125
     }
104 126
 
105
-	@ReactMethod
106
-	public void pop(String commandId, String componentId, @Nullable ReadableMap mergeOptions, Promise promise) {
107
-		handle(() -> navigator().pop(componentId, parse(mergeOptions), new NativeCommandListener(commandId, promise, eventEmitter, now)));
108
-	}
127
+    @ReactMethod
128
+    public void pop(String commandId, String componentId, @Nullable ReadableMap mergeOptions, Promise promise) {
129
+        handle(() -> navigator().pop(componentId, parse(mergeOptions), new NativeCommandListener(commandId, promise, eventEmitter, now)));
130
+    }
109 131
 
110
-	@ReactMethod
111
-	public void popTo(String commandId, String componentId, @Nullable ReadableMap mergeOptions, Promise promise) {
112
-		handle(() -> navigator().popTo(componentId, parse(mergeOptions), new NativeCommandListener(commandId, promise, eventEmitter, now)));
113
-	}
132
+    @ReactMethod
133
+    public void popTo(String commandId, String componentId, @Nullable ReadableMap mergeOptions, Promise promise) {
134
+        handle(() -> navigator().popTo(componentId, parse(mergeOptions), new NativeCommandListener(commandId, promise, eventEmitter, now)));
135
+    }
114 136
 
115
-	@ReactMethod
116
-	public void popToRoot(String commandId, String componentId, @Nullable ReadableMap mergeOptions, Promise promise) {
117
-		handle(() -> navigator().popToRoot(componentId, parse(mergeOptions), new NativeCommandListener(commandId, promise, eventEmitter, now)));
118
-	}
137
+    @ReactMethod
138
+    public void popToRoot(String commandId, String componentId, @Nullable ReadableMap mergeOptions, Promise promise) {
139
+        handle(() -> navigator().popToRoot(componentId, parse(mergeOptions), new NativeCommandListener(commandId, promise, eventEmitter, now)));
140
+    }
119 141
 
120
-	@ReactMethod
121
-	public void showModal(String commandId, ReadableMap rawLayoutTree, Promise promise) {
122
-		final LayoutNode layoutTree = LayoutNodeParser.parse(JSONParser.parse(rawLayoutTree));
123
-		handle(() -> {
124
-            final ViewController viewController = newLayoutFactory().create(layoutTree);
142
+    @ReactMethod
143
+    public void showModal(String commandId, ReadableMap rawLayoutTree, Promise promise) {
144
+        final LayoutNode layoutTree = LayoutNodeParser.parse(jsonParser.parse(rawLayoutTree));
145
+        handle(() -> {
146
+            final ViewController viewController = layoutFactory.create(layoutTree);
125 147
             navigator().showModal(viewController, new NativeCommandListener(commandId, promise, eventEmitter, now));
126 148
         });
127
-	}
149
+    }
128 150
 
129
-	@ReactMethod
130
-	public void dismissModal(String commandId, String componentId, @Nullable ReadableMap mergeOptions, Promise promise) {
131
-		handle(() -> {
151
+    @ReactMethod
152
+    public void dismissModal(String commandId, String componentId, @Nullable ReadableMap mergeOptions, Promise promise) {
153
+        handle(() -> {
132 154
             navigator().mergeOptions(componentId, parse(mergeOptions));
133 155
             navigator().dismissModal(componentId, new NativeCommandListener(commandId, promise, eventEmitter, now));
134 156
         });
135
-	}
157
+    }
136 158
 
137 159
     @ReactMethod
138
-	public void dismissAllModals(String commandId, @Nullable ReadableMap mergeOptions, Promise promise) {
139
-		handle(() -> navigator().dismissAllModals(parse(mergeOptions), new NativeCommandListener(commandId, promise, eventEmitter, now)));
140
-	}
160
+    public void dismissAllModals(String commandId, @Nullable ReadableMap mergeOptions, Promise promise) {
161
+        handle(() -> navigator().dismissAllModals(parse(mergeOptions), new NativeCommandListener(commandId, promise, eventEmitter, now)));
162
+    }
141 163
 
142
-	@ReactMethod
143
-	public void showOverlay(String commandId, ReadableMap rawLayoutTree, Promise promise) {
144
-        final LayoutNode layoutTree = LayoutNodeParser.parse(JSONParser.parse(rawLayoutTree));
164
+    @ReactMethod
165
+    public void showOverlay(String commandId, ReadableMap rawLayoutTree, Promise promise) {
166
+        final LayoutNode layoutTree = LayoutNodeParser.parse(jsonParser.parse(rawLayoutTree));
145 167
         handle(() -> {
146
-            final ViewController viewController = newLayoutFactory().create(layoutTree);
168
+            final ViewController viewController = layoutFactory.create(layoutTree);
147 169
             navigator().showOverlay(viewController, new NativeCommandListener(commandId, promise, eventEmitter, now));
148 170
         });
149
-	}
150
-
151
-	@ReactMethod
152
-	public void dismissOverlay(String commandId, String componentId, Promise promise) {
153
-		handle(() -> navigator().dismissOverlay(componentId, new NativeCommandListener(commandId, promise, eventEmitter, now)));
154
-	}
155
-
156
-	private Navigator navigator() {
157
-		return activity().getNavigator();
158
-	}
171
+    }
159 172
 
160
-	@NonNull
161
-	private LayoutFactory newLayoutFactory() {
162
-		return new LayoutFactory(activity(),
163
-                navigator().getChildRegistry(),
164
-                reactInstanceManager,
165
-                eventEmitter,
166
-                externalComponentCreator(),
167
-                navigator().getDefaultOptions()
168
-        );
169
-	}
173
+    @ReactMethod
174
+    public void dismissOverlay(String commandId, String componentId, Promise promise) {
175
+        handle(() -> navigator().dismissOverlay(componentId, new NativeCommandListener(commandId, promise, eventEmitter, now)));
176
+    }
170 177
 
171
-    private  Options parse(@Nullable ReadableMap mergeOptions) {
172
-        return mergeOptions == null ? Options.EMPTY : Options.parse(new TypefaceLoader(activity()), JSONParser.parse(mergeOptions));
178
+    private Navigator navigator() {
179
+        return activity().getNavigator();
173 180
     }
174 181
 
175
-	private Map<String, ExternalComponentCreator> externalComponentCreator() {
176
-        return ((NavigationApplication) activity().getApplication()).getExternalComponents();
182
+    private Options parse(@Nullable ReadableMap mergeOptions) {
183
+        return mergeOptions ==
184
+               null ? Options.EMPTY : Options.parse(new TypefaceLoader(activity()), jsonParser.parse(mergeOptions));
177 185
     }
178 186
 
179
-	private void handle(Runnable task) {
180
-		if (activity() == null || activity().isFinishing()) return;
181
-		UiThread.post(task);
182
-	}
187
+    private void handle(Runnable task) {
188
+        if (activity() == null || activity().isFinishing()) return;
189
+        UiThread.post(task);
190
+    }
183 191
 
184 192
     private NavigationActivity activity() {
185 193
         return (NavigationActivity) getCurrentActivity();

+ 20
- 10
lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationPackage.java View File

@@ -1,30 +1,40 @@
1 1
 package com.reactnativenavigation.react;
2 2
 
3
+import android.support.annotation.NonNull;
4
+
3 5
 import com.facebook.react.ReactNativeHost;
4 6
 import com.facebook.react.ReactPackage;
5 7
 import com.facebook.react.bridge.NativeModule;
6 8
 import com.facebook.react.bridge.ReactApplicationContext;
7 9
 import com.facebook.react.uimanager.ViewManager;
10
+import com.reactnativenavigation.parse.LayoutFactory;
8 11
 
9 12
 import java.util.Collections;
10 13
 import java.util.List;
11 14
 
12 15
 public class NavigationPackage implements ReactPackage {
13 16
 
14
-	private ReactNativeHost reactNativeHost;
17
+    private ReactNativeHost reactNativeHost;
15 18
 
16
-	@SuppressWarnings("WeakerAccess")
19
+    @SuppressWarnings("WeakerAccess")
17 20
     public NavigationPackage(final ReactNativeHost reactNativeHost) {
18
-		this.reactNativeHost = reactNativeHost;
19
-	}
21
+        this.reactNativeHost = reactNativeHost;
22
+    }
20 23
 
21
-	@Override
22
-	public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
23
-		return Collections.singletonList(new NavigationModule(reactContext, reactNativeHost.getReactInstanceManager()));
24
-	}
24
+    @NonNull
25
+    @Override
26
+    public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
27
+        return Collections.singletonList(new NavigationModule(
28
+                        reactContext,
29
+                        reactNativeHost.getReactInstanceManager(),
30
+                        new LayoutFactory(reactNativeHost.getReactInstanceManager())
31
+                )
32
+        );
33
+    }
25 34
 
26
-	@Override
27
-	public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
35
+    @NonNull
36
+    @Override
37
+    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
28 38
         return Collections.singletonList(new ElementViewManager());
29 39
     }
30 40
 }

+ 9
- 5
lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationReactInitializer.java View File

@@ -11,33 +11,36 @@ public class NavigationReactInitializer implements ReactInstanceManager.ReactIns
11 11
 	private final ReactInstanceManager reactInstanceManager;
12 12
 	private final DevPermissionRequest devPermissionRequest;
13 13
 	private boolean waitingForAppLaunchEvent = true;
14
+	private boolean isActivityReadyForUi = false;
14 15
 
15
-	public NavigationReactInitializer(ReactInstanceManager reactInstanceManager, boolean isDebug) {
16
+	NavigationReactInitializer(ReactInstanceManager reactInstanceManager, boolean isDebug) {
16 17
 		this.reactInstanceManager = reactInstanceManager;
17 18
 		this.devPermissionRequest = new DevPermissionRequest(isDebug);
18 19
 	}
19 20
 
20
-	public void onActivityCreated(final NavigationActivity activity) {
21
+	void onActivityCreated() {
21 22
 		reactInstanceManager.addReactInstanceEventListener(this);
22 23
 		waitingForAppLaunchEvent = true;
23 24
 	}
24 25
 
25
-	public void onActivityResumed(NavigationActivity activity) {
26
+	void onActivityResumed(NavigationActivity activity) {
26 27
 		if (devPermissionRequest.shouldAskPermission(activity)) {
27 28
 			devPermissionRequest.askPermission(activity);
28 29
 		} else {
29 30
 			reactInstanceManager.onHostResume(activity, activity);
31
+            isActivityReadyForUi = true;
30 32
 			prepareReactApp();
31 33
 		}
32 34
 	}
33 35
 
34
-	public void onActivityPaused(NavigationActivity activity) {
36
+	void onActivityPaused(NavigationActivity activity) {
37
+        isActivityReadyForUi = false;
35 38
 		if (reactInstanceManager.hasStartedCreatingInitialContext()) {
36 39
 			reactInstanceManager.onHostPause(activity);
37 40
 		}
38 41
 	}
39 42
 
40
-	public void onActivityDestroyed(NavigationActivity activity) {
43
+	void onActivityDestroyed(NavigationActivity activity) {
41 44
 		reactInstanceManager.removeReactInstanceEventListener(this);
42 45
 		if (reactInstanceManager.hasStartedCreatingInitialContext()) {
43 46
 			reactInstanceManager.onHostDestroy(activity);
@@ -55,6 +58,7 @@ public class NavigationReactInitializer implements ReactInstanceManager.ReactIns
55 58
 	}
56 59
 
57 60
 	private void emitAppLaunched(@NonNull ReactContext context) {
61
+	    if (!isActivityReadyForUi) return;
58 62
 		waitingForAppLaunchEvent = false;
59 63
 		new EventEmitter(context).appLaunched();
60 64
 	}

+ 1
- 1
lib/android/app/src/main/java/com/reactnativenavigation/react/ReactGateway.java View File

@@ -37,7 +37,7 @@ public class ReactGateway {
37 37
 	}
38 38
 
39 39
 	public void onActivityCreated(NavigationActivity activity) {
40
-		initializer.onActivityCreated(activity);
40
+		initializer.onActivityCreated();
41 41
         jsDevReloadHandler.setReloadListener(activity);
42 42
 	}
43 43
 

+ 2
- 2
lib/android/app/src/test/java/com/reactnativenavigation/parse/parsers/JSONParserTest.java View File

@@ -22,7 +22,7 @@ public class JSONParserTest extends BaseTest {
22 22
         input.putMap("keyMap", new JavaOnlyMap());
23 23
         input.putNull("bla");
24 24
 
25
-        JSONObject result = JSONParser.parse(input);
25
+        JSONObject result = new JSONParser().parse(input);
26 26
 
27 27
 
28 28
         assertThat(result.keys()).containsOnly(
@@ -52,7 +52,7 @@ public class JSONParserTest extends BaseTest {
52 52
         input.pushMap(new JavaOnlyMap());
53 53
         input.pushNull();
54 54
 
55
-        JSONArray result = JSONParser.parse(input);
55
+        JSONArray result = new JSONParser().parse(input);
56 56
         assertThat(result.length()).isEqualTo(6);
57 57
         assertThat(result.get(0)).isEqualTo("Hello");
58 58
         assertThat(result.get(1)).isEqualTo(123);

+ 81
- 0
lib/android/app/src/test/java/com/reactnativenavigation/react/NavigationModuleTest.java View File

@@ -0,0 +1,81 @@
1
+package com.reactnativenavigation.react;
2
+
3
+import com.facebook.react.ReactInstanceManager;
4
+import com.facebook.react.bridge.Promise;
5
+import com.facebook.react.bridge.ReactApplicationContext;
6
+import com.facebook.react.bridge.ReadableMap;
7
+import com.reactnativenavigation.BaseTest;
8
+import com.reactnativenavigation.NavigationActivity;
9
+import com.reactnativenavigation.parse.LayoutFactory;
10
+import com.reactnativenavigation.parse.LayoutNode;
11
+import com.reactnativenavigation.parse.parsers.JSONParser;
12
+import com.reactnativenavigation.viewcontrollers.ViewController;
13
+import com.reactnativenavigation.viewcontrollers.navigator.Navigator;
14
+
15
+import org.json.JSONException;
16
+import org.json.JSONObject;
17
+import org.junit.Test;
18
+
19
+import static org.mockito.ArgumentMatchers.any;
20
+import static org.mockito.ArgumentMatchers.eq;
21
+import static org.mockito.Mockito.mock;
22
+import static org.mockito.Mockito.spy;
23
+import static org.mockito.Mockito.verify;
24
+import static org.mockito.Mockito.when;
25
+
26
+public class NavigationModuleTest extends BaseTest {
27
+    private NavigationModule uut;
28
+    private Navigator navigator;
29
+    private JSONParser jsonParser;
30
+    private NavigationActivity activity;
31
+    private ReactApplicationContext reactApplicationContext;
32
+    private LayoutFactory layoutFactory;
33
+
34
+    @Override
35
+    public void beforeEach() {
36
+        jsonParser = mock(JSONParser.class);
37
+        navigator = mock(Navigator.class);
38
+        activity = mockActivity();
39
+        reactApplicationContext = mock(ReactApplicationContext.class);
40
+        layoutFactory = mock(LayoutFactory.class);
41
+
42
+        uut = spy(new NavigationModule(
43
+                reactApplicationContext,
44
+                mock(ReactInstanceManager.class),
45
+                jsonParser,
46
+                layoutFactory
47
+        ));
48
+    }
49
+
50
+    @Test
51
+    public void setRoot_delegatesToNavigator() throws JSONException {
52
+        when(reactApplicationContext.getCurrentActivity()).thenReturn(activity);
53
+        ReadableMap root = mock(ReadableMap.class);
54
+        when(jsonParser.parse(root)).thenReturn(rootJson());
55
+        ViewController rootViewController = mock(ViewController.class);
56
+        when(layoutFactory.create(any(LayoutNode.class))).thenReturn(rootViewController);
57
+
58
+        uut.setRoot("1", root, mock(Promise.class));
59
+        verify(navigator).setRoot(eq(rootViewController), any(), any());
60
+    }
61
+
62
+    private NavigationActivity mockActivity() {
63
+        NavigationActivity activity = mock(NavigationActivity.class);
64
+        when(activity.getNavigator()).thenReturn(navigator);
65
+        return activity;
66
+    }
67
+
68
+    private JSONObject rootJson() throws JSONException {
69
+        JSONObject root = new JSONObject();
70
+        root.put("root", componentJson());
71
+        return root;
72
+    }
73
+
74
+    private JSONObject componentJson() throws JSONException {
75
+        JSONObject component = new JSONObject();
76
+        component.put("id", "Component1");
77
+        component.put("type", "Component");
78
+        component.put("data", new JSONObject().put("name", "mockComponent"));
79
+        return component;
80
+    }
81
+}

+ 2
- 2
package.json View File

@@ -155,13 +155,13 @@
155 155
         "binaryPath": "playground/android/app/build/outputs/apk/debug/app-debug.apk",
156 156
         "build": "cd playground/android && ./gradlew app:assembleDebug app:assembleAndroidTest -DtestBuildType=debug",
157 157
         "type": "android.emulator",
158
-        "name": "Nexus_5_API_25"
158
+        "name": "Nexus_5X_API_23"
159 159
       },
160 160
       "android.emu.release.locked": {
161 161
         "binaryPath": "playground/android/app/build/outputs/apk/release/app-release.apk",
162 162
         "build": "cd playground/android && ./gradlew app:assembleRelease app:assembleAndroidTest -DtestBuildType=release",
163 163
         "type": "android.emulator",
164
-        "name": "Nexus_5_API_25"
164
+        "name": "Nexus_5X_API_23"
165 165
       }
166 166
     }
167 167
   }