Skip to content

Commit a3e2136

Browse files
committed
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.
1 parent ad07a8d commit a3e2136

File tree

2 files changed

+83
-8
lines changed

2 files changed

+83
-8
lines changed

src/urlMatcherFactory.js

+27-6
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ function UrlMatcher(pattern, config) {
137137
this.sourceSearch = '';
138138
}
139139

140-
compiled += quoteRegExp(segment) + '$';
140+
compiled += quoteRegExp(segment) + (config.strict === false ? '\/?' : '') + '$';
141141
segments.push(segment);
142142

143143
this.regexp = new RegExp(compiled, config.caseInsensitive ? 'i' : undefined);
@@ -346,7 +346,7 @@ Type.prototype.pattern = /.*/;
346346
*/
347347
function $UrlMatcherFactory() {
348348

349-
var isCaseInsensitive = false;
349+
var isCaseInsensitive = false, isStrictMode = true;
350350

351351
var enqueue = true, typeQueue = [], injector, defaultTypes = {
352352
int: {
@@ -392,20 +392,41 @@ function $UrlMatcherFactory() {
392392
}
393393
};
394394

395+
function getDefaultConfig() {
396+
return {
397+
strict: isStrictMode,
398+
caseInsensitive: isCaseInsensitive
399+
};
400+
}
401+
395402
/**
396403
* @ngdoc function
397404
* @name ui.router.util.$urlMatcherFactory#caseInsensitive
398405
* @methodOf ui.router.util.$urlMatcherFactory
399406
*
400407
* @description
401-
* Define if url matching should be case sensistive, the default behavior, or not.
402-
*
403-
* @param {bool} value false to match URL in a case sensitive manner; otherwise true;
408+
* Defines whether URL matching should be case sensitive (the default behavior), or not.
409+
*
410+
* @param {bool} value `false` to match URL in a case sensitive manner; otherwise `true`;
404411
*/
405412
this.caseInsensitive = function(value) {
406413
isCaseInsensitive = value;
407414
};
408415

416+
/**
417+
* @ngdoc function
418+
* @name ui.router.util.$urlMatcherFactory#strictMode
419+
* @methodOf ui.router.util.$urlMatcherFactory
420+
*
421+
* @description
422+
* Defines whether URLs should match trailing slashes, or not (the default behavior).
423+
*
424+
* @param {bool} value `false` to match trailing slashes in URLs, otherwise `true`.
425+
*/
426+
this.strictMode = function(value) {
427+
isStrictMode = value;
428+
};
429+
409430
/**
410431
* @ngdoc function
411432
* @name ui.router.util.$urlMatcherFactory#compile
@@ -419,7 +440,7 @@ function $UrlMatcherFactory() {
419440
* @returns {ui.router.util.type:UrlMatcher} The UrlMatcher.
420441
*/
421442
this.compile = function (pattern, config) {
422-
return new UrlMatcher(pattern, extend({ caseInsensitive: isCaseInsensitive }, config));
443+
return new UrlMatcher(pattern, extend(getDefaultConfig(), config));
423444
};
424445

425446
/**

test/urlMatcherFactorySpec.js

+56-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,37 @@
11
describe("UrlMatcher", function () {
22

3-
it("shoudl match static URLs", function () {
3+
describe("provider", function () {
4+
5+
var provider;
6+
7+
beforeEach(function() {
8+
angular.module('ui.router.router.test', function() {}).config(function ($urlMatcherFactoryProvider) {
9+
provider = $urlMatcherFactoryProvider;
10+
});
11+
12+
module('ui.router.router', 'ui.router.router.test');
13+
14+
inject(function($injector) {
15+
$injector.invoke(provider.$get);
16+
});
17+
});
18+
19+
it("should factory matchers with correct configuration", function () {
20+
provider.caseInsensitive(false);
21+
expect(provider.compile('/hello').exec('/HELLO')).toBeNull();
22+
23+
provider.caseInsensitive(true);
24+
expect(provider.compile('/hello').exec('/HELLO')).toEqual({});
25+
26+
provider.strictMode(true);
27+
expect(provider.compile('/hello').exec('/hello/')).toBeNull();
28+
29+
provider.strictMode(false);
30+
expect(provider.compile('/hello').exec('/hello/')).toEqual({});
31+
});
32+
});
33+
34+
it("should match static URLs", function () {
435
expect(new UrlMatcher('/hello/world').exec('/hello/world')).toEqual({});
536
});
637

@@ -14,7 +45,7 @@ describe("UrlMatcher", function () {
1445
expect(matcher.exec('/hello/world/suffix')).toBeNull();
1546
});
1647

17-
it("shoudl parse parameter placeholders", function () {
48+
it("should parse parameter placeholders", function () {
1849
var matcher = new UrlMatcher('/users/:id/details/{type}/{repeat:[0-9]+}?from&to');
1950
var params = matcher.parameters();
2051
expect(params.length).toBe(5);
@@ -267,4 +298,27 @@ describe("urlMatcherFactory", function () {
267298
});
268299
});
269300
});
301+
302+
describe("strict matching", function() {
303+
it("should match with or without trailing slash", function() {
304+
var m = new UrlMatcher('/users', { strict: false });
305+
expect(m.exec('/users')).toEqual({});
306+
expect(m.exec('/users/')).toEqual({});
307+
});
308+
309+
it("should not match multiple trailing slashes", function() {
310+
var m = new UrlMatcher('/users', { strict: false });
311+
expect(m.exec('/users//')).toBeNull();
312+
});
313+
314+
it("should match when defined with parameters", function() {
315+
var m = new UrlMatcher('/users/{name}', { strict: false, params: {
316+
name: { value: null }
317+
}});
318+
expect(m.exec('/users/')).toEqual({ name: null });
319+
expect(m.exec('/users/bob')).toEqual({ name: "bob" });
320+
expect(m.exec('/users/bob/')).toEqual({ name: "bob" });
321+
expect(m.exec('/users/bob//')).toBeNull();
322+
});
323+
});
270324
});

0 commit comments

Comments
 (0)