From df681065915f02949408888994a375e7ff2958bb Mon Sep 17 00:00:00 2001 From: sfoster Date: Sun, 26 Aug 2012 21:48:51 +0100 Subject: [PATCH 1/2] New implementation for parameterized route path matching, to avoid false matches when a token appears twice in the path --- path.js | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/path.js b/path.js index d56a2d8..7491970 100644 --- a/path.js +++ b/path.js @@ -53,6 +53,32 @@ var Path = { } }, 'match': function (path, parameterize) { + function matchPathToRoute(path, route) { + var tokens = route.split('/'); + var re = /\/?([^\/]+)/; + var isMatch = true; + var params = {}; + var match, routePart; + + for(; + isMatch && + tokens.length && + path.length && + (match = re.exec(path)); + path = path.substring(match[0].length)) { + var routePart = tokens.shift(); + if(routePart.charAt(0) == ':') { + // keep this param + params[routePart.substring(1)] = match[1]; + } else if(routePart !== match[1]) { + // non-parameterized part, they must match + isMatch = false; + } // else just continue + } + return(isMatch && (0 === tokens.length) && (0 === path.length)) ? + params : false; + } + var params = {}, route = null, possible_routes, slice, i, j, compare; for (route in Path.routes.defined) { if (route !== null && route !== undefined) { @@ -61,15 +87,8 @@ var Path = { for (j = 0; j < possible_routes.length; j++) { slice = possible_routes[j]; compare = path; - if (slice.search(/:/) > 0) { - for (i = 0; i < slice.split("/").length; i++) { - if ((i < compare.split("/").length) && (slice.split("/")[i].charAt(0) === ":")) { - params[slice.split('/')[i].replace(/:/, '')] = compare.split("/")[i]; - compare = compare.replace(compare.split("/")[i], slice.split("/")[i]); - } - } - } - if (slice === compare) { + params = matchPathToRoute(compare, slice); + if (params) { if (parameterize) { route.params = params; } From 2fd35e908d37dbfca1f05925f9a03d6e872dc8a5 Mon Sep 17 00:00:00 2001 From: sfoster Date: Sun, 26 Aug 2012 22:05:40 +0100 Subject: [PATCH 2/2] Add unit test for path matching fix, updating the 'back' test result --- tests/path.js.test.html | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/tests/path.js.test.html b/tests/path.js.test.html index 9d870fa..387d57b 100644 --- a/tests/path.js.test.html +++ b/tests/path.js.test.html @@ -15,9 +15,10 @@ "#E/params/3/check", "#F", "#G", - "#H", - "#H/10", - "#H/10/20" + "#H", + "#H/10", + "#H/10/20", + "#I/foran/I/anda/tooth/fora/tooth" ]; var index = 0; var timer = null; @@ -115,11 +116,17 @@ update("G[action - NOT HIT]"); }); - Path.map("#H(/:id_one)(/:id_two)").to(function(){ - var id_one = this.params["id_one"] || "N/A"; - var id_two = this.params["id_two"] || "N/A"; - update("H(one=" + id_one + ", two=" + id_two + ")"); - }); + Path.map("#H(/:id_one)(/:id_two)").to(function(){ + var id_one = this.params["id_one"] || "N/A"; + var id_two = this.params["id_two"] || "N/A"; + update("H(one=" + id_one + ", two=" + id_two + ")"); + }); + + Path.map("#I/foran/:one/anda/tooth/fora/:two").to(function(){ + var param_one = this.params["one"]; + var param_two = this.params["two"]; + update("I(one=" + param_one + ", two=" + param_two + ")"); + }); Path.rescue(function(){ update("RESCUE"); @@ -176,12 +183,13 @@

Test Suite

H(one=N/A, two=N/A) Optional parameters with only the require part submitted H(one=10, two=N/A) Optional parameters with one optional part submitted H(one=10, two=20) Optional parameters two levels deep - H(one=10, two=N/A) Testing "back" functionality + H(one=10, two=N/A) Testing "back" functionality + I(one=I, two=tooth) Matching with repeated tokens in a path

Expected

-
F[enter]::F[action]::A[enter]::A[action]::A[exit]::B[enter]::B[action]::C[action]::C[exit]::RESCUE::RESCUE::E[enter](parse id=1)::E[action](parse id=1)::E[enter](parse id=2)::E[action](parse id=2)::E[action](check id=3)::E[exit](check id=3)::F[enter]::F[action]::G[enter 1]::G[enter 2]::G[enter 3]::G[enter 4]::H(one=N/A, two=N/A)::H(one=10, two=N/A)::H(one=10, two=20)::H(one=10, two=N/A)
+
F[enter]::F[action]::A[enter]::A[action]::A[exit]::B[enter]::B[action]::C[action]::C[exit]::RESCUE::RESCUE::E[enter](parse id=1)::E[action](parse id=1)::E[enter](parse id=2)::E[action](parse id=2)::E[action](check id=3)::E[exit](check id=3)::F[enter]::F[action]::G[enter 1]::G[enter 2]::G[enter 3]::G[enter 4]::H(one=N/A, two=N/A)::H(one=10, two=N/A)::H(one=10, two=20)::I(one=I, two=tooth)::H(one=10, two=20)

Actual

Grade