diff --git a/src/__tests__/createAppContainer-test.js b/src/__tests__/createAppContainer-test.js
index a3dd106..2b6864d 100644
--- a/src/__tests__/createAppContainer-test.js
+++ b/src/__tests__/createAppContainer-test.js
@@ -1,8 +1,7 @@
import React from 'react';
-import { View } from 'react-native';
-
-import renderer from 'react-test-renderer';
-
+import { View, Linking } from 'react-native';
+import TestRenderer from 'react-test-renderer';
+import flushPromises from '../utils/flushPromises';
import createAppContainer, {
_TESTING_ONLY_reset_container_count,
} from '../createAppContainer';
@@ -59,10 +58,16 @@ describe('NavigationContainer', () => {
const NavigationContainer = createAppContainer(Stack);
describe('state.nav', () => {
- it("should be preloaded with the router's initial state", () => {
- const navigationContainer = renderer
- .create()
- .getInstance();
+ it("should be preloaded with the router's initial state", async () => {
+ const testRenderer = TestRenderer.create();
+ const navigationContainer = testRenderer.getInstance();
+
+ // the state only actually gets set asynchronously on componentDidMount
+ // thus on the first render the component returns null (or the result of renderLoadingExperimental)
+ expect(testRenderer.toJSON()).toEqual(null);
+ // wait for the state to be set
+ await flushPromises();
+
expect(navigationContainer.state.nav).toMatchObject({ index: 0 });
expect(navigationContainer.state.nav.routes).toBeInstanceOf(Array);
expect(navigationContainer.state.nav.routes.length).toBe(1);
@@ -70,14 +75,38 @@ describe('NavigationContainer', () => {
routeName: 'foo',
});
});
+ it('should be preloaded with the state corresponding to the URL', async () => {
+ const standardGetInitialURL = Linking.getInitialURL;
+ Linking.getInitialURL = () => Promise.resolve('host://elk');
+ const testRenderer = TestRenderer.create();
+ const navigationContainer = testRenderer.getInstance();
+
+ // the state only actually gets set asynchronously on componentDidMount
+ // wait for the state to be set
+ await flushPromises();
+
+ expect(navigationContainer.state.nav).toMatchObject({ index: 0 });
+ expect(navigationContainer.state.nav.routes).toBeInstanceOf(Array);
+ expect(navigationContainer.state.nav.routes.length).toBe(1);
+ expect(navigationContainer.state.nav.routes[0]).toMatchObject({
+ routeName: 'elk',
+ });
+
+ Linking.getInitialURL = standardGetInitialURL;
+ });
});
describe('dispatch', () => {
- it('returns true when given a valid action', () => {
- const navigationContainer = renderer
- .create()
- .getInstance();
+ it('returns true when given a valid action', async () => {
+ const testRenderer = TestRenderer.create();
+ const navigationContainer = testRenderer.getInstance();
+
+ // the state only actually gets set asynchronously on componentDidMount
+ // wait for the state to be set
+ await flushPromises();
+
jest.runOnlyPendingTimers();
+
expect(
navigationContainer.dispatch(
NavigationActions.navigate({ routeName: 'bar' })
@@ -85,20 +114,28 @@ describe('NavigationContainer', () => {
).toEqual(true);
});
- it('returns false when given an invalid action', () => {
- const navigationContainer = renderer
- .create()
- .getInstance();
+ it('returns false when given an invalid action', async () => {
+ const testRenderer = TestRenderer.create();
+ const navigationContainer = testRenderer.getInstance();
+
+ // the state only actually gets set asynchronously on componentDidMount
+ // wait for the state to be set
+ await flushPromises();
+
jest.runOnlyPendingTimers();
+
expect(navigationContainer.dispatch(NavigationActions.back())).toEqual(
false
);
});
- it('updates state.nav with an action by the next tick', () => {
- const navigationContainer = renderer
- .create()
- .getInstance();
+ it('updates state.nav with an action by the next tick', async () => {
+ const testRenderer = TestRenderer.create();
+ const navigationContainer = testRenderer.getInstance();
+
+ // the state only actually gets set asynchronously on componentDidMount
+ // wait for the state to be set
+ await flushPromises();
expect(
navigationContainer.dispatch(
@@ -115,10 +152,14 @@ describe('NavigationContainer', () => {
});
});
- it('does not discard actions when called twice in one tick', () => {
- const navigationContainer = renderer
- .create()
- .getInstance();
+ it('does not discard actions when called twice in one tick', async () => {
+ const testRenderer = TestRenderer.create();
+ const navigationContainer = testRenderer.getInstance();
+
+ // the state only actually gets set asynchronously on componentDidMount
+ // wait for the state to be set
+ await flushPromises();
+
const initialState = JSON.parse(
JSON.stringify(navigationContainer.state.nav)
);
@@ -153,10 +194,14 @@ describe('NavigationContainer', () => {
});
});
- it('does not discard actions when called more than 2 times in one tick', () => {
- const navigationContainer = renderer
- .create()
- .getInstance();
+ it('does not discard actions when called more than 2 times in one tick', async () => {
+ const testRenderer = TestRenderer.create();
+ const navigationContainer = testRenderer.getInstance();
+
+ // the state only actually gets set asynchronously on componentDidMount
+ // wait for the state to be set
+ await flushPromises();
+
const initialState = JSON.parse(
JSON.stringify(navigationContainer.state.nav)
);
@@ -238,7 +283,7 @@ describe('NavigationContainer', () => {
let spy = spyConsole();
- it('warns when you render more than one container explicitly', () => {
+ it('warns when you render more than one container explicitly', async () => {
class BlankScreen extends React.Component {
render() {
return ;
@@ -267,7 +312,12 @@ describe('NavigationContainer', () => {
})
);
- renderer.create().toJSON();
+ TestRenderer.create();
+
+ // the state only actually gets set asynchronously on componentDidMount
+ // wait for the state to be set
+ await flushPromises();
+
expect(spy).toMatchSnapshot();
});
});
diff --git a/src/createAppContainer.js b/src/createAppContainer.js
index 0c0daf5..56840b5 100644
--- a/src/createAppContainer.js
+++ b/src/createAppContainer.js
@@ -97,7 +97,9 @@ export default function createNavigationContainer(Component) {
this.state = {
nav:
- this._isStateful() && !props.persistenceKey
+ this._isStateful() &&
+ !props.persistenceKey &&
+ props.enableURLHandling === false
? Component.router.getStateForAction(this._initialAction)
: null,
};
@@ -220,23 +222,8 @@ export default function createNavigationContainer(Component) {
// Initialize state. This must be done *after* any async code
// so we don't end up with a different value for this.state.nav
// due to changes while async function was resolving
- let action = this._initialAction;
+ let action = null;
let startupState = this.state.nav;
- if (!startupState) {
- !!process.env.REACT_NAV_LOGGING &&
- console.log('Init new Navigation State');
- startupState = Component.router.getStateForAction(action);
- }
-
- // Pull persisted state from AsyncStorage
- if (startupStateJSON) {
- try {
- startupState = JSON.parse(startupStateJSON);
- _reactNavigationIsHydratingState = true;
- } catch (e) {
- /* do nothing */
- }
- }
// Pull state out of URL
if (parsedUrl) {
@@ -252,13 +239,29 @@ export default function createNavigationContainer(Component) {
parsedUrl
);
action = urlAction;
- startupState = Component.router.getStateForAction(
- urlAction,
- startupState
- );
+ // This is an **initial** URL, hence the null state as parameter
+ startupState = Component.router.getStateForAction(urlAction, null);
}
}
+ // Pull persisted state from AsyncStorage
+ if (startupStateJSON) {
+ try {
+ startupState = JSON.parse(startupStateJSON);
+ _reactNavigationIsHydratingState = true;
+ } catch (e) {
+ /* do nothing */
+ }
+ }
+
+ // Initialize state if there was no valid initial URL nor valid persisted state
+ if (!startupState) {
+ !!process.env.REACT_NAV_LOGGING &&
+ console.log('Init new Navigation State');
+ action = this._initialAction;
+ startupState = Component.router.getStateForAction(action);
+ }
+
const dispatchActions = () =>
this._actionEventSubscribers.forEach(subscriber =>
subscriber({
diff --git a/src/utils/flushPromises.js b/src/utils/flushPromises.js
new file mode 100644
index 0000000..ab7436b
--- /dev/null
+++ b/src/utils/flushPromises.js
@@ -0,0 +1,4 @@
+// see https://github.com/facebook/jest/issues/2157#issuecomment-279171856
+export default function flushPromises() {
+ return new Promise(resolve => setImmediate(resolve));
+}