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