array.prototype.tospliced
Version:
An ESnext spec-compliant `Array.prototype.toSpliced` shim/polyfill/replacement that works as far down as ES3.
465 lines (387 loc) • 13.7 kB
JavaScript
var hasOwn = require('hasown');
var mockProperty = require('mock-property');
var canDistinguishSparseFromUndefined = 0 in [undefined]; // IE 6 - 8 have a bug where this returns false.
function testSplice(t, toSpliced, input, args, expected, msg) {
var arr = input.slice();
arr.splice.apply(arr, args);
t.deepEqual(arr, expected, 'sanity check: splice ' + msg);
t.deepEqual(toSpliced.apply(null, [input].concat(args)), expected, 'toSpliced ' + msg);
}
function makeArray(l, givenPrefix) {
var prefix = givenPrefix || '';
var length = l;
var arr = [];
while (length--) { // eslint-disable-line no-plusplus
arr.unshift(prefix + Array(length + 1).join(' ') + length);
}
return arr;
}
module.exports = function (toSpliced, t) {
var b = ['b'];
var a = [1, 'a', b];
t.test('defaults deleteCount to length - start if there is only 1 argument', function (st) {
testSplice(st, toSpliced, [0, 1, 2], [-Infinity], [], 'with start -Infinity');
testSplice(st, toSpliced, [0, 1, 2], [-1], [0, 1], 'with start -1');
testSplice(st, toSpliced, [0, 1, 2], [0], [], 'with start 0');
testSplice(st, toSpliced, [0, 1, 2], [1], [0], 'with start 1');
st.end();
});
testSplice(t, toSpliced, a, [], a, 'noop with no arguments');
testSplice(t, toSpliced, a, [0, 0], a, '0 start and 0 deleteCount yields same array contents');
testSplice(t, toSpliced, a, [0, 2], [b], '0 start and 2 deleteCount removes 2 items');
t.test('multiple splices 1', function (st) {
var first = toSpliced([], 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20);
var second = toSpliced(first, 1, 0, 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12', 'F13', 'F14', 'F15', 'F16', 'F17', 'F18', 'F19', 'F20', 'F21', 'F22', 'F23', 'F24', 'F25', 'F26');
var third = toSpliced(second, 5, 0, 'XXX');
st.deepEqual(
third,
[1, 'F1', 'F2', 'F3', 'F4', 'XXX', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12', 'F13', 'F14', 'F15', 'F16', 'F17', 'F18', 'F19', 'F20', 'F21', 'F22', 'F23', 'F24', 'F25', 'F26', 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
);
st.end();
});
t.test('multiple splices 2', function (st) {
var array = makeArray(6);
var first = toSpliced(array, array.length - 1, 1, '');
var second = toSpliced(first, 0, 1, 1, 2, 3, 4);
var third = toSpliced(second, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45);
var fourth = toSpliced(third, 4, 0, '99999999999999');
st.deepEqual(
fourth,
[1, 2, 3, 4, '99999999999999', 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 1, 2, 3, 4, ' 1', ' 2', ' 3', ' 4', '']
);
st.end();
});
t.test('multiple splices 3', function (st) {
var array = [1, 2, 3];
var arrA = toSpliced(array, 0, array.length);
var arrB = toSpliced(arrA, 0, 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
var arrC = toSpliced(arrB, 1, 1, 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12', 'F13', 'F14', 'F15', 'F16', 'F17', 'F18', 'F19', 'F20', 'F21', 'F22', 'F23', 'F24', 'F25', 'F26');
var arrD = toSpliced(arrC, 5, 1, 'YYY', 'XXX');
var arrE = toSpliced(arrD, 0, 1);
var arrF = toSpliced(arrE, 0, 2).slice(0, -1).concat(makeArray(10, '-'));
var arrG = toSpliced(arrF, arrF.length - 2, 10);
var arrH = toSpliced(arrG);
var arrI = toSpliced(arrH, 1, 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9);
var arrJ = toSpliced(arrI, 1, 1, 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12', 'F13', 'F14', 'F15', 'F16', 'F17', 'F18', 'F19', 'F20', 'F21', 'F22', 'F23', 'F24', 'F25', 'F26', 1, 23, 4, 5, 6, 7, 8);
var arrK = toSpliced(arrJ, 30, 10);
var arrL = toSpliced(arrK, 30, 1);
var arrM = toSpliced(arrL, 30, 0);
var arrN = toSpliced(arrM, 2, 5, 1, 2, 3, 'P', 'LLL', 'CCC', 'YYY', 'XXX').concat(1, 2, 3, 4, 5, 6);
var arrO = toSpliced(arrN, 1, 6, 1, 2, 3, 4, 5, 6, 7, 8, 9, 4, 5, 6, 7, 8, 9);
var arrP = [7, 8, 9, 10, 11].concat(toSpliced(arrO, 3, 7)).slice(0, -1);
var arrQ = makeArray(8, '~').concat(toSpliced(arrP, 5, 2).slice(0, -1)).slice(0, -1);
var arrS = toSpliced(arrQ, 3, 1, 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12', 'F13', 'F14', 'F15', 'F16', 'F17', 'F18', 'F19', 'F20', 'F21', 'F22', 'F23', 'F24', 'F25', 'F26', 1, 23, 4, 5, 6, 7, 8);
var arrT = toSpliced(arrS, 4, 5, 'P', 'LLL', 'CCC', 'YYY', 'XXX');
st.deepEqual(
arrT,
['~0', '~ 1', '~ 2', 'F1', 'P', 'LLL', 'CCC', 'YYY', 'XXX', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12', 'F13', 'F14', 'F15', 'F16', 'F17', 'F18', 'F19', 'F20', 'F21', 'F22', 'F23', 'F24', 'F25', 'F26', 1, 23, 4, 5, 6, 7, 8, '~ 4', '~ 5', '~ 6', '~ 7', 7, 8, 9, 10, 11, 2, 4, 5, 6, 7, 8, 9, 'CCC', 'YYY', 'XXX', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12', 'F13', 'F14', 'F15', 'F16', 'F17', 'F18', 'F19', 'F20', 'F21', 'F22', 'F23', 'F24', 'F25', 'F26', 1, 23, 4, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'YYY', 'XXX', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12', 'F13', 'F14', 'F15', 'F16', 'F17', 'F18', 'F19', 'F20', 'F21', 'F22', 'F23', 'F24', 'F25', 'F26', 3, 4, 5, 6, 7, 8, 9, '-0', '- 1', '- 2', '- 3', '- 4', '- 5', '- 6', '- 7', 1, 2, 3]
);
st.end();
});
t.deepEqual(
toSpliced(a, void undefined, 2),
toSpliced(a, 0, 2),
'should set first argument to 0 if first argument is set but undefined'
);
t.test('objects', function (st) {
st.deepEqual(
toSpliced({}, 0, 0, 1, 2, 3),
[1, 2, 3],
'empty object'
);
st.deepEqual(
toSpliced({ 0: 1, length: 1 }, 1, 0, 2, 3),
[1, 2, 3],
'arraylike object of length 1'
);
st.deepEqual(
toSpliced({ 0: 1, 1: 2, 2: 3, length: 3 }, 0, 3),
[],
'arraylike object of length 3, deleteCount 3'
);
st.deepEqual(
toSpliced({ 0: 99, length: 1 }, 0, 1, 1, 2, 3),
[1, 2, 3],
'arraylike object, replacing items'
);
st.end();
});
t.test('can distinguish sparse from undefined', { skip: !canDistinguishSparseFromUndefined }, function (st) {
// test from https://github.com/wikimedia/VisualEditor/blob/d468b00311e69c2095b9da360c5745153342a5c3/src/ve.utils.js#L182-L196
var n = 256;
var arr = [];
arr[n] = 'a';
var result = toSpliced(arr, n + 1, 0, 'b');
st.equal(result[n], 'a');
// test from https://github.com/wikimedia/VisualEditor/blob/d468b00311e69c2095b9da360c5745153342a5c3/src/ve.utils.js#L182-L196
var original = Array(1e5 - 1);
original[10] = 'x';
var tooBig = Array(1e5);
tooBig[8] = 'x';
var copied = toSpliced(original, 1, 1);
original.splice(1, 1);
st.equal(8 in original, false, 'splice leaves holes');
st.equal(8 in copied, true, 'toSpliced does not leave holes');
st.equal(original.indexOf('x'), 9);
st.equal(copied.indexOf('x'), 9);
var tooBigCopy = toSpliced(tooBig, 1, 1);
tooBig.splice(1, 1);
st.equal(6 in tooBig, false, 'splice leaves holes');
st.equal(6 in tooBigCopy, true, 'toSpliced does not leave holes');
st.equal(tooBig.indexOf('x'), 7);
st.equal(tooBigCopy.indexOf('x'), 7);
st.end();
});
t.deepEqual(toSpliced({ length: '2', 0: 0, 1: 1, 2: 2 }, 0, 0), [0, 1]);
var arrayLikeLengthValueOf = {
length: {
valueOf: function () { return 2; }
},
0: 0,
1: 1,
2: 2
};
t.deepEqual(toSpliced(arrayLikeLengthValueOf, 0, 0), [0, 1]);
t.test('not positive integer lengths', function (st) {
st.deepEqual(toSpliced({ length: -2 }), []);
st.deepEqual(toSpliced({ length: 'dog' }), []);
st.deepEqual(toSpliced({ length: NaN }), []);
st.end();
});
t.deepEqual(toSpliced(['first', 'second', 'third'], 1), ['first']);
t.deepEqual(toSpliced(['first', 'second', 'third']), ['first', 'second', 'third']);
t.deepEqual(toSpliced(['first', 'second', 'third'], 1, undefined), ['first', 'second', 'third']);
t.deepEqual(toSpliced(['first', 'second', 'third'], undefined, undefined), ['first', 'second', 'third']);
t.deepEqual(
toSpliced([0, 1, 2, 3, 4], 10, 1, 5, 6),
[0, 1, 2, 3, 4, 5, 6],
'start-bigger-than-length'
);
t.deepEqual(
toSpliced([0, 1, 2, 3, 4], -Infinity, 2),
[2, 3, 4],
'start-neg-infinity-is-zero'
);
t.deepEqual(
toSpliced([0, 1, 2, 3, 4], -20, 2),
[2, 3, 4],
'start-neg-less-than-minus-length-is-zero'
);
t.deepEqual(
toSpliced([0, 1, 2, 3, 4], -3, 2),
[0, 1, 4],
'start-neg-subtracted-from-length'
);
t.test('too-large lengths', function (st) {
var arrayLike = {
9007199254740989: Math.pow(2, 53) - 3,
9007199254740990: Math.pow(2, 53) - 2,
9007199254740991: Math.pow(2, 53) - 1,
9007199254740992: Math.pow(2, 53),
9007199254740994: Math.pow(2, 53) + 2, // NOTE: 2 ** 53 + 1 is 2 ** 53
length: Math.pow(2, 53) + 20
};
var result = toSpliced(arrayLike, 0, Math.pow(2, 53) - 3);
st.equal(result.length, 2);
st.deepEqual(result, [Math.pow(2, 53) - 3, Math.pow(2, 53) - 2]);
var arrayLike2 = {
0: 0,
4294967295: 4294967295,
4294967296: 4294967296,
length: Math.pow(2, 32)
};
st['throws'](
function () { toSpliced(arrayLike2, 0, 0); },
RangeError
);
arrayLike2.length = Math.pow(2, 32) - 1;
st['throws'](
function () { toSpliced(arrayLike2, 0, 0, 1); },
RangeError
);
arrayLike2.length = Math.pow(2, 32);
st['throws'](
function () { toSpliced(arrayLike2, 0, 0, 1); },
RangeError
);
arrayLike2.length = Math.pow(2, 32) + 1;
st['throws'](
function () { toSpliced(arrayLike2, 0, 0, 1); },
RangeError
);
arrayLike2.length = Math.pow(2, 52) - 2;
st['throws'](
function () { toSpliced(arrayLike2, 0, 0, 1); },
RangeError
);
arrayLike2.length = Math.pow(2, 53) - 1;
st['throws'](
function () { toSpliced(arrayLike2, 0, 0, 1); },
TypeError
);
arrayLike2.length = Math.pow(2, 53);
st['throws'](
function () { toSpliced(arrayLike2, 0, 0, 1); },
TypeError
);
arrayLike2.length = Math.pow(2, 53) + 1;
st['throws'](
function () { toSpliced(arrayLike2, 0, 0, 1); },
TypeError
);
st['throws'](
function () { toSpliced({ length: Math.pow(2, 53) - 1 }, 0, 0, 1); },
TypeError,
'throws the proper kind of error for >= 2**53'
);
st['throws'](
function () { toSpliced({ length: Math.pow(2, 32) - 1 }, 0, 0, 1); },
RangeError,
'throws the proper kind of error for [2**32, 2**53]'
);
st.end();
});
t.deepEqual(toSpliced(true), [], 'true yields empty array');
t.deepEqual(toSpliced(false), [], 'false yields empty array');
t.test('deleteCount-clamped-between-zero-and-remaining-count', function (st) {
st.deepEqual(
toSpliced([0, 1, 2, 3, 4, 5], 2, -1),
[0, 1, 2, 3, 4, 5]
);
st.deepEqual(
toSpliced([0, 1, 2, 3, 4, 5], -4, -1),
[0, 1, 2, 3, 4, 5]
);
st.deepEqual(
toSpliced([0, 1, 2, 3, 4, 5], 2, 6),
[0, 1]
);
st.deepEqual(
toSpliced([0, 1, 2, 3, 4, 5], -4, 6),
[0, 1]
);
st.end();
});
t.test('getters', { skip: !Object.defineProperty }, function (st) {
var arrayLike = {
0: 'a',
1: 'b',
2: null,
3: 'c',
length: 4
};
Object.defineProperty(arrayLike, 2, {
get: function () { throw new SyntaxError(); }
});
st.deepEqual(
toSpliced(arrayLike, 2, 1),
['a', 'b', 'c'],
'index 2 is not invoked'
);
var order = [];
var arrayLike2 = {
0: 'a',
1: 'b',
2: 'none',
3: 'c',
length: 4
};
Object.defineProperty(arrayLike2, 0, {
get: function () {
order.push(0);
return 'a';
}
});
Object.defineProperty(arrayLike2, 1, {
get: function () {
order.push(1);
return 'b';
}
});
Object.defineProperty(arrayLike2, 3, {
get: function () {
order.push(3);
return 'c';
}
});
st.deepEqual(
toSpliced(arrayLike2, 2, 1),
['a', 'b', 'c'],
'splicing works as expected'
);
st.deepEqual(
order,
[0, 1, 3],
'indexes are Get-ed in expected order'
);
st.test('length-decreased-while-iterating', function (s2t) {
var arr = [0, 1, 2, 3, 4, 5];
s2t.teardown(mockProperty(Array.prototype, 3, { value: 6 }));
Object.defineProperty(arr, '2', {
get: function () {
arr.length = 1;
return 2;
}
});
s2t.deepEqual(toSpliced(arr, 0, 0), [0, 1, 2, 6, undefined, undefined]);
s2t.end();
});
st.test('length-increased-while-iterating', function (s2t) {
var arr = [0, 1, 2];
Object.defineProperty(arr, '0', {
get: function () {
arr.push(10);
return 0;
}
});
Object.defineProperty(arr, '2', {
get: function () {
arr.push(11);
return 2;
}
});
s2t.deepEqual(toSpliced(arr, 1, 0, 0.5), [0, 0.5, 1, 2]);
s2t.end();
});
st.test('mutate-while-iterating', function (s2t) {
var arr = [0, 1, 2, 3];
var zerothElementStorage = arr[0];
Object.defineProperty(arr, '0', {
get: function () {
arr[1] = 42;
return zerothElementStorage;
},
set: function (v) {
zerothElementStorage = v;
}
});
Object.defineProperty(arr, '2', {
get: function () {
arr[0] = 17;
arr[3] = 37;
return 2;
}
});
s2t.deepEqual(
toSpliced(arr, 1, 0, 0.5),
[0, 0.5, 42, 2, 37]
);
s2t.end();
});
st.end();
});
t.test('holes', function (st) {
var arr = [0, /* hole */, 2, /* hole */, 4]; // eslint-disable-line no-sparse-arrays
st.teardown(mockProperty(Array.prototype, 3, { value: 3 }));
var spliced = toSpliced(arr, 0, 0);
st.deepEqual(spliced, [0, undefined, 2, 3, 4]);
st.ok(hasOwn(spliced, 1));
st.ok(hasOwn(spliced, 3));
st.deepEqual(
toSpliced(arr, 0, 0, -1),
[-1, 0, undefined, 2, 3, 4]
);
st.end();
});
};
;