diff --git a/modules/components/Routes.js b/modules/components/Routes.js index 0e43de90b4..24cb3e40f8 100644 --- a/modules/components/Routes.js +++ b/modules/components/Routes.js @@ -32,7 +32,7 @@ function findMatches(path, routes, defaultRoute, notFoundRoute) { if (matches != null) { var rootParams = getRootMatch(matches).params; - + params = route.props.paramNames.reduce(function (params, paramName) { params[paramName] = rootParams[paramName]; return params; diff --git a/modules/utils/Path.js b/modules/utils/Path.js index 9fb814e222..7d30b50da2 100644 --- a/modules/utils/Path.js +++ b/modules/utils/Path.js @@ -15,7 +15,8 @@ function encodeURLPath(path) { } var paramCompileMatcher = /:([a-zA-Z_$][a-zA-Z0-9_$]*)|[*.()\[\]\\+|{}^$]/g; -var paramInjectMatcher = /:([a-zA-Z_$][a-zA-Z0-9_$]*)|[*]/g; +var paramInjectMatcher = /:([a-zA-Z_$][a-zA-Z0-9_$?]*[?]?)|[*]/g; +var paramInjectTrailingSlashMatcher = /\/\/\?|\/\?/g; var queryMatcher = /\?(.+)/; var _compiledPatterns = {}; @@ -86,10 +87,18 @@ var Path = { return pattern.replace(paramInjectMatcher, function (match, paramName) { paramName = paramName || 'splat'; - invariant( - params[paramName] != null, - 'Missing "' + paramName + '" parameter for path "' + pattern + '"' - ); + // If param is optional don't check for existence + if (paramName.slice(-1) !== '?') { + invariant( + params[paramName] != null, + 'Missing "' + paramName + '" parameter for path "' + pattern + '"' + ); + } else { + paramName = paramName.slice(0, -1) + if (params[paramName] == null) { + return ''; + } + } var segment; if (paramName === 'splat' && Array.isArray(params[paramName])) { @@ -104,7 +113,7 @@ var Path = { } return encodeURLPath(segment); - }); + }).replace(paramInjectTrailingSlashMatcher, '/'); }, /** diff --git a/modules/utils/__tests__/Path-test.js b/modules/utils/__tests__/Path-test.js index 1766012a34..59af3006a3 100644 --- a/modules/utils/__tests__/Path-test.js +++ b/modules/utils/__tests__/Path-test.js @@ -47,6 +47,34 @@ describe('Path.extractParams', function () { }); }); + describe('and the pattern is optional', function () { + var pattern = 'comments/:id?/edit' + + describe('and the path matches with supplied param', function () { + it('returns an object with the params', function () { + expect(Path.extractParams(pattern, 'comments/123/edit')).toEqual({ id: '123' }); + }); + }); + describe('and the path matches without supplied param', function () { + it('returns an object with param set to null', function () { + expect(Path.extractParams(pattern, 'comments//edit')).toEqual({id: null}); + }); + }); + }); + describe('and the pattern and forward slash are optional', function () { + var pattern = 'comments/:id?/?edit' + + describe('and the path matches with supplied param', function () { + it('returns an object with the params', function () { + expect(Path.extractParams(pattern, 'comments/123/edit')).toEqual({ id: '123' }); + }); + }); + describe('and the path matches without supplied param', function () { + it('returns an object with param set to null', function () { + expect(Path.extractParams(pattern, 'comments/edit')).toEqual({id: null}); + }); + }); + }); describe('and the path does not match', function () { it('returns null', function () { expect(Path.extractParams(pattern, 'users/123')).toBe(null); @@ -166,6 +194,30 @@ describe('Path.injectParams', function () { }); }); + describe('and a param is optional', function () { + var pattern = 'comments/:id?/edit'; + + it('returns the correct path when param is supplied', function () { + expect(Path.injectParams(pattern, {id:'123'})).toEqual('comments/123/edit'); + }); + + it('returns the correct path when param is not supplied', function () { + expect(Path.injectParams(pattern, {})).toEqual('comments//edit'); + }); + }); + + describe('and a param and forward slash are optional', function () { + var pattern = 'comments/:id?/?edit'; + + it('returns the correct path when param is supplied', function () { + expect(Path.injectParams(pattern, {id:'123'})).toEqual('comments/123/edit'); + }); + + it('returns the correct path when param is not supplied', function () { + expect(Path.injectParams(pattern, {})).toEqual('comments/edit'); + }); + }); + describe('and all params are present', function () { it('returns the correct path', function () { expect(Path.injectParams(pattern, { id: 'abc' })).toEqual('comments/abc/edit');