From 2a9548db997c10344be3cde31376fcc1ac492c0f Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Sun, 4 Nov 2018 18:34:29 -0500 Subject: [PATCH 1/4] rearrange lib array functions and add type-safe concat --- package-lock.json | 28 ++----- src/lib/array.js | 134 +++++++++++++++++++++++++++++++++ src/lib/coerce.js | 2 +- src/lib/ensure_array.js | 27 ------- src/lib/gl_format_color.js | 2 +- src/lib/index.js | 13 ++-- src/lib/is_array.js | 45 ----------- src/lib/nested_property.js | 2 +- src/lib/relink_private.js | 2 +- src/lib/stats.js | 2 +- test/jasmine/tests/lib_test.js | 52 +++++++++++++ 11 files changed, 205 insertions(+), 104 deletions(-) create mode 100644 src/lib/array.js delete mode 100644 src/lib/ensure_array.js delete mode 100644 src/lib/is_array.js diff --git a/package-lock.json b/package-lock.json index 9bc5dd98350..5a6f7964502 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3907,14 +3907,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3929,20 +3927,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -4059,8 +4054,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -4072,7 +4066,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -4087,7 +4080,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -4095,14 +4087,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -4121,7 +4111,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -4202,8 +4191,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -4215,7 +4203,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -4337,7 +4324,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", diff --git a/src/lib/array.js b/src/lib/array.js new file mode 100644 index 00000000000..a80f4b3f8ca --- /dev/null +++ b/src/lib/array.js @@ -0,0 +1,134 @@ +/** +* Copyright 2012-2018, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var isArray = Array.isArray; + +// IE9 fallbacks + +var ab = (typeof ArrayBuffer === 'undefined' || !ArrayBuffer.isView) ? + {isView: function() { return false; }} : + ArrayBuffer; + +var dv = (typeof DataView === 'undefined') ? + function() {} : + DataView; + +function isTypedArray(a) { + return ab.isView(a) && !(a instanceof dv); +} +exports.isTypedArray = isTypedArray; + +function isArrayOrTypedArray(a) { + return isArray(a) || isTypedArray(a); +} +exports.isArrayOrTypedArray = isArrayOrTypedArray; + +/* + * Test whether an input object is 1D. + * + * Assumes we already know the object is an array. + * + * Looks only at the first element, if the dimensionality is + * not consistent we won't figure that out here. + */ +function isArray1D(a) { + return !isArrayOrTypedArray(a[0]); +} +exports.isArray1D = isArray1D; + +/* + * Ensures an array has the right amount of storage space. If it doesn't + * exist, it creates an array. If it does exist, it returns it if too + * short or truncates it in-place. + * + * The goal is to just reuse memory to avoid a bit of excessive garbage + * collection. + */ +exports.ensureArray = function(out, n) { + // TODO: typed array support here? This is only used in + // traces/carpet/compute_control_points + if(!isArray(out)) out = []; + + // If too long, truncate. (If too short, it will grow + // automatically so we don't care about that case) + out.length = n; + + return out; +}; + +/* + * TypedArray-compatible concatenation of n arrays + * if all arrays are the same type it will preserve that type, + * otherwise it falls back on Array. + * Also tries to avoid copying, in case one array has zero length + * But never mutates an existing array + */ +exports.concat = function() { + var args = []; + var allArray = true; + var totalLen = 0; + + var _constructor, arg0, i, argi, posi, leni, out, j; + + for(i = 0; i < arguments.length; i++) { + argi = arguments[i]; + leni = argi.length; + if(leni) { + if(arg0) args.push(argi); + else { + arg0 = argi; + posi = leni; + } + + if(isArray(argi)) { + _constructor = false; + } + else { + allArray = false; + if(!totalLen) { + _constructor = argi.constructor; + } + else if(_constructor !== argi.constructor) { + // TODO: in principle we could upgrade here, + // ie keep typed array but convert all to Float64Array? + _constructor = false; + } + } + + totalLen += leni; + } + } + + if(!totalLen) return []; + if(!args.length) return arg0; + + if(allArray) return arg0.concat.apply(arg0, args); + if(_constructor) { + // matching typed arrays + out = new _constructor(totalLen); + out.set(arg0); + for(i = 0; i < args.length; i++) { + argi = args[i]; + out.set(argi, posi); + posi += argi.length; + } + return out; + } + + // mismatched types or Array + typed + out = new Array(totalLen); + for(j = 0; j < arg0.length; j++) out[j] = arg0[j]; + for(i = 0; i < args.length; i++) { + argi = args[i]; + for(j = 0; j < argi.length; j++) out[posi + j] = argi[j]; + posi += j; + } + return out; +}; diff --git a/src/lib/coerce.js b/src/lib/coerce.js index 421c9b4bd1c..87cc1473446 100644 --- a/src/lib/coerce.js +++ b/src/lib/coerce.js @@ -19,7 +19,7 @@ var nestedProperty = require('./nested_property'); var counterRegex = require('./regex').counter; var DESELECTDIM = require('../constants/interactions').DESELECTDIM; var modHalf = require('./mod').modHalf; -var isArrayOrTypedArray = require('./is_array').isArrayOrTypedArray; +var isArrayOrTypedArray = require('./array').isArrayOrTypedArray; exports.valObjectMeta = { data_array: { diff --git a/src/lib/ensure_array.js b/src/lib/ensure_array.js deleted file mode 100644 index 60c328da7be..00000000000 --- a/src/lib/ensure_array.js +++ /dev/null @@ -1,27 +0,0 @@ -/** -* Copyright 2012-2018, Plotly, Inc. -* All rights reserved. -* -* This source code is licensed under the MIT license found in the -* LICENSE file in the root directory of this source tree. -*/ - -'use strict'; - -/* - * Ensures an array has the right amount of storage space. If it doesn't - * exist, it creates an array. If it does exist, it returns it if too - * short or truncates it in-place. - * - * The goal is to just reuse memory to avoid a bit of excessive garbage - * collection. - */ -module.exports = function ensureArray(out, n) { - if(!Array.isArray(out)) out = []; - - // If too long, truncate. (If too short, it will grow - // automatically so we don't care about that case) - out.length = n; - - return out; -}; diff --git a/src/lib/gl_format_color.js b/src/lib/gl_format_color.js index 5f2e4b0348e..1fe622c8443 100644 --- a/src/lib/gl_format_color.js +++ b/src/lib/gl_format_color.js @@ -15,7 +15,7 @@ var rgba = require('color-normalize'); var Colorscale = require('../components/colorscale'); var colorDflt = require('../components/color/attributes').defaultLine; -var isArrayOrTypedArray = require('./is_array').isArrayOrTypedArray; +var isArrayOrTypedArray = require('./array').isArrayOrTypedArray; var colorDfltRgba = rgba(colorDflt); var opacityDflt = 1; diff --git a/src/lib/index.js b/src/lib/index.js index 0778748e09b..d0d2e34f802 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -24,17 +24,18 @@ lib.relativeAttr = require('./relative_attr'); lib.isPlainObject = require('./is_plain_object'); lib.toLogRange = require('./to_log_range'); lib.relinkPrivateKeys = require('./relink_private'); -lib.ensureArray = require('./ensure_array'); + +var arrayModule = require('./array'); +lib.isTypedArray = arrayModule.isTypedArray; +lib.isArrayOrTypedArray = arrayModule.isArrayOrTypedArray; +lib.isArray1D = arrayModule.isArray1D; +lib.ensureArray = arrayModule.ensureArray; +lib.concat = arrayModule.concat; var modModule = require('./mod'); lib.mod = modModule.mod; lib.modHalf = modModule.modHalf; -var isArrayModule = require('./is_array'); -lib.isTypedArray = isArrayModule.isTypedArray; -lib.isArrayOrTypedArray = isArrayModule.isArrayOrTypedArray; -lib.isArray1D = isArrayModule.isArray1D; - var coerceModule = require('./coerce'); lib.valObjectMeta = coerceModule.valObjectMeta; lib.coerce = coerceModule.coerce; diff --git a/src/lib/is_array.js b/src/lib/is_array.js deleted file mode 100644 index b8c5e1ae47c..00000000000 --- a/src/lib/is_array.js +++ /dev/null @@ -1,45 +0,0 @@ -/** -* Copyright 2012-2018, Plotly, Inc. -* All rights reserved. -* -* This source code is licensed under the MIT license found in the -* LICENSE file in the root directory of this source tree. -*/ - -'use strict'; - -// IE9 fallbacks - -var ab = (typeof ArrayBuffer === 'undefined' || !ArrayBuffer.isView) ? - {isView: function() { return false; }} : - ArrayBuffer; - -var dv = (typeof DataView === 'undefined') ? - function() {} : - DataView; - -function isTypedArray(a) { - return ab.isView(a) && !(a instanceof dv); -} - -function isArrayOrTypedArray(a) { - return Array.isArray(a) || isTypedArray(a); -} - -/* - * Test whether an input object is 1D. - * - * Assumes we already know the object is an array. - * - * Looks only at the first element, if the dimensionality is - * not consistent we won't figure that out here. - */ -function isArray1D(a) { - return !isArrayOrTypedArray(a[0]); -} - -module.exports = { - isTypedArray: isTypedArray, - isArrayOrTypedArray: isArrayOrTypedArray, - isArray1D: isArray1D -}; diff --git a/src/lib/nested_property.js b/src/lib/nested_property.js index a37bf5ca4ad..b88017445a0 100644 --- a/src/lib/nested_property.js +++ b/src/lib/nested_property.js @@ -10,7 +10,7 @@ 'use strict'; var isNumeric = require('fast-isnumeric'); -var isArrayOrTypedArray = require('./is_array').isArrayOrTypedArray; +var isArrayOrTypedArray = require('./array').isArrayOrTypedArray; /** * convert a string s (such as 'xaxis.range[0]') diff --git a/src/lib/relink_private.js b/src/lib/relink_private.js index e0c63b8fef4..19ef12a2f01 100644 --- a/src/lib/relink_private.js +++ b/src/lib/relink_private.js @@ -9,7 +9,7 @@ 'use strict'; -var isArrayOrTypedArray = require('./is_array').isArrayOrTypedArray; +var isArrayOrTypedArray = require('./array').isArrayOrTypedArray; var isPlainObject = require('./is_plain_object'); /** diff --git a/src/lib/stats.js b/src/lib/stats.js index 71c23d70375..1cfca38efc6 100644 --- a/src/lib/stats.js +++ b/src/lib/stats.js @@ -10,7 +10,7 @@ 'use strict'; var isNumeric = require('fast-isnumeric'); -var isArrayOrTypedArray = require('./is_array').isArrayOrTypedArray; +var isArrayOrTypedArray = require('./array').isArrayOrTypedArray; /** * aggNums() returns the result of an aggregate function applied to an array of diff --git a/test/jasmine/tests/lib_test.js b/test/jasmine/tests/lib_test.js index 6653cc9d46f..e7b6d9fa6e1 100644 --- a/test/jasmine/tests/lib_test.js +++ b/test/jasmine/tests/lib_test.js @@ -2459,6 +2459,58 @@ describe('Test lib.js:', function() { expect(toContainer).toEqual(expected); }); }); + + describe('concat', function() { + var concat = Lib.concat; + it('works with multiple Arrays', function() { + expect(concat([1], [[2], 3], [{a: 4}, 5, 6])) + .toEqual([1, [2], 3, {a: 4}, 5, 6]); + }); + + it('works with some empty arrays', function() { + var a1 = [1]; + expect(concat(a1, [], [2, 3])).toEqual([1, 2, 3]); + expect(a1).toEqual([1]); // did not mutate a1 + + var a1b = concat(a1, []); + var a1c = concat([], a1b); + var a1d = concat([], a1c, []); + + expect(a1d).toEqual([1]); + // does not mutate a1, but *will* return it unchanged if it's the + // only one with data + expect(a1d).toBe(a1); + + expect(concat([], [0], [1, 0], [2, 0, 0])).toEqual([0, 1, 0, 2, 0, 0]); + + // a single typedArray will keep its identity (and type) + // even if other empty arrays don't match type. + var f1 = new Float32Array([1, 2]); + expect(concat([], f1, new Float64Array([]))).toBe(f1); + expect(f1).toEqual(new Float32Array([1, 2])); + }); + + it('works with all empty arrays', function() { + expect(concat()).toEqual([]); + expect(concat([])).toEqual([]); + expect(concat([], [])).toEqual([]); + expect(concat([], [], [], [])).toEqual([]); + }); + + it('converts mismatched types to Array', function() { + expect(concat([1, 2], new Float64Array([3, 4]))).toEqual([1, 2, 3, 4]); + expect(concat(new Float64Array([1, 2]), [3, 4])).toEqual([1, 2, 3, 4]); + expect(concat(new Float64Array([1, 2]), new Float32Array([3, 4]))).toEqual([1, 2, 3, 4]); + }); + + it('concatenates matching TypedArrays preserving type', function() { + [Float32Array, Float64Array, Int16Array, Int32Array].forEach(function(Type, i) { + var v = i * 10; + expect(concat([], new Type([v]), new Type([v + 1, v]), new Type([v + 2, v, v]))) + .toEqual(new Type([v, v + 1, v, v + 2, v, v])); + }); + }); + }); }); describe('Queue', function() { From 47068cf3c885c63a0fa77f285abf7eb461a6a250 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Sun, 4 Nov 2018 18:51:43 -0500 Subject: [PATCH 2/4] fix and :lock: histograms with TypedArrays --- src/traces/histogram/calc.js | 2 +- test/jasmine/tests/histogram_test.js | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/traces/histogram/calc.js b/src/traces/histogram/calc.js index 1a60c9206e9..a03f3cec3ae 100644 --- a/src/traces/histogram/calc.js +++ b/src/traces/histogram/calc.js @@ -252,7 +252,7 @@ function calcAllAutoBins(gd, trace, pa, mainData, _overlayEdgeCase) { for(i = 0; i < traces.length; i++) { tracei = traces[i]; pos0 = tracei._pos0 = pa.makeCalcdata(tracei, mainData); - allPos = allPos.concat(pos0); + allPos = Lib.concat(allPos, pos0); delete tracei._autoBinFinished; if(trace.visible === true) { if(isFirstVisible) { diff --git a/test/jasmine/tests/histogram_test.js b/test/jasmine/tests/histogram_test.js index 1d86448b260..2cb91b71f26 100644 --- a/test/jasmine/tests/histogram_test.js +++ b/test/jasmine/tests/histogram_test.js @@ -536,6 +536,16 @@ describe('Test histogram', function() { expect(calcPositions(trace3)).toBeCloseToArray([1.1, 1.3], 5); }); + it('can handle TypedArrays', function() { + var trace1 = {x: new Float32Array([1, 2, 3, 4])}; + var trace2 = {x: new Float32Array([5, 5.5, 6, 6.5])}; + var trace3 = {x: new Float64Array([1, 1.1, 1.2, 1.3]), xaxis: 'x2'}; + var trace4 = {x: new Float64Array([1, 1.2, 1.4, 1.6]), yaxis: 'y2'}; + + expect(calcPositions(trace1, [trace2, trace3, trace4])).toEqual([1, 3, 5]); + expect(calcPositions(trace3)).toBeCloseToArray([1.1, 1.3], 5); + }); + describe('cumulative distribution functions', function() { var base = { x: [0, 5, 10, 15, 5, 10, 15, 10, 15, 15], From a735fbf141c7cdd919ae04da7a8c36c2680f035d Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Sun, 4 Nov 2018 18:56:27 -0500 Subject: [PATCH 3/4] revert accidental package-lock changes --- package-lock.json | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5a6f7964502..9bc5dd98350 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3907,12 +3907,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3927,17 +3929,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -4054,7 +4059,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -4066,6 +4072,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -4080,6 +4087,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -4087,12 +4095,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -4111,6 +4121,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -4191,7 +4202,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -4203,6 +4215,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -4324,6 +4337,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", From 1ca72c39344c54f086bf7d5f728337322c6d68fe Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Mon, 5 Nov 2018 14:07:49 -0500 Subject: [PATCH 4/4] check that Lib.concat calls Array.concat when it should --- test/jasmine/tests/lib_test.js | 56 ++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/test/jasmine/tests/lib_test.js b/test/jasmine/tests/lib_test.js index e7b6d9fa6e1..2ffe0710c55 100644 --- a/test/jasmine/tests/lib_test.js +++ b/test/jasmine/tests/lib_test.js @@ -2462,19 +2462,33 @@ describe('Test lib.js:', function() { describe('concat', function() { var concat = Lib.concat; + + beforeEach(function() { + spyOn(Array.prototype, 'concat').and.callThrough(); + }); + it('works with multiple Arrays', function() { - expect(concat([1], [[2], 3], [{a: 4}, 5, 6])) - .toEqual([1, [2], 3, {a: 4}, 5, 6]); + var res = concat([1], [[2], 3], [{a: 4}, 5, 6]); + expect(Array.prototype.concat.calls.count()).toBe(1); + + // note: can't `concat` in the `expect` if we want to count native + // `Array.concat calls`, because `toEqual` calls `Array.concat` + // profusely itself. + expect(res).toEqual([1, [2], 3, {a: 4}, 5, 6]); }); it('works with some empty arrays', function() { var a1 = [1]; - expect(concat(a1, [], [2, 3])).toEqual([1, 2, 3]); + var res = concat(a1, [], [2, 3]); + expect(Array.prototype.concat.calls.count()).toBe(1); + expect(res).toEqual([1, 2, 3]); expect(a1).toEqual([1]); // did not mutate a1 + Array.prototype.concat.calls.reset(); var a1b = concat(a1, []); var a1c = concat([], a1b); var a1d = concat([], a1c, []); + expect(Array.prototype.concat.calls.count()).toBe(0); expect(a1d).toEqual([1]); // does not mutate a1, but *will* return it unchanged if it's the @@ -2486,28 +2500,44 @@ describe('Test lib.js:', function() { // a single typedArray will keep its identity (and type) // even if other empty arrays don't match type. var f1 = new Float32Array([1, 2]); - expect(concat([], f1, new Float64Array([]))).toBe(f1); + Array.prototype.concat.calls.reset(); + res = concat([], f1, new Float64Array([])); + expect(Array.prototype.concat.calls.count()).toBe(0); + expect(res).toBe(f1); expect(f1).toEqual(new Float32Array([1, 2])); }); it('works with all empty arrays', function() { - expect(concat()).toEqual([]); - expect(concat([])).toEqual([]); - expect(concat([], [])).toEqual([]); - expect(concat([], [], [], [])).toEqual([]); + [[], [[]], [[], []], [[], [], [], []]].forEach(function(empties) { + Array.prototype.concat.calls.reset(); + var res = concat.apply(null, empties); + expect(Array.prototype.concat.calls.count()).toBe(0); + expect(res).toEqual([]); + }); }); it('converts mismatched types to Array', function() { - expect(concat([1, 2], new Float64Array([3, 4]))).toEqual([1, 2, 3, 4]); - expect(concat(new Float64Array([1, 2]), [3, 4])).toEqual([1, 2, 3, 4]); - expect(concat(new Float64Array([1, 2]), new Float32Array([3, 4]))).toEqual([1, 2, 3, 4]); + [ + [[1, 2], new Float64Array([3, 4])], + [new Float64Array([1, 2]), [3, 4]], + [new Float64Array([1, 2]), new Float32Array([3, 4])] + ].forEach(function(mismatch) { + Array.prototype.concat.calls.reset(); + var res = concat.apply(null, mismatch); + // no concat - all entries moved over individually + expect(Array.prototype.concat.calls.count()).toBe(0); + expect(res).toEqual([1, 2, 3, 4]); + }); }); it('concatenates matching TypedArrays preserving type', function() { [Float32Array, Float64Array, Int16Array, Int32Array].forEach(function(Type, i) { var v = i * 10; - expect(concat([], new Type([v]), new Type([v + 1, v]), new Type([v + 2, v, v]))) - .toEqual(new Type([v, v + 1, v, v + 2, v, v])); + Array.prototype.concat.calls.reset(); + var res = concat([], new Type([v]), new Type([v + 1, v]), new Type([v + 2, v, v])); + // no concat - uses `TypedArray.set` + expect(Array.prototype.concat.calls.count()).toBe(0); + expect(res).toEqual(new Type([v, v + 1, v, v + 2, v, v])); }); }); });