用工小程序前端代码
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1306 lines
52 KiB

11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
  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 mockProperty = require('mock-property');
  9. var emptyTestCases = require('./empty-keys-cases').emptyTestCases;
  10. var hasProto = require('has-proto')();
  11. var hasBigInt = require('has-bigints')();
  12. test('stringify()', function (t) {
  13. t.test('stringifies a querystring object', function (st) {
  14. st.equal(qs.stringify({ a: 'b' }), 'a=b');
  15. st.equal(qs.stringify({ a: 1 }), 'a=1');
  16. st.equal(qs.stringify({ a: 1, b: 2 }), 'a=1&b=2');
  17. st.equal(qs.stringify({ a: 'A_Z' }), 'a=A_Z');
  18. st.equal(qs.stringify({ a: '€' }), 'a=%E2%82%AC');
  19. st.equal(qs.stringify({ a: '' }), 'a=%EE%80%80');
  20. st.equal(qs.stringify({ a: 'א' }), 'a=%D7%90');
  21. st.equal(qs.stringify({ a: '𐐷' }), 'a=%F0%90%90%B7');
  22. st.end();
  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. t.test('stringifies symbols', { skip: !hasSymbols() }, function (st) {
  33. st.equal(qs.stringify(Symbol.iterator), '');
  34. st.equal(qs.stringify([Symbol.iterator]), '0=Symbol%28Symbol.iterator%29');
  35. st.equal(qs.stringify({ a: Symbol.iterator }), 'a=Symbol%28Symbol.iterator%29');
  36. st.equal(
  37. qs.stringify({ a: [Symbol.iterator] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }),
  38. 'a[]=Symbol%28Symbol.iterator%29'
  39. );
  40. st.end();
  41. });
  42. t.test('stringifies bigints', { skip: !hasBigInt }, function (st) {
  43. var three = BigInt(3);
  44. var encodeWithN = function (value, defaultEncoder, charset) {
  45. var result = defaultEncoder(value, defaultEncoder, charset);
  46. return typeof value === 'bigint' ? result + 'n' : result;
  47. };
  48. st.equal(qs.stringify(three), '');
  49. st.equal(qs.stringify([three]), '0=3');
  50. st.equal(qs.stringify([three], { encoder: encodeWithN }), '0=3n');
  51. st.equal(qs.stringify({ a: three }), 'a=3');
  52. st.equal(qs.stringify({ a: three }, { encoder: encodeWithN }), 'a=3n');
  53. st.equal(
  54. qs.stringify({ a: [three] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }),
  55. 'a[]=3'
  56. );
  57. st.equal(
  58. qs.stringify({ a: [three] }, { encodeValuesOnly: true, encoder: encodeWithN, arrayFormat: 'brackets' }),
  59. 'a[]=3n'
  60. );
  61. st.end();
  62. });
  63. t.test('encodes dot in key of object when encodeDotInKeys and allowDots is provided', function (st) {
  64. st.equal(
  65. qs.stringify(
  66. { 'name.obj': { first: 'John', last: 'Doe' } },
  67. { allowDots: false, encodeDotInKeys: false }
  68. ),
  69. 'name.obj%5Bfirst%5D=John&name.obj%5Blast%5D=Doe',
  70. 'with allowDots false and encodeDotInKeys false'
  71. );
  72. st.equal(
  73. qs.stringify(
  74. { 'name.obj': { first: 'John', last: 'Doe' } },
  75. { allowDots: true, encodeDotInKeys: false }
  76. ),
  77. 'name.obj.first=John&name.obj.last=Doe',
  78. 'with allowDots true and encodeDotInKeys false'
  79. );
  80. st.equal(
  81. qs.stringify(
  82. { 'name.obj': { first: 'John', last: 'Doe' } },
  83. { allowDots: false, encodeDotInKeys: true }
  84. ),
  85. 'name%252Eobj%5Bfirst%5D=John&name%252Eobj%5Blast%5D=Doe',
  86. 'with allowDots false and encodeDotInKeys true'
  87. );
  88. st.equal(
  89. qs.stringify(
  90. { 'name.obj': { first: 'John', last: 'Doe' } },
  91. { allowDots: true, encodeDotInKeys: true }
  92. ),
  93. 'name%252Eobj.first=John&name%252Eobj.last=Doe',
  94. 'with allowDots true and encodeDotInKeys true'
  95. );
  96. st.equal(
  97. qs.stringify(
  98. { 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } },
  99. { allowDots: false, encodeDotInKeys: false }
  100. ),
  101. 'name.obj.subobject%5Bfirst.godly.name%5D=John&name.obj.subobject%5Blast%5D=Doe',
  102. 'with allowDots false and encodeDotInKeys false'
  103. );
  104. st.equal(
  105. qs.stringify(
  106. { 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } },
  107. { allowDots: true, encodeDotInKeys: false }
  108. ),
  109. 'name.obj.subobject.first.godly.name=John&name.obj.subobject.last=Doe',
  110. 'with allowDots false and encodeDotInKeys false'
  111. );
  112. st.equal(
  113. qs.stringify(
  114. { 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } },
  115. { allowDots: false, encodeDotInKeys: true }
  116. ),
  117. 'name%252Eobj%252Esubobject%5Bfirst.godly.name%5D=John&name%252Eobj%252Esubobject%5Blast%5D=Doe',
  118. 'with allowDots false and encodeDotInKeys true'
  119. );
  120. st.equal(
  121. qs.stringify(
  122. { 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } },
  123. { allowDots: true, encodeDotInKeys: true }
  124. ),
  125. 'name%252Eobj%252Esubobject.first%252Egodly%252Ename=John&name%252Eobj%252Esubobject.last=Doe',
  126. 'with allowDots true and encodeDotInKeys true'
  127. );
  128. st.end();
  129. });
  130. t.test('should encode dot in key of object, and automatically set allowDots to `true` when encodeDotInKeys is true and allowDots in undefined', function (st) {
  131. st.equal(
  132. qs.stringify(
  133. { 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } },
  134. { encodeDotInKeys: true }
  135. ),
  136. 'name%252Eobj%252Esubobject.first%252Egodly%252Ename=John&name%252Eobj%252Esubobject.last=Doe',
  137. 'with allowDots undefined and encodeDotInKeys true'
  138. );
  139. st.end();
  140. });
  141. t.test('should encode dot in key of object when encodeDotInKeys and allowDots is provided, and nothing else when encodeValuesOnly is provided', function (st) {
  142. st.equal(
  143. qs.stringify({ 'name.obj': { first: 'John', last: 'Doe' } }, {
  144. encodeDotInKeys: true, allowDots: true, encodeValuesOnly: true
  145. }),
  146. 'name%2Eobj.first=John&name%2Eobj.last=Doe'
  147. );
  148. st.equal(
  149. qs.stringify({ 'name.obj.subobject': { 'first.godly.name': 'John', last: 'Doe' } }, { allowDots: true, encodeDotInKeys: true, encodeValuesOnly: true }),
  150. 'name%2Eobj%2Esubobject.first%2Egodly%2Ename=John&name%2Eobj%2Esubobject.last=Doe'
  151. );
  152. st.end();
  153. });
  154. t.test('throws when `commaRoundTrip` is not a boolean', function (st) {
  155. st['throws'](
  156. function () { qs.stringify({}, { commaRoundTrip: 'not a boolean' }); },
  157. TypeError,
  158. 'throws when `commaRoundTrip` is not a boolean'
  159. );
  160. st.end();
  161. });
  162. t.test('throws when `encodeDotInKeys` is not a boolean', function (st) {
  163. st['throws'](
  164. function () { qs.stringify({ a: [], b: 'zz' }, { encodeDotInKeys: 'foobar' }); },
  165. TypeError
  166. );
  167. st['throws'](
  168. function () { qs.stringify({ a: [], b: 'zz' }, { encodeDotInKeys: 0 }); },
  169. TypeError
  170. );
  171. st['throws'](
  172. function () { qs.stringify({ a: [], b: 'zz' }, { encodeDotInKeys: NaN }); },
  173. TypeError
  174. );
  175. st['throws'](
  176. function () { qs.stringify({ a: [], b: 'zz' }, { encodeDotInKeys: null }); },
  177. TypeError
  178. );
  179. st.end();
  180. });
  181. t.test('adds query prefix', function (st) {
  182. st.equal(qs.stringify({ a: 'b' }, { addQueryPrefix: true }), '?a=b');
  183. st.end();
  184. });
  185. t.test('with query prefix, outputs blank string given an empty object', function (st) {
  186. st.equal(qs.stringify({}, { addQueryPrefix: true }), '');
  187. st.end();
  188. });
  189. t.test('stringifies nested falsy values', function (st) {
  190. st.equal(qs.stringify({ a: { b: { c: null } } }), 'a%5Bb%5D%5Bc%5D=');
  191. st.equal(qs.stringify({ a: { b: { c: null } } }, { strictNullHandling: true }), 'a%5Bb%5D%5Bc%5D');
  192. st.equal(qs.stringify({ a: { b: { c: false } } }), 'a%5Bb%5D%5Bc%5D=false');
  193. st.end();
  194. });
  195. t.test('stringifies a nested object', function (st) {
  196. st.equal(qs.stringify({ a: { b: 'c' } }), 'a%5Bb%5D=c');
  197. st.equal(qs.stringify({ a: { b: { c: { d: 'e' } } } }), 'a%5Bb%5D%5Bc%5D%5Bd%5D=e');
  198. st.end();
  199. });
  200. t.test('`allowDots` option: stringifies a nested object with dots notation', function (st) {
  201. st.equal(qs.stringify({ a: { b: 'c' } }, { allowDots: true }), 'a.b=c');
  202. st.equal(qs.stringify({ a: { b: { c: { d: 'e' } } } }, { allowDots: true }), 'a.b.c.d=e');
  203. st.end();
  204. });
  205. t.test('stringifies an array value', function (st) {
  206. st.equal(
  207. qs.stringify({ a: ['b', 'c', 'd'] }, { arrayFormat: 'indices' }),
  208. 'a%5B0%5D=b&a%5B1%5D=c&a%5B2%5D=d',
  209. 'indices => indices'
  210. );
  211. st.equal(
  212. qs.stringify({ a: ['b', 'c', 'd'] }, { arrayFormat: 'brackets' }),
  213. 'a%5B%5D=b&a%5B%5D=c&a%5B%5D=d',
  214. 'brackets => brackets'
  215. );
  216. st.equal(
  217. qs.stringify({ a: ['b', 'c', 'd'] }, { arrayFormat: 'comma' }),
  218. 'a=b%2Cc%2Cd',
  219. 'comma => comma'
  220. );
  221. st.equal(
  222. qs.stringify({ a: ['b', 'c', 'd'] }, { arrayFormat: 'comma', commaRoundTrip: true }),
  223. 'a=b%2Cc%2Cd',
  224. 'comma round trip => comma'
  225. );
  226. st.equal(
  227. qs.stringify({ a: ['b', 'c', 'd'] }),
  228. 'a%5B0%5D=b&a%5B1%5D=c&a%5B2%5D=d',
  229. 'default => indices'
  230. );
  231. st.end();
  232. });
  233. t.test('`skipNulls` option', function (st) {
  234. st.equal(
  235. qs.stringify({ a: 'b', c: null }, { skipNulls: true }),
  236. 'a=b',
  237. 'omits nulls when asked'
  238. );
  239. st.equal(
  240. qs.stringify({ a: { b: 'c', d: null } }, { skipNulls: true }),
  241. 'a%5Bb%5D=c',
  242. 'omits nested nulls when asked'
  243. );
  244. st.end();
  245. });
  246. t.test('omits array indices when asked', function (st) {
  247. st.equal(qs.stringify({ a: ['b', 'c', 'd'] }, { indices: false }), 'a=b&a=c&a=d');
  248. st.end();
  249. });
  250. t.test('omits object key/value pair when value is empty array', function (st) {
  251. st.equal(qs.stringify({ a: [], b: 'zz' }), 'b=zz');
  252. st.end();
  253. });
  254. t.test('should not omit object key/value pair when value is empty array and when asked', function (st) {
  255. st.equal(qs.stringify({ a: [], b: 'zz' }), 'b=zz');
  256. st.equal(qs.stringify({ a: [], b: 'zz' }, { allowEmptyArrays: false }), 'b=zz');
  257. st.equal(qs.stringify({ a: [], b: 'zz' }, { allowEmptyArrays: true }), 'a[]&b=zz');
  258. st.end();
  259. });
  260. t.test('should throw when allowEmptyArrays is not of type boolean', function (st) {
  261. st['throws'](
  262. function () { qs.stringify({ a: [], b: 'zz' }, { allowEmptyArrays: 'foobar' }); },
  263. TypeError
  264. );
  265. st['throws'](
  266. function () { qs.stringify({ a: [], b: 'zz' }, { allowEmptyArrays: 0 }); },
  267. TypeError
  268. );
  269. st['throws'](
  270. function () { qs.stringify({ a: [], b: 'zz' }, { allowEmptyArrays: NaN }); },
  271. TypeError
  272. );
  273. st['throws'](
  274. function () { qs.stringify({ a: [], b: 'zz' }, { allowEmptyArrays: null }); },
  275. TypeError
  276. );
  277. st.end();
  278. });
  279. t.test('allowEmptyArrays + strictNullHandling', function (st) {
  280. st.equal(
  281. qs.stringify(
  282. { testEmptyArray: [] },
  283. { strictNullHandling: true, allowEmptyArrays: true }
  284. ),
  285. 'testEmptyArray[]'
  286. );
  287. st.end();
  288. });
  289. t.test('stringifies an array value with one item vs multiple items', function (st) {
  290. st.test('non-array item', function (s2t) {
  291. s2t.equal(qs.stringify({ a: 'c' }, { encodeValuesOnly: true, arrayFormat: 'indices' }), 'a=c');
  292. s2t.equal(qs.stringify({ a: 'c' }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), 'a=c');
  293. s2t.equal(qs.stringify({ a: 'c' }, { encodeValuesOnly: true, arrayFormat: 'comma' }), 'a=c');
  294. s2t.equal(qs.stringify({ a: 'c' }, { encodeValuesOnly: true }), 'a=c');
  295. s2t.end();
  296. });
  297. st.test('array with a single item', function (s2t) {
  298. s2t.equal(qs.stringify({ a: ['c'] }, { encodeValuesOnly: true, arrayFormat: 'indices' }), 'a[0]=c');
  299. s2t.equal(qs.stringify({ a: ['c'] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), 'a[]=c');
  300. s2t.equal(qs.stringify({ a: ['c'] }, { encodeValuesOnly: true, arrayFormat: 'comma' }), 'a=c');
  301. s2t.equal(qs.stringify({ a: ['c'] }, { encodeValuesOnly: true, arrayFormat: 'comma', commaRoundTrip: true }), 'a[]=c'); // so it parses back as an array
  302. s2t.equal(qs.stringify({ a: ['c'] }, { encodeValuesOnly: true }), 'a[0]=c');
  303. s2t.end();
  304. });
  305. st.test('array with multiple items', function (s2t) {
  306. s2t.equal(qs.stringify({ a: ['c', 'd'] }, { encodeValuesOnly: true, arrayFormat: 'indices' }), 'a[0]=c&a[1]=d');
  307. s2t.equal(qs.stringify({ a: ['c', 'd'] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), 'a[]=c&a[]=d');
  308. s2t.equal(qs.stringify({ a: ['c', 'd'] }, { encodeValuesOnly: true, arrayFormat: 'comma' }), 'a=c,d');
  309. s2t.equal(qs.stringify({ a: ['c', 'd'] }, { encodeValuesOnly: true, arrayFormat: 'comma', commaRoundTrip: true }), 'a=c,d');
  310. s2t.equal(qs.stringify({ a: ['c', 'd'] }, { encodeValuesOnly: true }), 'a[0]=c&a[1]=d');
  311. s2t.end();
  312. });
  313. st.test('array with multiple items with a comma inside', function (s2t) {
  314. s2t.equal(qs.stringify({ a: ['c,d', 'e'] }, { encodeValuesOnly: true, arrayFormat: 'comma' }), 'a=c%2Cd,e');
  315. s2t.equal(qs.stringify({ a: ['c,d', 'e'] }, { arrayFormat: 'comma' }), 'a=c%2Cd%2Ce');
  316. s2t.equal(qs.stringify({ a: ['c,d', 'e'] }, { encodeValuesOnly: true, arrayFormat: 'comma', commaRoundTrip: true }), 'a=c%2Cd,e');
  317. s2t.equal(qs.stringify({ a: ['c,d', 'e'] }, { arrayFormat: 'comma', commaRoundTrip: true }), 'a=c%2Cd%2Ce');
  318. s2t.end();
  319. });
  320. st.end();
  321. });
  322. t.test('stringifies a nested array value', function (st) {
  323. st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true, arrayFormat: 'indices' }), 'a[b][0]=c&a[b][1]=d');
  324. st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), 'a[b][]=c&a[b][]=d');
  325. st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true, arrayFormat: 'comma' }), 'a[b]=c,d');
  326. st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true }), 'a[b][0]=c&a[b][1]=d');
  327. st.end();
  328. });
  329. t.test('stringifies comma and empty array values', function (st) {
  330. st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: false, arrayFormat: 'indices' }), 'a[0]=,&a[1]=&a[2]=c,d%');
  331. st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: false, arrayFormat: 'brackets' }), 'a[]=,&a[]=&a[]=c,d%');
  332. st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: false, arrayFormat: 'comma' }), 'a=,,,c,d%');
  333. st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: false, arrayFormat: 'repeat' }), 'a=,&a=&a=c,d%');
  334. st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: true, arrayFormat: 'indices' }), 'a[0]=%2C&a[1]=&a[2]=c%2Cd%25');
  335. st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: true, arrayFormat: 'brackets' }), 'a[]=%2C&a[]=&a[]=c%2Cd%25');
  336. st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: true, arrayFormat: 'comma' }), 'a=%2C,,c%2Cd%25');
  337. st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: true, arrayFormat: 'repeat' }), 'a=%2C&a=&a=c%2Cd%25');
  338. st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: false, arrayFormat: 'indices' }), 'a%5B0%5D=%2C&a%5B1%5D=&a%5B2%5D=c%2Cd%25');
  339. st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: false, arrayFormat: 'brackets' }), 'a%5B%5D=%2C&a%5B%5D=&a%5B%5D=c%2Cd%25');
  340. st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: false, arrayFormat: 'comma' }), 'a=%2C%2C%2Cc%2Cd%25');
  341. st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: false, arrayFormat: 'repeat' }), 'a=%2C&a=&a=c%2Cd%25');
  342. st.end();
  343. });
  344. t.test('stringifies comma and empty non-array values', function (st) {
  345. st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: false, arrayFormat: 'indices' }), 'a=,&b=&c=c,d%');
  346. st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: false, arrayFormat: 'brackets' }), 'a=,&b=&c=c,d%');
  347. st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: false, arrayFormat: 'comma' }), 'a=,&b=&c=c,d%');
  348. st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: false, arrayFormat: 'repeat' }), 'a=,&b=&c=c,d%');
  349. st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: true, encodeValuesOnly: true, arrayFormat: 'indices' }), 'a=%2C&b=&c=c%2Cd%25');
  350. st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: true, encodeValuesOnly: true, arrayFormat: 'brackets' }), 'a=%2C&b=&c=c%2Cd%25');
  351. st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: true, encodeValuesOnly: true, arrayFormat: 'comma' }), 'a=%2C&b=&c=c%2Cd%25');
  352. st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: true, encodeValuesOnly: true, arrayFormat: 'repeat' }), 'a=%2C&b=&c=c%2Cd%25');
  353. st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: true, encodeValuesOnly: false, arrayFormat: 'indices' }), 'a=%2C&b=&c=c%2Cd%25');
  354. st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: true, encodeValuesOnly: false, arrayFormat: 'brackets' }), 'a=%2C&b=&c=c%2Cd%25');
  355. st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: true, encodeValuesOnly: false, arrayFormat: 'comma' }), 'a=%2C&b=&c=c%2Cd%25');
  356. st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: true, encodeValuesOnly: false, arrayFormat: 'repeat' }), 'a=%2C&b=&c=c%2Cd%25');
  357. st.end();
  358. });
  359. t.test('stringifies a nested array value with dots notation', function (st) {
  360. st.equal(
  361. qs.stringify(
  362. { a: { b: ['c', 'd'] } },
  363. { allowDots: true, encodeValuesOnly: true, arrayFormat: 'indices' }
  364. ),
  365. 'a.b[0]=c&a.b[1]=d',
  366. 'indices: stringifies with dots + indices'
  367. );
  368. st.equal(
  369. qs.stringify(
  370. { a: { b: ['c', 'd'] } },
  371. { allowDots: true, encodeValuesOnly: true, arrayFormat: 'brackets' }
  372. ),
  373. 'a.b[]=c&a.b[]=d',
  374. 'brackets: stringifies with dots + brackets'
  375. );
  376. st.equal(
  377. qs.stringify(
  378. { a: { b: ['c', 'd'] } },
  379. { allowDots: true, encodeValuesOnly: true, arrayFormat: 'comma' }
  380. ),
  381. 'a.b=c,d',
  382. 'comma: stringifies with dots + comma'
  383. );
  384. st.equal(
  385. qs.stringify(
  386. { a: { b: ['c', 'd'] } },
  387. { allowDots: true, encodeValuesOnly: true }
  388. ),
  389. 'a.b[0]=c&a.b[1]=d',
  390. 'default: stringifies with dots + indices'
  391. );
  392. st.end();
  393. });
  394. t.test('stringifies an object inside an array', function (st) {
  395. st.equal(
  396. qs.stringify({ a: [{ b: 'c' }] }, { arrayFormat: 'indices', encodeValuesOnly: true }),
  397. 'a[0][b]=c',
  398. 'indices => indices'
  399. );
  400. st.equal(
  401. qs.stringify({ a: [{ b: 'c' }] }, { arrayFormat: 'repeat', encodeValuesOnly: true }),
  402. 'a[b]=c',
  403. 'repeat => repeat'
  404. );
  405. st.equal(
  406. qs.stringify({ a: [{ b: 'c' }] }, { arrayFormat: 'brackets', encodeValuesOnly: true }),
  407. 'a[][b]=c',
  408. 'brackets => brackets'
  409. );
  410. st.equal(
  411. qs.stringify({ a: [{ b: 'c' }] }, { encodeValuesOnly: true }),
  412. 'a[0][b]=c',
  413. 'default => indices'
  414. );
  415. st.equal(
  416. qs.stringify({ a: [{ b: { c: [1] } }] }, { arrayFormat: 'indices', encodeValuesOnly: true }),
  417. 'a[0][b][c][0]=1',
  418. 'indices => indices'
  419. );
  420. st.equal(
  421. qs.stringify({ a: [{ b: { c: [1] } }] }, { arrayFormat: 'repeat', encodeValuesOnly: true }),
  422. 'a[b][c]=1',
  423. 'repeat => repeat'
  424. );
  425. st.equal(
  426. qs.stringify({ a: [{ b: { c: [1] } }] }, { arrayFormat: 'brackets', encodeValuesOnly: true }),
  427. 'a[][b][c][]=1',
  428. 'brackets => brackets'
  429. );
  430. st.equal(
  431. qs.stringify({ a: [{ b: { c: [1] } }] }, { encodeValuesOnly: true }),
  432. 'a[0][b][c][0]=1',
  433. 'default => indices'
  434. );
  435. st.end();
  436. });
  437. t.test('stringifies an array with mixed objects and primitives', function (st) {
  438. st.equal(
  439. qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true, arrayFormat: 'indices' }),
  440. 'a[0][b]=1&a[1]=2&a[2]=3',
  441. 'indices => indices'
  442. );
  443. st.equal(
  444. qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }),
  445. 'a[][b]=1&a[]=2&a[]=3',
  446. 'brackets => brackets'
  447. );
  448. st.equal(
  449. qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true, arrayFormat: 'comma' }),
  450. '???',
  451. 'brackets => brackets',
  452. { skip: 'TODO: figure out what this should do' }
  453. );
  454. st.equal(
  455. qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true }),
  456. 'a[0][b]=1&a[1]=2&a[2]=3',
  457. 'default => indices'
  458. );
  459. st.end();
  460. });
  461. t.test('stringifies an object inside an array with dots notation', function (st) {
  462. st.equal(
  463. qs.stringify(
  464. { a: [{ b: 'c' }] },
  465. { allowDots: true, encode: false, arrayFormat: 'indices' }
  466. ),
  467. 'a[0].b=c',
  468. 'indices => indices'
  469. );
  470. st.equal(
  471. qs.stringify(
  472. { a: [{ b: 'c' }] },
  473. { allowDots: true, encode: false, arrayFormat: 'brackets' }
  474. ),
  475. 'a[].b=c',
  476. 'brackets => brackets'
  477. );
  478. st.equal(
  479. qs.stringify(
  480. { a: [{ b: 'c' }] },
  481. { allowDots: true, encode: false }
  482. ),
  483. 'a[0].b=c',
  484. 'default => indices'
  485. );
  486. st.equal(
  487. qs.stringify(
  488. { a: [{ b: { c: [1] } }] },
  489. { allowDots: true, encode: false, arrayFormat: 'indices' }
  490. ),
  491. 'a[0].b.c[0]=1',
  492. 'indices => indices'
  493. );
  494. st.equal(
  495. qs.stringify(
  496. { a: [{ b: { c: [1] } }] },
  497. { allowDots: true, encode: false, arrayFormat: 'brackets' }
  498. ),
  499. 'a[].b.c[]=1',
  500. 'brackets => brackets'
  501. );
  502. st.equal(
  503. qs.stringify(
  504. { a: [{ b: { c: [1] } }] },
  505. { allowDots: true, encode: false }
  506. ),
  507. 'a[0].b.c[0]=1',
  508. 'default => indices'
  509. );
  510. st.end();
  511. });
  512. t.test('does not omit object keys when indices = false', function (st) {
  513. st.equal(qs.stringify({ a: [{ b: 'c' }] }, { indices: false }), 'a%5Bb%5D=c');
  514. st.end();
  515. });
  516. t.test('uses indices notation for arrays when indices=true', function (st) {
  517. st.equal(qs.stringify({ a: ['b', 'c'] }, { indices: true }), 'a%5B0%5D=b&a%5B1%5D=c');
  518. st.end();
  519. });
  520. t.test('uses indices notation for arrays when no arrayFormat is specified', function (st) {
  521. st.equal(qs.stringify({ a: ['b', 'c'] }), 'a%5B0%5D=b&a%5B1%5D=c');
  522. st.end();
  523. });
  524. t.test('uses indices notation for arrays when arrayFormat=indices', function (st) {
  525. st.equal(qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'indices' }), 'a%5B0%5D=b&a%5B1%5D=c');
  526. st.end();
  527. });
  528. t.test('uses repeat notation for arrays when arrayFormat=repeat', function (st) {
  529. st.equal(qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'repeat' }), 'a=b&a=c');
  530. st.end();
  531. });
  532. t.test('uses brackets notation for arrays when arrayFormat=brackets', function (st) {
  533. st.equal(qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'brackets' }), 'a%5B%5D=b&a%5B%5D=c');
  534. st.end();
  535. });
  536. t.test('stringifies a complicated object', function (st) {
  537. st.equal(qs.stringify({ a: { b: 'c', d: 'e' } }), 'a%5Bb%5D=c&a%5Bd%5D=e');
  538. st.end();
  539. });
  540. t.test('stringifies an empty value', function (st) {
  541. st.equal(qs.stringify({ a: '' }), 'a=');
  542. st.equal(qs.stringify({ a: null }, { strictNullHandling: true }), 'a');
  543. st.equal(qs.stringify({ a: '', b: '' }), 'a=&b=');
  544. st.equal(qs.stringify({ a: null, b: '' }, { strictNullHandling: true }), 'a&b=');
  545. st.equal(qs.stringify({ a: { b: '' } }), 'a%5Bb%5D=');
  546. st.equal(qs.stringify({ a: { b: null } }, { strictNullHandling: true }), 'a%5Bb%5D');
  547. st.equal(qs.stringify({ a: { b: null } }, { strictNullHandling: false }), 'a%5Bb%5D=');
  548. st.end();
  549. });
  550. t.test('stringifies an empty array in different arrayFormat', function (st) {
  551. st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false }), 'b[0]=&c=c');
  552. // arrayFormat default
  553. st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'indices' }), 'b[0]=&c=c');
  554. st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'brackets' }), 'b[]=&c=c');
  555. st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'repeat' }), 'b=&c=c');
  556. st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'comma' }), 'b=&c=c');
  557. st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'comma', commaRoundTrip: true }), 'b[]=&c=c');
  558. // with strictNullHandling
  559. st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'indices', strictNullHandling: true }), 'b[0]&c=c');
  560. st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'brackets', strictNullHandling: true }), 'b[]&c=c');
  561. st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'repeat', strictNullHandling: true }), 'b&c=c');
  562. st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'comma', strictNullHandling: true }), 'b&c=c');
  563. st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'comma', strictNullHandling: true, commaRoundTrip: true }), 'b[]&c=c');
  564. // with skipNulls
  565. st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'indices', skipNulls: true }), 'c=c');
  566. st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'brackets', skipNulls: true }), 'c=c');
  567. st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'repeat', skipNulls: true }), 'c=c');
  568. st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'comma', skipNulls: true }), 'c=c');
  569. st.end();
  570. });
  571. t.test('stringifies a null object', { skip: !hasProto }, function (st) {
  572. st.equal(qs.stringify({ __proto__: null, a: 'b' }), 'a=b');
  573. st.end();
  574. });
  575. t.test('returns an empty string for invalid input', function (st) {
  576. st.equal(qs.stringify(undefined), '');
  577. st.equal(qs.stringify(false), '');
  578. st.equal(qs.stringify(null), '');
  579. st.equal(qs.stringify(''), '');
  580. st.end();
  581. });
  582. t.test('stringifies an object with a null object as a child', { skip: !hasProto }, function (st) {
  583. st.equal(qs.stringify({ a: { __proto__: null, b: 'c' } }), 'a%5Bb%5D=c');
  584. st.end();
  585. });
  586. t.test('drops keys with a value of undefined', function (st) {
  587. st.equal(qs.stringify({ a: undefined }), '');
  588. st.equal(qs.stringify({ a: { b: undefined, c: null } }, { strictNullHandling: true }), 'a%5Bc%5D');
  589. st.equal(qs.stringify({ a: { b: undefined, c: null } }, { strictNullHandling: false }), 'a%5Bc%5D=');
  590. st.equal(qs.stringify({ a: { b: undefined, c: '' } }), 'a%5Bc%5D=');
  591. st.end();
  592. });
  593. t.test('url encodes values', function (st) {
  594. st.equal(qs.stringify({ a: 'b c' }), 'a=b%20c');
  595. st.end();
  596. });
  597. t.test('stringifies a date', function (st) {
  598. var now = new Date();
  599. var str = 'a=' + encodeURIComponent(now.toISOString());
  600. st.equal(qs.stringify({ a: now }), str);
  601. st.end();
  602. });
  603. t.test('stringifies the weird object from qs', function (st) {
  604. 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');
  605. st.end();
  606. });
  607. t.test('skips properties that are part of the object prototype', function (st) {
  608. st.intercept(Object.prototype, 'crash', { value: 'test' });
  609. st.equal(qs.stringify({ a: 'b' }), 'a=b');
  610. st.equal(qs.stringify({ a: { b: 'c' } }), 'a%5Bb%5D=c');
  611. st.end();
  612. });
  613. t.test('stringifies boolean values', function (st) {
  614. st.equal(qs.stringify({ a: true }), 'a=true');
  615. st.equal(qs.stringify({ a: { b: true } }), 'a%5Bb%5D=true');
  616. st.equal(qs.stringify({ b: false }), 'b=false');
  617. st.equal(qs.stringify({ b: { c: false } }), 'b%5Bc%5D=false');
  618. st.end();
  619. });
  620. t.test('stringifies buffer values', function (st) {
  621. st.equal(qs.stringify({ a: SaferBuffer.from('test') }), 'a=test');
  622. st.equal(qs.stringify({ a: { b: SaferBuffer.from('test') } }), 'a%5Bb%5D=test');
  623. st.end();
  624. });
  625. t.test('stringifies an object using an alternative delimiter', function (st) {
  626. st.equal(qs.stringify({ a: 'b', c: 'd' }, { delimiter: ';' }), 'a=b;c=d');
  627. st.end();
  628. });
  629. t.test('does not blow up when Buffer global is missing', function (st) {
  630. var restore = mockProperty(global, 'Buffer', { 'delete': true });
  631. var result = qs.stringify({ a: 'b', c: 'd' });
  632. restore();
  633. st.equal(result, 'a=b&c=d');
  634. st.end();
  635. });
  636. t.test('does not crash when parsing circular references', function (st) {
  637. var a = {};
  638. a.b = a;
  639. st['throws'](
  640. function () { qs.stringify({ 'foo[bar]': 'baz', 'foo[baz]': a }); },
  641. /RangeError: Cyclic object value/,
  642. 'cyclic values throw'
  643. );
  644. var circular = {
  645. a: 'value'
  646. };
  647. circular.a = circular;
  648. st['throws'](
  649. function () { qs.stringify(circular); },
  650. /RangeError: Cyclic object value/,
  651. 'cyclic values throw'
  652. );
  653. var arr = ['a'];
  654. st.doesNotThrow(
  655. function () { qs.stringify({ x: arr, y: arr }); },
  656. 'non-cyclic values do not throw'
  657. );
  658. st.end();
  659. });
  660. t.test('non-circular duplicated references can still work', function (st) {
  661. var hourOfDay = {
  662. 'function': 'hour_of_day'
  663. };
  664. var p1 = {
  665. 'function': 'gte',
  666. arguments: [hourOfDay, 0]
  667. };
  668. var p2 = {
  669. 'function': 'lte',
  670. arguments: [hourOfDay, 23]
  671. };
  672. st.equal(
  673. qs.stringify({ filters: { $and: [p1, p2] } }, { encodeValuesOnly: true, arrayFormat: 'indices' }),
  674. 'filters[$and][0][function]=gte&filters[$and][0][arguments][0][function]=hour_of_day&filters[$and][0][arguments][1]=0&filters[$and][1][function]=lte&filters[$and][1][arguments][0][function]=hour_of_day&filters[$and][1][arguments][1]=23'
  675. );
  676. st.equal(
  677. qs.stringify({ filters: { $and: [p1, p2] } }, { encodeValuesOnly: true, arrayFormat: 'brackets' }),
  678. 'filters[$and][][function]=gte&filters[$and][][arguments][][function]=hour_of_day&filters[$and][][arguments][]=0&filters[$and][][function]=lte&filters[$and][][arguments][][function]=hour_of_day&filters[$and][][arguments][]=23'
  679. );
  680. st.equal(
  681. qs.stringify({ filters: { $and: [p1, p2] } }, { encodeValuesOnly: true, arrayFormat: 'repeat' }),
  682. 'filters[$and][function]=gte&filters[$and][arguments][function]=hour_of_day&filters[$and][arguments]=0&filters[$and][function]=lte&filters[$and][arguments][function]=hour_of_day&filters[$and][arguments]=23'
  683. );
  684. st.end();
  685. });
  686. t.test('selects properties when filter=array', function (st) {
  687. st.equal(qs.stringify({ a: 'b' }, { filter: ['a'] }), 'a=b');
  688. st.equal(qs.stringify({ a: 1 }, { filter: [] }), '');
  689. st.equal(
  690. qs.stringify(
  691. { a: { b: [1, 2, 3, 4], c: 'd' }, c: 'f' },
  692. { filter: ['a', 'b', 0, 2], arrayFormat: 'indices' }
  693. ),
  694. 'a%5Bb%5D%5B0%5D=1&a%5Bb%5D%5B2%5D=3',
  695. 'indices => indices'
  696. );
  697. st.equal(
  698. qs.stringify(
  699. { a: { b: [1, 2, 3, 4], c: 'd' }, c: 'f' },
  700. { filter: ['a', 'b', 0, 2], arrayFormat: 'brackets' }
  701. ),
  702. 'a%5Bb%5D%5B%5D=1&a%5Bb%5D%5B%5D=3',
  703. 'brackets => brackets'
  704. );
  705. st.equal(
  706. qs.stringify(
  707. { a: { b: [1, 2, 3, 4], c: 'd' }, c: 'f' },
  708. { filter: ['a', 'b', 0, 2] }
  709. ),
  710. 'a%5Bb%5D%5B0%5D=1&a%5Bb%5D%5B2%5D=3',
  711. 'default => indices'
  712. );
  713. st.end();
  714. });
  715. t.test('supports custom representations when filter=function', function (st) {
  716. var calls = 0;
  717. var obj = { a: 'b', c: 'd', e: { f: new Date(1257894000000) } };
  718. var filterFunc = function (prefix, value) {
  719. calls += 1;
  720. if (calls === 1) {
  721. st.equal(prefix, '', 'prefix is empty');
  722. st.equal(value, obj);
  723. } else if (prefix === 'c') {
  724. return void 0;
  725. } else if (value instanceof Date) {
  726. st.equal(prefix, 'e[f]');
  727. return value.getTime();
  728. }
  729. return value;
  730. };
  731. st.equal(qs.stringify(obj, { filter: filterFunc }), 'a=b&e%5Bf%5D=1257894000000');
  732. st.equal(calls, 5);
  733. st.end();
  734. });
  735. t.test('can disable uri encoding', function (st) {
  736. st.equal(qs.stringify({ a: 'b' }, { encode: false }), 'a=b');
  737. st.equal(qs.stringify({ a: { b: 'c' } }, { encode: false }), 'a[b]=c');
  738. st.equal(qs.stringify({ a: 'b', c: null }, { strictNullHandling: true, encode: false }), 'a=b&c');
  739. st.end();
  740. });
  741. t.test('can sort the keys', function (st) {
  742. var sort = function (a, b) {
  743. return a.localeCompare(b);
  744. };
  745. st.equal(qs.stringify({ a: 'c', z: 'y', b: 'f' }, { sort: sort }), 'a=c&b=f&z=y');
  746. 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');
  747. st.end();
  748. });
  749. t.test('can sort the keys at depth 3 or more too', function (st) {
  750. var sort = function (a, b) {
  751. return a.localeCompare(b);
  752. };
  753. st.equal(
  754. qs.stringify(
  755. { a: 'a', z: { zj: { zjb: 'zjb', zja: 'zja' }, zi: { zib: 'zib', zia: 'zia' } }, b: 'b' },
  756. { sort: sort, encode: false }
  757. ),
  758. 'a=a&b=b&z[zi][zia]=zia&z[zi][zib]=zib&z[zj][zja]=zja&z[zj][zjb]=zjb'
  759. );
  760. st.equal(
  761. qs.stringify(
  762. { a: 'a', z: { zj: { zjb: 'zjb', zja: 'zja' }, zi: { zib: 'zib', zia: 'zia' } }, b: 'b' },
  763. { sort: null, encode: false }
  764. ),
  765. 'a=a&z[zj][zjb]=zjb&z[zj][zja]=zja&z[zi][zib]=zib&z[zi][zia]=zia&b=b'
  766. );
  767. st.end();
  768. });
  769. t.test('can stringify with custom encoding', function (st) {
  770. st.equal(qs.stringify({ : '大阪府', '': '' }, {
  771. encoder: function (str) {
  772. if (str.length === 0) {
  773. return '';
  774. }
  775. var buf = iconv.encode(str, 'shiftjis');
  776. var result = [];
  777. for (var i = 0; i < buf.length; ++i) {
  778. result.push(buf.readUInt8(i).toString(16));
  779. }
  780. return '%' + result.join('%');
  781. }
  782. }), '%8c%a7=%91%e5%8d%e3%95%7b&=');
  783. st.end();
  784. });
  785. t.test('receives the default encoder as a second argument', function (st) {
  786. st.plan(8);
  787. qs.stringify({ a: 1, b: new Date(), c: true, d: [1] }, {
  788. encoder: function (str) {
  789. st.match(typeof str, /^(?:string|number|boolean)$/);
  790. return '';
  791. }
  792. });
  793. st.end();
  794. });
  795. t.test('receives the default encoder as a second argument', function (st) {
  796. st.plan(2);
  797. qs.stringify({ a: 1 }, {
  798. encoder: function (str, defaultEncoder) {
  799. st.equal(defaultEncoder, utils.encode);
  800. }
  801. });
  802. st.end();
  803. });
  804. t.test('throws error with wrong encoder', function (st) {
  805. st['throws'](function () {
  806. qs.stringify({}, { encoder: 'string' });
  807. }, new TypeError('Encoder has to be a function.'));
  808. st.end();
  809. });
  810. t.test('can use custom encoder for a buffer object', { skip: typeof Buffer === 'undefined' }, function (st) {
  811. st.equal(qs.stringify({ a: SaferBuffer.from([1]) }, {
  812. encoder: function (buffer) {
  813. if (typeof buffer === 'string') {
  814. return buffer;
  815. }
  816. return String.fromCharCode(buffer.readUInt8(0) + 97);
  817. }
  818. }), 'a=b');
  819. st.equal(qs.stringify({ a: SaferBuffer.from('a b') }, {
  820. encoder: function (buffer) {
  821. return buffer;
  822. }
  823. }), 'a=a b');
  824. st.end();
  825. });
  826. t.test('serializeDate option', function (st) {
  827. var date = new Date();
  828. st.equal(
  829. qs.stringify({ a: date }),
  830. 'a=' + date.toISOString().replace(/:/g, '%3A'),
  831. 'default is toISOString'
  832. );
  833. var mutatedDate = new Date();
  834. mutatedDate.toISOString = function () {
  835. throw new SyntaxError();
  836. };
  837. st['throws'](function () {
  838. mutatedDate.toISOString();
  839. }, SyntaxError);
  840. st.equal(
  841. qs.stringify({ a: mutatedDate }),
  842. 'a=' + Date.prototype.toISOString.call(mutatedDate).replace(/:/g, '%3A'),
  843. 'toISOString works even when method is not locally present'
  844. );
  845. var specificDate = new Date(6);
  846. st.equal(
  847. qs.stringify(
  848. { a: specificDate },
  849. { serializeDate: function (d) { return d.getTime() * 7; } }
  850. ),
  851. 'a=42',
  852. 'custom serializeDate function called'
  853. );
  854. st.equal(
  855. qs.stringify(
  856. { a: [date] },
  857. {
  858. serializeDate: function (d) { return d.getTime(); },
  859. arrayFormat: 'comma'
  860. }
  861. ),
  862. 'a=' + date.getTime(),
  863. 'works with arrayFormat comma'
  864. );
  865. st.equal(
  866. qs.stringify(
  867. { a: [date] },
  868. {
  869. serializeDate: function (d) { return d.getTime(); },
  870. arrayFormat: 'comma',
  871. commaRoundTrip: true
  872. }
  873. ),
  874. 'a%5B%5D=' + date.getTime(),
  875. 'works with arrayFormat comma'
  876. );
  877. st.end();
  878. });
  879. t.test('RFC 1738 serialization', function (st) {
  880. st.equal(qs.stringify({ a: 'b c' }, { format: qs.formats.RFC1738 }), 'a=b+c');
  881. st.equal(qs.stringify({ 'a b': 'c d' }, { format: qs.formats.RFC1738 }), 'a+b=c+d');
  882. st.equal(qs.stringify({ 'a b': SaferBuffer.from('a b') }, { format: qs.formats.RFC1738 }), 'a+b=a+b');
  883. st.equal(qs.stringify({ 'foo(ref)': 'bar' }, { format: qs.formats.RFC1738 }), 'foo(ref)=bar');
  884. st.end();
  885. });
  886. t.test('RFC 3986 spaces serialization', function (st) {
  887. st.equal(qs.stringify({ a: 'b c' }, { format: qs.formats.RFC3986 }), 'a=b%20c');
  888. st.equal(qs.stringify({ 'a b': 'c d' }, { format: qs.formats.RFC3986 }), 'a%20b=c%20d');
  889. st.equal(qs.stringify({ 'a b': SaferBuffer.from('a b') }, { format: qs.formats.RFC3986 }), 'a%20b=a%20b');
  890. st.end();
  891. });
  892. t.test('Backward compatibility to RFC 3986', function (st) {
  893. st.equal(qs.stringify({ a: 'b c' }), 'a=b%20c');
  894. st.equal(qs.stringify({ 'a b': SaferBuffer.from('a b') }), 'a%20b=a%20b');
  895. st.end();
  896. });
  897. t.test('Edge cases and unknown formats', function (st) {
  898. ['UFO1234', false, 1234, null, {}, []].forEach(function (format) {
  899. st['throws'](
  900. function () {
  901. qs.stringify({ a: 'b c' }, { format: format });
  902. },
  903. new TypeError('Unknown format option provided.')
  904. );
  905. });
  906. st.end();
  907. });
  908. t.test('encodeValuesOnly', function (st) {
  909. st.equal(
  910. qs.stringify(
  911. { a: 'b', c: ['d', 'e=f'], f: [['g'], ['h']] },
  912. { encodeValuesOnly: true, arrayFormat: 'indices' }
  913. ),
  914. 'a=b&c[0]=d&c[1]=e%3Df&f[0][0]=g&f[1][0]=h',
  915. 'encodeValuesOnly + indices'
  916. );
  917. st.equal(
  918. qs.stringify(
  919. { a: 'b', c: ['d', 'e=f'], f: [['g'], ['h']] },
  920. { encodeValuesOnly: true, arrayFormat: 'brackets' }
  921. ),
  922. 'a=b&c[]=d&c[]=e%3Df&f[][]=g&f[][]=h',
  923. 'encodeValuesOnly + brackets'
  924. );
  925. st.equal(
  926. qs.stringify(
  927. { a: 'b', c: ['d', 'e=f'], f: [['g'], ['h']] },
  928. { encodeValuesOnly: true, arrayFormat: 'repeat' }
  929. ),
  930. 'a=b&c=d&c=e%3Df&f=g&f=h',
  931. 'encodeValuesOnly + repeat'
  932. );
  933. st.equal(
  934. qs.stringify(
  935. { a: 'b', c: ['d', 'e'], f: [['g'], ['h']] },
  936. { arrayFormat: 'indices' }
  937. ),
  938. 'a=b&c%5B0%5D=d&c%5B1%5D=e&f%5B0%5D%5B0%5D=g&f%5B1%5D%5B0%5D=h',
  939. 'no encodeValuesOnly + indices'
  940. );
  941. st.equal(
  942. qs.stringify(
  943. { a: 'b', c: ['d', 'e'], f: [['g'], ['h']] },
  944. { arrayFormat: 'brackets' }
  945. ),
  946. 'a=b&c%5B%5D=d&c%5B%5D=e&f%5B%5D%5B%5D=g&f%5B%5D%5B%5D=h',
  947. 'no encodeValuesOnly + brackets'
  948. );
  949. st.equal(
  950. qs.stringify(
  951. { a: 'b', c: ['d', 'e'], f: [['g'], ['h']] },
  952. { arrayFormat: 'repeat' }
  953. ),
  954. 'a=b&c=d&c=e&f=g&f=h',
  955. 'no encodeValuesOnly + repeat'
  956. );
  957. st.end();
  958. });
  959. t.test('encodeValuesOnly - strictNullHandling', function (st) {
  960. st.equal(
  961. qs.stringify(
  962. { a: { b: null } },
  963. { encodeValuesOnly: true, strictNullHandling: true }
  964. ),
  965. 'a[b]'
  966. );
  967. st.end();
  968. });
  969. t.test('throws if an invalid charset is specified', function (st) {
  970. st['throws'](function () {
  971. qs.stringify({ a: 'b' }, { charset: 'foobar' });
  972. }, new TypeError('The charset option must be either utf-8, iso-8859-1, or undefined'));
  973. st.end();
  974. });
  975. t.test('respects a charset of iso-8859-1', function (st) {
  976. st.equal(qs.stringify({ æ: 'æ' }, { charset: 'iso-8859-1' }), '%E6=%E6');
  977. st.end();
  978. });
  979. t.test('encodes unrepresentable chars as numeric entities in iso-8859-1 mode', function (st) {
  980. st.equal(qs.stringify({ a: '☺' }, { charset: 'iso-8859-1' }), 'a=%26%239786%3B');
  981. st.end();
  982. });
  983. t.test('respects an explicit charset of utf-8 (the default)', function (st) {
  984. st.equal(qs.stringify({ a: 'æ' }, { charset: 'utf-8' }), 'a=%C3%A6');
  985. st.end();
  986. });
  987. t.test('`charsetSentinel` option', function (st) {
  988. st.equal(
  989. qs.stringify({ a: 'æ' }, { charsetSentinel: true, charset: 'utf-8' }),
  990. 'utf8=%E2%9C%93&a=%C3%A6',
  991. 'adds the right sentinel when instructed to and the charset is utf-8'
  992. );
  993. st.equal(
  994. qs.stringify({ a: 'æ' }, { charsetSentinel: true, charset: 'iso-8859-1' }),
  995. 'utf8=%26%2310003%3B&a=%E6',
  996. 'adds the right sentinel when instructed to and the charset is iso-8859-1'
  997. );
  998. st.end();
  999. });
  1000. t.test('does not mutate the options argument', function (st) {
  1001. var options = {};
  1002. qs.stringify({}, options);
  1003. st.deepEqual(options, {});
  1004. st.end();
  1005. });
  1006. t.test('strictNullHandling works with custom filter', function (st) {
  1007. var filter = function (prefix, value) {
  1008. return value;
  1009. };
  1010. var options = { strictNullHandling: true, filter: filter };
  1011. st.equal(qs.stringify({ key: null }, options), 'key');
  1012. st.end();
  1013. });
  1014. t.test('strictNullHandling works with null serializeDate', function (st) {
  1015. var serializeDate = function () {
  1016. return null;
  1017. };
  1018. var options = { strictNullHandling: true, serializeDate: serializeDate };
  1019. var date = new Date();
  1020. st.equal(qs.stringify({ key: date }, options), 'key');
  1021. st.end();
  1022. });
  1023. t.test('allows for encoding keys and values differently', function (st) {
  1024. var encoder = function (str, defaultEncoder, charset, type) {
  1025. if (type === 'key') {
  1026. return defaultEncoder(str, defaultEncoder, charset, type).toLowerCase();
  1027. }
  1028. if (type === 'value') {
  1029. return defaultEncoder(str, defaultEncoder, charset, type).toUpperCase();
  1030. }
  1031. throw 'this should never happen! type: ' + type;
  1032. };
  1033. st.deepEqual(qs.stringify({ KeY: 'vAlUe' }, { encoder: encoder }), 'key=VALUE');
  1034. st.end();
  1035. });
  1036. t.test('objects inside arrays', function (st) {
  1037. var obj = { a: { b: { c: 'd', e: 'f' } } };
  1038. var withArray = { a: { b: [{ c: 'd', e: 'f' }] } };
  1039. st.equal(qs.stringify(obj, { encode: false }), 'a[b][c]=d&a[b][e]=f', 'no array, no arrayFormat');
  1040. st.equal(qs.stringify(obj, { encode: false, arrayFormat: 'brackets' }), 'a[b][c]=d&a[b][e]=f', 'no array, bracket');
  1041. st.equal(qs.stringify(obj, { encode: false, arrayFormat: 'indices' }), 'a[b][c]=d&a[b][e]=f', 'no array, indices');
  1042. st.equal(qs.stringify(obj, { encode: false, arrayFormat: 'repeat' }), 'a[b][c]=d&a[b][e]=f', 'no array, repeat');
  1043. st.equal(qs.stringify(obj, { encode: false, arrayFormat: 'comma' }), 'a[b][c]=d&a[b][e]=f', 'no array, comma');
  1044. st.equal(qs.stringify(withArray, { encode: false }), 'a[b][0][c]=d&a[b][0][e]=f', 'array, no arrayFormat');
  1045. st.equal(qs.stringify(withArray, { encode: false, arrayFormat: 'brackets' }), 'a[b][][c]=d&a[b][][e]=f', 'array, bracket');
  1046. st.equal(qs.stringify(withArray, { encode: false, arrayFormat: 'indices' }), 'a[b][0][c]=d&a[b][0][e]=f', 'array, indices');
  1047. st.equal(qs.stringify(withArray, { encode: false, arrayFormat: 'repeat' }), 'a[b][c]=d&a[b][e]=f', 'array, repeat');
  1048. st.equal(
  1049. qs.stringify(withArray, { encode: false, arrayFormat: 'comma' }),
  1050. '???',
  1051. 'array, comma',
  1052. { skip: 'TODO: figure out what this should do' }
  1053. );
  1054. st.end();
  1055. });
  1056. t.test('stringifies sparse arrays', function (st) {
  1057. /* eslint no-sparse-arrays: 0 */
  1058. st.equal(qs.stringify({ a: [, '2', , , '1'] }, { encodeValuesOnly: true, arrayFormat: 'indices' }), 'a[1]=2&a[4]=1');
  1059. st.equal(qs.stringify({ a: [, '2', , , '1'] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), 'a[]=2&a[]=1');
  1060. st.equal(qs.stringify({ a: [, '2', , , '1'] }, { encodeValuesOnly: true, arrayFormat: 'repeat' }), 'a=2&a=1');
  1061. st.equal(qs.stringify({ a: [, { b: [, , { c: '1' }] }] }, { encodeValuesOnly: true, arrayFormat: 'indices' }), 'a[1][b][2][c]=1');
  1062. st.equal(qs.stringify({ a: [, { b: [, , { c: '1' }] }] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), 'a[][b][][c]=1');
  1063. st.equal(qs.stringify({ a: [, { b: [, , { c: '1' }] }] }, { encodeValuesOnly: true, arrayFormat: 'repeat' }), 'a[b][c]=1');
  1064. st.equal(qs.stringify({ a: [, [, , [, , , { c: '1' }]]] }, { encodeValuesOnly: true, arrayFormat: 'indices' }), 'a[1][2][3][c]=1');
  1065. st.equal(qs.stringify({ a: [, [, , [, , , { c: '1' }]]] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), 'a[][][][c]=1');
  1066. st.equal(qs.stringify({ a: [, [, , [, , , { c: '1' }]]] }, { encodeValuesOnly: true, arrayFormat: 'repeat' }), 'a[c]=1');
  1067. st.equal(qs.stringify({ a: [, [, , [, , , { c: [, '1'] }]]] }, { encodeValuesOnly: true, arrayFormat: 'indices' }), 'a[1][2][3][c][1]=1');
  1068. st.equal(qs.stringify({ a: [, [, , [, , , { c: [, '1'] }]]] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), 'a[][][][c][]=1');
  1069. st.equal(qs.stringify({ a: [, [, , [, , , { c: [, '1'] }]]] }, { encodeValuesOnly: true, arrayFormat: 'repeat' }), 'a[c]=1');
  1070. st.end();
  1071. });
  1072. t.test('encodes a very long string', function (st) {
  1073. var chars = [];
  1074. var expected = [];
  1075. for (var i = 0; i < 5e3; i++) {
  1076. chars.push(' ' + i);
  1077. expected.push('%20' + i);
  1078. }
  1079. var obj = {
  1080. foo: chars.join('')
  1081. };
  1082. st.equal(
  1083. qs.stringify(obj, { arrayFormat: 'brackets', charset: 'utf-8' }),
  1084. 'foo=' + expected.join('')
  1085. );
  1086. st.end();
  1087. });
  1088. t.end();
  1089. });
  1090. test('stringifies empty keys', function (t) {
  1091. emptyTestCases.forEach(function (testCase) {
  1092. t.test('stringifies an object with empty string key with ' + testCase.input, function (st) {
  1093. st.deepEqual(
  1094. qs.stringify(testCase.withEmptyKeys, { encode: false, arrayFormat: 'indices' }),
  1095. testCase.stringifyOutput.indices,
  1096. 'test case: ' + testCase.input + ', indices'
  1097. );
  1098. st.deepEqual(
  1099. qs.stringify(testCase.withEmptyKeys, { encode: false, arrayFormat: 'brackets' }),
  1100. testCase.stringifyOutput.brackets,
  1101. 'test case: ' + testCase.input + ', brackets'
  1102. );
  1103. st.deepEqual(
  1104. qs.stringify(testCase.withEmptyKeys, { encode: false, arrayFormat: 'repeat' }),
  1105. testCase.stringifyOutput.repeat,
  1106. 'test case: ' + testCase.input + ', repeat'
  1107. );
  1108. st.end();
  1109. });
  1110. });
  1111. t.test('edge case with object/arrays', function (st) {
  1112. st.deepEqual(qs.stringify({ '': { '': [2, 3] } }, { encode: false }), '[][0]=2&[][1]=3');
  1113. st.deepEqual(qs.stringify({ '': { '': [2, 3], a: 2 } }, { encode: false }), '[][0]=2&[][1]=3&[a]=2');
  1114. st.deepEqual(qs.stringify({ '': { '': [2, 3] } }, { encode: false, arrayFormat: 'indices' }), '[][0]=2&[][1]=3');
  1115. st.deepEqual(qs.stringify({ '': { '': [2, 3], a: 2 } }, { encode: false, arrayFormat: 'indices' }), '[][0]=2&[][1]=3&[a]=2');
  1116. st.end();
  1117. });
  1118. t.test('stringifies non-string keys', function (st) {
  1119. var actual = qs.stringify({ a: 'b', 'false': {} }, {
  1120. filter: ['a', false, null],
  1121. allowDots: true,
  1122. encodeDotInKeys: true
  1123. });
  1124. st.equal(actual, 'a=b', 'stringifies correctly');
  1125. st.end();
  1126. });
  1127. });