Skip to content

Commit 74aa609

Browse files
fix(ArrayType): specify empty array mapping corner case
Empty array is mapped to undefined. Closes #1511
1 parent a479fbd commit 74aa609

File tree

2 files changed

+91
-17
lines changed

2 files changed

+91
-17
lines changed

src/urlMatcherFactory.js

+15-7
Original file line numberDiff line numberDiff line change
@@ -502,25 +502,33 @@ Type.prototype.$asArray = function(mode, isSearch) {
502502
};
503503
}
504504

505-
function toArray(val) { return isArray(val) ? val : [ val ]; }
506-
function fromArray(val) { return mode === "auto" && val && val.length === 1 ? val[0] : val; }
505+
// Wrap non-array value as array
506+
function arrayWrap(val) { return isArray(val) ? val : (isDefined(val) ? [ val ] : []); }
507+
// Unwrap array value for "auto" mode. Return undefined for empty array.
508+
function arrayUnwrap(val) {
509+
switch(val.length) {
510+
case 0: return undefined;
511+
case 1: return mode === "auto" ? val[0] : val;
512+
default: return val;
513+
}
514+
}
507515
function falsey(val) { return !val; }
508516

509517
// Wraps type (.is/.encode/.decode) functions to operate on each value of an array
510-
function arrayHandler(callback, alltrue) {
518+
function arrayHandler(callback, allTruthyMode) {
511519
return function handleArray(val) {
512-
val = toArray(val);
520+
val = arrayWrap(val);
513521
var result = map(val, callback);
514-
if (alltrue === true)
522+
if (allTruthyMode === true)
515523
return result.filter(falsey).length === 0;
516-
return fromArray(result);
524+
return arrayUnwrap(result);
517525
};
518526
}
519527

520528
// Wraps type (.equals) functions to operate on each value of an array
521529
function arrayEqualsHandler(callback) {
522530
return function handleArray(val1, val2) {
523-
var left = toArray(val1), right = toArray(val2);
531+
var left = arrayWrap(val1), right = arrayWrap(val2);
524532
if (left.length !== right.length) return false;
525533
for (var i = 0; i < left.length; i++) {
526534
if (!callback(left[i], right[i])) return false;

test/urlMatcherFactorySpec.js

+76-10
Original file line numberDiff line numberDiff line change
@@ -211,29 +211,65 @@ describe("UrlMatcher", function () {
211211
it("should conditionally be wrapped in an array by default", inject(function($location) {
212212
var m = new UrlMatcher('/foo?param1');
213213

214-
expect(m.exec("/foo")).toEqual({});
215-
214+
// empty array [] is treated like "undefined"
215+
expect(m.format({ param1: undefined })).toBe("/foo");
216+
expect(m.format({ param1: [] })).toBe("/foo");
217+
expect(m.format({ param1: "" })).toBe("/foo");
218+
expect(m.format({ param1: "1" })).toBe("/foo?param1=1");
219+
expect(m.format({ param1: [ "1" ] })).toBe("/foo?param1=1");
220+
expect(m.format({ param1: [ "1", "2" ] })).toBe("/foo?param1=1&param1=2");
221+
222+
expect(m.exec("/foo")).toEqual({ param1: undefined });
223+
expect(m.exec("/foo", {})).toEqual({ param1: undefined });
224+
expect(m.exec("/foo", {param1: ""})).toEqual({ param1: undefined });
225+
expect(m.exec("/foo", {param1: "1"})).toEqual({ param1: "1" }); // auto unwrap single values
226+
expect(m.exec("/foo", {param1: [ "1", "2" ]})).toEqual({ param1: [ "1", "2" ] });
227+
228+
$location.url("/foo");
229+
expect(m.exec($location.path(), $location.search())).toEqual( { param1: undefined } );
216230
$location.url("/foo?param1=bar");
217-
expect(m.exec($location.path(), $location.search())).toEqual( { param1: 'bar' } );
218-
expect(m.format({ param1: 'bar' })).toBe("/foo?param1=bar");
219-
231+
expect(m.exec($location.path(), $location.search())).toEqual( { param1: 'bar' } ); // auto unwrap
220232
$location.url("/foo?param1=bar&param1=baz");
221233
expect(m.exec($location.path(), $location.search())).toEqual( { param1: ['bar', 'baz'] } );
234+
235+
expect(m.format({ })).toBe("/foo");
236+
expect(m.format({ param1: undefined })).toBe("/foo");
237+
expect(m.format({ param1: "" })).toBe("/foo");
238+
expect(m.format({ param1: 'bar' })).toBe("/foo?param1=bar");
239+
expect(m.format({ param1: [ 'bar' ] })).toBe("/foo?param1=bar");
222240
expect(m.format({ param1: ['bar', 'baz'] })).toBe("/foo?param1=bar&param1=baz");
241+
223242
}));
224243

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

228-
expect(m.exec("/foo")).toEqual({});
229-
247+
// empty array [] is treated like "undefined"
248+
expect(m.format({ param1: undefined })).toBe("/foo");
249+
expect(m.format({ param1: [] })).toBe("/foo");
250+
expect(m.format({ param1: "" })).toBe("/foo");
251+
expect(m.format({ param1: "1" })).toBe("/foo?param1=1");
252+
expect(m.format({ param1: [ "1" ] })).toBe("/foo?param1=1");
253+
expect(m.format({ param1: [ "1", "2" ] })).toBe("/foo?param1=1&param1=2");
254+
255+
expect(m.exec("/foo")).toEqual({ param1: undefined });
256+
expect(m.exec("/foo", {})).toEqual({ param1: undefined });
257+
expect(m.exec("/foo", {param1: ""})).toEqual({ param1: undefined });
258+
expect(m.exec("/foo", {param1: "1"})).toEqual({ param1: [ "1" ] });
259+
expect(m.exec("/foo", {param1: [ "1", "2" ]})).toEqual({ param1: [ "1", "2" ] });
260+
261+
$location.url("/foo");
262+
expect(m.exec($location.path(), $location.search())).toEqual( { param1: undefined } );
230263
$location.url("/foo?param1=bar");
231264
expect(m.exec($location.path(), $location.search())).toEqual( { param1: [ 'bar' ] } );
232-
expect(m.format({ param1: 'bar' })).toBe("/foo?param1=bar");
233-
expect(m.format({ param1: [ 'bar' ] })).toBe("/foo?param1=bar");
234-
235265
$location.url("/foo?param1=bar&param1=baz");
236266
expect(m.exec($location.path(), $location.search())).toEqual( { param1: ['bar', 'baz'] } );
267+
268+
expect(m.format({ })).toBe("/foo");
269+
expect(m.format({ param1: undefined })).toBe("/foo");
270+
expect(m.format({ param1: "" })).toBe("/foo");
271+
expect(m.format({ param1: 'bar' })).toBe("/foo?param1=bar");
272+
expect(m.format({ param1: [ 'bar' ] })).toBe("/foo?param1=bar");
237273
expect(m.format({ param1: ['bar', 'baz'] })).toBe("/foo?param1=bar&param1=baz");
238274
}));
239275

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

331+
it("should behave similar to multi-value query params", inject(function($location) {
332+
var m = new UrlMatcher('/foo/:param1[]');
333+
334+
// empty array [] is treated like "undefined"
335+
expect(m.format({ "param1[]": undefined })).toBe("/foo/");
336+
expect(m.format({ "param1[]": [] })).toBe("/foo/");
337+
expect(m.format({ "param1[]": "" })).toBe("/foo/");
338+
expect(m.format({ "param1[]": "1" })).toBe("/foo/1");
339+
expect(m.format({ "param1[]": [ "1" ] })).toBe("/foo/1");
340+
expect(m.format({ "param1[]": [ "1", "2" ] })).toBe("/foo/1-2");
341+
342+
expect(m.exec("/foo/")).toEqual({ "param1[]": undefined });
343+
expect(m.exec("/foo/1")).toEqual({ "param1[]": [ "1" ] });
344+
expect(m.exec("/foo/1-2")).toEqual({ "param1[]": [ "1", "2" ] });
345+
346+
$location.url("/foo/");
347+
expect(m.exec($location.path(), $location.search())).toEqual( { "param1[]": undefined } );
348+
$location.url("/foo/bar");
349+
expect(m.exec($location.path(), $location.search())).toEqual( { "param1[]": [ 'bar' ] } );
350+
$location.url("/foo/bar-baz");
351+
expect(m.exec($location.path(), $location.search())).toEqual( { "param1[]": ['bar', 'baz'] } );
352+
353+
expect(m.format({ })).toBe("/foo/");
354+
expect(m.format({ "param1[]": undefined })).toBe("/foo/");
355+
expect(m.format({ "param1[]": "" })).toBe("/foo/");
356+
expect(m.format({ "param1[]": 'bar' })).toBe("/foo/bar");
357+
expect(m.format({ "param1[]": [ 'bar' ] })).toBe("/foo/bar");
358+
expect(m.format({ "param1[]": ['bar', 'baz'] })).toBe("/foo/bar-baz");
359+
}));
360+
295361
it("should be split on - in url and wrapped in an array if paramname looks like param[]", inject(function($location) {
296362
var m = new UrlMatcher('/foo/:param1[]');
297363

0 commit comments

Comments
 (0)