Skip to content

Commit

Permalink
fix(ArrayType): specify empty array mapping corner case
Browse files Browse the repository at this point in the history
Empty array is mapped to undefined.

Closes #1511
  • Loading branch information
christopherthielen committed Nov 17, 2014
1 parent a479fbd commit 74aa609
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 17 deletions.
22 changes: 15 additions & 7 deletions src/urlMatcherFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -502,25 +502,33 @@ Type.prototype.$asArray = function(mode, isSearch) {
};
}

function toArray(val) { return isArray(val) ? val : [ val ]; }
function fromArray(val) { return mode === "auto" && val && val.length === 1 ? val[0] : val; }
// Wrap non-array value as array
function arrayWrap(val) { return isArray(val) ? val : (isDefined(val) ? [ val ] : []); }
// Unwrap array value for "auto" mode. Return undefined for empty array.
function arrayUnwrap(val) {
switch(val.length) {
case 0: return undefined;
case 1: return mode === "auto" ? val[0] : val;
default: return val;
}
}
function falsey(val) { return !val; }

// Wraps type (.is/.encode/.decode) functions to operate on each value of an array
function arrayHandler(callback, alltrue) {
function arrayHandler(callback, allTruthyMode) {
return function handleArray(val) {
val = toArray(val);
val = arrayWrap(val);
var result = map(val, callback);
if (alltrue === true)
if (allTruthyMode === true)
return result.filter(falsey).length === 0;
return fromArray(result);
return arrayUnwrap(result);
};
}

// Wraps type (.equals) functions to operate on each value of an array
function arrayEqualsHandler(callback) {
return function handleArray(val1, val2) {
var left = toArray(val1), right = toArray(val2);
var left = arrayWrap(val1), right = arrayWrap(val2);
if (left.length !== right.length) return false;
for (var i = 0; i < left.length; i++) {
if (!callback(left[i], right[i])) return false;
Expand Down
86 changes: 76 additions & 10 deletions test/urlMatcherFactorySpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,29 +211,65 @@ describe("UrlMatcher", function () {
it("should conditionally be wrapped in an array by default", inject(function($location) {
var m = new UrlMatcher('/foo?param1');

expect(m.exec("/foo")).toEqual({});

// empty array [] is treated like "undefined"
expect(m.format({ param1: undefined })).toBe("/foo");
expect(m.format({ param1: [] })).toBe("/foo");
expect(m.format({ param1: "" })).toBe("/foo");
expect(m.format({ param1: "1" })).toBe("/foo?param1=1");
expect(m.format({ param1: [ "1" ] })).toBe("/foo?param1=1");
expect(m.format({ param1: [ "1", "2" ] })).toBe("/foo?param1=1&param1=2");

expect(m.exec("/foo")).toEqual({ param1: undefined });
expect(m.exec("/foo", {})).toEqual({ param1: undefined });
expect(m.exec("/foo", {param1: ""})).toEqual({ param1: undefined });
expect(m.exec("/foo", {param1: "1"})).toEqual({ param1: "1" }); // auto unwrap single values
expect(m.exec("/foo", {param1: [ "1", "2" ]})).toEqual({ param1: [ "1", "2" ] });

$location.url("/foo");
expect(m.exec($location.path(), $location.search())).toEqual( { param1: undefined } );
$location.url("/foo?param1=bar");
expect(m.exec($location.path(), $location.search())).toEqual( { param1: 'bar' } );
expect(m.format({ param1: 'bar' })).toBe("/foo?param1=bar");

expect(m.exec($location.path(), $location.search())).toEqual( { param1: 'bar' } ); // auto unwrap
$location.url("/foo?param1=bar&param1=baz");
expect(m.exec($location.path(), $location.search())).toEqual( { param1: ['bar', 'baz'] } );

expect(m.format({ })).toBe("/foo");
expect(m.format({ param1: undefined })).toBe("/foo");
expect(m.format({ param1: "" })).toBe("/foo");
expect(m.format({ param1: 'bar' })).toBe("/foo?param1=bar");
expect(m.format({ param1: [ 'bar' ] })).toBe("/foo?param1=bar");
expect(m.format({ param1: ['bar', 'baz'] })).toBe("/foo?param1=bar&param1=baz");

}));

it("should be wrapped in an array if array: true", inject(function($location) {
var m = new UrlMatcher('/foo?param1', { params: { param1: { array: true } } });

expect(m.exec("/foo")).toEqual({});

// empty array [] is treated like "undefined"
expect(m.format({ param1: undefined })).toBe("/foo");
expect(m.format({ param1: [] })).toBe("/foo");
expect(m.format({ param1: "" })).toBe("/foo");
expect(m.format({ param1: "1" })).toBe("/foo?param1=1");
expect(m.format({ param1: [ "1" ] })).toBe("/foo?param1=1");
expect(m.format({ param1: [ "1", "2" ] })).toBe("/foo?param1=1&param1=2");

expect(m.exec("/foo")).toEqual({ param1: undefined });
expect(m.exec("/foo", {})).toEqual({ param1: undefined });
expect(m.exec("/foo", {param1: ""})).toEqual({ param1: undefined });
expect(m.exec("/foo", {param1: "1"})).toEqual({ param1: [ "1" ] });
expect(m.exec("/foo", {param1: [ "1", "2" ]})).toEqual({ param1: [ "1", "2" ] });

$location.url("/foo");
expect(m.exec($location.path(), $location.search())).toEqual( { param1: undefined } );
$location.url("/foo?param1=bar");
expect(m.exec($location.path(), $location.search())).toEqual( { param1: [ 'bar' ] } );
expect(m.format({ param1: 'bar' })).toBe("/foo?param1=bar");
expect(m.format({ param1: [ 'bar' ] })).toBe("/foo?param1=bar");

$location.url("/foo?param1=bar&param1=baz");
expect(m.exec($location.path(), $location.search())).toEqual( { param1: ['bar', 'baz'] } );

expect(m.format({ })).toBe("/foo");
expect(m.format({ param1: undefined })).toBe("/foo");
expect(m.format({ param1: "" })).toBe("/foo");
expect(m.format({ param1: 'bar' })).toBe("/foo?param1=bar");
expect(m.format({ param1: [ 'bar' ] })).toBe("/foo?param1=bar");
expect(m.format({ param1: ['bar', 'baz'] })).toBe("/foo?param1=bar&param1=baz");
}));

Expand Down Expand Up @@ -292,6 +328,36 @@ describe("UrlMatcher", function () {
expect(m.format({ param1: [ 'bar', 'baz' ] })).toEqual("/foo/bar-baz");
}));

it("should behave similar to multi-value query params", inject(function($location) {
var m = new UrlMatcher('/foo/:param1[]');

// empty array [] is treated like "undefined"
expect(m.format({ "param1[]": undefined })).toBe("/foo/");
expect(m.format({ "param1[]": [] })).toBe("/foo/");
expect(m.format({ "param1[]": "" })).toBe("/foo/");
expect(m.format({ "param1[]": "1" })).toBe("/foo/1");
expect(m.format({ "param1[]": [ "1" ] })).toBe("/foo/1");
expect(m.format({ "param1[]": [ "1", "2" ] })).toBe("/foo/1-2");

expect(m.exec("/foo/")).toEqual({ "param1[]": undefined });
expect(m.exec("/foo/1")).toEqual({ "param1[]": [ "1" ] });
expect(m.exec("/foo/1-2")).toEqual({ "param1[]": [ "1", "2" ] });

$location.url("/foo/");
expect(m.exec($location.path(), $location.search())).toEqual( { "param1[]": undefined } );
$location.url("/foo/bar");
expect(m.exec($location.path(), $location.search())).toEqual( { "param1[]": [ 'bar' ] } );
$location.url("/foo/bar-baz");
expect(m.exec($location.path(), $location.search())).toEqual( { "param1[]": ['bar', 'baz'] } );

expect(m.format({ })).toBe("/foo/");
expect(m.format({ "param1[]": undefined })).toBe("/foo/");
expect(m.format({ "param1[]": "" })).toBe("/foo/");
expect(m.format({ "param1[]": 'bar' })).toBe("/foo/bar");
expect(m.format({ "param1[]": [ 'bar' ] })).toBe("/foo/bar");
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[]", inject(function($location) {
var m = new UrlMatcher('/foo/:param1[]');

Expand Down

0 comments on commit 74aa609

Please sign in to comment.