mirror of
https://github.com/status-im/react-navigation.git
synced 2025-02-24 09:08:15 +00:00
Routers: Deep Linking Overhaul (#4590)
* deep linking overhaul * clean up PlatformHelpers this had previously been required for old versions of react native and react-native-web
This commit is contained in:
parent
3d06d19d6a
commit
4e384f8057
@ -33,6 +33,7 @@
|
|||||||
"create-react-context": "^0.2.1",
|
"create-react-context": "^0.2.1",
|
||||||
"hoist-non-react-statics": "^2.2.0",
|
"hoist-non-react-statics": "^2.2.0",
|
||||||
"path-to-regexp": "^1.7.0",
|
"path-to-regexp": "^1.7.0",
|
||||||
|
"query-string": "^6.1.0",
|
||||||
"react-lifecycles-compat": "^3",
|
"react-lifecycles-compat": "^3",
|
||||||
"react-native-safe-area-view": "^0.8.0",
|
"react-native-safe-area-view": "^0.8.0",
|
||||||
"react-navigation-deprecated-tab-navigator": "1.3.0",
|
"react-navigation-deprecated-tab-navigator": "1.3.0",
|
||||||
|
@ -6,6 +6,7 @@ import NavigationActions from './NavigationActions';
|
|||||||
import getNavigation from './getNavigation';
|
import getNavigation from './getNavigation';
|
||||||
import invariant from './utils/invariant';
|
import invariant from './utils/invariant';
|
||||||
import docsUrl from './utils/docsUrl';
|
import docsUrl from './utils/docsUrl';
|
||||||
|
import { urlToPathAndParams } from './routers/pathUtils';
|
||||||
|
|
||||||
function isStateful(props) {
|
function isStateful(props) {
|
||||||
return !props.navigation;
|
return !props.navigation;
|
||||||
@ -128,23 +129,8 @@ export default function createNavigationContainer(Component) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_urlToPathAndParams(url) {
|
|
||||||
const params = {};
|
|
||||||
const delimiter = this.props.uriPrefix || '://';
|
|
||||||
let path = url.split(delimiter)[1];
|
|
||||||
if (typeof path === 'undefined') {
|
|
||||||
path = url;
|
|
||||||
} else if (path === '') {
|
|
||||||
path = '/';
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
path,
|
|
||||||
params,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
_handleOpenURL = ({ url }) => {
|
_handleOpenURL = ({ url }) => {
|
||||||
const parsedUrl = this._urlToPathAndParams(url);
|
const parsedUrl = urlToPathAndParams(url, this.props.uriPrefix);
|
||||||
if (parsedUrl) {
|
if (parsedUrl) {
|
||||||
const { path, params } = parsedUrl;
|
const { path, params } = parsedUrl;
|
||||||
const action = Component.router.getActionForPathAndParams(path, params);
|
const action = Component.router.getActionForPathAndParams(path, params);
|
||||||
@ -213,11 +199,11 @@ export default function createNavigationContainer(Component) {
|
|||||||
Linking.addEventListener('url', this._handleOpenURL);
|
Linking.addEventListener('url', this._handleOpenURL);
|
||||||
|
|
||||||
// Pull out anything that can impact state
|
// Pull out anything that can impact state
|
||||||
const { persistenceKey } = this.props;
|
const { persistenceKey, uriPrefix } = this.props;
|
||||||
const startupStateJSON =
|
const startupStateJSON =
|
||||||
persistenceKey && (await AsyncStorage.getItem(persistenceKey));
|
persistenceKey && (await AsyncStorage.getItem(persistenceKey));
|
||||||
const url = await Linking.getInitialURL();
|
const url = await Linking.getInitialURL();
|
||||||
const parsedUrl = url && this._urlToPathAndParams(url);
|
const parsedUrl = url && urlToPathAndParams(url, uriPrefix);
|
||||||
|
|
||||||
// Initialize state. This must be done *after* any async code
|
// Initialize state. This must be done *after* any async code
|
||||||
// so we don't end up with a different value for this.state.nav
|
// so we don't end up with a different value for this.state.nav
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import pathToRegexp from 'path-to-regexp';
|
|
||||||
|
|
||||||
import NavigationActions from '../NavigationActions';
|
import NavigationActions from '../NavigationActions';
|
||||||
import StackActions from './StackActions';
|
import StackActions from './StackActions';
|
||||||
import createConfigGetter from './createConfigGetter';
|
import createConfigGetter from './createConfigGetter';
|
||||||
@ -8,14 +6,7 @@ import StateUtils from '../StateUtils';
|
|||||||
import validateRouteConfigMap from './validateRouteConfigMap';
|
import validateRouteConfigMap from './validateRouteConfigMap';
|
||||||
import invariant from '../utils/invariant';
|
import invariant from '../utils/invariant';
|
||||||
import { generateKey } from './KeyGenerator';
|
import { generateKey } from './KeyGenerator';
|
||||||
|
import { createPathParser } from './pathUtils';
|
||||||
function isEmpty(obj) {
|
|
||||||
if (!obj) return true;
|
|
||||||
for (let key in obj) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function behavesLikePushAction(action) {
|
function behavesLikePushAction(action) {
|
||||||
return (
|
return (
|
||||||
@ -56,8 +47,6 @@ export default (routeConfigs, stackConfig = {}) => {
|
|||||||
const initialRouteName = stackConfig.initialRouteName || routeNames[0];
|
const initialRouteName = stackConfig.initialRouteName || routeNames[0];
|
||||||
|
|
||||||
const initialChildRouter = childRouters[initialRouteName];
|
const initialChildRouter = childRouters[initialRouteName];
|
||||||
const pathsByRouteNames = { ...stackConfig.paths } || {};
|
|
||||||
let paths = [];
|
|
||||||
|
|
||||||
function getInitialState(action) {
|
function getInitialState(action) {
|
||||||
let route = {};
|
let route = {};
|
||||||
@ -115,37 +104,16 @@ export default (routeConfigs, stackConfig = {}) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build paths for each route
|
const {
|
||||||
routeNames.forEach(routeName => {
|
getPathAndParamsForRoute,
|
||||||
let pathPattern =
|
getActionForPathAndParams,
|
||||||
pathsByRouteNames[routeName] || routeConfigs[routeName].path;
|
} = createPathParser(
|
||||||
let matchExact = !!pathPattern && !childRouters[routeName];
|
childRouters,
|
||||||
if (pathPattern === undefined) {
|
routeConfigs,
|
||||||
pathPattern = routeName;
|
stackConfig.paths,
|
||||||
}
|
initialRouteName,
|
||||||
const keys = [];
|
initialRouteParams
|
||||||
let re, toPath, priority;
|
);
|
||||||
if (typeof pathPattern === 'string') {
|
|
||||||
// pathPattern may be either a string or a regexp object according to path-to-regexp docs.
|
|
||||||
re = pathToRegexp(pathPattern, keys);
|
|
||||||
toPath = pathToRegexp.compile(pathPattern);
|
|
||||||
priority = 0;
|
|
||||||
} else {
|
|
||||||
// for wildcard match
|
|
||||||
re = pathToRegexp('*', keys);
|
|
||||||
toPath = () => '';
|
|
||||||
matchExact = true;
|
|
||||||
priority = -1;
|
|
||||||
}
|
|
||||||
if (!matchExact) {
|
|
||||||
const wildcardRe = pathToRegexp(`${pathPattern}/*`, keys);
|
|
||||||
re = new RegExp(`(?:${re.source})|(?:${wildcardRe.source})`);
|
|
||||||
}
|
|
||||||
pathsByRouteNames[routeName] = { re, keys, toPath, priority };
|
|
||||||
});
|
|
||||||
|
|
||||||
paths = Object.entries(pathsByRouteNames);
|
|
||||||
paths.sort((a, b) => b[1].priority - a[1].priority);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
childRouters,
|
childRouters,
|
||||||
@ -559,121 +527,11 @@ export default (routeConfigs, stackConfig = {}) => {
|
|||||||
|
|
||||||
getPathAndParamsForState(state) {
|
getPathAndParamsForState(state) {
|
||||||
const route = state.routes[state.index];
|
const route = state.routes[state.index];
|
||||||
const routeName = route.routeName;
|
return getPathAndParamsForRoute(route);
|
||||||
const screen = getScreenForRouteName(routeConfigs, routeName);
|
|
||||||
const subPath = pathsByRouteNames[routeName].toPath(route.params);
|
|
||||||
let path = subPath;
|
|
||||||
let params = route.params;
|
|
||||||
if (screen && screen.router) {
|
|
||||||
const stateRoute = route;
|
|
||||||
// If it has a router it's a navigator.
|
|
||||||
// If it doesn't have router it's an ordinary React component.
|
|
||||||
const child = screen.router.getPathAndParamsForState(stateRoute);
|
|
||||||
path = subPath ? `${subPath}/${child.path}` : child.path;
|
|
||||||
params = child.params ? { ...params, ...child.params } : params;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
path,
|
|
||||||
params,
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getActionForPathAndParams(pathToResolve, inputParams) {
|
getActionForPathAndParams(path, params) {
|
||||||
// If the path is empty (null or empty string)
|
return getActionForPathAndParams(path, params);
|
||||||
// just return the initial route action
|
|
||||||
if (!pathToResolve) {
|
|
||||||
return NavigationActions.navigate({
|
|
||||||
routeName: initialRouteName,
|
|
||||||
params: inputParams,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const [pathNameToResolve, queryString] = pathToResolve.split('?');
|
|
||||||
|
|
||||||
// Attempt to match `pathNameToResolve` with a route in this router's
|
|
||||||
// routeConfigs
|
|
||||||
let matchedRouteName;
|
|
||||||
let pathMatch;
|
|
||||||
let pathMatchKeys;
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
|
||||||
for (const [routeName, path] of paths) {
|
|
||||||
const { re, keys } = path;
|
|
||||||
pathMatch = re.exec(pathNameToResolve);
|
|
||||||
if (pathMatch && pathMatch.length) {
|
|
||||||
pathMatchKeys = keys;
|
|
||||||
matchedRouteName = routeName;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We didn't match -- return null
|
|
||||||
if (!matchedRouteName) {
|
|
||||||
// If the path is empty (null or empty string)
|
|
||||||
// just return the initial route action
|
|
||||||
if (!pathToResolve) {
|
|
||||||
return NavigationActions.navigate({
|
|
||||||
routeName: initialRouteName,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine nested actions:
|
|
||||||
// If our matched route for this router is a child router,
|
|
||||||
// get the action for the path AFTER the matched path for this
|
|
||||||
// router
|
|
||||||
let nestedAction;
|
|
||||||
let nestedQueryString = queryString ? '?' + queryString : '';
|
|
||||||
if (childRouters[matchedRouteName]) {
|
|
||||||
nestedAction = childRouters[matchedRouteName].getActionForPathAndParams(
|
|
||||||
pathMatch.slice(pathMatchKeys.length).join('/') + nestedQueryString
|
|
||||||
);
|
|
||||||
if (!nestedAction) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// reduce the items of the query string. any query params may
|
|
||||||
// be overridden by path params
|
|
||||||
const queryParams = !isEmpty(inputParams)
|
|
||||||
? inputParams
|
|
||||||
: (queryString || '').split('&').reduce((result, item) => {
|
|
||||||
if (item !== '') {
|
|
||||||
const nextResult = result || {};
|
|
||||||
const [key, value] = item.split('=');
|
|
||||||
nextResult[key] = value;
|
|
||||||
return nextResult;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}, null);
|
|
||||||
|
|
||||||
// reduce the matched pieces of the path into the params
|
|
||||||
// of the route. `params` is null if there are no params.
|
|
||||||
const params = pathMatch.slice(1).reduce((result, matchResult, i) => {
|
|
||||||
const key = pathMatchKeys[i];
|
|
||||||
if (key.asterisk || !key) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
const nextResult = result || inputParams || {};
|
|
||||||
const paramName = key.name;
|
|
||||||
|
|
||||||
let decodedMatchResult;
|
|
||||||
try {
|
|
||||||
decodedMatchResult = decodeURIComponent(matchResult);
|
|
||||||
} catch (e) {
|
|
||||||
// ignore `URIError: malformed URI`
|
|
||||||
}
|
|
||||||
|
|
||||||
nextResult[paramName] = decodedMatchResult || matchResult;
|
|
||||||
return nextResult;
|
|
||||||
}, queryParams);
|
|
||||||
|
|
||||||
return NavigationActions.navigate({
|
|
||||||
routeName: matchedRouteName,
|
|
||||||
...(params ? { params } : {}),
|
|
||||||
...(nestedAction ? { action: nestedAction } : {}),
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getScreenOptions: createConfigGetter(
|
getScreenOptions: createConfigGetter(
|
||||||
|
@ -5,6 +5,7 @@ import createConfigGetter from './createConfigGetter';
|
|||||||
import NavigationActions from '../NavigationActions';
|
import NavigationActions from '../NavigationActions';
|
||||||
import StackActions from './StackActions';
|
import StackActions from './StackActions';
|
||||||
import validateRouteConfigMap from './validateRouteConfigMap';
|
import validateRouteConfigMap from './validateRouteConfigMap';
|
||||||
|
import { createPathParser } from './pathUtils';
|
||||||
|
|
||||||
const defaultActionCreators = (route, navStateKey) => ({});
|
const defaultActionCreators = (route, navStateKey) => ({});
|
||||||
|
|
||||||
@ -21,7 +22,7 @@ export default (routeConfigs, config = {}) => {
|
|||||||
validateRouteConfigMap(routeConfigs);
|
validateRouteConfigMap(routeConfigs);
|
||||||
|
|
||||||
const order = config.order || Object.keys(routeConfigs);
|
const order = config.order || Object.keys(routeConfigs);
|
||||||
const paths = config.paths || {};
|
|
||||||
const getCustomActionCreators =
|
const getCustomActionCreators =
|
||||||
config.getCustomActionCreators || defaultActionCreators;
|
config.getCustomActionCreators || defaultActionCreators;
|
||||||
|
|
||||||
@ -36,16 +37,24 @@ export default (routeConfigs, config = {}) => {
|
|||||||
const childRouters = {};
|
const childRouters = {};
|
||||||
order.forEach(routeName => {
|
order.forEach(routeName => {
|
||||||
const routeConfig = routeConfigs[routeName];
|
const routeConfig = routeConfigs[routeName];
|
||||||
if (!paths[routeName]) {
|
|
||||||
paths[routeName] =
|
|
||||||
typeof routeConfig.path === 'string' ? routeConfig.path : routeName;
|
|
||||||
}
|
|
||||||
childRouters[routeName] = null;
|
childRouters[routeName] = null;
|
||||||
const screen = getScreenForRouteName(routeConfigs, routeName);
|
const screen = getScreenForRouteName(routeConfigs, routeName);
|
||||||
if (screen.router) {
|
if (screen.router) {
|
||||||
childRouters[routeName] = screen.router;
|
childRouters[routeName] = screen.router;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
getPathAndParamsForRoute,
|
||||||
|
getActionForPathAndParams,
|
||||||
|
} = createPathParser(
|
||||||
|
childRouters,
|
||||||
|
routeConfigs,
|
||||||
|
config.paths,
|
||||||
|
initialRouteName,
|
||||||
|
initialRouteParams
|
||||||
|
);
|
||||||
|
|
||||||
if (initialRouteIndex === -1) {
|
if (initialRouteIndex === -1) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Invalid initialRouteName '${initialRouteName}'.` +
|
`Invalid initialRouteName '${initialRouteName}'.` +
|
||||||
@ -309,73 +318,11 @@ export default (routeConfigs, config = {}) => {
|
|||||||
|
|
||||||
getPathAndParamsForState(state) {
|
getPathAndParamsForState(state) {
|
||||||
const route = state.routes[state.index];
|
const route = state.routes[state.index];
|
||||||
const routeName = order[state.index];
|
return getPathAndParamsForRoute(route);
|
||||||
const subPath = paths[routeName];
|
|
||||||
const screen = getScreenForRouteName(routeConfigs, routeName);
|
|
||||||
let path = subPath;
|
|
||||||
let params = route.params;
|
|
||||||
if (screen && screen.router) {
|
|
||||||
const stateRoute = route;
|
|
||||||
// If it has a router it's a navigator.
|
|
||||||
// If it doesn't have router it's an ordinary React component.
|
|
||||||
const child = screen.router.getPathAndParamsForState(stateRoute);
|
|
||||||
path = subPath ? `${subPath}/${child.path}` : child.path;
|
|
||||||
params = child.params ? { ...params, ...child.params } : params;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
path,
|
|
||||||
params,
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets an optional action, based on a relative path and query params.
|
|
||||||
*
|
|
||||||
* This will return null if there is no action matched
|
|
||||||
*/
|
|
||||||
getActionForPathAndParams(path, params) {
|
getActionForPathAndParams(path, params) {
|
||||||
if (!path) {
|
return getActionForPathAndParams(path, params);
|
||||||
return NavigationActions.navigate({
|
|
||||||
routeName: initialRouteName,
|
|
||||||
params,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
order
|
|
||||||
.map(childId => {
|
|
||||||
const parts = path.split('/');
|
|
||||||
const pathToTest = paths[childId];
|
|
||||||
const partsInTestPath = pathToTest.split('/').length;
|
|
||||||
const pathPartsToTest = parts.slice(0, partsInTestPath).join('/');
|
|
||||||
if (pathPartsToTest === pathToTest) {
|
|
||||||
const childRouter = childRouters[childId];
|
|
||||||
const action = NavigationActions.navigate({
|
|
||||||
routeName: childId,
|
|
||||||
});
|
|
||||||
if (childRouter && childRouter.getActionForPathAndParams) {
|
|
||||||
action.action = childRouter.getActionForPathAndParams(
|
|
||||||
parts.slice(partsInTestPath).join('/'),
|
|
||||||
params
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (params) {
|
|
||||||
action.params = params;
|
|
||||||
}
|
|
||||||
return action;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
})
|
|
||||||
.find(action => !!action) ||
|
|
||||||
order
|
|
||||||
.map(childId => {
|
|
||||||
const childRouter = childRouters[childId];
|
|
||||||
return (
|
|
||||||
childRouter && childRouter.getActionForPathAndParams(path, params)
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.find(action => !!action) ||
|
|
||||||
null
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getScreenOptions: createConfigGetter(
|
getScreenOptions: createConfigGetter(
|
||||||
|
299
src/routers/__tests__/PathHandling-test.js
Normal file
299
src/routers/__tests__/PathHandling-test.js
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
/* eslint no-shadow:0, react/no-multi-comp:0, react/display-name:0 */
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import SwitchRouter from '../SwitchRouter';
|
||||||
|
import StackRouter from '../StackRouter';
|
||||||
|
import StackActions from '../StackActions';
|
||||||
|
import NavigationActions from '../../NavigationActions';
|
||||||
|
import { urlToPathAndParams } from '../pathUtils';
|
||||||
|
import { _TESTING_ONLY_normalize_keys } from '../KeyGenerator';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
_TESTING_ONLY_normalize_keys();
|
||||||
|
});
|
||||||
|
|
||||||
|
const ListScreen = () => <div />;
|
||||||
|
|
||||||
|
const ProfileNavigator = () => <div />;
|
||||||
|
ProfileNavigator.router = StackRouter({
|
||||||
|
list: {
|
||||||
|
path: 'list/:id',
|
||||||
|
screen: ListScreen,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const MainNavigator = () => <div />;
|
||||||
|
MainNavigator.router = StackRouter({
|
||||||
|
profile: {
|
||||||
|
path: 'p/:id',
|
||||||
|
screen: ProfileNavigator,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const LoginScreen = () => <div />;
|
||||||
|
|
||||||
|
const AuthNavigator = () => <div />;
|
||||||
|
AuthNavigator.router = StackRouter({
|
||||||
|
login: {
|
||||||
|
screen: LoginScreen,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const BarScreen = () => <div />;
|
||||||
|
|
||||||
|
class FooNavigator extends React.Component {
|
||||||
|
static router = StackRouter({
|
||||||
|
bar: {
|
||||||
|
path: 'b/:barThing',
|
||||||
|
screen: BarScreen,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
render() {
|
||||||
|
return <div />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const PersonScreen = () => <div />;
|
||||||
|
|
||||||
|
const performRouterTest = createTestRouter => {
|
||||||
|
const testRouter = createTestRouter({
|
||||||
|
main: {
|
||||||
|
screen: MainNavigator,
|
||||||
|
},
|
||||||
|
baz: {
|
||||||
|
path: null,
|
||||||
|
screen: FooNavigator,
|
||||||
|
},
|
||||||
|
auth: {
|
||||||
|
screen: AuthNavigator,
|
||||||
|
},
|
||||||
|
person: {
|
||||||
|
path: 'people/:id',
|
||||||
|
screen: PersonScreen,
|
||||||
|
},
|
||||||
|
foo: {
|
||||||
|
path: 'fo/:fooThing',
|
||||||
|
screen: FooNavigator,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Handles empty URIs', () => {
|
||||||
|
const router = createTestRouter(
|
||||||
|
{
|
||||||
|
Foo: {
|
||||||
|
screen: () => <div />,
|
||||||
|
},
|
||||||
|
Bar: {
|
||||||
|
screen: () => <div />,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ initialRouteName: 'Bar', initialRouteParams: { foo: 42 } }
|
||||||
|
);
|
||||||
|
const action = router.getActionForPathAndParams('');
|
||||||
|
expect(action).toEqual({
|
||||||
|
type: NavigationActions.NAVIGATE,
|
||||||
|
routeName: 'Bar',
|
||||||
|
params: { foo: 42 },
|
||||||
|
});
|
||||||
|
const state = router.getStateForAction(action);
|
||||||
|
expect(state.routes[state.index]).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
routeName: 'Bar',
|
||||||
|
params: { foo: 42 },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Gets deep path with pure wildcard match', () => {
|
||||||
|
const ScreenA = () => <div />;
|
||||||
|
const ScreenB = () => <div />;
|
||||||
|
const ScreenC = () => <div />;
|
||||||
|
ScreenA.router = createTestRouter({
|
||||||
|
Boo: { path: 'boo', screen: ScreenC },
|
||||||
|
Baz: { path: 'baz/:bazId', screen: ScreenB },
|
||||||
|
});
|
||||||
|
ScreenC.router = createTestRouter({
|
||||||
|
Boo2: { path: '', screen: ScreenB },
|
||||||
|
});
|
||||||
|
const router = createTestRouter({
|
||||||
|
Foo: {
|
||||||
|
path: null,
|
||||||
|
screen: ScreenA,
|
||||||
|
},
|
||||||
|
Bar: {
|
||||||
|
screen: ScreenB,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
{
|
||||||
|
const state = {
|
||||||
|
index: 0,
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
index: 1,
|
||||||
|
key: 'Foo',
|
||||||
|
routeName: 'Foo',
|
||||||
|
params: {
|
||||||
|
id: '123',
|
||||||
|
},
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
index: 0,
|
||||||
|
key: 'Boo',
|
||||||
|
routeName: 'Boo',
|
||||||
|
routes: [{ key: 'Boo2', routeName: 'Boo2' }],
|
||||||
|
},
|
||||||
|
{ key: 'Baz', routeName: 'Baz', params: { bazId: '321' } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ key: 'Bar', routeName: 'Bar' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const { path, params } = router.getPathAndParamsForState(state);
|
||||||
|
expect(path).toEqual('baz/321');
|
||||||
|
expect(params.id).toEqual('123');
|
||||||
|
expect(params.bazId).toEqual('321');
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const state = {
|
||||||
|
index: 0,
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
index: 0,
|
||||||
|
key: 'Foo',
|
||||||
|
routeName: 'Foo',
|
||||||
|
params: {
|
||||||
|
id: '123',
|
||||||
|
},
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
index: 0,
|
||||||
|
key: 'Boo',
|
||||||
|
routeName: 'Boo',
|
||||||
|
routes: [{ key: 'Boo2', routeName: 'Boo2' }],
|
||||||
|
},
|
||||||
|
{ key: 'Baz', routeName: 'Baz', params: { bazId: '321' } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ key: 'Bar', routeName: 'Bar' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const { path, params } = router.getPathAndParamsForState(state);
|
||||||
|
expect(path).toEqual('boo');
|
||||||
|
expect(params).toEqual({ id: '123' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('URI encoded string get passed to deep link', () => {
|
||||||
|
const uri = 'people/2018%2F02%2F07';
|
||||||
|
const action = testRouter.getActionForPathAndParams(uri);
|
||||||
|
expect(action).toEqual({
|
||||||
|
routeName: 'person',
|
||||||
|
params: {
|
||||||
|
id: '2018/02/07',
|
||||||
|
},
|
||||||
|
type: NavigationActions.NAVIGATE,
|
||||||
|
});
|
||||||
|
|
||||||
|
const malformedUri = 'people/%E0%A4%A';
|
||||||
|
const action2 = testRouter.getActionForPathAndParams(malformedUri);
|
||||||
|
expect(action2).toEqual({
|
||||||
|
routeName: 'person',
|
||||||
|
params: {
|
||||||
|
id: '%E0%A4%A',
|
||||||
|
},
|
||||||
|
type: NavigationActions.NAVIGATE,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Querystring params get passed to nested deep link', () => {
|
||||||
|
const action = testRouter.getActionForPathAndParams(
|
||||||
|
'main/p/4/list/10259959195',
|
||||||
|
{ code: 'test', foo: 'bar' }
|
||||||
|
);
|
||||||
|
expect(action).toEqual({
|
||||||
|
type: NavigationActions.NAVIGATE,
|
||||||
|
routeName: 'main',
|
||||||
|
params: {
|
||||||
|
code: 'test',
|
||||||
|
foo: 'bar',
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
type: NavigationActions.NAVIGATE,
|
||||||
|
routeName: 'profile',
|
||||||
|
params: {
|
||||||
|
id: '4',
|
||||||
|
code: 'test',
|
||||||
|
foo: 'bar',
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
type: NavigationActions.NAVIGATE,
|
||||||
|
routeName: 'list',
|
||||||
|
params: {
|
||||||
|
id: '10259959195',
|
||||||
|
code: 'test',
|
||||||
|
foo: 'bar',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const action2 = testRouter.getActionForPathAndParams(
|
||||||
|
'main/p/4/list/10259959195',
|
||||||
|
{ code: '', foo: 'bar' }
|
||||||
|
);
|
||||||
|
expect(action2).toEqual({
|
||||||
|
type: NavigationActions.NAVIGATE,
|
||||||
|
routeName: 'main',
|
||||||
|
params: {
|
||||||
|
code: '',
|
||||||
|
foo: 'bar',
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
type: NavigationActions.NAVIGATE,
|
||||||
|
routeName: 'profile',
|
||||||
|
params: {
|
||||||
|
id: '4',
|
||||||
|
code: '',
|
||||||
|
foo: 'bar',
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
type: NavigationActions.NAVIGATE,
|
||||||
|
routeName: 'list',
|
||||||
|
params: {
|
||||||
|
id: '10259959195',
|
||||||
|
code: '',
|
||||||
|
foo: 'bar',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('paths option on router overrides path from route config', () => {
|
||||||
|
const router = createTestRouter(
|
||||||
|
{
|
||||||
|
main: {
|
||||||
|
screen: MainNavigator,
|
||||||
|
},
|
||||||
|
baz: {
|
||||||
|
path: null,
|
||||||
|
screen: FooNavigator,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ paths: { baz: 'overridden' } }
|
||||||
|
);
|
||||||
|
const action = router.getActionForPathAndParams('overridden', {});
|
||||||
|
expect(action.type).toEqual(NavigationActions.NAVIGATE);
|
||||||
|
expect(action.routeName).toEqual('baz');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Path handling for stack router', () => {
|
||||||
|
performRouterTest(StackRouter);
|
||||||
|
});
|
||||||
|
describe('Path handling for switch router', () => {
|
||||||
|
performRouterTest(SwitchRouter);
|
||||||
|
});
|
@ -208,6 +208,7 @@ describe('StackRouter', () => {
|
|||||||
expect(AuthNavigator.router.getActionForPathAndParams('login')).toEqual({
|
expect(AuthNavigator.router.getActionForPathAndParams('login')).toEqual({
|
||||||
type: NavigationActions.NAVIGATE,
|
type: NavigationActions.NAVIGATE,
|
||||||
routeName: 'login',
|
routeName: 'login',
|
||||||
|
params: {},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -223,7 +224,10 @@ describe('StackRouter', () => {
|
|||||||
|
|
||||||
test('Parses paths with a query', () => {
|
test('Parses paths with a query', () => {
|
||||||
expect(
|
expect(
|
||||||
TestStackRouter.getActionForPathAndParams('people/foo?code=test&foo=bar')
|
TestStackRouter.getActionForPathAndParams('people/foo', {
|
||||||
|
code: 'test',
|
||||||
|
foo: 'bar',
|
||||||
|
})
|
||||||
).toEqual({
|
).toEqual({
|
||||||
type: NavigationActions.NAVIGATE,
|
type: NavigationActions.NAVIGATE,
|
||||||
routeName: 'person',
|
routeName: 'person',
|
||||||
@ -237,7 +241,10 @@ describe('StackRouter', () => {
|
|||||||
|
|
||||||
test('Parses paths with an empty query value', () => {
|
test('Parses paths with an empty query value', () => {
|
||||||
expect(
|
expect(
|
||||||
TestStackRouter.getActionForPathAndParams('people/foo?code=&foo=bar')
|
TestStackRouter.getActionForPathAndParams('people/foo', {
|
||||||
|
code: '',
|
||||||
|
foo: 'bar',
|
||||||
|
})
|
||||||
).toEqual({
|
).toEqual({
|
||||||
type: NavigationActions.NAVIGATE,
|
type: NavigationActions.NAVIGATE,
|
||||||
routeName: 'person',
|
routeName: 'person',
|
||||||
@ -255,9 +262,11 @@ describe('StackRouter', () => {
|
|||||||
expect(action).toEqual({
|
expect(action).toEqual({
|
||||||
type: NavigationActions.NAVIGATE,
|
type: NavigationActions.NAVIGATE,
|
||||||
routeName: 'auth',
|
routeName: 'auth',
|
||||||
|
params: {},
|
||||||
action: {
|
action: {
|
||||||
type: NavigationActions.NAVIGATE,
|
type: NavigationActions.NAVIGATE,
|
||||||
routeName: 'login',
|
routeName: 'login',
|
||||||
|
params: {},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -268,6 +277,7 @@ describe('StackRouter', () => {
|
|||||||
expect(action).toEqual({
|
expect(action).toEqual({
|
||||||
type: NavigationActions.NAVIGATE,
|
type: NavigationActions.NAVIGATE,
|
||||||
routeName: 'main',
|
routeName: 'main',
|
||||||
|
params: {},
|
||||||
action: {
|
action: {
|
||||||
type: NavigationActions.NAVIGATE,
|
type: NavigationActions.NAVIGATE,
|
||||||
routeName: 'profile',
|
routeName: 'profile',
|
||||||
@ -291,6 +301,7 @@ describe('StackRouter', () => {
|
|||||||
expect(action).toEqual({
|
expect(action).toEqual({
|
||||||
type: NavigationActions.NAVIGATE,
|
type: NavigationActions.NAVIGATE,
|
||||||
routeName: 'baz',
|
routeName: 'baz',
|
||||||
|
params: {},
|
||||||
action: {
|
action: {
|
||||||
type: NavigationActions.NAVIGATE,
|
type: NavigationActions.NAVIGATE,
|
||||||
routeName: 'bar',
|
routeName: 'bar',
|
||||||
@ -313,9 +324,11 @@ describe('StackRouter', () => {
|
|||||||
expect(action).toEqual({
|
expect(action).toEqual({
|
||||||
type: NavigationActions.NAVIGATE,
|
type: NavigationActions.NAVIGATE,
|
||||||
routeName: 'auth',
|
routeName: 'auth',
|
||||||
|
params: {},
|
||||||
action: {
|
action: {
|
||||||
type: NavigationActions.NAVIGATE,
|
type: NavigationActions.NAVIGATE,
|
||||||
routeName: 'login',
|
routeName: 'login',
|
||||||
|
params: {},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -1047,6 +1060,48 @@ describe('StackRouter', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Gets deep path (stack behavior)', () => {
|
||||||
|
const ScreenA = () => <div />;
|
||||||
|
const ScreenB = () => <div />;
|
||||||
|
ScreenA.router = StackRouter({
|
||||||
|
Boo: { path: 'boo', screen: ScreenB },
|
||||||
|
Baz: { path: 'baz/:bazId', screen: ScreenB },
|
||||||
|
});
|
||||||
|
const router = StackRouter({
|
||||||
|
Foo: {
|
||||||
|
path: 'f/:id',
|
||||||
|
screen: ScreenA,
|
||||||
|
},
|
||||||
|
Bar: {
|
||||||
|
screen: ScreenB,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
index: 0,
|
||||||
|
isTransitioning: false,
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
index: 1,
|
||||||
|
key: 'Foo',
|
||||||
|
routeName: 'Foo',
|
||||||
|
params: {
|
||||||
|
id: '123',
|
||||||
|
},
|
||||||
|
routes: [
|
||||||
|
{ key: 'Boo', routeName: 'Boo' },
|
||||||
|
{ key: 'Baz', routeName: 'Baz', params: { bazId: '321' } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ key: 'Bar', routeName: 'Bar' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const { path, params } = router.getPathAndParamsForState(state);
|
||||||
|
expect(path).toEqual('f/123/baz/321');
|
||||||
|
expect(params.id).toEqual('123');
|
||||||
|
expect(params.bazId).toEqual('321');
|
||||||
|
});
|
||||||
|
|
||||||
test('Handle goBack identified by key', () => {
|
test('Handle goBack identified by key', () => {
|
||||||
const FooScreen = () => <div />;
|
const FooScreen = () => <div />;
|
||||||
const BarScreen = () => <div />;
|
const BarScreen = () => <div />;
|
||||||
@ -1634,400 +1689,164 @@ describe('StackRouter', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Handles empty URIs', () => {
|
test('Handles deep navigate completion action', () => {
|
||||||
const router = StackRouter(
|
const LeafScreen = () => <div />;
|
||||||
{
|
const FooScreen = () => <div />;
|
||||||
Foo: {
|
FooScreen.router = StackRouter({
|
||||||
screen: () => <div />,
|
Boo: { path: 'boo', screen: LeafScreen },
|
||||||
},
|
Baz: { path: 'baz/:bazId', screen: LeafScreen },
|
||||||
Bar: {
|
|
||||||
screen: () => <div />,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ initialRouteName: 'Bar' }
|
|
||||||
);
|
|
||||||
const action = router.getActionForPathAndParams('');
|
|
||||||
expect(action).toEqual({
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'Bar',
|
|
||||||
});
|
});
|
||||||
let state = null;
|
const router = StackRouter({
|
||||||
if (action) {
|
Foo: {
|
||||||
state = router.getStateForAction(action);
|
screen: FooScreen,
|
||||||
}
|
},
|
||||||
|
Bar: {
|
||||||
|
screen: LeafScreen,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
||||||
expect(state && state.index).toEqual(0);
|
expect(state && state.index).toEqual(0);
|
||||||
expect(state && state.routes[0]).toEqual(
|
expect(state && state.routes[0].routeName).toEqual('Foo');
|
||||||
expect.objectContaining({
|
const key = state && state.routes[0].key;
|
||||||
routeName: 'Bar',
|
const state2 = router.getStateForAction(
|
||||||
})
|
{
|
||||||
|
type: NavigationActions.NAVIGATE,
|
||||||
|
routeName: 'Baz',
|
||||||
|
},
|
||||||
|
state
|
||||||
);
|
);
|
||||||
|
expect(state2 && state2.index).toEqual(0);
|
||||||
|
expect(state2 && state2.isTransitioning).toEqual(false);
|
||||||
|
expect(state2 && state2.routes[0].index).toEqual(1);
|
||||||
|
expect(state2 && state2.routes[0].isTransitioning).toEqual(true);
|
||||||
|
expect(!!key).toEqual(true);
|
||||||
|
const state3 = router.getStateForAction(
|
||||||
|
{
|
||||||
|
type: StackActions.COMPLETE_TRANSITION,
|
||||||
|
},
|
||||||
|
state2
|
||||||
|
);
|
||||||
|
expect(state3 && state3.index).toEqual(0);
|
||||||
|
expect(state3 && state3.isTransitioning).toEqual(false);
|
||||||
|
expect(state3 && state3.routes[0].index).toEqual(1);
|
||||||
|
expect(state3 && state3.routes[0].isTransitioning).toEqual(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Gets deep path', () => {
|
test('order of handling navigate action is correct for nested stackrouters', () => {
|
||||||
const ScreenA = () => <div />;
|
const Screen = () => <div />;
|
||||||
const ScreenB = () => <div />;
|
const NestedStack = () => <div />;
|
||||||
ScreenA.router = StackRouter({
|
let nestedRouter = StackRouter({
|
||||||
Boo: { path: 'boo', screen: ScreenB },
|
Foo: Screen,
|
||||||
Baz: { path: 'baz/:bazId', screen: ScreenB },
|
|
||||||
});
|
|
||||||
const router = StackRouter({
|
|
||||||
Foo: {
|
|
||||||
path: 'f/:id',
|
|
||||||
screen: ScreenA,
|
|
||||||
},
|
|
||||||
Bar: {
|
|
||||||
screen: ScreenB,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const state = {
|
|
||||||
index: 0,
|
|
||||||
isTransitioning: false,
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
index: 1,
|
|
||||||
key: 'Foo',
|
|
||||||
routeName: 'Foo',
|
|
||||||
params: {
|
|
||||||
id: '123',
|
|
||||||
},
|
|
||||||
routes: [
|
|
||||||
{ key: 'Boo', routeName: 'Boo' },
|
|
||||||
{ key: 'Baz', routeName: 'Baz', params: { bazId: '321' } },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{ key: 'Bar', routeName: 'Bar' },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
const { path, params } = router.getPathAndParamsForState(state);
|
|
||||||
expect(path).toEqual('f/123/baz/321');
|
|
||||||
expect(params.id).toEqual('123');
|
|
||||||
expect(params.bazId).toEqual('321');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Gets deep path with pure wildcard match', () => {
|
|
||||||
const ScreenA = () => <div />;
|
|
||||||
const ScreenB = () => <div />;
|
|
||||||
const ScreenC = () => <div />;
|
|
||||||
ScreenA.router = StackRouter({
|
|
||||||
Boo: { path: 'boo', screen: ScreenC },
|
|
||||||
Baz: { path: 'baz/:bazId', screen: ScreenB },
|
|
||||||
});
|
|
||||||
ScreenC.router = StackRouter({
|
|
||||||
Boo2: { path: '', screen: ScreenB },
|
|
||||||
});
|
|
||||||
const router = StackRouter({
|
|
||||||
Foo: {
|
|
||||||
path: null,
|
|
||||||
screen: ScreenA,
|
|
||||||
},
|
|
||||||
Bar: {
|
|
||||||
screen: ScreenB,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
{
|
|
||||||
const state = {
|
|
||||||
index: 0,
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
index: 1,
|
|
||||||
key: 'Foo',
|
|
||||||
routeName: 'Foo',
|
|
||||||
params: {
|
|
||||||
id: '123',
|
|
||||||
},
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
index: 0,
|
|
||||||
key: 'Boo',
|
|
||||||
routeName: 'Boo',
|
|
||||||
routes: [{ key: 'Boo2', routeName: 'Boo2' }],
|
|
||||||
},
|
|
||||||
{ key: 'Baz', routeName: 'Baz', params: { bazId: '321' } },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{ key: 'Bar', routeName: 'Bar' },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
const { path, params } = router.getPathAndParamsForState(state);
|
|
||||||
expect(path).toEqual('baz/321');
|
|
||||||
expect(params.id).toEqual('123');
|
|
||||||
expect(params.bazId).toEqual('321');
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const state = {
|
|
||||||
index: 0,
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
index: 0,
|
|
||||||
key: 'Foo',
|
|
||||||
routeName: 'Foo',
|
|
||||||
params: {
|
|
||||||
id: '123',
|
|
||||||
},
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
index: 0,
|
|
||||||
key: 'Boo',
|
|
||||||
routeName: 'Boo',
|
|
||||||
routes: [{ key: 'Boo2', routeName: 'Boo2' }],
|
|
||||||
},
|
|
||||||
{ key: 'Baz', routeName: 'Baz', params: { bazId: '321' } },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{ key: 'Bar', routeName: 'Bar' },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
const { path, params } = router.getPathAndParamsForState(state);
|
|
||||||
expect(path).toEqual('boo/');
|
|
||||||
expect(params).toEqual({ id: '123' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('URI encoded string get passed to deep link', () => {
|
|
||||||
const uri = 'people/2018%2F02%2F07';
|
|
||||||
const action = TestStackRouter.getActionForPathAndParams(uri);
|
|
||||||
expect(action).toEqual({
|
|
||||||
routeName: 'person',
|
|
||||||
params: {
|
|
||||||
id: '2018/02/07',
|
|
||||||
},
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
});
|
|
||||||
|
|
||||||
const malformedUri = 'people/%E0%A4%A';
|
|
||||||
const action2 = TestStackRouter.getActionForPathAndParams(malformedUri);
|
|
||||||
expect(action2).toEqual({
|
|
||||||
routeName: 'person',
|
|
||||||
params: {
|
|
||||||
id: '%E0%A4%A',
|
|
||||||
},
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Querystring params get passed to nested deep link', () => {
|
|
||||||
// uri with two non-empty query param values
|
|
||||||
const uri = 'main/p/4/list/10259959195?code=test&foo=bar';
|
|
||||||
const action = TestStackRouter.getActionForPathAndParams(uri);
|
|
||||||
expect(action).toEqual({
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'main',
|
|
||||||
params: {
|
|
||||||
code: 'test',
|
|
||||||
foo: 'bar',
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'profile',
|
|
||||||
params: {
|
|
||||||
id: '4',
|
|
||||||
code: 'test',
|
|
||||||
foo: 'bar',
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'list',
|
|
||||||
params: {
|
|
||||||
id: '10259959195',
|
|
||||||
code: 'test',
|
|
||||||
foo: 'bar',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// uri with one empty and one non-empty query param value
|
|
||||||
const uri2 = 'main/p/4/list/10259959195?code=&foo=bar';
|
|
||||||
const action2 = TestStackRouter.getActionForPathAndParams(uri2);
|
|
||||||
expect(action2).toEqual({
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'main',
|
|
||||||
params: {
|
|
||||||
code: '',
|
|
||||||
foo: 'bar',
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'profile',
|
|
||||||
params: {
|
|
||||||
id: '4',
|
|
||||||
code: '',
|
|
||||||
foo: 'bar',
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'list',
|
|
||||||
params: {
|
|
||||||
id: '10259959195',
|
|
||||||
code: '',
|
|
||||||
foo: 'bar',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Handles deep navigate completion action', () => {
|
|
||||||
const LeafScreen = () => <div />;
|
|
||||||
const FooScreen = () => <div />;
|
|
||||||
FooScreen.router = StackRouter({
|
|
||||||
Boo: { path: 'boo', screen: LeafScreen },
|
|
||||||
Baz: { path: 'baz/:bazId', screen: LeafScreen },
|
|
||||||
});
|
|
||||||
const router = StackRouter({
|
|
||||||
Foo: {
|
|
||||||
screen: FooScreen,
|
|
||||||
},
|
|
||||||
Bar: {
|
|
||||||
screen: LeafScreen,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
|
||||||
expect(state && state.index).toEqual(0);
|
|
||||||
expect(state && state.routes[0].routeName).toEqual('Foo');
|
|
||||||
const key = state && state.routes[0].key;
|
|
||||||
const state2 = router.getStateForAction(
|
|
||||||
{
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'Baz',
|
|
||||||
},
|
|
||||||
state
|
|
||||||
);
|
|
||||||
expect(state2 && state2.index).toEqual(0);
|
|
||||||
expect(state2 && state2.isTransitioning).toEqual(false);
|
|
||||||
expect(state2 && state2.routes[0].index).toEqual(1);
|
|
||||||
expect(state2 && state2.routes[0].isTransitioning).toEqual(true);
|
|
||||||
expect(!!key).toEqual(true);
|
|
||||||
const state3 = router.getStateForAction(
|
|
||||||
{
|
|
||||||
type: StackActions.COMPLETE_TRANSITION,
|
|
||||||
},
|
|
||||||
state2
|
|
||||||
);
|
|
||||||
expect(state3 && state3.index).toEqual(0);
|
|
||||||
expect(state3 && state3.isTransitioning).toEqual(false);
|
|
||||||
expect(state3 && state3.routes[0].index).toEqual(1);
|
|
||||||
expect(state3 && state3.routes[0].isTransitioning).toEqual(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('order of handling navigate action is correct for nested stackrouters', () => {
|
|
||||||
const Screen = () => <div />;
|
|
||||||
const NestedStack = () => <div />;
|
|
||||||
let nestedRouter = StackRouter({
|
|
||||||
Foo: Screen,
|
|
||||||
Bar: Screen,
|
|
||||||
});
|
|
||||||
|
|
||||||
NestedStack.router = nestedRouter;
|
|
||||||
|
|
||||||
let router = StackRouter(
|
|
||||||
{
|
|
||||||
NestedStack,
|
|
||||||
Bar: Screen,
|
Bar: Screen,
|
||||||
Baz: Screen,
|
});
|
||||||
},
|
|
||||||
{
|
|
||||||
initialRouteName: 'Baz',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
NestedStack.router = nestedRouter;
|
||||||
expect(state.routes[state.index].routeName).toEqual('Baz');
|
|
||||||
|
|
||||||
const state2 = router.getStateForAction(
|
let router = StackRouter(
|
||||||
{
|
{
|
||||||
type: NavigationActions.NAVIGATE,
|
NestedStack,
|
||||||
routeName: 'Bar',
|
Bar: Screen,
|
||||||
},
|
Baz: Screen,
|
||||||
state
|
},
|
||||||
);
|
{
|
||||||
expect(state2.routes[state2.index].routeName).toEqual('Bar');
|
initialRouteName: 'Baz',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const state3 = router.getStateForAction(
|
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
||||||
{
|
expect(state.routes[state.index].routeName).toEqual('Baz');
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'Baz',
|
|
||||||
},
|
|
||||||
state2
|
|
||||||
);
|
|
||||||
expect(state3.routes[state3.index].routeName).toEqual('Baz');
|
|
||||||
|
|
||||||
const state4 = router.getStateForAction(
|
const state2 = router.getStateForAction(
|
||||||
{
|
{
|
||||||
type: NavigationActions.NAVIGATE,
|
type: NavigationActions.NAVIGATE,
|
||||||
routeName: 'Foo',
|
routeName: 'Bar',
|
||||||
},
|
},
|
||||||
state3
|
state
|
||||||
);
|
);
|
||||||
let activeState4 = state4.routes[state4.index];
|
expect(state2.routes[state2.index].routeName).toEqual('Bar');
|
||||||
expect(activeState4.routeName).toEqual('NestedStack');
|
|
||||||
expect(activeState4.routes[activeState4.index].routeName).toEqual('Foo');
|
|
||||||
|
|
||||||
const state5 = router.getStateForAction(
|
const state3 = router.getStateForAction(
|
||||||
{
|
{
|
||||||
type: NavigationActions.NAVIGATE,
|
type: NavigationActions.NAVIGATE,
|
||||||
routeName: 'Bar',
|
routeName: 'Baz',
|
||||||
},
|
},
|
||||||
state4
|
state2
|
||||||
);
|
);
|
||||||
let activeState5 = state5.routes[state5.index];
|
expect(state3.routes[state3.index].routeName).toEqual('Baz');
|
||||||
expect(activeState5.routeName).toEqual('NestedStack');
|
|
||||||
expect(activeState5.routes[activeState5.index].routeName).toEqual('Bar');
|
const state4 = router.getStateForAction(
|
||||||
});
|
{
|
||||||
|
type: NavigationActions.NAVIGATE,
|
||||||
test('order of handling navigate action is correct for nested stackrouters', () => {
|
routeName: 'Foo',
|
||||||
const Screen = () => <div />;
|
},
|
||||||
const NestedStack = () => <div />;
|
state3
|
||||||
const OtherNestedStack = () => <div />;
|
);
|
||||||
|
let activeState4 = state4.routes[state4.index];
|
||||||
let nestedRouter = StackRouter({ Foo: Screen, Bar: Screen });
|
expect(activeState4.routeName).toEqual('NestedStack');
|
||||||
let otherNestedRouter = StackRouter({ Foo: Screen });
|
expect(activeState4.routes[activeState4.index].routeName).toEqual('Foo');
|
||||||
NestedStack.router = nestedRouter;
|
|
||||||
OtherNestedStack.router = otherNestedRouter;
|
const state5 = router.getStateForAction(
|
||||||
|
{
|
||||||
let router = StackRouter(
|
type: NavigationActions.NAVIGATE,
|
||||||
{
|
routeName: 'Bar',
|
||||||
NestedStack,
|
},
|
||||||
OtherNestedStack,
|
state4
|
||||||
Bar: Screen,
|
);
|
||||||
},
|
let activeState5 = state5.routes[state5.index];
|
||||||
{
|
expect(activeState5.routeName).toEqual('NestedStack');
|
||||||
initialRouteName: 'OtherNestedStack',
|
expect(activeState5.routes[activeState5.index].routeName).toEqual('Bar');
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
test('order of handling navigate action is correct for nested stackrouters', () => {
|
||||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
const Screen = () => <div />;
|
||||||
expect(state.routes[state.index].routeName).toEqual('OtherNestedStack');
|
const NestedStack = () => <div />;
|
||||||
|
const OtherNestedStack = () => <div />;
|
||||||
const state2 = router.getStateForAction(
|
|
||||||
{
|
let nestedRouter = StackRouter({ Foo: Screen, Bar: Screen });
|
||||||
type: NavigationActions.NAVIGATE,
|
let otherNestedRouter = StackRouter({ Foo: Screen });
|
||||||
routeName: 'Bar',
|
NestedStack.router = nestedRouter;
|
||||||
},
|
OtherNestedStack.router = otherNestedRouter;
|
||||||
state
|
|
||||||
);
|
let router = StackRouter(
|
||||||
expect(state2.routes[state2.index].routeName).toEqual('Bar');
|
{
|
||||||
|
NestedStack,
|
||||||
const state3 = router.getStateForAction(
|
OtherNestedStack,
|
||||||
{
|
Bar: Screen,
|
||||||
type: NavigationActions.NAVIGATE,
|
},
|
||||||
routeName: 'NestedStack',
|
{
|
||||||
},
|
initialRouteName: 'OtherNestedStack',
|
||||||
state2
|
}
|
||||||
);
|
);
|
||||||
const state4 = router.getStateForAction(
|
|
||||||
{
|
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
||||||
type: NavigationActions.NAVIGATE,
|
expect(state.routes[state.index].routeName).toEqual('OtherNestedStack');
|
||||||
routeName: 'Bar',
|
|
||||||
},
|
const state2 = router.getStateForAction(
|
||||||
state3
|
{
|
||||||
);
|
type: NavigationActions.NAVIGATE,
|
||||||
let activeState4 = state4.routes[state4.index];
|
routeName: 'Bar',
|
||||||
expect(activeState4.routeName).toEqual('NestedStack');
|
},
|
||||||
expect(activeState4.routes[activeState4.index].routeName).toEqual('Bar');
|
state
|
||||||
|
);
|
||||||
|
expect(state2.routes[state2.index].routeName).toEqual('Bar');
|
||||||
|
|
||||||
|
const state3 = router.getStateForAction(
|
||||||
|
{
|
||||||
|
type: NavigationActions.NAVIGATE,
|
||||||
|
routeName: 'NestedStack',
|
||||||
|
},
|
||||||
|
state2
|
||||||
|
);
|
||||||
|
const state4 = router.getStateForAction(
|
||||||
|
{
|
||||||
|
type: NavigationActions.NAVIGATE,
|
||||||
|
routeName: 'Bar',
|
||||||
|
},
|
||||||
|
state3
|
||||||
|
);
|
||||||
|
let activeState4 = state4.routes[state4.index];
|
||||||
|
expect(activeState4.routeName).toEqual('NestedStack');
|
||||||
|
expect(activeState4.routes[activeState4.index].routeName).toEqual('Bar');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -78,56 +78,6 @@ describe('SwitchRouter', () => {
|
|||||||
expect(state3.index).toEqual(0);
|
expect(state3.index).toEqual(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('paths option on SwitchRouter overrides path from route config', () => {
|
|
||||||
const router = getExampleRouter({ paths: { A: 'overridden' } });
|
|
||||||
const action = router.getActionForPathAndParams('overridden', {});
|
|
||||||
expect(action.type).toEqual(NavigationActions.NAVIGATE);
|
|
||||||
expect(action.routeName).toEqual('A');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('provides correct action for getActionForPathAndParams', () => {
|
|
||||||
const router = getExampleRouter({ backBehavior: 'initialRoute' });
|
|
||||||
const action = router.getActionForPathAndParams('A1', { foo: 'bar' });
|
|
||||||
expect(action.type).toEqual(NavigationActions.NAVIGATE);
|
|
||||||
expect(action.routeName).toEqual('A1');
|
|
||||||
|
|
||||||
const action1 = router.getActionForPathAndParams('', {});
|
|
||||||
expect(action1.type).toEqual(NavigationActions.NAVIGATE);
|
|
||||||
expect(action1.routeName).toEqual('A');
|
|
||||||
|
|
||||||
const action2 = router.getActionForPathAndParams(null, {});
|
|
||||||
expect(action2.type).toEqual(NavigationActions.NAVIGATE);
|
|
||||||
expect(action2.routeName).toEqual('A');
|
|
||||||
|
|
||||||
const action3 = router.getActionForPathAndParams('great/path', {
|
|
||||||
foo: 'baz',
|
|
||||||
});
|
|
||||||
expect(action3).toEqual({
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'B',
|
|
||||||
params: { foo: 'baz' },
|
|
||||||
action: {
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'B1',
|
|
||||||
params: { foo: 'baz' },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const action4 = router.getActionForPathAndParams('great/path/B2', {
|
|
||||||
foo: 'baz',
|
|
||||||
});
|
|
||||||
expect(action4).toEqual({
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'B',
|
|
||||||
params: { foo: 'baz' },
|
|
||||||
action: {
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'B2',
|
|
||||||
params: { foo: 'baz' },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('order of handling navigate action is correct for nested switchrouters', () => {
|
test('order of handling navigate action is correct for nested switchrouters', () => {
|
||||||
// router = switch({ Nested: switch({ Foo, Bar }), Other: switch({ Foo }), Bar })
|
// router = switch({ Nested: switch({ Foo, Bar }), Other: switch({ Foo }), Bar })
|
||||||
// if we are focused on Other and navigate to Bar, what should happen?
|
// if we are focused on Other and navigate to Bar, what should happen?
|
||||||
|
@ -528,7 +528,7 @@ describe('TabRouter', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Handles path configuration', () => {
|
test.only('Handles path configuration', () => {
|
||||||
const ScreenA = () => <div />;
|
const ScreenA = () => <div />;
|
||||||
const ScreenB = () => <div />;
|
const ScreenB = () => <div />;
|
||||||
const router = TabRouter({
|
const router = TabRouter({
|
||||||
@ -537,14 +537,17 @@ describe('TabRouter', () => {
|
|||||||
screen: ScreenA,
|
screen: ScreenA,
|
||||||
},
|
},
|
||||||
Bar: {
|
Bar: {
|
||||||
path: 'b',
|
path: 'b/:great',
|
||||||
screen: ScreenB,
|
screen: ScreenB,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const params = { foo: '42' };
|
const params = { foo: '42' };
|
||||||
const action = router.getActionForPathAndParams('b/anything', params);
|
const action = router.getActionForPathAndParams('b/anything', params);
|
||||||
const expectedAction = {
|
const expectedAction = {
|
||||||
params,
|
params: {
|
||||||
|
foo: '42',
|
||||||
|
great: 'anything',
|
||||||
|
},
|
||||||
routeName: 'Bar',
|
routeName: 'Bar',
|
||||||
type: NavigationActions.NAVIGATE,
|
type: NavigationActions.NAVIGATE,
|
||||||
};
|
};
|
||||||
@ -565,15 +568,21 @@ describe('TabRouter', () => {
|
|||||||
index: 1,
|
index: 1,
|
||||||
isTransitioning: false,
|
isTransitioning: false,
|
||||||
routes: [
|
routes: [
|
||||||
{ key: 'Foo', routeName: 'Foo' },
|
{ key: 'Foo', routeName: 'Foo', params: undefined },
|
||||||
{ key: 'Bar', routeName: 'Bar', params },
|
{
|
||||||
|
key: 'Bar',
|
||||||
|
routeName: 'Bar',
|
||||||
|
params: { foo: '42', great: 'anything' },
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
expect(state2).toEqual(expectedState2);
|
expect(state2).toEqual(expectedState2);
|
||||||
expect(router.getComponentForState(expectedState)).toEqual(ScreenA);
|
expect(router.getComponentForState(expectedState)).toEqual(ScreenA);
|
||||||
expect(router.getComponentForState(expectedState2)).toEqual(ScreenB);
|
expect(router.getComponentForState(expectedState2)).toEqual(ScreenB);
|
||||||
expect(router.getPathAndParamsForState(expectedState).path).toEqual('f');
|
expect(router.getPathAndParamsForState(expectedState).path).toEqual('f');
|
||||||
expect(router.getPathAndParamsForState(expectedState2).path).toEqual('b');
|
expect(router.getPathAndParamsForState(expectedState2).path).toEqual(
|
||||||
|
'b/anything'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Handles default configuration', () => {
|
test('Handles default configuration', () => {
|
||||||
|
34
src/routers/__tests__/pathUtils-test.js
Normal file
34
src/routers/__tests__/pathUtils-test.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { urlToPathAndParams } from '../pathUtils';
|
||||||
|
|
||||||
|
test('urlToPathAndParams empty', () => {
|
||||||
|
const { path, params } = urlToPathAndParams('foo://');
|
||||||
|
expect(path).toBe('');
|
||||||
|
expect(params).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('urlToPathAndParams empty params', () => {
|
||||||
|
const { path, params } = urlToPathAndParams('foo://foo/bar/b');
|
||||||
|
expect(path).toBe('foo/bar/b');
|
||||||
|
expect(params).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('urlToPathAndParams trailing slash', () => {
|
||||||
|
const { path, params } = urlToPathAndParams('foo://foo/bar/');
|
||||||
|
expect(path).toBe('foo/bar');
|
||||||
|
expect(params).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('urlToPathAndParams with params', () => {
|
||||||
|
const { path, params } = urlToPathAndParams('foo://foo/bar?asdf=1&dude=foo');
|
||||||
|
expect(path).toBe('foo/bar');
|
||||||
|
expect(params).toEqual({ asdf: '1', dude: 'foo' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('urlToPathAndParams with custom delimeter', () => {
|
||||||
|
const { path, params } = urlToPathAndParams(
|
||||||
|
'https://example.com/foo/bar?asdf=1',
|
||||||
|
'https://example.com/'
|
||||||
|
);
|
||||||
|
expect(path).toBe('foo/bar');
|
||||||
|
expect(params).toEqual({ asdf: '1' });
|
||||||
|
});
|
172
src/routers/pathUtils.js
Normal file
172
src/routers/pathUtils.js
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
import pathToRegexp from 'path-to-regexp';
|
||||||
|
import NavigationActions from '../NavigationActions';
|
||||||
|
const queryString = require('query-string');
|
||||||
|
|
||||||
|
function isEmpty(obj) {
|
||||||
|
if (!obj) return true;
|
||||||
|
for (let key in obj) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const urlToPathAndParams = (url, uriPrefix) => {
|
||||||
|
const searchMatch = url.match(/^(.*)\?(.*)$/);
|
||||||
|
const params = searchMatch ? queryString.parse(searchMatch[2]) : {};
|
||||||
|
const urlWithoutSearch = searchMatch ? searchMatch[1] : url;
|
||||||
|
const delimiter = uriPrefix || '://';
|
||||||
|
let path = urlWithoutSearch.split(delimiter)[1];
|
||||||
|
if (path === undefined) {
|
||||||
|
path = urlWithoutSearch;
|
||||||
|
}
|
||||||
|
if (path === '/') {
|
||||||
|
path = '';
|
||||||
|
}
|
||||||
|
if (path[path.length - 1] === '/') {
|
||||||
|
path = path.slice(0, -1);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
path,
|
||||||
|
params,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createPathParser = (
|
||||||
|
childRouters,
|
||||||
|
routeConfigs,
|
||||||
|
pathConfigs = {},
|
||||||
|
initialRouteName,
|
||||||
|
initialRouteParams
|
||||||
|
) => {
|
||||||
|
const pathsByRouteNames = {};
|
||||||
|
let paths = [];
|
||||||
|
|
||||||
|
// Build paths for each route
|
||||||
|
Object.keys(childRouters).forEach(routeName => {
|
||||||
|
let pathPattern = pathConfigs[routeName] || routeConfigs[routeName].path;
|
||||||
|
let matchExact = !!pathPattern && !childRouters[routeName];
|
||||||
|
if (pathPattern === undefined) {
|
||||||
|
pathPattern = routeName;
|
||||||
|
}
|
||||||
|
const keys = [];
|
||||||
|
let re, toPath, priority;
|
||||||
|
if (typeof pathPattern === 'string') {
|
||||||
|
// pathPattern may be either a string or a regexp object according to path-to-regexp docs.
|
||||||
|
re = pathToRegexp(pathPattern, keys);
|
||||||
|
toPath = pathToRegexp.compile(pathPattern);
|
||||||
|
priority = 0;
|
||||||
|
} else if (pathPattern === null) {
|
||||||
|
// for wildcard match
|
||||||
|
re = pathToRegexp('*', keys);
|
||||||
|
toPath = () => '';
|
||||||
|
matchExact = true;
|
||||||
|
priority = -1;
|
||||||
|
}
|
||||||
|
if (!matchExact) {
|
||||||
|
const wildcardRe = pathToRegexp(`${pathPattern}/*`, keys);
|
||||||
|
re = new RegExp(`(?:${re.source})|(?:${wildcardRe.source})`);
|
||||||
|
}
|
||||||
|
pathsByRouteNames[routeName] = { re, keys, toPath, priority, pathPattern };
|
||||||
|
});
|
||||||
|
|
||||||
|
paths = Object.entries(pathsByRouteNames);
|
||||||
|
paths.sort((a, b) => b[1].priority - a[1].priority);
|
||||||
|
|
||||||
|
const getActionForPathAndParams = (pathToResolve, inputParams = {}) => {
|
||||||
|
// If the path is empty (null or empty string)
|
||||||
|
// just return the initial route action
|
||||||
|
if (!pathToResolve) {
|
||||||
|
return NavigationActions.navigate({
|
||||||
|
routeName: initialRouteName,
|
||||||
|
params: { ...inputParams, ...initialRouteParams },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to match `pathToResolve` with a route in this router's
|
||||||
|
// routeConfigs
|
||||||
|
let matchedRouteName;
|
||||||
|
let pathMatch;
|
||||||
|
let pathMatchKeys;
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
for (const [routeName, path] of paths) {
|
||||||
|
const { re, keys } = path;
|
||||||
|
pathMatch = re.exec(pathToResolve);
|
||||||
|
if (pathMatch && pathMatch.length) {
|
||||||
|
pathMatchKeys = keys;
|
||||||
|
matchedRouteName = routeName;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We didn't match -- return null to signify no action available
|
||||||
|
if (!matchedRouteName) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine nested actions:
|
||||||
|
// If our matched route for this router is a child router,
|
||||||
|
// get the action for the path AFTER the matched path for this
|
||||||
|
// router
|
||||||
|
let nestedAction;
|
||||||
|
if (childRouters[matchedRouteName]) {
|
||||||
|
nestedAction = childRouters[matchedRouteName].getActionForPathAndParams(
|
||||||
|
pathMatch.slice(pathMatchKeys.length).join('/'),
|
||||||
|
inputParams
|
||||||
|
);
|
||||||
|
if (!nestedAction) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = pathMatch.slice(1).reduce(
|
||||||
|
// iterate over matched path params
|
||||||
|
(paramsOut, matchResult, i) => {
|
||||||
|
const key = pathMatchKeys[i];
|
||||||
|
if (!key || key.asterisk) {
|
||||||
|
return paramsOut;
|
||||||
|
}
|
||||||
|
const paramName = key.name;
|
||||||
|
|
||||||
|
let decodedMatchResult;
|
||||||
|
try {
|
||||||
|
decodedMatchResult = decodeURIComponent(matchResult);
|
||||||
|
} catch (e) {
|
||||||
|
// ignore `URIError: malformed URI`
|
||||||
|
}
|
||||||
|
|
||||||
|
paramsOut[paramName] = decodedMatchResult || matchResult;
|
||||||
|
return paramsOut;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// start with the input(query string) params, which will get overridden by path params
|
||||||
|
...inputParams,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return NavigationActions.navigate({
|
||||||
|
routeName: matchedRouteName,
|
||||||
|
...(params ? { params } : {}),
|
||||||
|
...(nestedAction ? { action: nestedAction } : {}),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const getPathAndParamsForRoute = route => {
|
||||||
|
const { routeName, params } = route;
|
||||||
|
const childRouter = childRouters[routeName];
|
||||||
|
const subPath = pathsByRouteNames[routeName].toPath(params);
|
||||||
|
if (childRouter) {
|
||||||
|
// If it has a router it's a navigator.
|
||||||
|
// If it doesn't have router it's an ordinary React component.
|
||||||
|
const child = childRouter.getPathAndParamsForState(route);
|
||||||
|
return {
|
||||||
|
path: subPath ? `${subPath}/${child.path}` : child.path,
|
||||||
|
params: child.params ? { ...params, ...child.params } : params,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
path: subPath,
|
||||||
|
params,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
return { getActionForPathAndParams, getPathAndParamsForRoute };
|
||||||
|
};
|
11
yarn.lock
11
yarn.lock
@ -4659,6 +4659,13 @@ qs@~6.5.1:
|
|||||||
version "6.5.2"
|
version "6.5.2"
|
||||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
||||||
|
|
||||||
|
query-string@^6.1.0:
|
||||||
|
version "6.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.1.0.tgz#01e7d69f6a0940dac67a937d6c6325647aa4532a"
|
||||||
|
dependencies:
|
||||||
|
decode-uri-component "^0.2.0"
|
||||||
|
strict-uri-encode "^2.0.0"
|
||||||
|
|
||||||
random-bytes@~1.0.0:
|
random-bytes@~1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b"
|
resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b"
|
||||||
@ -5565,6 +5572,10 @@ stream-to-observable@^0.1.0:
|
|||||||
version "0.1.0"
|
version "0.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/stream-to-observable/-/stream-to-observable-0.1.0.tgz#45bf1d9f2d7dc09bed81f1c307c430e68b84cffe"
|
resolved "https://registry.yarnpkg.com/stream-to-observable/-/stream-to-observable-0.1.0.tgz#45bf1d9f2d7dc09bed81f1c307c430e68b84cffe"
|
||||||
|
|
||||||
|
strict-uri-encode@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546"
|
||||||
|
|
||||||
string-length@^2.0.0:
|
string-length@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed"
|
resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user