Skip to content

Commit 8552f96

Browse files
committed
Rewrite and add proper tests
1 parent d9582e0 commit 8552f96

File tree

2 files changed

+58
-28
lines changed

2 files changed

+58
-28
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: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -822,9 +822,16 @@ QUnit.test("Deserialize query param nested object", (assert: Assert) => {
822822
const router = new RouteRecognizer<{}>();
823823
router.add([{ path: "/foo/bar", handler }]);
824824

825-
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' } } } });
825+
const results = queryParams(router.recognize("/foo/bar?filter=[user][name][$contains]=nick"));
826+
assert.deepEqual(results, {
827+
filter: {
828+
user: {
829+
name: {
830+
$contains: 'nick'
831+
}
832+
}
833+
}
834+
});
828835
});
829836

830837
QUnit.test("Multiple `/` routes recognize", (assert: Assert) => {
@@ -1639,15 +1646,15 @@ QUnit.module("Route Generation", hooks => {
16391646
QUnit.test(
16401647
"Parsing and generation results into the same input string",
16411648
(assert: Assert) => {
1642-
const query = "filter%20data=date";
1649+
const query = "filter data=date";
16431650
assert.equal(
16441651
router.generateQueryString(router.parseQueryString(query)),
16451652
"?" + query
16461653
);
16471654
}
16481655
);
16491656

1650-
QUnit.only("Generation works with query params", (assert: Assert) => {
1657+
QUnit.test("Generation works with query params", (assert: Assert) => {
16511658
assert.equal(
16521659
router.generate("index", { queryParams: { filter: "date" } }),
16531660
"/?filter=date"
@@ -1732,8 +1739,16 @@ QUnit.module("Route Generation", hooks => {
17321739
"/?filter=date&sort=0"
17331740
);
17341741
assert.equal(
1735-
router.generate("index", { queryParams: { filter: { user: { name: { $contains: 'scoot' } } }, sort: 0 } }),
1736-
"/?filter=[user][name][$contains]=scoot"
1742+
router.generate("index", { queryParams: { filter: { age: 10, user: { name: { $contains: 'scoot' } } }, sort: 0 } }),
1743+
"/?filter=[age]=10&filter=[user][name][$contains]=scoot&sort=0"
1744+
);
1745+
assert.equal(
1746+
router.generate("index", { queryParams: { sort: 0, filter: { age: 0, user: { name: { $contains: 'scoot' } } } } }),
1747+
"/?filter=[age]=0&filter=[user][name][$contains]=scoot&sort=0"
1748+
);
1749+
assert.equal(
1750+
router.generate("index", { queryParams: { sort: 0, filter: { name: 'bike', children: [{ name: { $contains: 'scoot' } }, { name: { $contains: 'er' }}] } }, sort: 0 }),
1751+
"/?filter=[children][0][name][$contains]=scoot&filter=[children][1][name][$contains]=er&filter=[name]=bike&sort=0"
17371752
);
17381753
});
17391754

0 commit comments

Comments
 (0)