UNPKG

27.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 a null object', { skip: !Object.create }, function (st) {
340 var obj = Object.create(null);
341 obj.a = 'b';
342 st.equal(qs.stringify(obj), 'a=b');
343 st.end();
344 });
345
346 t.test('returns an empty string for invalid input', function (st) {
347 st.equal(qs.stringify(undefined), '');
348 st.equal(qs.stringify(false), '');
349 st.equal(qs.stringify(null), '');
350 st.equal(qs.stringify(''), '');
351 st.end();
352 });
353
354 t.test('stringifies an object with a null object as a child', { skip: !Object.create }, function (st) {
355 var obj = { a: Object.create(null) };
356
357 obj.a.b = 'c';
358 st.equal(qs.stringify(obj), 'a%5Bb%5D=c');
359 st.end();
360 });
361
362 t.test('drops keys with a value of undefined', function (st) {
363 st.equal(qs.stringify({ a: undefined }), '');
364
365 st.equal(qs.stringify({ a: { b: undefined, c: null } }, { strictNullHandling: true }), 'a%5Bc%5D');
366 st.equal(qs.stringify({ a: { b: undefined, c: null } }, { strictNullHandling: false }), 'a%5Bc%5D=');
367 st.equal(qs.stringify({ a: { b: undefined, c: '' } }), 'a%5Bc%5D=');
368 st.end();
369 });
370
371 t.test('url encodes values', function (st) {
372 st.equal(qs.stringify({ a: 'b c' }), 'a=b%20c');
373 st.end();
374 });
375
376 t.test('stringifies a date', function (st) {
377 var now = new Date();
378 var str = 'a=' + encodeURIComponent(now.toISOString());
379 st.equal(qs.stringify({ a: now }), str);
380 st.end();
381 });
382
383 t.test('stringifies the weird object from qs', function (st) {
384 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');
385 st.end();
386 });
387
388 t.test('skips properties that are part of the object prototype', function (st) {
389 Object.prototype.crash = 'test';
390 st.equal(qs.stringify({ a: 'b' }), 'a=b');
391 st.equal(qs.stringify({ a: { b: 'c' } }), 'a%5Bb%5D=c');
392 delete Object.prototype.crash;
393 st.end();
394 });
395
396 t.test('stringifies boolean values', function (st) {
397 st.equal(qs.stringify({ a: true }), 'a=true');
398 st.equal(qs.stringify({ a: { b: true } }), 'a%5Bb%5D=true');
399 st.equal(qs.stringify({ b: false }), 'b=false');
400 st.equal(qs.stringify({ b: { c: false } }), 'b%5Bc%5D=false');
401 st.end();
402 });
403
404 t.test('stringifies buffer values', function (st) {
405 st.equal(qs.stringify({ a: SaferBuffer.from('test') }), 'a=test');
406 st.equal(qs.stringify({ a: { b: SaferBuffer.from('test') } }), 'a%5Bb%5D=test');
407 st.end();
408 });
409
410 t.test('stringifies an object using an alternative delimiter', function (st) {
411 st.equal(qs.stringify({ a: 'b', c: 'd' }, { delimiter: ';' }), 'a=b;c=d');
412 st.end();
413 });
414
415 t.test('doesn\'t blow up when Buffer global is missing', function (st) {
416 var tempBuffer = global.Buffer;
417 delete global.Buffer;
418 var result = qs.stringify({ a: 'b', c: 'd' });
419 global.Buffer = tempBuffer;
420 st.equal(result, 'a=b&c=d');
421 st.end();
422 });
423
424 t.test('selects properties when filter=array', function (st) {
425 st.equal(qs.stringify({ a: 'b' }, { filter: ['a'] }), 'a=b');
426 st.equal(qs.stringify({ a: 1 }, { filter: [] }), '');
427
428 st.equal(
429 qs.stringify(
430 { a: { b: [1, 2, 3, 4], c: 'd' }, c: 'f' },
431 { filter: ['a', 'b', 0, 2], arrayFormat: 'indices' }
432 ),
433 'a%5Bb%5D%5B0%5D=1&a%5Bb%5D%5B2%5D=3',
434 'indices => indices'
435 );
436 st.equal(
437 qs.stringify(
438 { a: { b: [1, 2, 3, 4], c: 'd' }, c: 'f' },
439 { filter: ['a', 'b', 0, 2], arrayFormat: 'brackets' }
440 ),
441 'a%5Bb%5D%5B%5D=1&a%5Bb%5D%5B%5D=3',
442 'brackets => brackets'
443 );
444 st.equal(
445 qs.stringify(
446 { a: { b: [1, 2, 3, 4], c: 'd' }, c: 'f' },
447 { filter: ['a', 'b', 0, 2] }
448 ),
449 'a%5Bb%5D%5B0%5D=1&a%5Bb%5D%5B2%5D=3',
450 'default => indices'
451 );
452
453 st.end();
454 });
455
456 t.test('supports custom representations when filter=function', function (st) {
457 var calls = 0;
458 var obj = { a: 'b', c: 'd', e: { f: new Date(1257894000000) } };
459 var filterFunc = function (prefix, value) {
460 calls += 1;
461 if (calls === 1) {
462 st.equal(prefix, '', 'prefix is empty');
463 st.equal(value, obj);
464 } else if (prefix === 'c') {
465 return void 0;
466 } else if (value instanceof Date) {
467 st.equal(prefix, 'e[f]');
468 return value.getTime();
469 }
470 return value;
471 };
472
473 st.equal(qs.stringify(obj, { filter: filterFunc }), 'a=b&e%5Bf%5D=1257894000000');
474 st.equal(calls, 5);
475 st.end();
476 });
477
478 t.test('can disable uri encoding', function (st) {
479 st.equal(qs.stringify({ a: 'b' }, { encode: false }), 'a=b');
480 st.equal(qs.stringify({ a: { b: 'c' } }, { encode: false }), 'a[b]=c');
481 st.equal(qs.stringify({ a: 'b', c: null }, { strictNullHandling: true, encode: false }), 'a=b&c');
482 st.end();
483 });
484
485 t.test('can sort the keys', function (st) {
486 var sort = function (a, b) {
487 return a.localeCompare(b);
488 };
489 st.equal(qs.stringify({ a: 'c', z: 'y', b: 'f' }, { sort: sort }), 'a=c&b=f&z=y');
490 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');
491 st.end();
492 });
493
494 t.test('can sort the keys at depth 3 or more too', function (st) {
495 var sort = function (a, b) {
496 return a.localeCompare(b);
497 };
498 st.equal(
499 qs.stringify(
500 { a: 'a', z: { zj: { zjb: 'zjb', zja: 'zja' }, zi: { zib: 'zib', zia: 'zia' } }, b: 'b' },
501 { sort: sort, encode: false }
502 ),
503 'a=a&b=b&z[zi][zia]=zia&z[zi][zib]=zib&z[zj][zja]=zja&z[zj][zjb]=zjb'
504 );
505 st.equal(
506 qs.stringify(
507 { a: 'a', z: { zj: { zjb: 'zjb', zja: 'zja' }, zi: { zib: 'zib', zia: 'zia' } }, b: 'b' },
508 { sort: null, encode: false }
509 ),
510 'a=a&z[zj][zjb]=zjb&z[zj][zja]=zja&z[zi][zib]=zib&z[zi][zia]=zia&b=b'
511 );
512 st.end();
513 });
514
515 t.test('can stringify with custom encoding', function (st) {
516 st.equal(qs.stringify({ 県: '大阪府', '': '' }, {
517 encoder: function (str) {
518 if (str.length === 0) {
519 return '';
520 }
521 var buf = iconv.encode(str, 'shiftjis');
522 var result = [];
523 for (var i = 0; i < buf.length; ++i) {
524 result.push(buf.readUInt8(i).toString(16));
525 }
526 return '%' + result.join('%');
527 }
528 }), '%8c%a7=%91%e5%8d%e3%95%7b&=');
529 st.end();
530 });
531
532 t.test('receives the default encoder as a second argument', function (st) {
533 st.plan(2);
534 qs.stringify({ a: 1 }, {
535 encoder: function (str, defaultEncoder) {
536 st.equal(defaultEncoder, utils.encode);
537 }
538 });
539 st.end();
540 });
541
542 t.test('throws error with wrong encoder', function (st) {
543 st['throws'](function () {
544 qs.stringify({}, { encoder: 'string' });
545 }, new TypeError('Encoder has to be a function.'));
546 st.end();
547 });
548
549 t.test('can use custom encoder for a buffer object', { skip: typeof Buffer === 'undefined' }, function (st) {
550 st.equal(qs.stringify({ a: SaferBuffer.from([1]) }, {
551 encoder: function (buffer) {
552 if (typeof buffer === 'string') {
553 return buffer;
554 }
555 return String.fromCharCode(buffer.readUInt8(0) + 97);
556 }
557 }), 'a=b');
558
559 st.equal(qs.stringify({ a: SaferBuffer.from('a b') }, {
560 encoder: function (buffer) {
561 return buffer;
562 }
563 }), 'a=a b');
564 st.end();
565 });
566
567 t.test('serializeDate option', function (st) {
568 var date = new Date();
569 st.equal(
570 qs.stringify({ a: date }),
571 'a=' + date.toISOString().replace(/:/g, '%3A'),
572 'default is toISOString'
573 );
574
575 var mutatedDate = new Date();
576 mutatedDate.toISOString = function () {
577 throw new SyntaxError();
578 };
579 st['throws'](function () {
580 mutatedDate.toISOString();
581 }, SyntaxError);
582 st.equal(
583 qs.stringify({ a: mutatedDate }),
584 'a=' + Date.prototype.toISOString.call(mutatedDate).replace(/:/g, '%3A'),
585 'toISOString works even when method is not locally present'
586 );
587
588 var specificDate = new Date(6);
589 st.equal(
590 qs.stringify(
591 { a: specificDate },
592 { serializeDate: function (d) { return d.getTime() * 7; } }
593 ),
594 'a=42',
595 'custom serializeDate function called'
596 );
597
598 st.equal(
599 qs.stringify(
600 { a: [date] },
601 {
602 serializeDate: function (d) { return d.getTime(); },
603 arrayFormat: 'comma'
604 }
605 ),
606 'a=' + date.getTime(),
607 'works with arrayFormat comma'
608 );
609
610 st.end();
611 });
612
613 t.test('RFC 1738 spaces serialization', function (st) {
614 st.equal(qs.stringify({ a: 'b c' }, { format: qs.formats.RFC1738 }), 'a=b+c');
615 st.equal(qs.stringify({ 'a b': 'c d' }, { format: qs.formats.RFC1738 }), 'a+b=c+d');
616 st.equal(qs.stringify({ 'a b': SaferBuffer.from('a b') }, { format: qs.formats.RFC1738 }), 'a+b=a+b');
617 st.end();
618 });
619
620 t.test('RFC 3986 spaces serialization', function (st) {
621 st.equal(qs.stringify({ a: 'b c' }, { format: qs.formats.RFC3986 }), 'a=b%20c');
622 st.equal(qs.stringify({ 'a b': 'c d' }, { format: qs.formats.RFC3986 }), 'a%20b=c%20d');
623 st.equal(qs.stringify({ 'a b': SaferBuffer.from('a b') }, { format: qs.formats.RFC3986 }), 'a%20b=a%20b');
624 st.end();
625 });
626
627 t.test('Backward compatibility to RFC 3986', function (st) {
628 st.equal(qs.stringify({ a: 'b c' }), 'a=b%20c');
629 st.equal(qs.stringify({ 'a b': SaferBuffer.from('a b') }), 'a%20b=a%20b');
630 st.end();
631 });
632
633 t.test('Edge cases and unknown formats', function (st) {
634 ['UFO1234', false, 1234, null, {}, []].forEach(
635 function (format) {
636 st['throws'](
637 function () {
638 qs.stringify({ a: 'b c' }, { format: format });
639 },
640 new TypeError('Unknown format option provided.')
641 );
642 }
643 );
644 st.end();
645 });
646
647 t.test('encodeValuesOnly', function (st) {
648 st.equal(
649 qs.stringify(
650 { a: 'b', c: ['d', 'e=f'], f: [['g'], ['h']] },
651 { encodeValuesOnly: true }
652 ),
653 'a=b&c[0]=d&c[1]=e%3Df&f[0][0]=g&f[1][0]=h'
654 );
655 st.equal(
656 qs.stringify(
657 { a: 'b', c: ['d', 'e'], f: [['g'], ['h']] }
658 ),
659 'a=b&c%5B0%5D=d&c%5B1%5D=e&f%5B0%5D%5B0%5D=g&f%5B1%5D%5B0%5D=h'
660 );
661 st.end();
662 });
663
664 t.test('encodeValuesOnly - strictNullHandling', function (st) {
665 st.equal(
666 qs.stringify(
667 { a: { b: null } },
668 { encodeValuesOnly: true, strictNullHandling: true }
669 ),
670 'a[b]'
671 );
672 st.end();
673 });
674
675 t.test('throws if an invalid charset is specified', function (st) {
676 st['throws'](function () {
677 qs.stringify({ a: 'b' }, { charset: 'foobar' });
678 }, new TypeError('The charset option must be either utf-8, iso-8859-1, or undefined'));
679 st.end();
680 });
681
682 t.test('respects a charset of iso-8859-1', function (st) {
683 st.equal(qs.stringify({ æ: 'æ' }, { charset: 'iso-8859-1' }), '%E6=%E6');
684 st.end();
685 });
686
687 t.test('encodes unrepresentable chars as numeric entities in iso-8859-1 mode', function (st) {
688 st.equal(qs.stringify({ a: '☺' }, { charset: 'iso-8859-1' }), 'a=%26%239786%3B');
689 st.end();
690 });
691
692 t.test('respects an explicit charset of utf-8 (the default)', function (st) {
693 st.equal(qs.stringify({ a: 'æ' }, { charset: 'utf-8' }), 'a=%C3%A6');
694 st.end();
695 });
696
697 t.test('adds the right sentinel when instructed to and the charset is utf-8', function (st) {
698 st.equal(qs.stringify({ a: 'æ' }, { charsetSentinel: true, charset: 'utf-8' }), 'utf8=%E2%9C%93&a=%C3%A6');
699 st.end();
700 });
701
702 t.test('adds the right sentinel when instructed to and the charset is iso-8859-1', function (st) {
703 st.equal(qs.stringify({ a: 'æ' }, { charsetSentinel: true, charset: 'iso-8859-1' }), 'utf8=%26%2310003%3B&a=%E6');
704 st.end();
705 });
706
707 t.test('does not mutate the options argument', function (st) {
708 var options = {};
709 qs.stringify({}, options);
710 st.deepEqual(options, {});
711 st.end();
712 });
713
714 t.test('strictNullHandling works with custom filter', function (st) {
715 var filter = function (prefix, value) {
716 return value;
717 };
718
719 var options = { strictNullHandling: true, filter: filter };
720 st.equal(qs.stringify({ key: null }, options), 'key');
721 st.end();
722 });
723
724 t.test('strictNullHandling works with null serializeDate', function (st) {
725 var serializeDate = function () {
726 return null;
727 };
728 var options = { strictNullHandling: true, serializeDate: serializeDate };
729 var date = new Date();
730 st.equal(qs.stringify({ key: date }, options), 'key');
731 st.end();
732 });
733
734 t.test('allows for encoding keys and values differently', function (st) {
735 var encoder = function (str, defaultEncoder, charset, type) {
736 if (type === 'key') {
737 return defaultEncoder(str, defaultEncoder, charset, type).toLowerCase();
738 }
739 if (type === 'value') {
740 return defaultEncoder(str, defaultEncoder, charset, type).toUpperCase();
741 }
742 throw 'this should never happen! type: ' + type;
743 };
744
745 st.deepEqual(qs.stringify({ KeY: 'vAlUe' }, { encoder: encoder }), 'key=VALUE');
746 st.end();
747 });
748
749 t.end();
750});