All new
alle new
This commit is contained in:
1 parent
da64472251
commit
5b20adee48
1605 files changed
+396517
-1674
No files matched your search
+192
-3
@@ -1475,6 +1475,73 @@ test('parse()', function (t) {
|
||||
sst.end();
|
||||
});
|
||||
|
||||
st.test('spreads a comma group appended to an already-overflowed object', function (sst) {
|
||||
var result = qs.parse('a=1,2,3,4,5,6&a=7,8', { comma: true, arrayLimit: 5 });
|
||||
sst.deepEqual(
|
||||
result,
|
||||
{ a: { 0: '1', 1: '2', 2: '3', 3: '4', 4: '5', 5: '6', 6: '7', 7: '8' } },
|
||||
'appended comma values are flattened, not nested'
|
||||
);
|
||||
sst.end();
|
||||
});
|
||||
|
||||
st.test('keeps a `[]=` comma group as one element when appended to an already-overflowed key', function (sst) {
|
||||
var result = qs.parse('a[]=1&a[]=2&a[]=3&a[]=4,5', { comma: true, arrayLimit: 2 });
|
||||
sst.deepEqual(
|
||||
result,
|
||||
{ a: { 0: '1', 1: '2', 2: '3', 3: ['4', '5'] } },
|
||||
'nested comma group stays a single element, not double-nested'
|
||||
);
|
||||
sst.end();
|
||||
});
|
||||
|
||||
st.test('spreads every later comma group appended to an already-overflowed object', function (sst) {
|
||||
var result = qs.parse('a=1,2,3,4,5,6&a=7,8&a=9,10', { comma: true, arrayLimit: 5 });
|
||||
sst.deepEqual(
|
||||
result,
|
||||
{ a: { 0: '1', 1: '2', 2: '3', 3: '4', 4: '5', 5: '6', 6: '7', 7: '8', 8: '9', 9: '10' } },
|
||||
'each appended group is flattened in order'
|
||||
);
|
||||
sst.end();
|
||||
});
|
||||
|
||||
st.test('spreads a comma group appended to an object overflowed under the default arrayLimit', function (sst) {
|
||||
var values = [];
|
||||
for (var i = 0; i <= 20; i += 1) {
|
||||
values[values.length] = String(i);
|
||||
}
|
||||
var expected = {};
|
||||
for (var j = 0; j < values.length; j += 1) {
|
||||
expected[j] = values[j];
|
||||
}
|
||||
expected[21] = 'x';
|
||||
expected[22] = 'y';
|
||||
|
||||
var result = qs.parse('a=' + values.join(',') + '&a=x,y', { comma: true });
|
||||
sst.deepEqual(result, { a: expected }, 'appended values land at indices 21 and 22');
|
||||
sst.end();
|
||||
});
|
||||
|
||||
st.test('spreads a comma group appended to an already-overflowed object with plainObjects', function (sst) {
|
||||
var result = qs.parse('a=1,2,3,4,5,6&a=7,8', { comma: true, arrayLimit: 5, plainObjects: true });
|
||||
sst.deepEqual(
|
||||
result,
|
||||
{ __proto__: null, a: { __proto__: null, 0: '1', 1: '2', 2: '3', 3: '4', 4: '5', 5: '6', 6: '7', 7: '8' } },
|
||||
'appended comma values are flattened into the null object'
|
||||
);
|
||||
sst.end();
|
||||
});
|
||||
|
||||
st.test('still throws with throwOnLimitExceeded when a comma group is appended to an already-overflowed object', function (sst) {
|
||||
sst['throws'](
|
||||
function () {
|
||||
qs.parse('a=1,2,3,4,5,6&a=7,8', { comma: true, arrayLimit: 5, throwOnLimitExceeded: true });
|
||||
},
|
||||
/Array limit exceeded/,
|
||||
'throws before spreading the appended group'
|
||||
);
|
||||
sst.end();
|
||||
});
|
||||
st.test('does not throw for comma groups nested under bracket notation, counting each group as one element', function (sst) {
|
||||
var result = qs.parse('a[]=1,2,3&a[]=4,5,6', { comma: true, arrayLimit: 5, throwOnLimitExceeded: true });
|
||||
sst.deepEqual(result, { a: [['1', '2', '3'], ['4', '5', '6']] }, 'nested comma groups count as one element each');
|
||||
@@ -1522,9 +1589,131 @@ test('parse()', function (t) {
|
||||
sst.end();
|
||||
});
|
||||
|
||||
st.test('does not throw for a bracketed comma group within arrayLimit', function (sst) {
|
||||
var result = qs.parse('a[]=1,2,3,4,5,6', { comma: true, arrayLimit: 5, throwOnLimitExceeded: true });
|
||||
sst.deepEqual(result, { a: [['1', '2', '3', '4', '5', '6']] }, 'a bracketed comma group is a single element');
|
||||
st.test('throws before splitting when a bracketed comma group exceeds arrayLimit', function (sst) {
|
||||
sst['throws'](
|
||||
function () {
|
||||
qs.parse('a[]=1,2,3,4,5,6', { comma: true, arrayLimit: 5, throwOnLimitExceeded: true });
|
||||
},
|
||||
new RangeError('Array limit exceeded. Only 5 elements allowed in an array.'),
|
||||
'the inner group is itself an array and is subject to arrayLimit'
|
||||
);
|
||||
sst.end();
|
||||
});
|
||||
|
||||
st.test('throws before splitting when a comma group under `[]=` exceeds arrayLimit', function (sst) {
|
||||
sst['throws'](
|
||||
function () {
|
||||
qs.parse('a[]=1,2,3,4', { comma: true, arrayLimit: 3, throwOnLimitExceeded: true });
|
||||
},
|
||||
new RangeError('Array limit exceeded. Only 3 elements allowed in an array.'),
|
||||
'a bracket-push comma group over the limit throws like the flat form'
|
||||
);
|
||||
|
||||
sst['throws'](
|
||||
function () {
|
||||
qs.parse('a[]=1,2', { comma: true, arrayLimit: 1, throwOnLimitExceeded: true });
|
||||
},
|
||||
new RangeError('Array limit exceeded. Only 1 element allowed in an array.'),
|
||||
'singular message at arrayLimit 1'
|
||||
);
|
||||
|
||||
sst['throws'](
|
||||
function () {
|
||||
qs.parse('a[b][]=1,2,3,4', { comma: true, arrayLimit: 3, throwOnLimitExceeded: true });
|
||||
},
|
||||
new RangeError('Array limit exceeded. Only 3 elements allowed in an array.'),
|
||||
'a nested bracket-push key throws'
|
||||
);
|
||||
|
||||
sst['throws'](
|
||||
function () {
|
||||
qs.parse('a[][]=1,2,3,4', { comma: true, arrayLimit: 3, throwOnLimitExceeded: true });
|
||||
},
|
||||
new RangeError('Array limit exceeded. Only 3 elements allowed in an array.'),
|
||||
'a doubly bracket-pushed key throws'
|
||||
);
|
||||
|
||||
sst['throws'](
|
||||
function () {
|
||||
qs.parse('a[]=5&a[]=1,2,3,4', { comma: true, arrayLimit: 3, throwOnLimitExceeded: true });
|
||||
},
|
||||
new RangeError('Array limit exceeded. Only 3 elements allowed in an array.'),
|
||||
'an oversized group appended after a scalar throws'
|
||||
);
|
||||
|
||||
sst['throws'](
|
||||
function () {
|
||||
qs.parse('a[]=1,2,3,4&a[]=5', { comma: true, arrayLimit: 3, throwOnLimitExceeded: true });
|
||||
},
|
||||
new RangeError('Array limit exceeded. Only 3 elements allowed in an array.'),
|
||||
'an oversized group throws even when a later part would fit'
|
||||
);
|
||||
|
||||
sst['throws'](
|
||||
function () {
|
||||
qs.parse('a[]=1,2,3,4&a[]=5', { comma: true, arrayLimit: 3, throwOnLimitExceeded: true, duplicates: 'first' });
|
||||
},
|
||||
new RangeError('Array limit exceeded. Only 3 elements allowed in an array.'),
|
||||
'throws with duplicates: first'
|
||||
);
|
||||
|
||||
sst['throws'](
|
||||
function () {
|
||||
qs.parse('a[]=5&a[]=1,2,3,4', { comma: true, arrayLimit: 3, throwOnLimitExceeded: true, duplicates: 'last' });
|
||||
},
|
||||
new RangeError('Array limit exceeded. Only 3 elements allowed in an array.'),
|
||||
'throws with duplicates: last'
|
||||
);
|
||||
|
||||
sst['throws'](
|
||||
function () {
|
||||
qs.parse({ a: '1,2,3,4' }, { comma: true, arrayLimit: 3, throwOnLimitExceeded: true });
|
||||
},
|
||||
new RangeError('Array limit exceeded. Only 3 elements allowed in an array.'),
|
||||
'an oversized comma value in object input throws'
|
||||
);
|
||||
|
||||
var big = 'a[]=' + new Array(1e5 + 1).join('1,') + '1';
|
||||
sst['throws'](
|
||||
function () {
|
||||
qs.parse(big, { comma: true, arrayLimit: 3, throwOnLimitExceeded: true });
|
||||
},
|
||||
new RangeError('Array limit exceeded. Only 3 elements allowed in an array.'),
|
||||
'a very long bracket-push comma group throws'
|
||||
);
|
||||
sst.end();
|
||||
});
|
||||
|
||||
st.test('does not throw for a comma group under `[]=` within arrayLimit', function (sst) {
|
||||
sst.deepEqual(
|
||||
qs.parse('a[]=1,2,3', { comma: true, arrayLimit: 3, throwOnLimitExceeded: true }),
|
||||
{ a: [['1', '2', '3']] },
|
||||
'an inner group exactly at the limit stays a single nested element'
|
||||
);
|
||||
sst.deepEqual(
|
||||
qs.parse('a[]=1,2,3&a[]=4,5,6&a[]=7', { comma: true, arrayLimit: 3, throwOnLimitExceeded: true }),
|
||||
{ a: [['1', '2', '3'], ['4', '5', '6'], '7'] },
|
||||
'each group counts as one element of the outer array'
|
||||
);
|
||||
sst.deepEqual(
|
||||
qs.parse('a[b][]=1,2,3', { comma: true, arrayLimit: 3, throwOnLimitExceeded: true }),
|
||||
{ a: { b: [['1', '2', '3']] } },
|
||||
'nested bracket-push key within the limit'
|
||||
);
|
||||
sst.deepEqual(
|
||||
qs.parse({ a: '1,2,3' }, { comma: true, arrayLimit: 3, throwOnLimitExceeded: true }),
|
||||
{ a: ['1', '2', '3'] },
|
||||
'object input within the limit'
|
||||
);
|
||||
sst.end();
|
||||
});
|
||||
|
||||
st.test('keeps an oversized comma group under `[]=` as a nested array without throwOnLimitExceeded', function (sst) {
|
||||
sst.deepEqual(
|
||||
qs.parse('a[]=1,2,3,4', { comma: true, arrayLimit: 3 }),
|
||||
{ a: [['1', '2', '3', '4']] },
|
||||
'the inner group is not converted to an overflow object'
|
||||
);
|
||||
sst.end();
|
||||
});
|
||||
|
||||
|
||||
+317
@@ -136,6 +136,144 @@ test('stringify()', function (t) {
|
||||
st.end();
|
||||
});
|
||||
|
||||
t.test('encodes dot in key of object with a primitive value when encodeDotInKeys and allowDots is provided', function (st) {
|
||||
st.equal(
|
||||
qs.stringify(
|
||||
{ 'name.obj': 'John' },
|
||||
{ allowDots: true, encodeDotInKeys: true }
|
||||
),
|
||||
'name%252Eobj=John',
|
||||
'with allowDots true and encodeDotInKeys true'
|
||||
);
|
||||
st.equal(
|
||||
qs.stringify(
|
||||
{ 'name.obj': 'John' },
|
||||
{ allowDots: false, encodeDotInKeys: true }
|
||||
),
|
||||
'name%252Eobj=John',
|
||||
'with allowDots false and encodeDotInKeys true'
|
||||
);
|
||||
st.equal(
|
||||
qs.stringify(
|
||||
{ 'name.obj': 'John' },
|
||||
{ encodeDotInKeys: true, allowDots: true, encodeValuesOnly: true }
|
||||
),
|
||||
'name%2Eobj=John',
|
||||
'with encodeValuesOnly true'
|
||||
);
|
||||
|
||||
// With `encodeValuesOnly` the key encoder is skipped, so the dot is encoded once
|
||||
// (`%2E`) rather than twice. That single `%2E` decodes back to a structural dot, so
|
||||
// unlike the default output below this does not round-trip to the original dotted key
|
||||
// (a pre-existing `encodeValuesOnly` + `encodeDotInKeys` limitation, not new here).
|
||||
st.deepEqual(
|
||||
qs.parse(
|
||||
qs.stringify(
|
||||
{ 'name.obj': 'John' },
|
||||
{ encodeDotInKeys: true, allowDots: true, encodeValuesOnly: true }
|
||||
),
|
||||
{ allowDots: true, decodeDotInKeys: true }
|
||||
),
|
||||
{ name: { obj: 'John' } },
|
||||
'with encodeValuesOnly the single-encoded dot decodes structurally (does not round-trip)'
|
||||
);
|
||||
|
||||
st.deepEqual(
|
||||
qs.parse(
|
||||
qs.stringify({ 'name.obj': 'John' }, { allowDots: true, encodeDotInKeys: true }),
|
||||
{ allowDots: true, decodeDotInKeys: true }
|
||||
),
|
||||
{ 'name.obj': 'John' },
|
||||
'round-trips a dotted key with a primitive value'
|
||||
);
|
||||
|
||||
st.equal(
|
||||
qs.stringify({ 'a.b': null }, { allowDots: true, encodeDotInKeys: true, strictNullHandling: true }),
|
||||
'a%252Eb',
|
||||
'encodes a dotted key with a null value'
|
||||
);
|
||||
st.equal(
|
||||
qs.stringify({ 'a.b': '1', 'c.d': '2' }, { allowDots: true, encodeDotInKeys: true }),
|
||||
'a%252Eb=1&c%252Ed=2',
|
||||
'encodes dots in every top-level key, not just the first'
|
||||
);
|
||||
st.equal(
|
||||
qs.stringify({ 'a.b.c': 'x' }, { allowDots: true, encodeDotInKeys: true }),
|
||||
'a%252Eb%252Ec=x',
|
||||
'encodes every dot in a key'
|
||||
);
|
||||
var primitiveKey = qs.stringify({ 'a.b': 'c' }, { allowDots: true, encodeDotInKeys: true }).split('=')[0];
|
||||
st.equal(
|
||||
qs.stringify({ 'a.b': { x: 'c' } }, { allowDots: true, encodeDotInKeys: true }).indexOf(primitiveKey + '.'),
|
||||
0,
|
||||
'a top-level dotted key encodes identically for primitive and object values'
|
||||
);
|
||||
|
||||
st.end();
|
||||
});
|
||||
|
||||
// Characterization of the CURRENT (buggy) behavior: with `allowDots` + `encodeDotInKeys`,
|
||||
// `stringify` percent-encodes the structural `.` separators that `allowDots` inserts between
|
||||
// nested keys, not just the literal dots inside a key, which breaks the stringify -> parse
|
||||
// round-trip. These assertions pin that behavior so it cannot change silently: the fix in
|
||||
// https://github.com/ljharb/qs/pull/564 changes every output below, so landing that fix MUST
|
||||
// update this test (that is the point — it forces the behavior change to be explicit).
|
||||
t.test('[characterization] encodeDotInKeys currently over-encodes structural separator dots (pre-#564)', function (st) {
|
||||
st.equal(
|
||||
qs.stringify({ a: { b: { c: 'd' } } }, { allowDots: true, encodeDotInKeys: true }),
|
||||
'a%252Eb.c=d',
|
||||
'over-encodes the a->b separator (#564: should become a.b.c=d)'
|
||||
);
|
||||
st.equal(
|
||||
qs.stringify({ a: { b: { c: { d: 'e' } } } }, { allowDots: true, encodeDotInKeys: true }),
|
||||
'a%252Eb%252Ec.d=e',
|
||||
'clobbers every separator but the last (#564: should become a.b.c.d=e)'
|
||||
);
|
||||
st.equal(
|
||||
qs.stringify({ 'a.b': { c: { d: 'e' } } }, { allowDots: true, encodeDotInKeys: true }),
|
||||
'a%252Eb%252Ec.d=e',
|
||||
'top-level dotted key: deeper separators also get encoded (#564: should become a%252Eb.c.d=e)'
|
||||
);
|
||||
st.equal(
|
||||
qs.stringify({ a: { 'b.c': { d: 'e' } } }, { allowDots: true, encodeDotInKeys: true }),
|
||||
'a%252Eb%252Ec.d=e',
|
||||
'nested dotted key + object value: the separator around it is encoded (#564: should become a.b%252Ec.d=e)'
|
||||
);
|
||||
st.equal(
|
||||
qs.stringify({ a: { b: { 'c.d': { e: 'f' } } } }, { allowDots: true, encodeDotInKeys: true }),
|
||||
'a%252Eb%252Ec%252Ed.e=f',
|
||||
'deeply nested dotted key: all separators encoded (#564: should become a.b.c%252Ed.e=f)'
|
||||
);
|
||||
|
||||
st.equal(
|
||||
qs.stringify({ a: { b: [1, 2] } }, { allowDots: true, encodeDotInKeys: true }),
|
||||
'a%252Eb%5B0%5D=1&a%252Eb%5B1%5D=2',
|
||||
'array value under nested keys: separator encoded (#564: should become a.b%5B0%5D=1&a.b%5B1%5D=2)'
|
||||
);
|
||||
st.equal(
|
||||
qs.stringify({ a: { 'b.c': [1, 2] } }, { allowDots: true, encodeDotInKeys: true }),
|
||||
'a%252Eb%252Ec%5B0%5D=1&a%252Eb%252Ec%5B1%5D=2',
|
||||
'array value under nested dotted key (#564: should become a.b%252Ec%5B0%5D=1&a.b%252Ec%5B1%5D=2)'
|
||||
);
|
||||
|
||||
st.equal(
|
||||
qs.stringify({ a: { 'b.c': { d: 'e' } } }, { allowDots: false, encodeDotInKeys: true }),
|
||||
'a%5Bb%252Ec%5D%5Bd%5D=e',
|
||||
'allowDots:false object value encodes the nested-key dot, unlike the primitive-value case (#564: should become a%5Bb.c%5D%5Bd%5D=e)'
|
||||
);
|
||||
|
||||
st.deepEqual(
|
||||
qs.parse(
|
||||
qs.stringify({ a: { b: { c: 'd' } } }, { allowDots: true, encodeDotInKeys: true }),
|
||||
{ allowDots: true, decodeDotInKeys: true }
|
||||
),
|
||||
{ 'a.b': { c: 'd' } },
|
||||
'the over-encoding breaks the round-trip: {a:{b:{c}}} comes back as {"a.b":{c}} (#564 restores it)'
|
||||
);
|
||||
|
||||
st.end();
|
||||
});
|
||||
|
||||
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) {
|
||||
st.equal(
|
||||
qs.stringify(
|
||||
@@ -328,6 +466,39 @@ test('stringify()', function (t) {
|
||||
st.end();
|
||||
});
|
||||
|
||||
t.test('allowEmptyArrays only short-circuits arrays with no own keys', function (st) {
|
||||
st.equal(
|
||||
qs.stringify({ a: [] }, { allowEmptyArrays: true }),
|
||||
'a[]',
|
||||
'a genuinely empty array is still emitted as `[]`'
|
||||
);
|
||||
|
||||
var withKey = [];
|
||||
withKey.extra = 'x';
|
||||
st.equal(
|
||||
qs.stringify({ a: withKey }, { allowEmptyArrays: true }),
|
||||
'a%5Bextra%5D=x',
|
||||
'an own key on an empty array is serialized rather than dropped'
|
||||
);
|
||||
st.equal(
|
||||
qs.stringify({ a: withKey }, { allowEmptyArrays: true }),
|
||||
qs.stringify({ a: withKey }),
|
||||
'the allowEmptyArrays fast path matches the default path when the array has own keys'
|
||||
);
|
||||
|
||||
var container = {};
|
||||
var cyclic = [];
|
||||
cyclic.back = container;
|
||||
container.a = cyclic;
|
||||
st['throws'](
|
||||
function () { qs.stringify(container, { allowEmptyArrays: true }); },
|
||||
RangeError,
|
||||
'a cycle through an empty array with an own key is still detected'
|
||||
);
|
||||
|
||||
st.end();
|
||||
});
|
||||
|
||||
t.test('stringifies an array value with one item vs multiple items', function (st) {
|
||||
st.test('non-array item', function (s2t) {
|
||||
s2t.equal(qs.stringify({ a: 'c' }, { encodeValuesOnly: true, arrayFormat: 'indices' }), 'a=c');
|
||||
@@ -761,6 +932,33 @@ test('stringify()', function (t) {
|
||||
st.end();
|
||||
});
|
||||
|
||||
t.test('does not throw on a non-callable `constructor.isBuffer` key', function (st) {
|
||||
var input = { a: { constructor: { isBuffer: 'x' } } };
|
||||
st.doesNotThrow(function () { qs.stringify(input); });
|
||||
st.equal(qs.stringify(input), 'a%5Bconstructor%5D%5BisBuffer%5D=x');
|
||||
st.equal(qs.stringify({ a: { constructor: { isBuffer: { b: 'c' } } } }), 'a%5Bconstructor%5D%5BisBuffer%5D%5Bb%5D=c');
|
||||
st.equal(qs.stringify({ a: { constructor: { isBuffer: ['b'] } } }), 'a%5Bconstructor%5D%5BisBuffer%5D%5B0%5D=b');
|
||||
st.equal(qs.stringify({ a: { constructor: { isBuffer: true } } }), 'a%5Bconstructor%5D%5BisBuffer%5D=true');
|
||||
st.end();
|
||||
});
|
||||
|
||||
t.test('round-trips a parsed `constructor[isBuffer]` key', function (st) {
|
||||
var str = 'x%5Bconstructor%5D%5BisBuffer%5D=y';
|
||||
|
||||
var plain = qs.parse(str, { plainObjects: true });
|
||||
st.deepEqual(plain, { __proto__: null, x: { __proto__: null, constructor: { __proto__: null, isBuffer: 'y' } } }, 'plainObjects keeps the key');
|
||||
st.doesNotThrow(function () { qs.stringify(plain); });
|
||||
st.equal(qs.stringify(plain), str, 'plainObjects round-trips');
|
||||
|
||||
var proto = qs.parse(str, { allowPrototypes: true });
|
||||
st.deepEqual(proto, { x: { constructor: { isBuffer: 'y' } } }, 'allowPrototypes keeps the key');
|
||||
st.doesNotThrow(function () { qs.stringify(proto); });
|
||||
st.equal(qs.stringify(proto), str, 'allowPrototypes round-trips');
|
||||
|
||||
st.equal(qs.stringify(qs.parse(str)), '', 'default options drop the key');
|
||||
st.end();
|
||||
});
|
||||
|
||||
t.test('stringifies an object using an alternative delimiter', function (st) {
|
||||
st.equal(qs.stringify({ a: 'b', c: 'd' }, { delimiter: ';' }), 'a=b;c=d');
|
||||
st.end();
|
||||
@@ -919,6 +1117,66 @@ test('stringify()', function (t) {
|
||||
st.end();
|
||||
});
|
||||
|
||||
t.test('serializes Date values unless filter transforms them', function (st) {
|
||||
var obj = {
|
||||
a: new Date(Date.UTC(2023, 2, 7, 9, 2, 41, 260)),
|
||||
b: [new Date(Date.UTC(2023, 2, 7, 9, 2, 41, 260))]
|
||||
};
|
||||
var iso = '2023-03-07T09%3A02%3A41.260Z';
|
||||
st.equal(
|
||||
qs.stringify(obj, { filter: function (prefix, value) { return value; } }),
|
||||
'a=' + iso + '&b%5B0%5D=' + iso,
|
||||
'Dates returned unchanged by a filter are serialized'
|
||||
);
|
||||
st.equal(
|
||||
qs.stringify({ a: new Date(0) }, { filter: function (prefix, value) { return value instanceof Date ? 'kept' : value; } }),
|
||||
'a=kept',
|
||||
'a filter that transforms a Date wins over default serialization'
|
||||
);
|
||||
st.end();
|
||||
});
|
||||
|
||||
t.test('applies default Date handling to whatever a filter function returns', function (st) {
|
||||
var date = new Date(Date.UTC(2023, 2, 7, 9, 2, 41, 260));
|
||||
var iso = '2023-03-07T09%3A02%3A41.260Z';
|
||||
var identity = function (prefix, value) { return value; };
|
||||
|
||||
st.equal(
|
||||
qs.stringify({ a: { b: date } }, { filter: identity }),
|
||||
'a%5Bb%5D=' + iso,
|
||||
'a nested Date is serialized through a filter'
|
||||
);
|
||||
st.equal(
|
||||
qs.stringify(
|
||||
{ a: date },
|
||||
{ filter: identity, serializeDate: function (d) { return String(d.getTime()); } }
|
||||
),
|
||||
'a=1678179761260',
|
||||
'`serializeDate` is honored after a filter'
|
||||
);
|
||||
st.equal(
|
||||
qs.stringify({ a: [date, 'x'] }, { filter: identity, arrayFormat: 'comma' }),
|
||||
'a=' + iso + '%2Cx',
|
||||
'Dates inside a comma array are serialized through a filter, not stringified as locale text'
|
||||
);
|
||||
st.equal(
|
||||
qs.stringify({ a: 'x' }, { filter: function (prefix, value) { return prefix === 'a' ? date : value; } }),
|
||||
'a=' + iso,
|
||||
'a Date produced by the filter is serialized'
|
||||
);
|
||||
st.equal(
|
||||
qs.stringify({ a: date, b: 1 }, { filter: ['a'] }),
|
||||
'a=' + iso,
|
||||
'an array filter still serializes Dates'
|
||||
);
|
||||
st['throws'](
|
||||
function () { qs.stringify({ a: new Date(NaN) }, { filter: identity }); },
|
||||
RangeError,
|
||||
'an invalid Date through a filter throws like the no-filter path'
|
||||
);
|
||||
st.end();
|
||||
});
|
||||
|
||||
t.test('can disable uri encoding', function (st) {
|
||||
st.equal(qs.stringify({ a: 'b' }, { encode: false }), 'a=b');
|
||||
st.equal(qs.stringify({ a: { b: 'c' } }, { encode: false }), 'a[b]=c');
|
||||
@@ -1445,4 +1703,63 @@ test('stringifies empty keys', function (t) {
|
||||
|
||||
st.end();
|
||||
});
|
||||
|
||||
t.test('limits nesting depth with the `depth` option', function (st) {
|
||||
st.equal(
|
||||
qs.stringify({ a: { b: { c: 'd' } } }),
|
||||
'a%5Bb%5D%5Bc%5D=d',
|
||||
'the default depth is unlimited and does not alter output'
|
||||
);
|
||||
st.equal(
|
||||
qs.stringify({ a: { b: { c: 'd' } } }, { depth: 2 }),
|
||||
'a%5Bb%5D%5Bc%5D=d',
|
||||
'does not throw when nesting is within the depth limit'
|
||||
);
|
||||
st.equal(
|
||||
qs.stringify({ a: { b: 'c' } }, { depth: Infinity }),
|
||||
'a%5Bb%5D=c',
|
||||
'an explicit Infinity depth is unlimited'
|
||||
);
|
||||
st.equal(
|
||||
qs.stringify({ a: { b: { c: { d: 'e' } } } }, { depth: null }),
|
||||
qs.stringify({ a: { b: { c: { d: 'e' } } } }),
|
||||
'a non-number depth is ignored and treated as unlimited'
|
||||
);
|
||||
st.equal(
|
||||
qs.stringify({ a: 'b' }, { depth: 0 }),
|
||||
'a=b',
|
||||
'a flat object serializes with a depth of 0'
|
||||
);
|
||||
|
||||
var deep = {};
|
||||
var cursor = deep;
|
||||
for (var i = 0; i < 250; i++) {
|
||||
cursor.a = {};
|
||||
cursor = cursor.a;
|
||||
}
|
||||
cursor.b = 'c';
|
||||
st.equal(typeof qs.stringify(deep), 'string', 'the default depth serializes deeply nested objects without throwing');
|
||||
|
||||
st['throws'](
|
||||
function () { qs.stringify({ a: { b: { c: { d: 'e' } } } }, { depth: 2 }); },
|
||||
RangeError,
|
||||
'throws a RangeError when nesting exceeds the depth limit'
|
||||
);
|
||||
st['throws'](
|
||||
function () { qs.stringify({ a: { b: 'c' } }, { depth: 0 }); },
|
||||
RangeError,
|
||||
'a depth of 0 throws for any nested object'
|
||||
);
|
||||
|
||||
var caught;
|
||||
try {
|
||||
qs.stringify({ a: { b: { c: { d: 'e' } } } }, { depth: 2 });
|
||||
} catch (e) {
|
||||
caught = e;
|
||||
}
|
||||
st.ok(caught instanceof RangeError, 'the thrown value is a RangeError');
|
||||
st.equal(caught.message, 'Input depth exceeded depth option of 2', 'the RangeError names the exceeded depth');
|
||||
|
||||
st.end();
|
||||
});
|
||||
});
|
||||
+12
@@ -604,6 +604,18 @@ test('isBuffer()', function (t) {
|
||||
var fakeBuffer = { constructor: Buffer };
|
||||
t.equal(utils.isBuffer(fakeBuffer), false, 'fake buffer is not a buffer');
|
||||
|
||||
forEach(['x', 1, true, {}, [], { isBuffer: true }], function (notCallable) {
|
||||
var obj = { constructor: { isBuffer: notCallable } };
|
||||
t.doesNotThrow(function () { utils.isBuffer(obj); }, 'non-callable `constructor.isBuffer` (' + inspect(notCallable) + ') does not throw');
|
||||
t.equal(utils.isBuffer(obj), false, 'non-callable `constructor.isBuffer` (' + inspect(notCallable) + ') is not a buffer');
|
||||
});
|
||||
|
||||
var nullObject = { __proto__: null, constructor: { __proto__: null, isBuffer: 'y' } };
|
||||
t.equal(utils.isBuffer(nullObject), false, 'null object with non-callable `constructor.isBuffer` is not a buffer');
|
||||
|
||||
var duckBuffer = { constructor: { isBuffer: function () { return true; } } };
|
||||
t.equal(utils.isBuffer(duckBuffer), true, 'callable `constructor.isBuffer` is still honored');
|
||||
|
||||
var saferBuffer = SaferBuffer.from('abc');
|
||||
t.equal(utils.isBuffer(saferBuffer), true, 'SaferBuffer instance is a buffer');
|
||||
|
||||
|
||||
Reference in new issue
Block a user