From 423e02aeaed09b1e2f16ba31559ef8d2ed7813d4 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 31 Dec 2017 13:25:52 -0500 Subject: [PATCH] handle nested routes - fixes #55 --- lib/utils/create_routes.js | 25 +++++++++++++---- test/unit/create_routes.test.js | 50 +++++++++++++++++++++++++++++---- 2 files changed, 64 insertions(+), 11 deletions(-) diff --git a/lib/utils/create_routes.js b/lib/utils/create_routes.js index 90ae1ee..3842caa 100644 --- a/lib/utils/create_routes.js +++ b/lib/utils/create_routes.js @@ -16,9 +16,22 @@ module.exports = function create_matchers(files) { .filter(part => part[0] === '[') .map(part => part.slice(1, -1)); - const pattern = new RegExp( - `^\\/${parts.map(p => p[0] === '[' ? '([^/]+)' : p).join('\\/')}$` - ); + let pattern_string = ''; + let i = parts.length; + let nested = true; + while (i--) { + const part = parts[i]; + const dynamic = part[0] === '['; + + if (dynamic) { + pattern_string = nested ? `(?:\\/([^/]+)${pattern_string})?` : `\\/([^/]+)${pattern_string}`; + } else { + nested = false; + pattern_string = `\\/${part}${pattern_string}`; + } + } + + const pattern = new RegExp(`^${pattern_string}$`); const test = url => pattern.test(url); @@ -28,7 +41,7 @@ module.exports = function create_matchers(files) { const params = {}; dynamic.forEach((param, i) => { - params[param] = match[i + 1]; + params[param] = match[i + 1] || null; }); return params; @@ -58,8 +71,8 @@ module.exports = function create_matchers(files) { return 0; } - if (!a_part) return 1; - if (!b_part) return -1; + if (!a_part) return -1; + if (!b_part) return 1; const a_is_dynamic = a_part[0] === '['; const b_is_dynamic = b_part[0] === '['; diff --git a/test/unit/create_routes.test.js b/test/unit/create_routes.test.js index 74b35ad..49f52f6 100644 --- a/test/unit/create_routes.test.js +++ b/test/unit/create_routes.test.js @@ -10,12 +10,12 @@ describe('create_routes', () => { assert.deepEqual( routes.map(r => r.file), [ + 'index.html', + 'about.html', 'post/foo.html', 'post/bar.html', 'post/[id].html', - 'about.html', - '[wildcard].html', - 'index.html' + '[wildcard].html' ] ); }); @@ -45,8 +45,8 @@ describe('create_routes', () => { assert.deepEqual( routes.map(r => r.file), [ - 'e/f/g/h.html', - 'index.html' + 'index.html', + 'e/f/g/h.html' ] ); }); @@ -71,4 +71,44 @@ describe('create_routes', () => { create_routes(['[foo].html', '[bar]/index.html']); }, /The \[foo\].html and \[bar\]\/index.html routes clash/); }); + + it('matches nested routes', () => { + const route = create_routes(['settings/[submenu].html'])[0]; + + assert.deepEqual(route.exec('/settings/foo'), { + submenu: 'foo' + }); + + assert.deepEqual(route.exec('/settings'), { + submenu: null + }); + }); + + it('prefers index routes to nested routes', () => { + const routes = create_routes(['settings/[submenu].html', 'settings.html']); + + assert.deepEqual( + routes.map(r => r.file), + ['settings.html', 'settings/[submenu].html'] + ); + }); + + it('matches deeply nested routes', () => { + const route = create_routes(['settings/[a]/[b]/index.html'])[0]; + + assert.deepEqual(route.exec('/settings/foo/bar'), { + a: 'foo', + b: 'bar' + }); + + assert.deepEqual(route.exec('/settings/foo'), { + a: 'foo', + b: null + }); + + assert.deepEqual(route.exec('/settings'), { + a: null, + b: null + }); + }); }); \ No newline at end of file