From a3e21366bee0475c9795a1ec76f70eec41c5b4e3 Mon Sep 17 00:00:00 2001 From: Nate Abele Date: Wed, 16 Apr 2014 07:26:24 -0400 Subject: [PATCH] feat(UrlMatcher): implement non-strict matching Implements strict (and non-strict) matching, for configuring whether UrlMatchers should treat URLs with and without trailing slashes identically. --- src/urlMatcherFactory.js | 33 ++++++++++++++++---- test/urlMatcherFactorySpec.js | 58 +++++++++++++++++++++++++++++++++-- 2 files changed, 83 insertions(+), 8 deletions(-) diff --git a/src/urlMatcherFactory.js b/src/urlMatcherFactory.js index 0521fd531..4e49e5708 100644 --- a/src/urlMatcherFactory.js +++ b/src/urlMatcherFactory.js @@ -137,7 +137,7 @@ function UrlMatcher(pattern, config) { this.sourceSearch = ''; } - compiled += quoteRegExp(segment) + '$'; + compiled += quoteRegExp(segment) + (config.strict === false ? '\/?' : '') + '$'; segments.push(segment); this.regexp = new RegExp(compiled, config.caseInsensitive ? 'i' : undefined); @@ -346,7 +346,7 @@ Type.prototype.pattern = /.*/; */ function $UrlMatcherFactory() { - var isCaseInsensitive = false; + var isCaseInsensitive = false, isStrictMode = true; var enqueue = true, typeQueue = [], injector, defaultTypes = { int: { @@ -392,20 +392,41 @@ function $UrlMatcherFactory() { } }; + function getDefaultConfig() { + return { + strict: isStrictMode, + caseInsensitive: isCaseInsensitive + }; + } + /** * @ngdoc function * @name ui.router.util.$urlMatcherFactory#caseInsensitive * @methodOf ui.router.util.$urlMatcherFactory * * @description - * Define if url matching should be case sensistive, the default behavior, or not. - * - * @param {bool} value false to match URL in a case sensitive manner; otherwise true; + * Defines whether URL matching should be case sensitive (the default behavior), or not. + * + * @param {bool} value `false` to match URL in a case sensitive manner; otherwise `true`; */ this.caseInsensitive = function(value) { isCaseInsensitive = value; }; + /** + * @ngdoc function + * @name ui.router.util.$urlMatcherFactory#strictMode + * @methodOf ui.router.util.$urlMatcherFactory + * + * @description + * Defines whether URLs should match trailing slashes, or not (the default behavior). + * + * @param {bool} value `false` to match trailing slashes in URLs, otherwise `true`. + */ + this.strictMode = function(value) { + isStrictMode = value; + }; + /** * @ngdoc function * @name ui.router.util.$urlMatcherFactory#compile @@ -419,7 +440,7 @@ function $UrlMatcherFactory() { * @returns {ui.router.util.type:UrlMatcher} The UrlMatcher. */ this.compile = function (pattern, config) { - return new UrlMatcher(pattern, extend({ caseInsensitive: isCaseInsensitive }, config)); + return new UrlMatcher(pattern, extend(getDefaultConfig(), config)); }; /** diff --git a/test/urlMatcherFactorySpec.js b/test/urlMatcherFactorySpec.js index 0654d98fc..12fc4b33f 100644 --- a/test/urlMatcherFactorySpec.js +++ b/test/urlMatcherFactorySpec.js @@ -1,6 +1,37 @@ describe("UrlMatcher", function () { - it("shoudl match static URLs", function () { + describe("provider", function () { + + var provider; + + beforeEach(function() { + angular.module('ui.router.router.test', function() {}).config(function ($urlMatcherFactoryProvider) { + provider = $urlMatcherFactoryProvider; + }); + + module('ui.router.router', 'ui.router.router.test'); + + inject(function($injector) { + $injector.invoke(provider.$get); + }); + }); + + it("should factory matchers with correct configuration", function () { + provider.caseInsensitive(false); + expect(provider.compile('/hello').exec('/HELLO')).toBeNull(); + + provider.caseInsensitive(true); + expect(provider.compile('/hello').exec('/HELLO')).toEqual({}); + + provider.strictMode(true); + expect(provider.compile('/hello').exec('/hello/')).toBeNull(); + + provider.strictMode(false); + expect(provider.compile('/hello').exec('/hello/')).toEqual({}); + }); + }); + + it("should match static URLs", function () { expect(new UrlMatcher('/hello/world').exec('/hello/world')).toEqual({}); }); @@ -14,7 +45,7 @@ describe("UrlMatcher", function () { expect(matcher.exec('/hello/world/suffix')).toBeNull(); }); - it("shoudl parse parameter placeholders", function () { + it("should parse parameter placeholders", function () { var matcher = new UrlMatcher('/users/:id/details/{type}/{repeat:[0-9]+}?from&to'); var params = matcher.parameters(); expect(params.length).toBe(5); @@ -267,4 +298,27 @@ describe("urlMatcherFactory", function () { }); }); }); + + describe("strict matching", function() { + it("should match with or without trailing slash", function() { + var m = new UrlMatcher('/users', { strict: false }); + expect(m.exec('/users')).toEqual({}); + expect(m.exec('/users/')).toEqual({}); + }); + + it("should not match multiple trailing slashes", function() { + var m = new UrlMatcher('/users', { strict: false }); + expect(m.exec('/users//')).toBeNull(); + }); + + it("should match when defined with parameters", function() { + var m = new UrlMatcher('/users/{name}', { strict: false, params: { + name: { value: null } + }}); + expect(m.exec('/users/')).toEqual({ name: null }); + expect(m.exec('/users/bob')).toEqual({ name: "bob" }); + expect(m.exec('/users/bob/')).toEqual({ name: "bob" }); + expect(m.exec('/users/bob//')).toBeNull(); + }); + }); });