Skip to content

Commit 877fc19

Browse files
committed
Rewrite and add proper tests
1 parent d9582e0 commit 877fc19

File tree

2 files changed

+49
-27
lines changed

2 files changed

+49
-27
lines changed

lib/route-recognizer.ts

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -688,32 +688,47 @@ class RouteRecognizer<THandler = string> {
688688
}
689689

690690
generateQueryString(params: Params): string {
691-
const pairs: string[] = [];
692-
const keys: string[] = Object.keys(params);
693-
keys.sort();
694-
for (let i = 0; i < keys.length; i++) {
695-
const key = keys[i];
696-
const value = params[key];
697-
if (value == null) {
698-
continue;
691+
const reducer = (obj: Params, parentPrefix: string | null = null) => (prev: string[], key: string) => {
692+
const val = obj[key];
693+
if (val === null || val === undefined) {
694+
return prev;
699695
}
700-
let pair = encodeURIComponent(key);
701-
if (isArray(value)) {
702-
for (let j = 0; j < value.length; j++) {
703-
const arrayPair = key + "[]" + "=" + encodeURIComponent(value[j]);
704-
pairs.push(arrayPair);
705-
}
706-
} else {
707-
pair += "=" + encodeURIComponent(value as string);
708-
pairs.push(pair);
696+
697+
const prefix = parentPrefix ? `${parentPrefix}[${key}]` : key;
698+
699+
if (val == null || typeof val === 'function') {
700+
prev.push(`${prefix}=`);
701+
return prev;
709702
}
710-
}
703+
704+
if (isArray(val)) {
705+
for (let j = 0; j < val.length; j++) {
706+
// handle array query params. array format brackets. (Other options are indices a[0]=b&a[1]=c or repeat a=b&a=c)
707+
if (['string', 'number', 'boolean'].includes(typeof val[j])) {
708+
const arrayPair = key + "[]" + "=" + encodeURIComponent(val[j]);
709+
prev.push(arrayPair);
710+
} else {
711+
prev.push(Object.keys(val[j] as Params).sort().reduce(reducer(val[j] as Params, prefix + `[${j}]`), []).join('&'));
712+
}
713+
}
711714

712-
if (pairs.length === 0) {
713-
return "";
715+
return prev;
716+
} else if (['string', 'number', 'boolean'].includes(typeof val)) {
717+
prev.push(`${prefix}=${encodeURIComponent(val as string)}`);
718+
return prev;
719+
}
720+
721+
prev.push(Object.keys(val as Params).sort().reduce(reducer(val as Params, parentPrefix ? prefix : `${prefix}=`), []).join('&'));
722+
return prev;
723+
};
724+
725+
const sortedKeys = Object.keys(params).sort();
726+
// avoid appending unnecessary '?'
727+
if (!sortedKeys.length || sortedKeys.every((k) => params[k] === undefined || params[k] === null)) {
728+
return '';
714729
}
715730

716-
return "?" + pairs.join("&");
731+
return '?' + sortedKeys.reduce(reducer(params), []).join('&');
717732
}
718733

719734
parseQueryString(queryString: string): QueryParams {

tests/recognizer-tests.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -823,8 +823,7 @@ QUnit.test("Deserialize query param nested object", (assert: Assert) => {
823823
router.add([{ path: "/foo/bar", handler }]);
824824

825825
const results = router.recognize("/foo/bar?filter=[user][name][$contains]=nick");
826-
const p = results && results.queryParams;
827-
assert.deepEqual(p, { filter: { user: { name: { $contains: 'scoot' } } } });
826+
assert.deepEqual(results!.queryParams, { filter: { user: { name: { $contains: 'nick' } } } });
828827
});
829828

830829
QUnit.test("Multiple `/` routes recognize", (assert: Assert) => {
@@ -1639,15 +1638,15 @@ QUnit.module("Route Generation", hooks => {
16391638
QUnit.test(
16401639
"Parsing and generation results into the same input string",
16411640
(assert: Assert) => {
1642-
const query = "filter%20data=date";
1641+
const query = "filter data=date";
16431642
assert.equal(
16441643
router.generateQueryString(router.parseQueryString(query)),
16451644
"?" + query
16461645
);
16471646
}
16481647
);
16491648

1650-
QUnit.only("Generation works with query params", (assert: Assert) => {
1649+
QUnit.test("Generation works with query params", (assert: Assert) => {
16511650
assert.equal(
16521651
router.generate("index", { queryParams: { filter: "date" } }),
16531652
"/?filter=date"
@@ -1732,8 +1731,16 @@ QUnit.module("Route Generation", hooks => {
17321731
"/?filter=date&sort=0"
17331732
);
17341733
assert.equal(
1735-
router.generate("index", { queryParams: { filter: { user: { name: { $contains: 'scoot' } } }, sort: 0 } }),
1736-
"/?filter=[user][name][$contains]=scoot"
1734+
router.generate("index", { queryParams: { filter: { age: 10, user: { name: { $contains: 'scoot' } } }, sort: 0 } }),
1735+
"/?filter=[age]=10&filter=[user][name][$contains]=scoot&sort=0"
1736+
);
1737+
assert.equal(
1738+
router.generate("index", { queryParams: { sort: 0, filter: { age: 0, user: { name: { $contains: 'scoot' } } } } }),
1739+
"/?filter=[age]=0&filter=[user][name][$contains]=scoot&sort=0"
1740+
);
1741+
assert.equal(
1742+
router.generate("index", { queryParams: { sort: 0, filter: { name: 'bike', children: [{ name: { $contains: 'scoot' } }] } }, sort: 0 }),
1743+
"/?filter=[children][0][name][$contains]=scoot&filter=[name]=bike&sort=0"
17371744
);
17381745
});
17391746

0 commit comments

Comments
 (0)