The LM Control website. Simple yet efficient.
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

stringify.js 29KB

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