Skip to content

Commit dcfa8b1

Browse files
committed
test: add qs package tests
1 parent 3de5c9f commit dcfa8b1

2 files changed

Lines changed: 344 additions & 1 deletion

File tree

lib/parse.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ function parse(input) {
4545
} else if (k === currentKeyLength - 1) {
4646
root[key] = [root[key], currentValue];
4747
} else {
48-
Object.assign(root[key], { [key]: {} })
48+
Object.assign(root[key], { [key]: {} });
4949
}
5050
}
5151

test/qs.test.ts

Lines changed: 343 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
1+
"use strict";
2+
3+
import { test, assert } from "vitest";
4+
import qs from "../lib";
5+
6+
test("parses a simple string", () => {
7+
assert.deepEqual(qs.parse("0=foo"), { 0: "foo" });
8+
assert.deepEqual(qs.parse("foo=c++"), { foo: "c " });
9+
assert.deepEqual(qs.parse("a[>=]=23"), { a: { ">=": "23" } });
10+
assert.deepEqual(qs.parse("a[<=>]==23"), { a: { "<=>": "=23" } });
11+
assert.deepEqual(qs.parse("a[==]=23"), { a: { "==": "23" } });
12+
assert.deepEqual(qs.parse("foo", { strictNullHandling: true }), {
13+
foo: null,
14+
});
15+
assert.deepEqual(qs.parse("foo"), { foo: "" });
16+
assert.deepEqual(qs.parse("foo="), { foo: "" });
17+
assert.deepEqual(qs.parse("foo=bar"), { foo: "bar" });
18+
assert.deepEqual(qs.parse(" foo = bar = baz "), { " foo ": " bar = baz " });
19+
assert.deepEqual(qs.parse("foo=bar=baz"), { foo: "bar=baz" });
20+
assert.deepEqual(qs.parse("foo=bar&bar=baz"), { foo: "bar", bar: "baz" });
21+
assert.deepEqual(qs.parse("foo2=bar2&baz2="), { foo2: "bar2", baz2: "" });
22+
assert.deepEqual(qs.parse("foo=bar&baz", { strictNullHandling: true }), {
23+
foo: "bar",
24+
baz: null,
25+
});
26+
assert.deepEqual(qs.parse("foo=bar&baz"), { foo: "bar", baz: "" });
27+
assert.deepEqual(qs.parse("cht=p3&chd=t:60,40&chs=250x100&chl=Hello|World"), {
28+
cht: "p3",
29+
chd: "t:60,40",
30+
chs: "250x100",
31+
chl: "Hello|World",
32+
});
33+
assert.deepEqual(
34+
qs.parse("a[b]=c"),
35+
{ a: { b: "c" } },
36+
"parses a single nested string",
37+
);
38+
assert.deepEqual(
39+
qs.parse("a[b][c]=d"),
40+
{ a: { b: { c: "d" } } },
41+
"parses a double nested string",
42+
);
43+
44+
assert.deepEqual(
45+
qs.parse("a=b&a=c"),
46+
{ a: ["b", "c"] },
47+
"parses a simple array",
48+
);
49+
});
50+
51+
test("allows dot inside key", () => {
52+
assert.deepEqual(qs.parse("a.b=c"), { "a.b": "c" });
53+
});
54+
55+
test("parses an explicit array", () => {
56+
assert.deepEqual(qs.parse("a[]=b"), { a: ["b"] });
57+
assert.deepEqual(qs.parse("a[]=b&a[]=c"), { a: ["b", "c"] });
58+
assert.deepEqual(qs.parse("a[]=b&a[]=c&a[]=d"), { a: ["b", "c", "d"] });
59+
});
60+
61+
test("parses a nested array", () => {
62+
assert.deepEqual(qs.parse("a[b][]=c&a[b][]=d"), { a: { b: ["c", "d"] } });
63+
assert.deepEqual(qs.parse("a[>=]=25"), { a: { ">=": "25" } });
64+
});
65+
66+
test("supports encoded = signs", () => {
67+
assert.deepEqual(qs.parse("he%3Dllo=th%3Dere"), { "he=llo": "th=ere" });
68+
});
69+
70+
test("is ok with url encoded strings", () => {
71+
assert.deepEqual(qs.parse("a[b%20c]=d"), { a: { "b c": "d" } });
72+
assert.deepEqual(qs.parse("a[b]=c%20d"), { a: { b: "c d" } });
73+
});
74+
75+
test("transforms arrays to objects (dot notation)", () => {
76+
assert.deepEqual(
77+
qs.parse("foo[0].baz=bar&fool.bad=baz", {
78+
allowDots: true,
79+
}),
80+
{ foo: [{ baz: "bar" }], fool: { bad: "baz" } },
81+
);
82+
assert.deepEqual(
83+
qs.parse("foo[0].baz=bar&fool.bad.boo=baz", {
84+
allowDots: true,
85+
}),
86+
{ foo: [{ baz: "bar" }], fool: { bad: { boo: "baz" } } },
87+
);
88+
assert.deepEqual(
89+
qs.parse("foo[0][0].baz=bar&fool.bad=baz", {
90+
allowDots: true,
91+
}),
92+
{ foo: [[{ baz: "bar" }]], fool: { bad: "baz" } },
93+
);
94+
assert.deepEqual(
95+
qs.parse("foo[0].baz[0]=15&foo[0].bar=2", {
96+
allowDots: true,
97+
}),
98+
{ foo: [{ baz: ["15"], bar: "2" }] },
99+
);
100+
assert.deepEqual(
101+
qs.parse("foo[0].baz[0]=15&foo[0].baz[1]=16&foo[0].bar=2", {
102+
allowDots: true,
103+
}),
104+
{ foo: [{ baz: ["15", "16"], bar: "2" }] },
105+
);
106+
assert.deepEqual(qs.parse("foo.bad=baz&foo[0]=bar", { allowDots: true }), {
107+
foo: { bad: "baz", 0: "bar" },
108+
});
109+
assert.deepEqual(qs.parse("foo.bad=baz&foo[]=bar", { allowDots: true }), {
110+
foo: { bad: "baz", 0: "bar" },
111+
});
112+
assert.deepEqual(qs.parse("foo[]=bar&foo.bad=baz", { allowDots: true }), {
113+
foo: { 0: "bar", bad: "baz" },
114+
});
115+
assert.deepEqual(
116+
qs.parse("foo.bad=baz&foo[]=bar&foo[]=foo", {
117+
allowDots: true,
118+
}),
119+
{ foo: { bad: "baz", 0: "bar", 1: "foo" } },
120+
);
121+
assert.deepEqual(
122+
qs.parse("foo[0].a=a&foo[0].b=b&foo[1].a=aa&foo[1].b=bb", {
123+
allowDots: true,
124+
}),
125+
{ foo: [{ a: "a", b: "b" }, { a: "aa", b: "bb" }] },
126+
);
127+
});
128+
129+
test("supports malformed uri characters", () => {
130+
assert.deepEqual(qs.parse("{%:%}"), {
131+
"{%:%}": null,
132+
});
133+
assert.deepEqual(qs.parse("{%:%}="), { "{%:%}": "" });
134+
assert.deepEqual(qs.parse("foo=%:%}"), { foo: "%:%}" });
135+
});
136+
137+
test("doesn't produce empty keys", () => {
138+
assert.deepEqual(qs.parse("_r=1&"), { _r: "1" });
139+
});
140+
141+
test("cannot access Object prototype", () => {
142+
qs.parse("constructor[prototype][bad]=bad");
143+
qs.parse("bad[constructor][prototype][bad]=bad");
144+
assert.equal(typeof Object.prototype.bad, "undefined");
145+
});
146+
147+
test("parses arrays of objects", () => {
148+
assert.deepEqual(qs.parse("a[][b]=c"), { a: [{ b: "c" }] });
149+
assert.deepEqual(qs.parse("a[0][b]=c"), { a: [{ b: "c" }] });
150+
});
151+
152+
test("allows for empty strings in arrays", () => {
153+
assert.deepEqual(qs.parse("a[]=b&a[]=&a[]=c"), { a: ["b", "", "c"] });
154+
155+
assert.deepEqual(
156+
qs.parse("a[0]=b&a[1]&a[2]=c&a[19]=", {
157+
strictNullHandling: true,
158+
arrayLimit: 20,
159+
}),
160+
{ a: ["b", null, "c", ""] },
161+
"with arrayLimit 20 + array indices: null then empty string works",
162+
);
163+
assert.deepEqual(
164+
qs.parse("a[]=b&a[]&a[]=c&a[]=", {
165+
strictNullHandling: true,
166+
arrayLimit: 0,
167+
}),
168+
{ a: ["b", null, "c", ""] },
169+
"with arrayLimit 0 + array brackets: null then empty string works",
170+
);
171+
172+
assert.deepEqual(
173+
qs.parse("a[0]=b&a[1]=&a[2]=c&a[19]", {
174+
strictNullHandling: true,
175+
arrayLimit: 20,
176+
}),
177+
{ a: ["b", "", "c", null] },
178+
"with arrayLimit 20 + array indices: empty string then null works",
179+
);
180+
assert.deepEqual(
181+
qs.parse("a[]=b&a[]=&a[]=c&a[]", {
182+
strictNullHandling: true,
183+
arrayLimit: 0,
184+
}),
185+
{ a: ["b", "", "c", null] },
186+
"with arrayLimit 0 + array brackets: empty string then null works",
187+
);
188+
189+
assert.deepEqual(
190+
qs.parse("a[]=&a[]=b&a[]=c"),
191+
{ a: ["", "b", "c"] },
192+
"array brackets: empty strings work",
193+
);
194+
});
195+
196+
test("compacts sparse arrays", () => {
197+
assert.deepEqual(qs.parse("a[10]=1&a[2]=2", { arrayLimit: 20 }), {
198+
a: ["2", "1"],
199+
});
200+
assert.deepEqual(qs.parse("a[1][b][2][c]=1", { arrayLimit: 20 }), {
201+
a: [{ b: [{ c: "1" }] }],
202+
});
203+
assert.deepEqual(qs.parse("a[1][2][3][c]=1", { arrayLimit: 20 }), {
204+
a: [[[{ c: "1" }]]],
205+
});
206+
assert.deepEqual(qs.parse("a[1][2][3][c][1]=1", { arrayLimit: 20 }), {
207+
a: [[[{ c: ["1"] }]]],
208+
});
209+
});
210+
211+
test("continues parsing when no parent is found", () => {
212+
assert.deepEqual(qs.parse("[]=&a=b"), { 0: "", a: "b" });
213+
assert.deepEqual(qs.parse("[]&a=b"), {
214+
0: null,
215+
a: "b",
216+
});
217+
assert.deepEqual(qs.parse("[foo]=bar"), { foo: "bar" });
218+
});
219+
220+
test("does not error when parsing a very long array", () => {
221+
var str = "a[]=a";
222+
while (Buffer.byteLength(str) < 128 * 1024) {
223+
str = str + "&" + str;
224+
}
225+
226+
assert.doesNotThrow(function () {
227+
qs.parse(str);
228+
});
229+
});
230+
231+
test("should not throw when a native prototype has an enumerable property", () => {
232+
Object.prototype.crash = "";
233+
Array.prototype.crash = "";
234+
assert.doesNotThrow(qs.parse.bind(null, "a=b"));
235+
assert.deepEqual(qs.parse("a=b"), { a: "b" });
236+
assert.doesNotThrow(qs.parse.bind(null, "a[][b]=c"));
237+
assert.deepEqual(qs.parse("a[][b]=c"), { a: [{ b: "c" }] });
238+
delete Object.prototype.crash;
239+
delete Array.prototype.crash;
240+
});
241+
242+
test("parses an object and not child values", () => {
243+
var input = {
244+
"user[name]": { "pop[bob]": { test: 3 } },
245+
"user[email]": null,
246+
};
247+
248+
var expected = {
249+
user: {
250+
name: { "pop[bob]": { test: 3 } },
251+
email: null,
252+
},
253+
};
254+
255+
assert.deepEqual(qs.parse(input), expected);
256+
});
257+
258+
test("does not blow up when Buffer global is missing", () => {
259+
var tempBuffer = global.Buffer;
260+
delete global.Buffer;
261+
var result = qs.parse("a=b&c=d");
262+
global.Buffer = tempBuffer;
263+
assert.deepEqual(result, { a: "b", c: "d" });
264+
});
265+
266+
test("does not crash when parsing deep objects", () => {
267+
var parsed;
268+
var str = "foo";
269+
270+
for (var i = 0; i < 5000; i++) {
271+
str += "[p]";
272+
}
273+
274+
str += "=bar";
275+
276+
assert.doesNotThrow(function () {
277+
parsed = qs.parse(str, { depth: 5000 });
278+
});
279+
280+
assert.equal("foo" in parsed, true, 'parsed has "foo" property');
281+
});
282+
283+
test("does not allow overwriting prototype properties", () => {
284+
assert.deepEqual(qs.parse("a[hasOwnProperty]=b"), {
285+
a: { hasOwnProperty: "b" },
286+
});
287+
assert.deepEqual(qs.parse("hasOwnProperty=b"), { hasOwnProperty: "b" });
288+
289+
assert.deepEqual(qs.parse("toString"), {}, 'bare "toString" results in {}');
290+
});
291+
292+
test("can allow overwriting prototype properties", () => {
293+
assert.deepEqual(qs.parse("a[hasOwnProperty]=b", { allowPrototypes: true }), {
294+
a: { hasOwnProperty: "b" },
295+
});
296+
assert.deepEqual(qs.parse("hasOwnProperty=b", { allowPrototypes: true }), {
297+
hasOwnProperty: "b",
298+
});
299+
300+
assert.deepEqual(
301+
qs.parse("toString", { allowPrototypes: true }),
302+
{ toString: "" },
303+
'bare "toString" results in { toString: "" }',
304+
);
305+
});
306+
307+
test("params starting with a closing bracket", () => {
308+
assert.deepEqual(qs.parse("]=toString"), { "]": "toString" });
309+
assert.deepEqual(qs.parse("]]=toString"), { "]]": "toString" });
310+
assert.deepEqual(qs.parse("]hello]=toString"), { "]hello]": "toString" });
311+
});
312+
313+
test("params starting with a starting bracket", () => {
314+
assert.deepEqual(qs.parse("[=toString"), { "[": "toString" });
315+
assert.deepEqual(qs.parse("[[=toString"), { "[[": "toString" });
316+
assert.deepEqual(qs.parse("[hello[=toString"), { "[hello[": "toString" });
317+
});
318+
319+
test("add keys to objects", () => {
320+
assert.deepEqual(
321+
qs.parse("a[b]=c&a=d"),
322+
{ a: { b: "c", d: true } },
323+
"can add keys to objects",
324+
);
325+
326+
assert.deepEqual(
327+
qs.parse("a[b]=c&a=toString"),
328+
{ a: { b: "c" } },
329+
"can not overwrite prototype",
330+
);
331+
332+
assert.deepEqual(
333+
qs.parse("a[b]=c&a=toString", { allowPrototypes: true }),
334+
{ a: { b: "c", toString: true } },
335+
"can overwrite prototype with allowPrototypes true",
336+
);
337+
338+
assert.deepEqual(
339+
qs.parse("a[b]=c&a=toString", { plainObjects: true }),
340+
{ __proto__: null, a: { __proto__: null, b: "c", toString: true } },
341+
"can overwrite prototype with plainObjects true",
342+
);
343+
});

0 commit comments

Comments
 (0)