UNPKG

32.1 kBJavaScriptView Raw
1'use strict';
2
3var test = require('tape');
4var qs = require('../');
5var utils = require('../lib/utils');
6var iconv = require('iconv-lite');
7var SaferBuffer = require('safer-buffer').Buffer;
8var hasSymbols = require('has-symbols');
9var hasBigInt = typeof BigInt === 'function';
10
11test('stringify()', function (t) {
12 t.test('stringifies a querystring object', function (st) {
13 st.equal(qs.stringify({ a: 'b' }), 'a=b');
14 st.equal(qs.stringify({ a: 1 }), 'a=1');
15 st.equal(qs.stringify({ a: 1, b: 2 }), 'a=1&b=2');
16 st.equal(qs.stringify({ a: 'A_Z' }), 'a=A_Z');
17 st.equal(qs.stringify({ a: '€' }), 'a=%E2%82%AC');
18 st.equal(qs.stringify({ a: '' }), 'a=%EE%80%80');
19 st.equal(qs.stringify({ a: 'א' }), 'a=%D7%90');
20 st.equal(qs.stringify({ a: '𐐷' }), 'a=%F0%90%90%B7');
21 st.end();
22 });
23
24 t.test('stringifies falsy values', function (st) {
25 st.equal(qs.stringify(undefined), '');
26 st.equal(qs.stringify(null), '');
27 st.equal(qs.stringify(null, { strictNullHandling: true }), '');
28 st.equal(qs.stringify(false), '');
29 st.equal(qs.stringify(0), '');
30 st.end();
31 });
32
33 t.test('stringifies symbols', { skip: !hasSymbols() }, function (st) {
34 st.equal(qs.stringify(Symbol.iterator), '');
35 st.equal(qs.stringify([Symbol.iterator]), '0=Symbol%28Symbol.iterator%29');
36 st.equal(qs.stringify({ a: Symbol.iterator }), 'a=Symbol%28Symbol.iterator%29');
37 st.equal(
38 qs.stringify({ a: [Symbol.iterator] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }),
39 'a[]=Symbol%28Symbol.iterator%29'
40 );
41 st.end();
42 });
43
44 t.test('stringifies bigints', { skip: !hasBigInt }, function (st) {
45 var three = BigInt(3);
46 var encodeWithN = function (value, defaultEncoder, charset) {
47 var result = defaultEncoder(value, defaultEncoder, charset);
48 return typeof value === 'bigint' ? result + 'n' : result;
49 };
50 st.equal(qs.stringify(three), '');
51 st.equal(qs.stringify([three]), '0=3');
52 st.equal(qs.stringify([three], { encoder: encodeWithN }), '0=3n');
53 st.equal(qs.stringify({ a: three }), 'a=3');
54 st.equal(qs.stringify({ a: three }, { encoder: encodeWithN }), 'a=3n');
55 st.equal(
56 qs.stringify({ a: [three] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }),
57 'a[]=3'
58 );
59 st.equal(
60 qs.stringify({ a: [three] }, { encodeValuesOnly: true, encoder: encodeWithN, arrayFormat: 'brackets' }),
61 'a[]=3n'
62 );
63 st.end();
64 });
65
66 t.test('adds query prefix', function (st) {
67 st.equal(qs.stringify({ a: 'b' }, { addQueryPrefix: true }), '?a=b');
68 st.end();
69 });
70
71 t.test('with query prefix, outputs blank string given an empty object', function (st) {
72 st.equal(qs.stringify({}, { addQueryPrefix: true }), '');
73 st.end();
74 });
75
76 t.test('stringifies nested falsy values', function (st) {
77 st.equal(qs.stringify({ a: { b: { c: null } } }), 'a%5Bb%5D%5Bc%5D=');
78 st.equal(qs.stringify({ a: { b: { c: null } } }, { strictNullHandling: true }), 'a%5Bb%5D%5Bc%5D');
79 st.equal(qs.stringify({ a: { b: { c: false } } }), 'a%5Bb%5D%5Bc%5D=false');
80 st.end();
81 });
82
83 t.test('stringifies a nested object', function (st) {
84 st.equal(qs.stringify({ a: { b: 'c' } }), 'a%5Bb%5D=c');
85 st.equal(qs.stringify({ a: { b: { c: { d: 'e' } } } }), 'a%5Bb%5D%5Bc%5D%5Bd%5D=e');
86 st.end();
87 });
88
89 t.test('stringifies a nested object with dots notation', function (st) {
90 st.equal(qs.stringify({ a: { b: 'c' } }, { allowDots: true }), 'a.b=c');
91 st.equal(qs.stringify({ a: { b: { c: { d: 'e' } } } }, { allowDots: true }), 'a.b.c.d=e');
92 st.end();
93 });
94
95 t.test('stringifies an array value', function (st) {
96 st.equal(
97 qs.stringify({ a: ['b', 'c', 'd'] }, { arrayFormat: 'indices' }),
98 'a%5B0%5D=b&a%5B1%5D=c&a%5B2%5D=d',
99 'indices => indices'
100 );
101 st.equal(
102 qs.stringify({ a: ['b', 'c', 'd'] }, { arrayFormat: 'brackets' }),
103 'a%5B%5D=b&a%5B%5D=c&a%5B%5D=d',
104 'brackets => brackets'
105 );
106 st.equal(
107 qs.stringify({ a: ['b', 'c', 'd'] }, { arrayFormat: 'comma' }),
108 'a=b%2Cc%2Cd',
109 'comma => comma'
110 );
111 st.equal(
112 qs.stringify({ a: ['b', 'c', 'd'] }),
113 'a%5B0%5D=b&a%5B1%5D=c&a%5B2%5D=d',
114 'default => indices'
115 );
116 st.end();
117 });
118
119 t.test('omits nulls when asked', function (st) {
120 st.equal(qs.stringify({ a: 'b', c: null }, { skipNulls: true }), 'a=b');
121 st.end();
122 });
123
124 t.test('omits nested nulls when asked', function (st) {
125 st.equal(qs.stringify({ a: { b: 'c', d: null } }, { skipNulls: true }), 'a%5Bb%5D=c');
126 st.end();
127 });
128
129 t.test('omits array indices when asked', function (st) {
130 st.equal(qs.stringify({ a: ['b', 'c', 'd'] }, { indices: false }), 'a=b&a=c&a=d');
131 st.end();
132 });
133
134 t.test('stringifies a nested array value', function (st) {
135 st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { arrayFormat: 'indices' }), 'a%5Bb%5D%5B0%5D=c&a%5Bb%5D%5B1%5D=d');
136 st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { arrayFormat: 'brackets' }), 'a%5Bb%5D%5B%5D=c&a%5Bb%5D%5B%5D=d');
137 st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { arrayFormat: 'comma' }), 'a%5Bb%5D=c%2Cd'); // a[b]=c,d
138 st.equal(qs.stringify({ a: { b: ['c', 'd'] } }), 'a%5Bb%5D%5B0%5D=c&a%5Bb%5D%5B1%5D=d');
139 st.end();
140 });
141
142 t.test('stringifies a nested array value with dots notation', function (st) {
143 st.equal(
144 qs.stringify(
145 { a: { b: ['c', 'd'] } },
146 { allowDots: true, encode: false, arrayFormat: 'indices' }
147 ),
148 'a.b[0]=c&a.b[1]=d',
149 'indices: stringifies with dots + indices'
150 );
151 st.equal(
152 qs.stringify(
153 { a: { b: ['c', 'd'] } },
154 { allowDots: true, encode: false, arrayFormat: 'brackets' }
155 ),
156 'a.b[]=c&a.b[]=d',
157 'brackets: stringifies with dots + brackets'
158 );
159 st.equal(
160 qs.stringify(
161 { a: { b: ['c', 'd'] } },
162 { allowDots: true, encode: false, arrayFormat: 'comma' }
163 ),
164 'a.b=c,d',
165 'comma: stringifies with dots + comma'
166 );
167 st.equal(
168 qs.stringify(
169 { a: { b: ['c', 'd'] } },
170 { allowDots: true, encode: false }
171 ),
172 'a.b[0]=c&a.b[1]=d',
173 'default: stringifies with dots + indices'
174 );
175 st.end();
176 });
177
178 t.test('stringifies an object inside an array', function (st) {
179 st.equal(
180 qs.stringify({ a: [{ b: 'c' }] }, { arrayFormat: 'indices' }),
181 'a%5B0%5D%5Bb%5D=c', // a[0][b]=c
182 'indices => brackets'
183 );
184 st.equal(
185 qs.stringify({ a: [{ b: 'c' }] }, { arrayFormat: 'brackets' }),
186 'a%5B%5D%5Bb%5D=c', // a[][b]=c
187 'brackets => brackets'
188 );
189 st.equal(
190 qs.stringify({ a: [{ b: 'c' }] }),
191 'a%5B0%5D%5Bb%5D=c',
192 'default => indices'
193 );
194
195 st.equal(
196 qs.stringify({ a: [{ b: { c: [1] } }] }, { arrayFormat: 'indices' }),
197 'a%5B0%5D%5Bb%5D%5Bc%5D%5B0%5D=1',
198 'indices => indices'
199 );
200
201 st.equal(
202 qs.stringify({ a: [{ b: { c: [1] } }] }, { arrayFormat: 'brackets' }),
203 'a%5B%5D%5Bb%5D%5Bc%5D%5B%5D=1',
204 'brackets => brackets'
205 );
206
207 st.equal(
208 qs.stringify({ a: [{ b: { c: [1] } }] }),
209 'a%5B0%5D%5Bb%5D%5Bc%5D%5B0%5D=1',
210 'default => indices'
211 );
212
213 st.end();
214 });
215
216 t.test('stringifies an array with mixed objects and primitives', function (st) {
217 st.equal(
218 qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encode: false, arrayFormat: 'indices' }),
219 'a[0][b]=1&a[1]=2&a[2]=3',
220 'indices => indices'
221 );
222 st.equal(
223 qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encode: false, arrayFormat: 'brackets' }),
224 'a[][b]=1&a[]=2&a[]=3',
225 'brackets => brackets'
226 );
227 st.equal(
228 qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encode: false }),
229 'a[0][b]=1&a[1]=2&a[2]=3',
230 'default => indices'
231 );
232
233 st.end();
234 });
235
236 t.test('stringifies an object inside an array with dots notation', function (st) {
237 st.equal(
238 qs.stringify(
239 { a: [{ b: 'c' }] },
240 { allowDots: true, encode: false, arrayFormat: 'indices' }
241 ),
242 'a[0].b=c',
243 'indices => indices'
244 );
245 st.equal(
246 qs.stringify(
247 { a: [{ b: 'c' }] },
248 { allowDots: true, encode: false, arrayFormat: 'brackets' }
249 ),
250 'a[].b=c',
251 'brackets => brackets'
252 );
253 st.equal(
254 qs.stringify(
255 { a: [{ b: 'c' }] },
256 { allowDots: true, encode: false }
257 ),
258 'a[0].b=c',
259 'default => indices'
260 );
261
262 st.equal(
263 qs.stringify(
264 { a: [{ b: { c: [1] } }] },
265 { allowDots: true, encode: false, arrayFormat: 'indices' }
266 ),
267 'a[0].b.c[0]=1',
268 'indices => indices'
269 );
270 st.equal(
271 qs.stringify(
272 { a: [{ b: { c: [1] } }] },
273 { allowDots: true, encode: false, arrayFormat: 'brackets' }
274 ),
275 'a[].b.c[]=1',
276 'brackets => brackets'
277 );
278 st.equal(
279 qs.stringify(
280 { a: [{ b: { c: [1] } }] },
281 { allowDots: true, encode: false }
282 ),
283 'a[0].b.c[0]=1',
284 'default => indices'
285 );
286
287 st.end();
288 });
289
290 t.test('does not omit object keys when indices = false', function (st) {
291 st.equal(qs.stringify({ a: [{ b: 'c' }] }, { indices: false }), 'a%5Bb%5D=c');
292 st.end();
293 });
294
295 t.test('uses indices notation for arrays when indices=true', function (st) {
296 st.equal(qs.stringify({ a: ['b', 'c'] }, { indices: true }), 'a%5B0%5D=b&a%5B1%5D=c');
297 st.end();
298 });
299
300 t.test('uses indices notation for arrays when no arrayFormat is specified', function (st) {
301 st.equal(qs.stringify({ a: ['b', 'c'] }), 'a%5B0%5D=b&a%5B1%5D=c');
302 st.end();
303 });
304
305 t.test('uses indices notation for arrays when no arrayFormat=indices', function (st) {
306 st.equal(qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'indices' }), 'a%5B0%5D=b&a%5B1%5D=c');
307 st.end();
308 });
309
310 t.test('uses repeat notation for arrays when no arrayFormat=repeat', function (st) {
311 st.equal(qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'repeat' }), 'a=b&a=c');
312 st.end();
313 });
314
315 t.test('uses brackets notation for arrays when no arrayFormat=brackets', function (st) {
316 st.equal(qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'brackets' }), 'a%5B%5D=b&a%5B%5D=c');
317 st.end();
318 });
319
320 t.test('stringifies a complicated object', function (st) {
321 st.equal(qs.stringify({ a: { b: 'c', d: 'e' } }), 'a%5Bb%5D=c&a%5Bd%5D=e');
322 st.end();
323 });
324
325 t.test('stringifies an empty value', function (st) {
326 st.equal(qs.stringify({ a: '' }), 'a=');
327 st.equal(qs.stringify({ a: null }, { strictNullHandling: true }), 'a');
328
329 st.equal(qs.stringify({ a: '', b: '' }), 'a=&b=');
330 st.equal(qs.stringify({ a: null, b: '' }, { strictNullHandling: true }), 'a&b=');
331
332 st.equal(qs.stringify({ a: { b: '' } }), 'a%5Bb%5D=');
333 st.equal(qs.stringify({ a: { b: null } }, { strictNullHandling: true }), 'a%5Bb%5D');
334 st.equal(qs.stringify({ a: { b: null } }, { strictNullHandling: false }), 'a%5Bb%5D=');
335
336 st.end();
337 });
338
339 t.test('stringifies an empty array in different arrayFormat', function (st) {
340 st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false }), 'b[0]=&c=c');
341 // arrayFormat default
342 st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'indices' }), 'b[0]=&c=c');
343 st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'brackets' }), 'b[]=&c=c');
344 st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'repeat' }), 'b=&c=c');
345 st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'comma' }), 'b=&c=c');
346 // with strictNullHandling
347 st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'indices', strictNullHandling: true }), 'b[0]&c=c');
348 st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'brackets', strictNullHandling: true }), 'b[]&c=c');
349 st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'repeat', strictNullHandling: true }), 'b&c=c');
350 st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'comma', strictNullHandling: true }), 'b&c=c');
351 // with skipNulls
352 st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'indices', skipNulls: true }), 'c=c');
353 st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'brackets', skipNulls: true }), 'c=c');
354 st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'repeat', skipNulls: true }), 'c=c');
355 st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'comma', skipNulls: true }), 'c=c');
356
357 st.end();
358 });
359
360 t.test('stringifies a null object', { skip: !Object.create }, function (st) {
361 var obj = Object.create(null);
362 obj.a = 'b';
363 st.equal(qs.stringify(obj), 'a=b');
364 st.end();
365 });
366
367 t.test('returns an empty string for invalid input', function (st) {
368 st.equal(qs.stringify(undefined), '');
369 st.equal(qs.stringify(false), '');
370 st.equal(qs.stringify(null), '');
371 st.equal(qs.stringify(''), '');
372 st.end();
373 });
374
375 t.test('stringifies an object with a null object as a child', { skip: !Object.create }, function (st) {
376 var obj = { a: Object.create(null) };
377
378 obj.a.b = 'c';
379 st.equal(qs.stringify(obj), 'a%5Bb%5D=c');
380 st.end();
381 });
382
383 t.test('drops keys with a value of undefined', function (st) {
384 st.equal(qs.stringify({ a: undefined }), '');
385
386 st.equal(qs.stringify({ a: { b: undefined, c: null } }, { strictNullHandling: true }), 'a%5Bc%5D');
387 st.equal(qs.stringify({ a: { b: undefined, c: null } }, { strictNullHandling: false }), 'a%5Bc%5D=');
388 st.equal(qs.stringify({ a: { b: undefined, c: '' } }), 'a%5Bc%5D=');
389 st.end();
390 });
391
392 t.test('url encodes values', function (st) {
393 st.equal(qs.stringify({ a: 'b c' }), 'a=b%20c');
394 st.end();
395 });
396
397 t.test('stringifies a date', function (st) {
398 var now = new Date();
399 var str = 'a=' + encodeURIComponent(now.toISOString());
400 st.equal(qs.stringify({ a: now }), str);
401 st.end();
402 });
403
404 t.test('stringifies the weird object from qs', function (st) {
405 st.equal(qs.stringify({ 'my weird field': '~q1!2"\'w$5&7/z8)?' }), 'my%20weird%20field=~q1%212%22%27w%245%267%2Fz8%29%3F');
406 st.end();
407 });
408
409 t.test('skips properties that are part of the object prototype', function (st) {
410 Object.prototype.crash = 'test';
411 st.equal(qs.stringify({ a: 'b' }), 'a=b');
412 st.equal(qs.stringify({ a: { b: 'c' } }), 'a%5Bb%5D=c');
413 delete Object.prototype.crash;
414 st.end();
415 });
416
417 t.test('stringifies boolean values', function (st) {
418 st.equal(qs.stringify({ a: true }), 'a=true');
419 st.equal(qs.stringify({ a: { b: true } }), 'a%5Bb%5D=true');
420 st.equal(qs.stringify({ b: false }), 'b=false');
421 st.equal(qs.stringify({ b: { c: false } }), 'b%5Bc%5D=false');
422 st.end();
423 });
424
425 t.test('stringifies buffer values', function (st) {
426 st.equal(qs.stringify({ a: SaferBuffer.from('test') }), 'a=test');
427 st.equal(qs.stringify({ a: { b: SaferBuffer.from('test') } }), 'a%5Bb%5D=test');
428 st.end();
429 });
430
431 t.test('stringifies an object using an alternative delimiter', function (st) {
432 st.equal(qs.stringify({ a: 'b', c: 'd' }, { delimiter: ';' }), 'a=b;c=d');
433 st.end();
434 });
435
436 t.test('does not blow up when Buffer global is missing', function (st) {
437 var tempBuffer = global.Buffer;
438 delete global.Buffer;
439 var result = qs.stringify({ a: 'b', c: 'd' });
440 global.Buffer = tempBuffer;
441 st.equal(result, 'a=b&c=d');
442 st.end();
443 });
444
445 t.test('does not crash when parsing circular references', function (st) {
446 var a = {};
447 a.b = a;
448
449 st['throws'](
450 function () { qs.stringify({ 'foo[bar]': 'baz', 'foo[baz]': a }); },
451 RangeError,
452 'cyclic values throw'
453 );
454
455 var circular = {
456 a: 'value'
457 };
458 circular.a = circular;
459 st['throws'](
460 function () { qs.stringify(circular); },
461 RangeError,
462 'cyclic values throw'
463 );
464
465 st.end();
466 });
467
468 t.test('non-circular duplicated references can still work', function (st) {
469 var hourOfDay = {
470 'function': 'hour_of_day'
471 };
472
473 var p1 = {
474 'function': 'gte',
475 arguments: [hourOfDay, 0]
476 };
477 var p2 = {
478 'function': 'lte',
479 arguments: [hourOfDay, 23]
480 };
481
482 st.equal(
483 qs.stringify({ filters: { $and: [p1, p2] } }, { encodeValuesOnly: true }),
484 'filters[$and][0][function]=gte&filters[$and][0][arguments][0][function]=hour_of_day&filters[$and][0][arguments][1]=0&filters[$and][1][function]=lte&filters[$and][1][arguments][0][function]=hour_of_day&filters[$and][1][arguments][1]=23'
485 );
486
487 st.end();
488 });
489
490 t.test('selects properties when filter=array', function (st) {
491 st.equal(qs.stringify({ a: 'b' }, { filter: ['a'] }), 'a=b');
492 st.equal(qs.stringify({ a: 1 }, { filter: [] }), '');
493
494 st.equal(
495 qs.stringify(
496 { a: { b: [1, 2, 3, 4], c: 'd' }, c: 'f' },
497 { filter: ['a', 'b', 0, 2], arrayFormat: 'indices' }
498 ),
499 'a%5Bb%5D%5B0%5D=1&a%5Bb%5D%5B2%5D=3',
500 'indices => indices'
501 );
502 st.equal(
503 qs.stringify(
504 { a: { b: [1, 2, 3, 4], c: 'd' }, c: 'f' },
505 { filter: ['a', 'b', 0, 2], arrayFormat: 'brackets' }
506 ),
507 'a%5Bb%5D%5B%5D=1&a%5Bb%5D%5B%5D=3',
508 'brackets => brackets'
509 );
510 st.equal(
511 qs.stringify(
512 { a: { b: [1, 2, 3, 4], c: 'd' }, c: 'f' },
513 { filter: ['a', 'b', 0, 2] }
514 ),
515 'a%5Bb%5D%5B0%5D=1&a%5Bb%5D%5B2%5D=3',
516 'default => indices'
517 );
518
519 st.end();
520 });
521
522 t.test('supports custom representations when filter=function', function (st) {
523 var calls = 0;
524 var obj = { a: 'b', c: 'd', e: { f: new Date(1257894000000) } };
525 var filterFunc = function (prefix, value) {
526 calls += 1;
527 if (calls === 1) {
528 st.equal(prefix, '', 'prefix is empty');
529 st.equal(value, obj);
530 } else if (prefix === 'c') {
531 return void 0;
532 } else if (value instanceof Date) {
533 st.equal(prefix, 'e[f]');
534 return value.getTime();
535 }
536 return value;
537 };
538
539 st.equal(qs.stringify(obj, { filter: filterFunc }), 'a=b&e%5Bf%5D=1257894000000');
540 st.equal(calls, 5);
541 st.end();
542 });
543
544 t.test('can disable uri encoding', function (st) {
545 st.equal(qs.stringify({ a: 'b' }, { encode: false }), 'a=b');
546 st.equal(qs.stringify({ a: { b: 'c' } }, { encode: false }), 'a[b]=c');
547 st.equal(qs.stringify({ a: 'b', c: null }, { strictNullHandling: true, encode: false }), 'a=b&c');
548 st.end();
549 });
550
551 t.test('can sort the keys', function (st) {
552 var sort = function (a, b) {
553 return a.localeCompare(b);
554 };
555 st.equal(qs.stringify({ a: 'c', z: 'y', b: 'f' }, { sort: sort }), 'a=c&b=f&z=y');
556 st.equal(qs.stringify({ a: 'c', z: { j: 'a', i: 'b' }, b: 'f' }, { sort: sort }), 'a=c&b=f&z%5Bi%5D=b&z%5Bj%5D=a');
557 st.end();
558 });
559
560 t.test('can sort the keys at depth 3 or more too', function (st) {
561 var sort = function (a, b) {
562 return a.localeCompare(b);
563 };
564 st.equal(
565 qs.stringify(
566 { a: 'a', z: { zj: { zjb: 'zjb', zja: 'zja' }, zi: { zib: 'zib', zia: 'zia' } }, b: 'b' },
567 { sort: sort, encode: false }
568 ),
569 'a=a&b=b&z[zi][zia]=zia&z[zi][zib]=zib&z[zj][zja]=zja&z[zj][zjb]=zjb'
570 );
571 st.equal(
572 qs.stringify(
573 { a: 'a', z: { zj: { zjb: 'zjb', zja: 'zja' }, zi: { zib: 'zib', zia: 'zia' } }, b: 'b' },
574 { sort: null, encode: false }
575 ),
576 'a=a&z[zj][zjb]=zjb&z[zj][zja]=zja&z[zi][zib]=zib&z[zi][zia]=zia&b=b'
577 );
578 st.end();
579 });
580
581 t.test('can stringify with custom encoding', function (st) {
582 st.equal(qs.stringify({ 県: '大阪府', '': '' }, {
583 encoder: function (str) {
584 if (str.length === 0) {
585 return '';
586 }
587 var buf = iconv.encode(str, 'shiftjis');
588 var result = [];
589 for (var i = 0; i < buf.length; ++i) {
590 result.push(buf.readUInt8(i).toString(16));
591 }
592 return '%' + result.join('%');
593 }
594 }), '%8c%a7=%91%e5%8d%e3%95%7b&=');
595 st.end();
596 });
597
598 t.test('receives the default encoder as a second argument', function (st) {
599 st.plan(2);
600 qs.stringify({ a: 1 }, {
601 encoder: function (str, defaultEncoder) {
602 st.equal(defaultEncoder, utils.encode);
603 }
604 });
605 st.end();
606 });
607
608 t.test('throws error with wrong encoder', function (st) {
609 st['throws'](function () {
610 qs.stringify({}, { encoder: 'string' });
611 }, new TypeError('Encoder has to be a function.'));
612 st.end();
613 });
614
615 t.test('can use custom encoder for a buffer object', { skip: typeof Buffer === 'undefined' }, function (st) {
616 st.equal(qs.stringify({ a: SaferBuffer.from([1]) }, {
617 encoder: function (buffer) {
618 if (typeof buffer === 'string') {
619 return buffer;
620 }
621 return String.fromCharCode(buffer.readUInt8(0) + 97);
622 }
623 }), 'a=b');
624
625 st.equal(qs.stringify({ a: SaferBuffer.from('a b') }, {
626 encoder: function (buffer) {
627 return buffer;
628 }
629 }), 'a=a b');
630 st.end();
631 });
632
633 t.test('serializeDate option', function (st) {
634 var date = new Date();
635 st.equal(
636 qs.stringify({ a: date }),
637 'a=' + date.toISOString().replace(/:/g, '%3A'),
638 'default is toISOString'
639 );
640
641 var mutatedDate = new Date();
642 mutatedDate.toISOString = function () {
643 throw new SyntaxError();
644 };
645 st['throws'](function () {
646 mutatedDate.toISOString();
647 }, SyntaxError);
648 st.equal(
649 qs.stringify({ a: mutatedDate }),
650 'a=' + Date.prototype.toISOString.call(mutatedDate).replace(/:/g, '%3A'),
651 'toISOString works even when method is not locally present'
652 );
653
654 var specificDate = new Date(6);
655 st.equal(
656 qs.stringify(
657 { a: specificDate },
658 { serializeDate: function (d) { return d.getTime() * 7; } }
659 ),
660 'a=42',
661 'custom serializeDate function called'
662 );
663
664 st.equal(
665 qs.stringify(
666 { a: [date] },
667 {
668 serializeDate: function (d) { return d.getTime(); },
669 arrayFormat: 'comma'
670 }
671 ),
672 'a=' + date.getTime(),
673 'works with arrayFormat comma'
674 );
675
676 st.end();
677 });
678
679 t.test('RFC 1738 serialization', function (st) {
680 st.equal(qs.stringify({ a: 'b c' }, { format: qs.formats.RFC1738 }), 'a=b+c');
681 st.equal(qs.stringify({ 'a b': 'c d' }, { format: qs.formats.RFC1738 }), 'a+b=c+d');
682 st.equal(qs.stringify({ 'a b': SaferBuffer.from('a b') }, { format: qs.formats.RFC1738 }), 'a+b=a+b');
683
684 st.equal(qs.stringify({ 'foo(ref)': 'bar' }, { format: qs.formats.RFC1738 }), 'foo(ref)=bar');
685
686 st.end();
687 });
688
689 t.test('RFC 3986 spaces serialization', function (st) {
690 st.equal(qs.stringify({ a: 'b c' }, { format: qs.formats.RFC3986 }), 'a=b%20c');
691 st.equal(qs.stringify({ 'a b': 'c d' }, { format: qs.formats.RFC3986 }), 'a%20b=c%20d');
692 st.equal(qs.stringify({ 'a b': SaferBuffer.from('a b') }, { format: qs.formats.RFC3986 }), 'a%20b=a%20b');
693
694 st.end();
695 });
696
697 t.test('Backward compatibility to RFC 3986', function (st) {
698 st.equal(qs.stringify({ a: 'b c' }), 'a=b%20c');
699 st.equal(qs.stringify({ 'a b': SaferBuffer.from('a b') }), 'a%20b=a%20b');
700
701 st.end();
702 });
703
704 t.test('Edge cases and unknown formats', function (st) {
705 ['UFO1234', false, 1234, null, {}, []].forEach(
706 function (format) {
707 st['throws'](
708 function () {
709 qs.stringify({ a: 'b c' }, { format: format });
710 },
711 new TypeError('Unknown format option provided.')
712 );
713 }
714 );
715 st.end();
716 });
717
718 t.test('encodeValuesOnly', function (st) {
719 st.equal(
720 qs.stringify(
721 { a: 'b', c: ['d', 'e=f'], f: [['g'], ['h']] },
722 { encodeValuesOnly: true }
723 ),
724 'a=b&c[0]=d&c[1]=e%3Df&f[0][0]=g&f[1][0]=h'
725 );
726 st.equal(
727 qs.stringify(
728 { a: 'b', c: ['d', 'e'], f: [['g'], ['h']] }
729 ),
730 'a=b&c%5B0%5D=d&c%5B1%5D=e&f%5B0%5D%5B0%5D=g&f%5B1%5D%5B0%5D=h'
731 );
732 st.end();
733 });
734
735 t.test('encodeValuesOnly - strictNullHandling', function (st) {
736 st.equal(
737 qs.stringify(
738 { a: { b: null } },
739 { encodeValuesOnly: true, strictNullHandling: true }
740 ),
741 'a[b]'
742 );
743 st.end();
744 });
745
746 t.test('throws if an invalid charset is specified', function (st) {
747 st['throws'](function () {
748 qs.stringify({ a: 'b' }, { charset: 'foobar' });
749 }, new TypeError('The charset option must be either utf-8, iso-8859-1, or undefined'));
750 st.end();
751 });
752
753 t.test('respects a charset of iso-8859-1', function (st) {
754 st.equal(qs.stringify({ æ: 'æ' }, { charset: 'iso-8859-1' }), '%E6=%E6');
755 st.end();
756 });
757
758 t.test('encodes unrepresentable chars as numeric entities in iso-8859-1 mode', function (st) {
759 st.equal(qs.stringify({ a: '☺' }, { charset: 'iso-8859-1' }), 'a=%26%239786%3B');
760 st.end();
761 });
762
763 t.test('respects an explicit charset of utf-8 (the default)', function (st) {
764 st.equal(qs.stringify({ a: 'æ' }, { charset: 'utf-8' }), 'a=%C3%A6');
765 st.end();
766 });
767
768 t.test('adds the right sentinel when instructed to and the charset is utf-8', function (st) {
769 st.equal(qs.stringify({ a: 'æ' }, { charsetSentinel: true, charset: 'utf-8' }), 'utf8=%E2%9C%93&a=%C3%A6');
770 st.end();
771 });
772
773 t.test('adds the right sentinel when instructed to and the charset is iso-8859-1', function (st) {
774 st.equal(qs.stringify({ a: 'æ' }, { charsetSentinel: true, charset: 'iso-8859-1' }), 'utf8=%26%2310003%3B&a=%E6');
775 st.end();
776 });
777
778 t.test('does not mutate the options argument', function (st) {
779 var options = {};
780 qs.stringify({}, options);
781 st.deepEqual(options, {});
782 st.end();
783 });
784
785 t.test('strictNullHandling works with custom filter', function (st) {
786 var filter = function (prefix, value) {
787 return value;
788 };
789
790 var options = { strictNullHandling: true, filter: filter };
791 st.equal(qs.stringify({ key: null }, options), 'key');
792 st.end();
793 });
794
795 t.test('strictNullHandling works with null serializeDate', function (st) {
796 var serializeDate = function () {
797 return null;
798 };
799 var options = { strictNullHandling: true, serializeDate: serializeDate };
800 var date = new Date();
801 st.equal(qs.stringify({ key: date }, options), 'key');
802 st.end();
803 });
804
805 t.test('allows for encoding keys and values differently', function (st) {
806 var encoder = function (str, defaultEncoder, charset, type) {
807 if (type === 'key') {
808 return defaultEncoder(str, defaultEncoder, charset, type).toLowerCase();
809 }
810 if (type === 'value') {
811 return defaultEncoder(str, defaultEncoder, charset, type).toUpperCase();
812 }
813 throw 'this should never happen! type: ' + type;
814 };
815
816 st.deepEqual(qs.stringify({ KeY: 'vAlUe' }, { encoder: encoder }), 'key=VALUE');
817 st.end();
818 });
819
820 t.test('objects inside arrays', function (st) {
821 var obj = { a: { b: { c: 'd', e: 'f' } } };
822 var withArray = { a: { b: [{ c: 'd', e: 'f' }] } };
823
824 st.equal(qs.stringify(obj, { encode: false }), 'a[b][c]=d&a[b][e]=f', 'no array, no arrayFormat');
825 st.equal(qs.stringify(obj, { encode: false, arrayFormat: 'bracket' }), 'a[b][c]=d&a[b][e]=f', 'no array, bracket');
826 st.equal(qs.stringify(obj, { encode: false, arrayFormat: 'indices' }), 'a[b][c]=d&a[b][e]=f', 'no array, indices');
827 st.equal(qs.stringify(obj, { encode: false, arrayFormat: 'comma' }), 'a[b][c]=d&a[b][e]=f', 'no array, comma');
828
829 st.equal(qs.stringify(withArray, { encode: false }), 'a[b][0][c]=d&a[b][0][e]=f', 'array, no arrayFormat');
830 st.equal(qs.stringify(withArray, { encode: false, arrayFormat: 'bracket' }), 'a[b][0][c]=d&a[b][0][e]=f', 'array, bracket');
831 st.equal(qs.stringify(withArray, { encode: false, arrayFormat: 'indices' }), 'a[b][0][c]=d&a[b][0][e]=f', 'array, indices');
832 st.equal(qs.stringify(obj, { encode: false, arrayFormat: 'comma' }), '???', 'array, comma (pending issue #378)', { skip: true });
833
834 st.end();
835 });
836
837 t.test('stringifies sparse arrays', function (st) {
838 /* eslint no-sparse-arrays: 0 */
839 st.equal(qs.stringify({ a: [, '2', , , '1'] }, { encodeValuesOnly: true }), 'a[1]=2&a[4]=1');
840 st.equal(qs.stringify({ a: [, { b: [, , { c: '1' }] }] }, { encodeValuesOnly: true }), 'a[1][b][2][c]=1');
841 st.equal(qs.stringify({ a: [, [, , [, , , { c: '1' }]]] }, { encodeValuesOnly: true }), 'a[1][2][3][c]=1');
842 st.equal(qs.stringify({ a: [, [, , [, , , { c: [, '1'] }]]] }, { encodeValuesOnly: true }), 'a[1][2][3][c][1]=1');
843
844 st.end();
845 });
846
847 t.end();
848});