second
[josuexyz/.git] / node_modules / qs / test / stringify.js
1 'use strict';
2
3 var test = require('tape');
4 var qs = require('../');
5 var utils = require('../lib/utils');
6 var iconv = require('iconv-lite');
7 var SaferBuffer = require('safer-buffer').Buffer;
8
9 test('stringify()', function (t) {
10     t.test('stringifies a querystring object', function (st) {
11         st.equal(qs.stringify({ a: 'b' }), 'a=b');
12         st.equal(qs.stringify({ a: 1 }), 'a=1');
13         st.equal(qs.stringify({ a: 1, b: 2 }), 'a=1&b=2');
14         st.equal(qs.stringify({ a: 'A_Z' }), 'a=A_Z');
15         st.equal(qs.stringify({ a: '€' }), 'a=%E2%82%AC');
16         st.equal(qs.stringify({ a: 'ξ€€' }), 'a=%EE%80%80');
17         st.equal(qs.stringify({ a: 'א' }), 'a=%D7%90');
18         st.equal(qs.stringify({ a: '𐐷' }), 'a=%F0%90%90%B7');
19         st.end();
20     });
21
22     t.test('adds query prefix', function (st) {
23         st.equal(qs.stringify({ a: 'b' }, { addQueryPrefix: true }), '?a=b');
24         st.end();
25     });
26
27     t.test('with query prefix, outputs blank string given an empty object', function (st) {
28         st.equal(qs.stringify({}, { addQueryPrefix: true }), '');
29         st.end();
30     });
31
32     t.test('stringifies a nested object', function (st) {
33         st.equal(qs.stringify({ a: { b: 'c' } }), 'a%5Bb%5D=c');
34         st.equal(qs.stringify({ a: { b: { c: { d: 'e' } } } }), 'a%5Bb%5D%5Bc%5D%5Bd%5D=e');
35         st.end();
36     });
37
38     t.test('stringifies a nested object with dots notation', function (st) {
39         st.equal(qs.stringify({ a: { b: 'c' } }, { allowDots: true }), 'a.b=c');
40         st.equal(qs.stringify({ a: { b: { c: { d: 'e' } } } }, { allowDots: true }), 'a.b.c.d=e');
41         st.end();
42     });
43
44     t.test('stringifies an array value', function (st) {
45         st.equal(
46             qs.stringify({ a: ['b', 'c', 'd'] }, { arrayFormat: 'indices' }),
47             'a%5B0%5D=b&a%5B1%5D=c&a%5B2%5D=d',
48             'indices => indices'
49         );
50         st.equal(
51             qs.stringify({ a: ['b', 'c', 'd'] }, { arrayFormat: 'brackets' }),
52             'a%5B%5D=b&a%5B%5D=c&a%5B%5D=d',
53             'brackets => brackets'
54         );
55         st.equal(
56             qs.stringify({ a: ['b', 'c', 'd'] }),
57             'a%5B0%5D=b&a%5B1%5D=c&a%5B2%5D=d',
58             'default => indices'
59         );
60         st.end();
61     });
62
63     t.test('omits nulls when asked', function (st) {
64         st.equal(qs.stringify({ a: 'b', c: null }, { skipNulls: true }), 'a=b');
65         st.end();
66     });
67
68     t.test('omits nested nulls when asked', function (st) {
69         st.equal(qs.stringify({ a: { b: 'c', d: null } }, { skipNulls: true }), 'a%5Bb%5D=c');
70         st.end();
71     });
72
73     t.test('omits array indices when asked', function (st) {
74         st.equal(qs.stringify({ a: ['b', 'c', 'd'] }, { indices: false }), 'a=b&a=c&a=d');
75         st.end();
76     });
77
78     t.test('stringifies a nested array value', function (st) {
79         st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { arrayFormat: 'indices' }), 'a%5Bb%5D%5B0%5D=c&a%5Bb%5D%5B1%5D=d');
80         st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { arrayFormat: 'brackets' }), 'a%5Bb%5D%5B%5D=c&a%5Bb%5D%5B%5D=d');
81         st.equal(qs.stringify({ a: { b: ['c', 'd'] } }), 'a%5Bb%5D%5B0%5D=c&a%5Bb%5D%5B1%5D=d');
82         st.end();
83     });
84
85     t.test('stringifies a nested array value with dots notation', function (st) {
86         st.equal(
87             qs.stringify(
88                 { a: { b: ['c', 'd'] } },
89                 { allowDots: true, encode: false, arrayFormat: 'indices' }
90             ),
91             'a.b[0]=c&a.b[1]=d',
92             'indices: stringifies with dots + indices'
93         );
94         st.equal(
95             qs.stringify(
96                 { a: { b: ['c', 'd'] } },
97                 { allowDots: true, encode: false, arrayFormat: 'brackets' }
98             ),
99             'a.b[]=c&a.b[]=d',
100             'brackets: stringifies with dots + brackets'
101         );
102         st.equal(
103             qs.stringify(
104                 { a: { b: ['c', 'd'] } },
105                 { allowDots: true, encode: false }
106             ),
107             'a.b[0]=c&a.b[1]=d',
108             'default: stringifies with dots + indices'
109         );
110         st.end();
111     });
112
113     t.test('stringifies an object inside an array', function (st) {
114         st.equal(
115             qs.stringify({ a: [{ b: 'c' }] }, { arrayFormat: 'indices' }),
116             'a%5B0%5D%5Bb%5D=c',
117             'indices => brackets'
118         );
119         st.equal(
120             qs.stringify({ a: [{ b: 'c' }] }, { arrayFormat: 'brackets' }),
121             'a%5B%5D%5Bb%5D=c',
122             'brackets => brackets'
123         );
124         st.equal(
125             qs.stringify({ a: [{ b: 'c' }] }),
126             'a%5B0%5D%5Bb%5D=c',
127             'default => indices'
128         );
129
130         st.equal(
131             qs.stringify({ a: [{ b: { c: [1] } }] }, { arrayFormat: 'indices' }),
132             'a%5B0%5D%5Bb%5D%5Bc%5D%5B0%5D=1',
133             'indices => indices'
134         );
135
136         st.equal(
137             qs.stringify({ a: [{ b: { c: [1] } }] }, { arrayFormat: 'brackets' }),
138             'a%5B%5D%5Bb%5D%5Bc%5D%5B%5D=1',
139             'brackets => brackets'
140         );
141
142         st.equal(
143             qs.stringify({ a: [{ b: { c: [1] } }] }),
144             'a%5B0%5D%5Bb%5D%5Bc%5D%5B0%5D=1',
145             'default => indices'
146         );
147
148         st.end();
149     });
150
151     t.test('stringifies an array with mixed objects and primitives', function (st) {
152         st.equal(
153             qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encode: false, arrayFormat: 'indices' }),
154             'a[0][b]=1&a[1]=2&a[2]=3',
155             'indices => indices'
156         );
157         st.equal(
158             qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encode: false, arrayFormat: 'brackets' }),
159             'a[][b]=1&a[]=2&a[]=3',
160             'brackets => brackets'
161         );
162         st.equal(
163             qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encode: false }),
164             'a[0][b]=1&a[1]=2&a[2]=3',
165             'default => indices'
166         );
167
168         st.end();
169     });
170
171     t.test('stringifies an object inside an array with dots notation', function (st) {
172         st.equal(
173             qs.stringify(
174                 { a: [{ b: 'c' }] },
175                 { allowDots: true, encode: false, arrayFormat: 'indices' }
176             ),
177             'a[0].b=c',
178             'indices => indices'
179         );
180         st.equal(
181             qs.stringify(
182                 { a: [{ b: 'c' }] },
183                 { allowDots: true, encode: false, arrayFormat: 'brackets' }
184             ),
185             'a[].b=c',
186             'brackets => brackets'
187         );
188         st.equal(
189             qs.stringify(
190                 { a: [{ b: 'c' }] },
191                 { allowDots: true, encode: false }
192             ),
193             'a[0].b=c',
194             'default => indices'
195         );
196
197         st.equal(
198             qs.stringify(
199                 { a: [{ b: { c: [1] } }] },
200                 { allowDots: true, encode: false, arrayFormat: 'indices' }
201             ),
202             'a[0].b.c[0]=1',
203             'indices => indices'
204         );
205         st.equal(
206             qs.stringify(
207                 { a: [{ b: { c: [1] } }] },
208                 { allowDots: true, encode: false, arrayFormat: 'brackets' }
209             ),
210             'a[].b.c[]=1',
211             'brackets => brackets'
212         );
213         st.equal(
214             qs.stringify(
215                 { a: [{ b: { c: [1] } }] },
216                 { allowDots: true, encode: false }
217             ),
218             'a[0].b.c[0]=1',
219             'default => indices'
220         );
221
222         st.end();
223     });
224
225     t.test('does not omit object keys when indices = false', function (st) {
226         st.equal(qs.stringify({ a: [{ b: 'c' }] }, { indices: false }), 'a%5Bb%5D=c');
227         st.end();
228     });
229
230     t.test('uses indices notation for arrays when indices=true', function (st) {
231         st.equal(qs.stringify({ a: ['b', 'c'] }, { indices: true }), 'a%5B0%5D=b&a%5B1%5D=c');
232         st.end();
233     });
234
235     t.test('uses indices notation for arrays when no arrayFormat is specified', function (st) {
236         st.equal(qs.stringify({ a: ['b', 'c'] }), 'a%5B0%5D=b&a%5B1%5D=c');
237         st.end();
238     });
239
240     t.test('uses indices notation for arrays when no arrayFormat=indices', function (st) {
241         st.equal(qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'indices' }), 'a%5B0%5D=b&a%5B1%5D=c');
242         st.end();
243     });
244
245     t.test('uses repeat notation for arrays when no arrayFormat=repeat', function (st) {
246         st.equal(qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'repeat' }), 'a=b&a=c');
247         st.end();
248     });
249
250     t.test('uses brackets notation for arrays when no arrayFormat=brackets', function (st) {
251         st.equal(qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'brackets' }), 'a%5B%5D=b&a%5B%5D=c');
252         st.end();
253     });
254
255     t.test('stringifies a complicated object', function (st) {
256         st.equal(qs.stringify({ a: { b: 'c', d: 'e' } }), 'a%5Bb%5D=c&a%5Bd%5D=e');
257         st.end();
258     });
259
260     t.test('stringifies an empty value', function (st) {
261         st.equal(qs.stringify({ a: '' }), 'a=');
262         st.equal(qs.stringify({ a: null }, { strictNullHandling: true }), 'a');
263
264         st.equal(qs.stringify({ a: '', b: '' }), 'a=&b=');
265         st.equal(qs.stringify({ a: null, b: '' }, { strictNullHandling: true }), 'a&b=');
266
267         st.equal(qs.stringify({ a: { b: '' } }), 'a%5Bb%5D=');
268         st.equal(qs.stringify({ a: { b: null } }, { strictNullHandling: true }), 'a%5Bb%5D');
269         st.equal(qs.stringify({ a: { b: null } }, { strictNullHandling: false }), 'a%5Bb%5D=');
270
271         st.end();
272     });
273
274     t.test('stringifies a null object', { skip: !Object.create }, function (st) {
275         var obj = Object.create(null);
276         obj.a = 'b';
277         st.equal(qs.stringify(obj), 'a=b');
278         st.end();
279     });
280
281     t.test('returns an empty string for invalid input', function (st) {
282         st.equal(qs.stringify(undefined), '');
283         st.equal(qs.stringify(false), '');
284         st.equal(qs.stringify(null), '');
285         st.equal(qs.stringify(''), '');
286         st.end();
287     });
288
289     t.test('stringifies an object with a null object as a child', { skip: !Object.create }, function (st) {
290         var obj = { a: Object.create(null) };
291
292         obj.a.b = 'c';
293         st.equal(qs.stringify(obj), 'a%5Bb%5D=c');
294         st.end();
295     });
296
297     t.test('drops keys with a value of undefined', function (st) {
298         st.equal(qs.stringify({ a: undefined }), '');
299
300         st.equal(qs.stringify({ a: { b: undefined, c: null } }, { strictNullHandling: true }), 'a%5Bc%5D');
301         st.equal(qs.stringify({ a: { b: undefined, c: null } }, { strictNullHandling: false }), 'a%5Bc%5D=');
302         st.equal(qs.stringify({ a: { b: undefined, c: '' } }), 'a%5Bc%5D=');
303         st.end();
304     });
305
306     t.test('url encodes values', function (st) {
307         st.equal(qs.stringify({ a: 'b c' }), 'a=b%20c');
308         st.end();
309     });
310
311     t.test('stringifies a date', function (st) {
312         var now = new Date();
313         var str = 'a=' + encodeURIComponent(now.toISOString());
314         st.equal(qs.stringify({ a: now }), str);
315         st.end();
316     });
317
318     t.test('stringifies the weird object from qs', function (st) {
319         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');
320         st.end();
321     });
322
323     t.test('skips properties that are part of the object prototype', function (st) {
324         Object.prototype.crash = 'test';
325         st.equal(qs.stringify({ a: 'b' }), 'a=b');
326         st.equal(qs.stringify({ a: { b: 'c' } }), 'a%5Bb%5D=c');
327         delete Object.prototype.crash;
328         st.end();
329     });
330
331     t.test('stringifies boolean values', function (st) {
332         st.equal(qs.stringify({ a: true }), 'a=true');
333         st.equal(qs.stringify({ a: { b: true } }), 'a%5Bb%5D=true');
334         st.equal(qs.stringify({ b: false }), 'b=false');
335         st.equal(qs.stringify({ b: { c: false } }), 'b%5Bc%5D=false');
336         st.end();
337     });
338
339     t.test('stringifies buffer values', function (st) {
340         st.equal(qs.stringify({ a: SaferBuffer.from('test') }), 'a=test');
341         st.equal(qs.stringify({ a: { b: SaferBuffer.from('test') } }), 'a%5Bb%5D=test');
342         st.end();
343     });
344
345     t.test('stringifies an object using an alternative delimiter', function (st) {
346         st.equal(qs.stringify({ a: 'b', c: 'd' }, { delimiter: ';' }), 'a=b;c=d');
347         st.end();
348     });
349
350     t.test('doesn\'t blow up when Buffer global is missing', function (st) {
351         var tempBuffer = global.Buffer;
352         delete global.Buffer;
353         var result = qs.stringify({ a: 'b', c: 'd' });
354         global.Buffer = tempBuffer;
355         st.equal(result, 'a=b&c=d');
356         st.end();
357     });
358
359     t.test('selects properties when filter=array', function (st) {
360         st.equal(qs.stringify({ a: 'b' }, { filter: ['a'] }), 'a=b');
361         st.equal(qs.stringify({ a: 1 }, { filter: [] }), '');
362
363         st.equal(
364             qs.stringify(
365                 { a: { b: [1, 2, 3, 4], c: 'd' }, c: 'f' },
366                 { filter: ['a', 'b', 0, 2], arrayFormat: 'indices' }
367             ),
368             'a%5Bb%5D%5B0%5D=1&a%5Bb%5D%5B2%5D=3',
369             'indices => indices'
370         );
371         st.equal(
372             qs.stringify(
373                 { a: { b: [1, 2, 3, 4], c: 'd' }, c: 'f' },
374                 { filter: ['a', 'b', 0, 2], arrayFormat: 'brackets' }
375             ),
376             'a%5Bb%5D%5B%5D=1&a%5Bb%5D%5B%5D=3',
377             'brackets => brackets'
378         );
379         st.equal(
380             qs.stringify(
381                 { a: { b: [1, 2, 3, 4], c: 'd' }, c: 'f' },
382                 { filter: ['a', 'b', 0, 2] }
383             ),
384             'a%5Bb%5D%5B0%5D=1&a%5Bb%5D%5B2%5D=3',
385             'default => indices'
386         );
387
388         st.end();
389     });
390
391     t.test('supports custom representations when filter=function', function (st) {
392         var calls = 0;
393         var obj = { a: 'b', c: 'd', e: { f: new Date(1257894000000) } };
394         var filterFunc = function (prefix, value) {
395             calls += 1;
396             if (calls === 1) {
397                 st.equal(prefix, '', 'prefix is empty');
398                 st.equal(value, obj);
399             } else if (prefix === 'c') {
400                 return void 0;
401             } else if (value instanceof Date) {
402                 st.equal(prefix, 'e[f]');
403                 return value.getTime();
404             }
405             return value;
406         };
407
408         st.equal(qs.stringify(obj, { filter: filterFunc }), 'a=b&e%5Bf%5D=1257894000000');
409         st.equal(calls, 5);
410         st.end();
411     });
412
413     t.test('can disable uri encoding', function (st) {
414         st.equal(qs.stringify({ a: 'b' }, { encode: false }), 'a=b');
415         st.equal(qs.stringify({ a: { b: 'c' } }, { encode: false }), 'a[b]=c');
416         st.equal(qs.stringify({ a: 'b', c: null }, { strictNullHandling: true, encode: false }), 'a=b&c');
417         st.end();
418     });
419
420     t.test('can sort the keys', function (st) {
421         var sort = function (a, b) {
422             return a.localeCompare(b);
423         };
424         st.equal(qs.stringify({ a: 'c', z: 'y', b: 'f' }, { sort: sort }), 'a=c&b=f&z=y');
425         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');
426         st.end();
427     });
428
429     t.test('can sort the keys at depth 3 or more too', function (st) {
430         var sort = function (a, b) {
431             return a.localeCompare(b);
432         };
433         st.equal(
434             qs.stringify(
435                 { a: 'a', z: { zj: { zjb: 'zjb', zja: 'zja' }, zi: { zib: 'zib', zia: 'zia' } }, b: 'b' },
436                 { sort: sort, encode: false }
437             ),
438             'a=a&b=b&z[zi][zia]=zia&z[zi][zib]=zib&z[zj][zja]=zja&z[zj][zjb]=zjb'
439         );
440         st.equal(
441             qs.stringify(
442                 { a: 'a', z: { zj: { zjb: 'zjb', zja: 'zja' }, zi: { zib: 'zib', zia: 'zia' } }, b: 'b' },
443                 { sort: null, encode: false }
444             ),
445             'a=a&z[zj][zjb]=zjb&z[zj][zja]=zja&z[zi][zib]=zib&z[zi][zia]=zia&b=b'
446         );
447         st.end();
448     });
449
450     t.test('can stringify with custom encoding', function (st) {
451         st.equal(qs.stringify({ ηœŒ: '倧ι˜ͺ府', '': '' }, {
452             encoder: function (str) {
453                 if (str.length === 0) {
454                     return '';
455                 }
456                 var buf = iconv.encode(str, 'shiftjis');
457                 var result = [];
458                 for (var i = 0; i < buf.length; ++i) {
459                     result.push(buf.readUInt8(i).toString(16));
460                 }
461                 return '%' + result.join('%');
462             }
463         }), '%8c%a7=%91%e5%8d%e3%95%7b&=');
464         st.end();
465     });
466
467     t.test('receives the default encoder as a second argument', function (st) {
468         st.plan(2);
469         qs.stringify({ a: 1 }, {
470             encoder: function (str, defaultEncoder) {
471                 st.equal(defaultEncoder, utils.encode);
472             }
473         });
474         st.end();
475     });
476
477     t.test('throws error with wrong encoder', function (st) {
478         st['throws'](function () {
479             qs.stringify({}, { encoder: 'string' });
480         }, new TypeError('Encoder has to be a function.'));
481         st.end();
482     });
483
484     t.test('can use custom encoder for a buffer object', { skip: typeof Buffer === 'undefined' }, function (st) {
485         st.equal(qs.stringify({ a: SaferBuffer.from([1]) }, {
486             encoder: function (buffer) {
487                 if (typeof buffer === 'string') {
488                     return buffer;
489                 }
490                 return String.fromCharCode(buffer.readUInt8(0) + 97);
491             }
492         }), 'a=b');
493         st.end();
494     });
495
496     t.test('serializeDate option', function (st) {
497         var date = new Date();
498         st.equal(
499             qs.stringify({ a: date }),
500             'a=' + date.toISOString().replace(/:/g, '%3A'),
501             'default is toISOString'
502         );
503
504         var mutatedDate = new Date();
505         mutatedDate.toISOString = function () {
506             throw new SyntaxError();
507         };
508         st['throws'](function () {
509             mutatedDate.toISOString();
510         }, SyntaxError);
511         st.equal(
512             qs.stringify({ a: mutatedDate }),
513             'a=' + Date.prototype.toISOString.call(mutatedDate).replace(/:/g, '%3A'),
514             'toISOString works even when method is not locally present'
515         );
516
517         var specificDate = new Date(6);
518         st.equal(
519             qs.stringify(
520                 { a: specificDate },
521                 { serializeDate: function (d) { return d.getTime() * 7; } }
522             ),
523             'a=42',
524             'custom serializeDate function called'
525         );
526
527         st.end();
528     });
529
530     t.test('RFC 1738 spaces serialization', function (st) {
531         st.equal(qs.stringify({ a: 'b c' }, { format: qs.formats.RFC1738 }), 'a=b+c');
532         st.equal(qs.stringify({ 'a b': 'c d' }, { format: qs.formats.RFC1738 }), 'a+b=c+d');
533         st.end();
534     });
535
536     t.test('RFC 3986 spaces serialization', function (st) {
537         st.equal(qs.stringify({ a: 'b c' }, { format: qs.formats.RFC3986 }), 'a=b%20c');
538         st.equal(qs.stringify({ 'a b': 'c d' }, { format: qs.formats.RFC3986 }), 'a%20b=c%20d');
539         st.end();
540     });
541
542     t.test('Backward compatibility to RFC 3986', function (st) {
543         st.equal(qs.stringify({ a: 'b c' }), 'a=b%20c');
544         st.end();
545     });
546
547     t.test('Edge cases and unknown formats', function (st) {
548         ['UFO1234', false, 1234, null, {}, []].forEach(
549             function (format) {
550                 st['throws'](
551                     function () {
552                         qs.stringify({ a: 'b c' }, { format: format });
553                     },
554                     new TypeError('Unknown format option provided.')
555                 );
556             }
557         );
558         st.end();
559     });
560
561     t.test('encodeValuesOnly', function (st) {
562         st.equal(
563             qs.stringify(
564                 { a: 'b', c: ['d', 'e=f'], f: [['g'], ['h']] },
565                 { encodeValuesOnly: true }
566             ),
567             'a=b&c[0]=d&c[1]=e%3Df&f[0][0]=g&f[1][0]=h'
568         );
569         st.equal(
570             qs.stringify(
571                 { a: 'b', c: ['d', 'e'], f: [['g'], ['h']] }
572             ),
573             'a=b&c%5B0%5D=d&c%5B1%5D=e&f%5B0%5D%5B0%5D=g&f%5B1%5D%5B0%5D=h'
574         );
575         st.end();
576     });
577
578     t.test('encodeValuesOnly - strictNullHandling', function (st) {
579         st.equal(
580             qs.stringify(
581                 { a: { b: null } },
582                 { encodeValuesOnly: true, strictNullHandling: true }
583             ),
584             'a[b]'
585         );
586         st.end();
587     });
588
589     t.test('does not mutate the options argument', function (st) {
590         var options = {};
591         qs.stringify({}, options);
592         st.deepEqual(options, {});
593         st.end();
594     });
595
596     t.end();
597 });