diff --git a/modules/components/Route.js b/modules/components/Route.js index 694fde17a6..07baac606c 100644 --- a/modules/components/Route.js +++ b/modules/components/Route.js @@ -24,7 +24,7 @@ var RESERVED_PROPS = { /** * components specify components that are rendered to the page when the * URL matches a given pattern. - * + * * Routes are arranged in a nested tree structure. When a new URL is requested, * the tree is searched depth-first to find a route whose path matches the URL. * When one is found, all routes in the tree that lead to it are considered @@ -251,7 +251,9 @@ function Redirect(to, params, query) { } function findMatches(path, route) { - var children = route.props.children, matches; + var children = route.props.children, + constraints = route.props.constraints || {}, + matches; // Check the subtree first to find the most deeply-nested match. if (Array.isArray(children)) { @@ -276,7 +278,7 @@ function findMatches(path, route) { } // No routes in the subtree matched, so check this route. - var params = Path.extractParams(route.props.path, path); + var params = Path.extractParams(route.props.path, path, constraints); if (params) return [ makeMatch(route, params) ]; diff --git a/modules/helpers/Path.js b/modules/helpers/Path.js index deefa95c7b..93bb7dbd61 100644 --- a/modules/helpers/Path.js +++ b/modules/helpers/Path.js @@ -41,7 +41,7 @@ var Path = { * and returns an object of param name => value pairs. Returns null if the * pattern does not match the given path. */ - extractParams: function (pattern, path) { + extractParams: function (pattern, path, constraints) { if (!isDynamicPattern(pattern)) { if (pattern === urlDecode(path)) return {}; // No dynamic segments, but the paths match. @@ -61,7 +61,29 @@ var Path = { params[paramName] = match[index + 1]; }); - return params; + if (this.testConstraints(params, constraints)) { + return params; + } + + return null; + }, + + testConstraints: function (params, constraints) { + var pass = true; + + if (! constraints) { + return true; + } + + Object.keys(params).forEach(function(param) { + if (constraints[param]) { + if (! constraints[param].test(params[param])) { + pass = false; + } + } + }); + + return pass; }, /** diff --git a/specs/Path.spec.js b/specs/Path.spec.js index 0c2bf6ca7d..fb448da2a2 100644 --- a/specs/Path.spec.js +++ b/specs/Path.spec.js @@ -81,6 +81,23 @@ describe('Path.extractParams', function () { }); }); }); + + describe('when a pattern has dynamic segments with constraints', function() { + var pattern = '/comments/:id/edit', + constraints = { + id : /\d+/ + }; + + describe('and the constraints match', function() { + expect(Path.extractParams(pattern, '/comments/123/edit', constraints)) + .toEqual({ id : 123 }); + }); + + describe('and the constraints do not match', function() { + expect(Path.extractParams(pattern, '/comments/abc/edit', constraints)) + .toBe(null); + }); + }); }); describe('Path.extractParamNames', function () { @@ -118,7 +135,7 @@ describe('Path.injectParams', function () { describe('and a param is missing', function () { it('throws an Error', function () { expect(function () { - Path.injectParams(pattern, {}) + Path.injectParams(pattern, {}); }).toThrow(Error); }); }); @@ -186,3 +203,33 @@ describe('Path.normalize', function () { }); }); }); + +describe('Path.testConstraints', function () { + it('returns false when one or more constraints fail', function () { + var params = { + id : 123, + name : 'Abc' + }; + + var constraints = { + id : /^\d+$/, + name : /^[a-z]+$/ + }; + + expect(Path.testConstraints(params, constraints)).toBe(false); + }); + + it('returns true when constraints pass', function () { + var params = { + id : 123, + name : 'Abc' + }; + + var constraints = { + id : /^\d+$/, + name : /^[A-Za-z]+$/ + }; + + expect(Path.testConstraints(params, constraints)).toBe(true); + }); +});