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 | var hasSymbols = require('has-symbols');
|
9 | var hasBigInt = typeof BigInt === 'function';
|
10 |
|
11 | test('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');
|
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',
|
182 | 'indices => brackets'
|
183 | );
|
184 | st.equal(
|
185 | qs.stringify({ a: [{ b: 'c' }] }, { arrayFormat: 'brackets' }),
|
186 | 'a%5B%5D%5Bb%5D=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 | });
|