From a4d0064a328477b899bb89c58dedb53e28f395a1 Mon Sep 17 00:00:00 2001 From: Brandon John Lewis Date: Mon, 15 Oct 2018 18:02:03 -0400 Subject: [PATCH] add support for an array of paths in and matchPath (#5889) --- packages/react-router/docs/api/Route.md | 8 +- packages/react-router/docs/api/matchPath.md | 2 +- packages/react-router/modules/Route.js | 5 +- .../modules/__tests__/Route-test.js | 77 +++++++++++++++++++ .../modules/__tests__/matchPath-test.js | 30 ++++++++ packages/react-router/modules/matchPath.js | 47 ++++++----- 6 files changed, 146 insertions(+), 23 deletions(-) diff --git a/packages/react-router/docs/api/Route.md b/packages/react-router/docs/api/Route.md index 108ea345e..30f8087e9 100644 --- a/packages/react-router/docs/api/Route.md +++ b/packages/react-router/docs/api/Route.md @@ -133,14 +133,18 @@ This could also be useful for animations: **Warning:** Both `` and `` take precedence over `` so don't use more than one in the same ``. -## path: string +## path: string | string[] -Any valid URL path that [`path-to-regexp@^1.7.0`](https://github.com/pillarjs/path-to-regexp/tree/v1.7.0) understands. +Any valid URL path or array of paths that [`path-to-regexp@^1.7.0`](https://github.com/pillarjs/path-to-regexp/tree/v1.7.0) understands. ```jsx ``` +```jsx + +``` + Routes without a `path` _always_ match. ## exact: bool diff --git a/packages/react-router/docs/api/matchPath.md b/packages/react-router/docs/api/matchPath.md index 6dadb5d8a..c6d973c05 100644 --- a/packages/react-router/docs/api/matchPath.md +++ b/packages/react-router/docs/api/matchPath.md @@ -24,7 +24,7 @@ to the matching props `Route` accepts: ```js { - path, // like /users/:id + path, // like /users/:id; either a single string or an array of strings strict, // optional, defaults to false exact; // optional, defaults to false } diff --git a/packages/react-router/modules/Route.js b/packages/react-router/modules/Route.js index 7a2d044ca..a06072704 100644 --- a/packages/react-router/modules/Route.js +++ b/packages/react-router/modules/Route.js @@ -130,7 +130,10 @@ if (__DEV__) { component: PropTypes.func, exact: PropTypes.bool, location: PropTypes.object, - path: PropTypes.string, + path: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.arrayOf(PropTypes.string) + ]), render: PropTypes.func, sensitive: PropTypes.bool, strict: PropTypes.bool diff --git a/packages/react-router/modules/__tests__/Route-test.js b/packages/react-router/modules/__tests__/Route-test.js index 8c038f6b9..c6defa9ce 100644 --- a/packages/react-router/modules/__tests__/Route-test.js +++ b/packages/react-router/modules/__tests__/Route-test.js @@ -98,6 +98,83 @@ describe("A ", () => { }); }); + describe("with an array of paths", () => { + it("matches the first provided path", () => { + const node = document.createElement("div"); + ReactDOM.render( + +
Hello World
} + /> +
, + node + ); + + expect(node.innerHTML).toContain("Hello World"); + }); + + it("matches other provided paths", () => { + const node = document.createElement("div"); + ReactDOM.render( + +
Hello World
} + /> +
, + node + ); + + expect(node.innerHTML).toContain("Hello World"); + }); + + it("provides the matched path as a string", () => { + const node = document.createElement("div"); + ReactDOM.render( + +
{match.path}
} + /> +
, + node + ); + + expect(node.innerHTML).toContain("/world"); + }); + + it("doesn't remount when moving from one matching path to another", () => { + const node = document.createElement("div"); + const history = createHistory(); + const mount = jest.fn(); + class MatchedRoute extends React.Component { + componentWillMount() { + mount(); + } + + render() { + return
Hello World
; + } + } + history.push("/hello"); + ReactDOM.render( + + + , + node + ); + + expect(mount).toHaveBeenCalledTimes(1); + expect(node.innerHTML).toContain("Hello World"); + + history.push("/world/somewhere/else"); + + expect(mount).toHaveBeenCalledTimes(1); + expect(node.innerHTML).toContain("Hello World"); + }); + }); + describe("with a unicode path", () => { it("is able to match", () => { renderStrict( diff --git a/packages/react-router/modules/__tests__/matchPath-test.js b/packages/react-router/modules/__tests__/matchPath-test.js index 41a015d65..7c91d2c3f 100644 --- a/packages/react-router/modules/__tests__/matchPath-test.js +++ b/packages/react-router/modules/__tests__/matchPath-test.js @@ -33,6 +33,36 @@ describe("matchPath", () => { }); }); + describe("with an array of paths", () => { + it('return the correct url at "/elsewhere"', () => { + const path = ["/somewhere", "/elsewhere"]; + const pathname = "/elsewhere"; + const match = matchPath(pathname, { path }); + expect(match.url).toBe("/elsewhere"); + }); + + it('returns correct url at "/elsewhere/else"', () => { + const path = ["/somewhere", "/elsewhere"]; + const pathname = "/elsewhere/else"; + const match = matchPath(pathname, { path }); + expect(match.url).toBe("/elsewhere"); + }); + + it('returns correct url at "/elsewhere/else" with path "/" in array', () => { + const path = ["/somewhere", "/"]; + const pathname = "/elsewhere/else"; + const match = matchPath(pathname, { path }); + expect(match.url).toBe("/"); + }); + + it('returns correct url at "/somewhere" with path "/" in array', () => { + const path = ["/somewhere", "/"]; + const pathname = "/somewhere"; + const match = matchPath(pathname, { path }); + expect(match.url).toBe("/somewhere"); + }); + }); + describe("with sensitive path", () => { it("returns non-sensitive url", () => { const options = { diff --git a/packages/react-router/modules/matchPath.js b/packages/react-router/modules/matchPath.js index 7b4d73c19..b0be891a2 100644 --- a/packages/react-router/modules/matchPath.js +++ b/packages/react-router/modules/matchPath.js @@ -30,25 +30,34 @@ function matchPath(pathname, options = {}) { const { path, exact = false, strict = false, sensitive = false } = options; - const { regexp, keys } = compilePath(path, { end: exact, strict, sensitive }); - const match = regexp.exec(pathname); - - if (!match) return null; - - const [url, ...values] = match; - const isExact = pathname === url; - - if (exact && !isExact) return null; - - return { - path, // the path used to match - url: path === "/" && url === "" ? "/" : url, // the matched portion of the URL - isExact, // whether or not we matched exactly - params: keys.reduce((memo, key, index) => { - memo[key.name] = values[index]; - return memo; - }, {}) - }; + const paths = [].concat(path); + + return paths.reduce((matched, path) => { + if (matched) return matched; + const { regexp, keys } = compilePath(path, { + end: exact, + strict, + sensitive + }); + const match = regexp.exec(pathname); + + if (!match) return null; + + const [url, ...values] = match; + const isExact = pathname === url; + + if (exact && !isExact) return null; + + return { + path, // the path used to match + url: path === "/" && url === "" ? "/" : url, // the matched portion of the URL + isExact, // whether or not we matched exactly + params: keys.reduce((memo, key, index) => { + memo[key.name] = values[index]; + return memo; + }, {}) + }; + }, null); } export default matchPath;