From 89e99cd6e9727d476b96b50ed9b33a1060e4ef01 Mon Sep 17 00:00:00 2001 From: wawyed Date: Fri, 17 Jul 2020 17:42:30 +0100 Subject: [PATCH] fix(params): Bi-directionally en/decode path and search params. (#618) - fixes urls double and triple encoding some characters when synchronising the state with the current url. - adds a flag on UrlConfig to disable this decoding (for AngularJS which pre-decodes in the $location api) Fixes https://github.com/ui-router/angular/issues/340 --- src/url/interface.ts | 2 + src/url/urlConfig.ts | 1 + src/url/urlMatcher.ts | 25 +++- src/url/urlMatcherFactory.ts | 13 +- test/urlMatcherFactorySpec.ts | 248 +++++++++++++++++----------------- 5 files changed, 159 insertions(+), 130 deletions(-) diff --git a/src/url/interface.ts b/src/url/interface.ts index 73b7a1e0..1e010030 100644 --- a/src/url/interface.ts +++ b/src/url/interface.ts @@ -21,6 +21,8 @@ export interface UrlMatcherCompileConfig { state?: StateDeclaration; strict?: boolean; caseInsensitive?: boolean; + // If params are pre-decoded, set to false to avoid double decoding + decodeParams?: boolean; } /** @deprecated use [[UrlConfig]] */ diff --git a/src/url/urlConfig.ts b/src/url/urlConfig.ts index b65e2886..613f8c5f 100644 --- a/src/url/urlConfig.ts +++ b/src/url/urlConfig.ts @@ -18,6 +18,7 @@ import { isDefined, isString } from '../common'; */ export class UrlConfig implements Disposable { /** @internal */ paramTypes = new ParamTypes(); + /** @internal */ _decodeParams = true; /** @internal */ _isCaseInsensitive = false; /** @internal */ _isStrictMode = true; /** @internal */ _defaultSquashPolicy: boolean | string = false; diff --git a/src/url/urlMatcher.ts b/src/url/urlMatcher.ts index 1f6720f4..218994cc 100644 --- a/src/url/urlMatcher.ts +++ b/src/url/urlMatcher.ts @@ -53,6 +53,7 @@ const defaultConfig: UrlMatcherCompileConfig = { state: { params: {} }, strict: true, caseInsensitive: true, + decodeParams: true, }; /** @@ -338,6 +339,18 @@ export class UrlMatcher { return this.pattern; } + private _getDecodedParamValue(value: any, param: Param): any { + if (isDefined(value)) { + if (this.config.decodeParams && !param.type.raw && !isArray(value)) { + value = decodeURIComponent(value); + } + + value = param.type.decode(value); + } + + return param.value(value); + } + /** * Tests the specified url/path against this matcher. * @@ -406,17 +419,19 @@ export class UrlMatcher { for (let j = 0; j < param.replace.length; j++) { if (param.replace[j].from === value) value = param.replace[j].to; } + if (value && param.array === true) value = decodePathArray(value); - if (isDefined(value)) value = param.type.decode(value); - values[param.id] = param.value(value); + + values[param.id] = this._getDecodedParamValue(value, param); } - searchParams.forEach((param) => { + searchParams.forEach((param: Param) => { let value = search[param.id]; + for (let j = 0; j < param.replace.length; j++) { if (param.replace[j].from === value) value = param.replace[j].to; } - if (isDefined(value)) value = param.type.decode(value); - values[param.id] = param.value(value); + + values[param.id] = this._getDecodedParamValue(value, param); }); if (hash) values['#'] = hash; diff --git a/src/url/urlMatcherFactory.ts b/src/url/urlMatcherFactory.ts index a9e73631..66a10427 100644 --- a/src/url/urlMatcherFactory.ts +++ b/src/url/urlMatcherFactory.ts @@ -30,11 +30,12 @@ export class ParamFactory { export class UrlMatcherFactory { /** Creates a new [[Param]] for a given location (DefType) */ paramFactory = new ParamFactory(this.router); + // TODO: Check if removal of this will break anything, then remove these + UrlMatcher: typeof UrlMatcher = UrlMatcher; + Param: typeof Param = Param; // TODO: move implementations to UrlConfig (urlService.config) - constructor(/** @internal */ private router: UIRouter) { - extend(this, { UrlMatcher, Param }); - } + constructor(/** @internal */ private router: UIRouter) {} /** * Creates a [[UrlMatcher]] for the specified pattern. @@ -48,7 +49,11 @@ export class UrlMatcherFactory { // backward-compatible support for config.params -> config.state.params const params = config && !config.state && (config as any).params; config = params ? { state: { params }, ...config } : config; - const globalConfig = { strict: urlConfig._isStrictMode, caseInsensitive: urlConfig._isCaseInsensitive }; + const globalConfig: UrlMatcherCompileConfig = { + strict: urlConfig._isStrictMode, + caseInsensitive: urlConfig._isCaseInsensitive, + decodeParams: urlConfig._decodeParams, + }; return new UrlMatcher(pattern, urlConfig.paramTypes, this.paramFactory, extend(globalConfig, config)); } diff --git a/test/urlMatcherFactorySpec.ts b/test/urlMatcherFactorySpec.ts index 99d87d28..96f60ede 100644 --- a/test/urlMatcherFactorySpec.ts +++ b/test/urlMatcherFactorySpec.ts @@ -15,11 +15,11 @@ function init() { $location = locationPlugin.service; } -describe('UrlMatcher', function() { +describe('UrlMatcher', function () { beforeEach(init); - describe('provider', function() { - it('should factory matchers with correct configuration', function() { + describe('provider', function () { + it('should factory matchers with correct configuration', function () { $umf.caseInsensitive(false); expect($umf.compile('/hello').exec('/HELLO')).toBeNull(); @@ -33,7 +33,7 @@ describe('UrlMatcher', function() { expect($umf.compile('/hello').exec('/hello/')).toEqual({}); }); - it('should correctly validate UrlMatcher interface', function() { + it('should correctly validate UrlMatcher interface', function () { let m = $umf.compile('/'); expect($umf.isMatcher(m)).toBe(true); @@ -42,87 +42,87 @@ describe('UrlMatcher', function() { }); }); - it('should match static URLs', function() { + it('should match static URLs', function () { expect($umf.compile('/hello/world').exec('/hello/world')).toEqual({}); }); - it('should match static case insensitive URLs', function() { + it('should match static case insensitive URLs', function () { expect($umf.compile('/hello/world', { caseInsensitive: true }).exec('/heLLo/World')).toEqual({}); }); - it('should match against the entire path', function() { + it('should match against the entire path', function () { const matcher = $umf.compile('/hello/world', { strict: true }); expect(matcher.exec('/hello/world/')).toBeNull(); expect(matcher.exec('/hello/world/suffix')).toBeNull(); }); - it('should parse parameter placeholders', function() { + it('should parse parameter placeholders', function () { const matcher = $umf.compile('/users/:id/details/{type}/{repeat:[0-9]+}?from&to'); expect(matcher.parameters().map(prop('id'))).toEqual(['id', 'type', 'repeat', 'from', 'to']); }); - it('should encode and decode duplicate query string values as array', function() { + it('should encode and decode duplicate query string values as array', function () { const matcher = $umf.compile('/?foo'), array = { foo: ['bar', 'baz'] }; expect(matcher.exec('/', array)).toEqual(array); expect(matcher.format(array)).toBe('/?foo=bar&foo=baz'); }); - describe('snake-case parameters', function() { - it('should match if properly formatted', function() { + describe('snake-case parameters', function () { + it('should match if properly formatted', function () { const matcher = $umf.compile('/users/?from&to&snake-case&snake-case-triple'); expect(matcher.parameters().map(prop('id'))).toEqual(['from', 'to', 'snake-case', 'snake-case-triple']); }); - it('should not match if invalid', function() { + it('should not match if invalid', function () { let err = "Invalid parameter name '-snake' in pattern '/users/?from&to&-snake'"; - expect(function() { + expect(function () { $umf.compile('/users/?from&to&-snake'); }).toThrowError(err); err = "Invalid parameter name 'snake-' in pattern '/users/?from&to&snake-'"; - expect(function() { + expect(function () { $umf.compile('/users/?from&to&snake-'); }).toThrowError(err); }); }); - describe('parameters containing periods', function() { - it('should match if properly formatted', function() { + describe('parameters containing periods', function () { + it('should match if properly formatted', function () { const matcher = $umf.compile('/users/?from&to&with.periods&with.periods.also'); - const params = matcher.parameters().map(function(p) { + const params = matcher.parameters().map(function (p) { return p.id; }); expect(params.sort()).toEqual(['from', 'to', 'with.periods', 'with.periods.also']); }); - it('should not match if invalid', function() { + it('should not match if invalid', function () { let err = new Error("Invalid parameter name '.periods' in pattern '/users/?from&to&.periods'"); - expect(function() { + expect(function () { $umf.compile('/users/?from&to&.periods'); }).toThrow(err); err = new Error("Invalid parameter name 'periods.' in pattern '/users/?from&to&periods.'"); - expect(function() { + expect(function () { $umf.compile('/users/?from&to&periods.'); }).toThrow(err); }); }); - describe('.exec()', function() { - it('should capture parameter values', function() { + describe('.exec()', function () { + it('should capture parameter values', function () { const m = $umf.compile('/users/:id/details/{type}/{repeat:[0-9]+}?from&to', { strict: false }); expect(m.exec('/users/123/details//0', {})).toEqualData({ id: '123', type: '', repeat: '0' }); }); - it('should capture catch-all parameters', function() { + it('should capture catch-all parameters', function () { const m = $umf.compile('/document/*path'); expect(m.exec('/document/a/b/c', {})).toEqual({ path: 'a/b/c' }); expect(m.exec('/document/', {})).toEqual({ path: '' }); }); - it('should capture catch-all parameters in multiline url', function() { + it('should capture catch-all parameters in multiline url', function () { const m = $umf.compile('/document/*path'); expect(m.exec('/document/a/b/c\r\n/d', {})).toEqual({ path: 'a/b/c\r\n/d' }); expect(m.exec('/document/\r\na/b\r\n/c', {})).toEqual({ path: '\r\na/b\r\n/c' }); @@ -131,37 +131,42 @@ describe('UrlMatcher', function() { expect(m.exec('/document/\r\n', {})).toEqual({ path: '\r\n' }); }); - it('should use the optional regexp with curly brace placeholders', function() { + it('should use the optional regexp with curly brace placeholders', function () { const m = $umf.compile('/users/:id/details/{type}/{repeat:[0-9]+}?from&to'); expect(m.exec('/users/123/details/what/thisShouldBeDigits', {})).toBeNull(); }); - it("should not use optional regexp for '/'", function() { + it("should not use optional regexp for '/'", function () { const m = $umf.compile('/{language:(?:fr|en|de)}'); expect(m.exec('/', {})).toBeNull(); }); - it('should work with empty default value', function() { + it('should work with empty default value', function () { const m = $umf.compile('/foo/:str', { state: { params: { str: { value: '' } } } }); expect(m.exec('/foo/', {})).toEqual({ str: '' }); }); - it('should work with empty default value for regex', function() { + it('should work with empty default value for regex', function () { const m = $umf.compile('/foo/{param:(?:foo|bar|)}', { state: { params: { param: { value: '' } } } }); expect(m.exec('/foo/', {})).toEqual({ param: '' }); }); - it('should treat the URL as already decoded and does not decode it further', function() { + it('should decode params from .exec() urls', function () { + expect($umf.compile('/users/:id').exec('/users/100%25', {})).toEqual({ id: '100%' }); + }); + + it('should treat params as already decoded and not decode it further if decodeParams is false', function () { + router.urlService.config._decodeParams = false; expect($umf.compile('/users/:id').exec('/users/100%25', {})).toEqual({ id: '100%25' }); }); - xit('should allow embedded capture groups', function() { + xit('should allow embedded capture groups', function () { const shouldPass = { '/url/{matchedParam:([a-z]+)}/child/{childParam}': '/url/someword/child/childParam', '/url/{matchedParam:([a-z]+)}/child/{childParam}?foo': '/url/someword/child/childParam', }; - forEach(shouldPass, function(url, route) { + forEach(shouldPass, function (url, route) { expect($umf.compile(route).exec(url, {})).toEqual({ childParam: 'childParam', matchedParam: 'someword', @@ -169,14 +174,14 @@ describe('UrlMatcher', function() { }); }); - it('should throw on unbalanced capture list', function() { + it('should throw on unbalanced capture list', function () { const shouldThrow = { '/url/{matchedParam:([a-z]+)}/child/{childParam}': '/url/someword/child/childParam', '/url/{matchedParam:([a-z]+)}/child/{childParam}?foo': '/url/someword/child/childParam', }; - forEach(shouldThrow, function(url, route) { - expect(function() { + forEach(shouldThrow, function (url, route) { + expect(function () { $umf.compile(route).exec(url, {}); }).toThrowError("Unbalanced capture group in route '" + route + "'"); }); @@ -186,32 +191,32 @@ describe('UrlMatcher', function() { '/url/{matchedParam:[a-z]+}/child/{childParam}?foo': '/url/someword/child/childParam', }; - forEach(shouldPass, function(url, route) { - expect(function() { + forEach(shouldPass, function (url, route) { + expect(function () { $umf.compile(route).exec(url, {}); }).not.toThrow(); }); }); }); - describe('.format()', function() { - it('should reconstitute the URL', function() { + describe('.format()', function () { + it('should reconstitute the URL', function () { const m = $umf.compile('/users/:id/details/{type}/{repeat:[0-9]+}?from'), params = { id: '123', type: 'default', repeat: 444, ignored: 'value', from: '1970' }; expect(m.format(params)).toEqual('/users/123/details/default/444?from=1970'); }); - it('should encode URL parameters', function() { + it('should encode URL parameters', function () { expect($umf.compile('/users/:id').format({ id: '100%' })).toEqual('/users/100%25'); }); - it('encodes URL parameters with hashes', function() { + it('encodes URL parameters with hashes', function () { const m = $umf.compile('/users/:id#:section'); expect(m.format({ id: 'bob', section: 'contact-details' })).toEqual('/users/bob#contact-details'); }); - it('should trim trailing slashes when the terminal value is optional', function() { + it('should trim trailing slashes when the terminal value is optional', function () { const config = { state: { params: { id: { squash: true, value: '123' } } } }; const m = $umf.compile('/users/:id', config); const params = { id: '123' }; @@ -219,7 +224,7 @@ describe('UrlMatcher', function() { expect(m.format(params)).toEqual('/users'); }); - it('should format query parameters from parent, child, grandchild matchers', function() { + it('should format query parameters from parent, child, grandchild matchers', function () { const m = $umf.compile('/parent?qParent'); const m2 = m.append($umf.compile('/child?qChild')); const m3 = m2.append($umf.compile('/grandchild?qGrandchild')); @@ -233,20 +238,20 @@ describe('UrlMatcher', function() { }); }); - describe('.append()', function() { - it('should append matchers', function() { + describe('.append()', function () { + it('should append matchers', function () { const matcher = $umf.compile('/users/:id/details/{type}?from').append($umf.compile('/{repeat:[0-9]+}?to')); const params = matcher.parameters(); expect(params.map(prop('id'))).toEqual(['id', 'type', 'from', 'repeat', 'to']); }); - it('should return a new matcher', function() { + it('should return a new matcher', function () { const base = $umf.compile('/users/:id/details/{type}?from'); const matcher = base.append($umf.compile('/{repeat:[0-9]+}?to')); expect(matcher).not.toBe(base); }); - it('should respect $urlMatcherFactoryProvider.strictMode', function() { + it('should respect $urlMatcherFactoryProvider.strictMode', function () { let m = $umf.compile('/'); $umf.strictMode(false); m = m.append($umf.compile('foo')); @@ -254,7 +259,7 @@ describe('UrlMatcher', function() { expect(m.exec('/foo/')).toEqual({}); }); - it('should respect $urlMatcherFactoryProvider.caseInsensitive', function() { + it('should respect $urlMatcherFactoryProvider.caseInsensitive', function () { let m = $umf.compile('/'); $umf.caseInsensitive(true); m = m.append($umf.compile('foo')); @@ -262,14 +267,14 @@ describe('UrlMatcher', function() { expect(m.exec('/FOO')).toEqual({}); }); - it('should respect $urlMatcherFactoryProvider.caseInsensitive when validating regex params', function() { + it('should respect $urlMatcherFactoryProvider.caseInsensitive when validating regex params', function () { let m = $umf.compile('/'); $umf.caseInsensitive(true); m = m.append($umf.compile('foo/{param:bar}')); expect(m.validates({ param: 'BAR' })).toEqual(true); }); - it('should generate/match params in the proper order', function() { + it('should generate/match params in the proper order', function () { let m = $umf.compile('/foo?queryparam'); m = m.append($umf.compile('/bar/:pathparam')); expect(m.exec('/foo/bar/pathval', { queryparam: 'queryval' })).toEqual({ @@ -279,15 +284,15 @@ describe('UrlMatcher', function() { }); }); - describe('multivalue-query-parameters', function() { - it('should handle .is() for an array of values', function() { + describe('multivalue-query-parameters', function () { + it('should handle .is() for an array of values', function () { const m = $umf.compile('/foo?{param1:int}'), param = m.parameter('param1'); expect(param.type.is([1, 2, 3])).toBe(true); expect(param.type.is([1, '2', 3])).toBe(false); }); - it('should handle .equals() for two arrays of values', function() { + it('should handle .equals() for two arrays of values', function () { const m = $umf.compile('/foo?{param1:int}&{param2:date}'), param1 = m.parameter('param1'), param2 = m.parameter('param2'); @@ -308,7 +313,7 @@ describe('UrlMatcher', function() { ).toBe(false); }); - it('should conditionally be wrapped in an array by default', function() { + it('should conditionally be wrapped in an array by default', function () { const m = $umf.compile('/foo?param1'); // empty array [] is treated like "undefined" @@ -342,7 +347,7 @@ describe('UrlMatcher', function() { expect(m.format({ param1: ['bar', 'baz'] })).toBe('/foo?param1=bar¶m1=baz'); }); - it('should be wrapped in an array if array: true', function() { + it('should be wrapped in an array if array: true', function () { const m = $umf.compile('/foo?param1', { state: { params: { param1: { array: true } } } }); // empty array [] is treated like "undefined" @@ -376,7 +381,7 @@ describe('UrlMatcher', function() { expect(m.format({ param1: ['bar', 'baz'] })).toBe('/foo?param1=bar¶m1=baz'); }); - it('should be wrapped in an array if paramname looks like param[]', function() { + it('should be wrapped in an array if paramname looks like param[]', function () { const m = $umf.compile('/foo?param1[]'); expect(m.exec('/foo')).toEqualData({}); @@ -392,7 +397,7 @@ describe('UrlMatcher', function() { }); // Test for issue #2222 - it('should return default value, if query param is missing.', function() { + it('should return default value, if query param is missing.', function () { const m = $umf.compile('/state?param1¶m2¶m3¶m5', { state: { params: { @@ -401,7 +406,7 @@ describe('UrlMatcher', function() { param3: { array: true, value: [] }, param5: { array: true, - value: function() { + value: function () { return []; }, }, @@ -422,15 +427,15 @@ describe('UrlMatcher', function() { // Pass again through Param.value() for normalization (like transitionTo) const paramDefs = m.parameters(); - const values = map(parsed, function(val, key) { - return find(paramDefs, function(def) { + const values = map(parsed, function (val, key) { + return find(paramDefs, function (def) { return def.id === key; }).value(val); }); expect(values).toEqualData(expected); }); - it('should not be wrapped by ui-router into an array if array: false', function() { + it('should not be wrapped by ui-router into an array if array: false', function () { const m = $umf.compile('/foo?param1', { state: { params: { param1: { array: false } } } }); expect(m.exec('/foo')).toEqualData({}); @@ -446,8 +451,8 @@ describe('UrlMatcher', function() { }); }); - describe('multivalue-path-parameters', function() { - it('should behave as a single-value by default', function() { + describe('multivalue-path-parameters', function () { + it('should behave as a single-value by default', function () { const m = $umf.compile('/foo/:param1'); expect(m.exec('/foo/')).toEqual({ param1: '' }); @@ -457,7 +462,7 @@ describe('UrlMatcher', function() { expect(m.format({ param1: ['bar', 'baz'] })).toBe('/foo/bar%2Cbaz'); // coerced to string }); - it('should be split on - in url and wrapped in an array if array: true', function() { + it('should be split on - in url and wrapped in an array if array: true', function () { const m = $umf.compile('/foo/:param1', { state: { params: { param1: { array: true } } } }); expect(m.exec('/foo/')).toEqual({ param1: undefined }); @@ -470,7 +475,7 @@ describe('UrlMatcher', function() { expect(m.format({ param1: ['bar', 'baz'] })).toEqual('/foo/bar-baz'); }); - it('should behave similar to multi-value query params', function() { + it('should behave similar to multi-value query params', function () { const m = $umf.compile('/foo/:param1[]'); // empty array [] is treated like "undefined" @@ -500,7 +505,7 @@ describe('UrlMatcher', function() { expect(m.format({ 'param1[]': ['bar', 'baz'] })).toBe('/foo/bar-baz'); }); - it('should be split on - in url and wrapped in an array if paramname looks like param[]', function() { + it('should be split on - in url and wrapped in an array if paramname looks like param[]', function () { const m = $umf.compile('/foo/:param1[]'); expect(m.exec('/foo/')).toEqual({ 'param1[]': undefined }); @@ -512,7 +517,7 @@ describe('UrlMatcher', function() { expect(m.format({ 'param1[]': ['bar', 'baz'] })).toEqual('/foo/bar-baz'); }); - it("should allow path param arrays with '-' in the values", function() { + it("should allow path param arrays with '-' in the values", function () { const m = $umf.compile('/foo/:param1[]'); expect(m.exec('/foo/')).toEqual({ 'param1[]': undefined }); @@ -544,15 +549,15 @@ describe('UrlMatcher', function() { }); }); -describe('urlMatcherFactoryProvider', function() { +describe('urlMatcherFactoryProvider', function () { beforeEach(init); - describe('.type()', function() { + describe('.type()', function () { let m; - beforeEach(function() { - $umf.type('myType', {} as any, function() { + beforeEach(function () { + $umf.type('myType', {} as any, function () { return { - decode: function() { + decode: function () { return { status: 'decoded' }; }, is: isObject, @@ -561,27 +566,27 @@ describe('urlMatcherFactoryProvider', function() { m = $umf.compile('/test?{foo:myType}'); }); - it('should handle arrays properly with config-time custom type definitions', function() { + it('should handle arrays properly with config-time custom type definitions', function () { expect(m.exec('/test', { foo: '1' })).toEqual({ foo: { status: 'decoded' } }); expect(m.exec('/test', { foo: ['1', '2'] })).toEqual({ foo: [{ status: 'decoded' }, { status: 'decoded' }] }); }); }); // TODO: Fix object pollution between tests for urlMatcherConfig - afterEach(function() { + afterEach(function () { $umf.caseInsensitive(false); }); }); -describe('urlMatcherFactory', function() { +describe('urlMatcherFactory', function () { beforeEach(init); - it('compiles patterns', function() { + it('compiles patterns', function () { const matcher = $umf.compile('/hello/world'); expect(matcher instanceof UrlMatcher).toBe(true); }); - it('recognizes matchers', function() { + it('recognizes matchers', function () { expect($umf.isMatcher($umf.compile('/'))).toBe(true); const custom = { @@ -592,29 +597,30 @@ describe('urlMatcherFactory', function() { validates: noop, parameters: noop, parameter: noop, + _getDecodedParamValue: noop, }; expect($umf.isMatcher(custom)).toBe(true); }); - it('should handle case sensitive URL by default', function() { + it('should handle case sensitive URL by default', function () { expect($umf.compile('/hello/world').exec('/heLLo/WORLD')).toBeNull(); }); - it('should handle case insensitive URL', function() { + it('should handle case insensitive URL', function () { $umf.caseInsensitive(true); expect($umf.compile('/hello/world').exec('/heLLo/WORLD')).toEqual({}); }); - describe('typed parameters', function() { - it('should accept object definitions', function() { - const type = { encode: function() {}, decode: function() {} }; + describe('typed parameters', function () { + it('should accept object definitions', function () { + const type = { encode: function () {}, decode: function () {} }; $umf.type('myType1', type as any); expect($umf.type('myType1').encode).toBe(type.encode); }); - it('should reject duplicate definitions', function() { - $umf.type('myType2', { encode: function() {}, decode: function() {} } as any); - expect(function() { + it('should reject duplicate definitions', function () { + $umf.type('myType2', { encode: function () {}, decode: function () {} } as any); + expect(function () { $umf.type('myType2', {} as any); }).toThrowError("A type named 'myType2' has already been defined."); }); @@ -643,7 +649,7 @@ describe('urlMatcherFactory', function() { // expect(provider.type("myAnnotatedType").decode()).toBe($stateParams); // })); - it('should match built-in types', function() { + it('should match built-in types', function () { const m = $umf.compile('/{foo:int}/{flag:bool}'); expect(m.exec('/1138/1')).toEqual({ foo: 1138, flag: true }); expect(m.format({ foo: 5, flag: true })).toBe('/5/1'); @@ -652,13 +658,13 @@ describe('urlMatcherFactory', function() { expect(m.format({ foo: -5, flag: true })).toBe('/-5/1'); }); - it('should match built-in types with spaces', function() { + it('should match built-in types with spaces', function () { const m = $umf.compile('/{foo: int}/{flag: bool}'); expect(m.exec('/1138/1')).toEqual({ foo: 1138, flag: true }); expect(m.format({ foo: 5, flag: true })).toBe('/5/1'); }); - it('should not throw on null value', function() { + it('should not throw on null value', function () { let m = $umf.compile('/{foo:int}'); expect(m.exec('/1138')).toEqual({ foo: 1138 }); expect(m.format({ foo: null })).toBe(null); @@ -667,7 +673,7 @@ describe('urlMatcherFactory', function() { expect(m.format({ foo: null })).toBe('/1'); }); - it('should match types named only in params', function() { + it('should match types named only in params', function () { const m = $umf.compile('/{foo}/{flag}', { state: { params: { @@ -680,8 +686,8 @@ describe('urlMatcherFactory', function() { expect(m.format({ foo: 5, flag: true })).toBe('/5/1'); }); - it('should throw an error if a param type is declared twice', function() { - expect(function() { + it('should throw an error if a param type is declared twice', function () { + expect(function () { $umf.compile('/{foo:int}', { state: { params: { @@ -692,7 +698,7 @@ describe('urlMatcherFactory', function() { }).toThrow(new Error("Param 'foo' has two type configurations.")); }); - it('should encode/decode dates', function() { + it('should encode/decode dates', function () { const m = $umf.compile('/calendar/{date:date}'), result = m.exec('/calendar/2014-03-26'); const date = new Date(2014, 2, 26); @@ -702,7 +708,7 @@ describe('urlMatcherFactory', function() { expect(m.format({ date: date })).toBe('/calendar/2014-03-26'); }); - it('should encode/decode arbitrary objects to json', function() { + it('should encode/decode arbitrary objects to json', function () { const m = $umf.compile('/state/{param1:json}/{param2:json}'); const params = { @@ -717,7 +723,7 @@ describe('urlMatcherFactory', function() { expect(m.exec('/state/' + json1 + '/' + json2)).toEqual(params); }); - it('should not match invalid typed parameter values', function() { + it('should not match invalid typed parameter values', function () { const m = $umf.compile('/users/{id:int}'); expect(m.exec('/users/1138')['id']).toBe(1138); @@ -727,7 +733,7 @@ describe('urlMatcherFactory', function() { expect(m.format({ id: 'alpha' })).toBeNull(); }); - it('should automatically handle multiple search param values', function() { + it('should automatically handle multiple search param values', function () { const m = $umf.compile('/foo/{fooid:int}?{bar:int}'); $location.url('/foo/5?bar=1'); @@ -741,12 +747,12 @@ describe('urlMatcherFactory', function() { m.format(); }); - it('should allow custom types to handle multiple search param values manually', function() { + it('should allow custom types to handle multiple search param values manually', function () { $umf.type('custArray', { - encode: function(array) { + encode: function (array) { return array.join('-'); }, - decode: function(val) { + decode: function (val) { return isArray(val) ? val : val.split(/-/); }, equals: equals, @@ -765,8 +771,8 @@ describe('urlMatcherFactory', function() { }); }); - describe('optional parameters', function() { - it('should match with or without values', function() { + describe('optional parameters', function () { + it('should match with or without values', function () { const m = $umf.compile('/users/{id:int}', { state: { params: { id: { value: null, squash: true } } }, }); @@ -776,7 +782,7 @@ describe('urlMatcherFactory', function() { expect(m.exec('/users')['id']).toBeNull(); }); - it('should correctly match multiple', function() { + it('should correctly match multiple', function () { const m = $umf.compile('/users/{id:int}/{state:[A-Z]+}', { state: { params: { id: { value: null, squash: true }, state: { value: null, squash: true } } }, }); @@ -793,7 +799,7 @@ describe('urlMatcherFactory', function() { expect(m.exec('/users/NY')['id']).toBeNull(); }); - it('should correctly format with or without values', function() { + it('should correctly format with or without values', function () { const m = $umf.compile('/users/{id:int}', { state: { params: { id: { value: null } } }, }); @@ -801,7 +807,7 @@ describe('urlMatcherFactory', function() { expect(m.format({ id: 1138 })).toBe('/users/1138'); }); - it('should correctly format multiple', function() { + it('should correctly format multiple', function () { const m = $umf.compile('/users/{id:int}/{state:[A-Z]+}', { state: { params: { id: { value: null, squash: true }, state: { value: null, squash: true } } }, }); @@ -812,7 +818,7 @@ describe('urlMatcherFactory', function() { expect(m.format({ id: 1138, state: 'NY' })).toBe('/users/1138/NY'); }); - it('should match in between static segments', function() { + it('should match in between static segments', function () { const m = $umf.compile('/users/{user:int}/photos', { state: { params: { user: { value: 5, squash: true } } }, }); @@ -822,7 +828,7 @@ describe('urlMatcherFactory', function() { expect(m.format({ user: 1138 })).toBe('/users/1138/photos'); }); - it('should correctly format with an optional followed by a required parameter', function() { + it('should correctly format with an optional followed by a required parameter', function () { const m = $umf.compile('/home/:user/gallery/photos/:photo', { state: { params: { @@ -835,8 +841,8 @@ describe('urlMatcherFactory', function() { expect(m.format({ user: 1138, photo: 13 })).toBe('/home/1138/gallery/photos/13'); }); - describe('default values', function() { - it('should populate if not supplied in URL', function() { + describe('default values', function () { + it('should populate if not supplied in URL', function () { const m = $umf.compile('/users/{id:int}/{test}', { state: { params: { id: { value: 0, squash: true }, test: { value: 'foo', squash: true } } }, }); @@ -847,7 +853,7 @@ describe('urlMatcherFactory', function() { expect(m.exec('/users/bar/2')).toBeNull(); }); - it('should populate even if the regexp requires 1 or more chars', function() { + it('should populate even if the regexp requires 1 or more chars', function () { const m = $umf.compile('/record/{appId}/{recordId:[0-9a-fA-F]{10,24}}', { state: { params: { appId: null, recordId: null } }, }); @@ -857,14 +863,14 @@ describe('urlMatcherFactory', function() { }); }); - it('should allow shorthand definitions', function() { + it('should allow shorthand definitions', function () { const m = $umf.compile('/foo/:foo', { state: { params: { foo: 'bar' } }, }); expect(m.exec('/foo/')).toEqual({ foo: 'bar' }); }); - it('should populate query params', function() { + it('should populate query params', function () { const defaults = { order: 'name', limit: 25, page: 1 }; const m = $umf.compile('/foo?order&{limit:int}&{page:int}', { state: { params: defaults }, @@ -872,7 +878,7 @@ describe('urlMatcherFactory', function() { expect(m.exec('/foo')).toEqual(defaults); }); - it('should allow function-calculated values', function() { + it('should allow function-calculated values', function () { function barFn() { return 'Value from bar()'; } @@ -907,7 +913,7 @@ describe('urlMatcherFactory', function() { // expect(m.exec('/users/').user).toBe(user); // })); - xit('should match when used as prefix', function() { + xit('should match when used as prefix', function () { const m = $umf.compile('/{lang:[a-z]{2}}/foo', { state: { params: { lang: 'de' } }, }); @@ -915,7 +921,7 @@ describe('urlMatcherFactory', function() { expect(m.exec('/foo')).toEqual({ lang: 'de' }); }); - describe('squash policy', function() { + describe('squash policy', function () { const Session = { username: 'loggedinuser' }; function getMatcher(squash) { return $umf.compile('/user/:userid/gallery/:galleryid/photo/:photoid', { @@ -923,7 +929,7 @@ describe('urlMatcherFactory', function() { params: { userid: { squash: squash, - value: function() { + value: function () { return Session.username; }, }, @@ -933,7 +939,7 @@ describe('urlMatcherFactory', function() { }); } - it(': true should squash the default value and one slash', function() { + it(': true should squash the default value and one slash', function () { const m = getMatcher(true); const defaultParams = { userid: 'loggedinuser', galleryid: 'favorites', photoid: '123' }; @@ -946,7 +952,7 @@ describe('urlMatcherFactory', function() { expect(m.format(nonDefaultParams)).toBe('/user/otheruser/gallery/travel/photo/987'); }); - it(': false should not squash default values', function() { + it(': false should not squash default values', function () { const m = getMatcher(false); const defaultParams = { userid: 'loggedinuser', galleryid: 'favorites', photoid: '123' }; @@ -958,7 +964,7 @@ describe('urlMatcherFactory', function() { expect(m.format(nonDefaultParams)).toBe('/user/otheruser/gallery/travel/photo/987'); }); - it(": '' should squash the default value to an empty string", function() { + it(": '' should squash the default value to an empty string", function () { const m = getMatcher(''); const defaultParams = { userid: 'loggedinuser', galleryid: 'favorites', photoid: '123' }; @@ -970,7 +976,7 @@ describe('urlMatcherFactory', function() { expect(m.format(nonDefaultParams)).toBe('/user/otheruser/gallery/travel/photo/987'); }); - it(": '~' should squash the default value and replace it with '~'", function() { + it(": '~' should squash the default value and replace it with '~'", function () { const m = getMatcher('~'); const defaultParams = { userid: 'loggedinuser', galleryid: 'favorites', photoid: '123' }; @@ -986,19 +992,19 @@ describe('urlMatcherFactory', function() { }); }); - describe('strict matching', function() { - it('should match with or without trailing slash', function() { + describe('strict matching', function () { + it('should match with or without trailing slash', function () { const m = $umf.compile('/users', { strict: false }); expect(m.exec('/users')).toEqual({}); expect(m.exec('/users/')).toEqual({}); }); - it('should not match multiple trailing slashes', function() { + it('should not match multiple trailing slashes', function () { const m = $umf.compile('/users', { strict: false }); expect(m.exec('/users//')).toBeNull(); }); - it('should match when defined with parameters', function() { + it('should match when defined with parameters', function () { const m = $umf.compile('/users/{name}', { strict: false, state: {