Allow pure-wildcard route for nested navigators. (#2929)

* Allow pure-wildcard route for nested navigators.

* Treat empty paths as they are.
This commit is contained in:
Moriyoshi Koizumi
2018-01-27 08:52:59 +09:00
parent a7d0221709
commit 46264bb81d
2 changed files with 139 additions and 8 deletions

View File

@@ -46,24 +46,42 @@ export default (routeConfigs, stackConfig = {}) => {
const initialRouteName = stackConfig.initialRouteName || routeNames[0];
const initialChildRouter = childRouters[initialRouteName];
const paths = stackConfig.paths || {};
const pathsByRouteNames = { ...stackConfig.paths } || {};
let paths = [];
// Build paths for each route
routeNames.forEach(routeName => {
let pathPattern = paths[routeName] || routeConfigs[routeName].path;
const matchExact = !!pathPattern && !childRouters[routeName];
if (typeof pathPattern !== 'string') {
let pathPattern =
pathsByRouteNames[routeName] || routeConfigs[routeName].path;
let matchExact = !!pathPattern && !childRouters[routeName];
if (pathPattern === undefined) {
pathPattern = routeName;
}
const keys = [];
let re = pathToRegexp(pathPattern, 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 {
// 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})`);
}
paths[routeName] = { re, keys, toPath: pathToRegexp.compile(pathPattern) };
pathsByRouteNames[routeName] = { re, keys, toPath, priority };
});
paths = Object.entries(pathsByRouteNames);
/* $FlowFixMe */
paths.sort((a: [string, *], b: [string, *]) => b[1].priority - a[1].priority);
return {
getComponentForState(state) {
const activeChildRoute = state.routes[state.index];
@@ -293,7 +311,7 @@ export default (routeConfigs, stackConfig = {}) => {
const route = state.routes[state.index];
const routeName = route.routeName;
const screen = getScreenForRouteName(routeConfigs, routeName);
const subPath = paths[routeName].toPath(route.params);
const subPath = pathsByRouteNames[routeName].toPath(route.params);
let path = subPath;
let params = route.params;
if (screen && screen.router) {
@@ -328,7 +346,7 @@ export default (routeConfigs, stackConfig = {}) => {
let pathMatchKeys;
// eslint-disable-next-line no-restricted-syntax
for (const [routeName, path] of Object.entries(paths)) {
for (const [routeName, path] of paths) {
const { re, keys } = path;
pathMatch = re.exec(pathNameToResolve);
if (pathMatch && pathMatch.length) {
@@ -340,6 +358,13 @@ export default (routeConfigs, stackConfig = {}) => {
// 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;
}
@@ -353,6 +378,9 @@ export default (routeConfigs, stackConfig = {}) => {
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

View File

@@ -54,6 +54,10 @@ const TestStackRouter = StackRouter({
main: {
screen: MainNavigator,
},
baz: {
path: null,
screen: FooNavigator,
},
auth: {
screen: AuthNavigator,
},
@@ -277,6 +281,22 @@ describe('StackRouter', () => {
});
});
test('Correctly parses a path to the router connected to another router through a pure wildcard route into an action chain', () => {
const uri = 'b/123';
const action = TestStackRouter.getActionForPathAndParams(uri);
expect(action).toEqual({
type: NavigationActions.NAVIGATE,
routeName: 'baz',
action: {
type: NavigationActions.NAVIGATE,
routeName: 'bar',
params: {
barThing: '123',
},
},
});
});
test('Correctly returns null action for non-existent path', () => {
const uri = 'asdf/1234';
const action = TestStackRouter.getActionForPathAndParams(uri);
@@ -944,6 +964,89 @@ describe('StackRouter', () => {
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');
/* $FlowFixMe: params.id has to exist */
expect(params.id).toEqual('123');
/* $FlowFixMe: params.bazId has to exist */
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('Maps old actions (uses "Handles the reset action" test)', () => {
global.console.warn = jest.fn();
const router = StackRouter({