es6IsConcatSpreadable.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741
  1. //-------------------------------------------------------------------------------------------------------
  2. // Copyright (C) Microsoft. All rights reserved.
  3. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
  4. //-------------------------------------------------------------------------------------------------------
  5. WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");
  6. function areEqual(a, b, msg)
  7. {
  8. assert.areEqual(a.length, b.length, msg);
  9. for(var i = 0;i < a.length; i++)
  10. {
  11. assert.areEqual(a[i], b[i], msg);
  12. }
  13. }
  14. function compareConcats(a, b)
  15. {
  16. var c = a.concat(b);
  17. b[Symbol.isConcatSpreadable] = false;
  18. var d = a.concat(b);
  19. assert.areEqual(b, d[a.length], "Indexing d at a.length should return b");
  20. for(var i = 0;i < a.length; i++)
  21. {
  22. assert.areEqual(a[i], c[i], "confirm array a makes up the first half of array c");
  23. assert.areEqual(a[i], d[i], "confirm array a makes up the first half of array d");
  24. }
  25. for(var i = 0;i < b.length; i++)
  26. {
  27. assert.areEqual(b[i], c[a.length+i], "confirm array b makes up the second half of array c");
  28. assert.areEqual(b[i], d[a.length][i], "confirm array b makes up a sub array at d[a.length]");
  29. }
  30. assert.areEqual(a.length+1, d.length, "since we are not flattening the top level array grows only by 1");
  31. excludeLengthCheck = false;
  32. delete b[Symbol.isConcatSpreadable];
  33. }
  34. var tests = [
  35. {
  36. name: "nativeInt Arrays",
  37. body: function ()
  38. {
  39. var a = [1, 2, 3];
  40. var b = [4, 5, 6];
  41. compareConcats(a, b);
  42. }
  43. },
  44. {
  45. name: "nativefloat Arrays",
  46. body: function ()
  47. {
  48. var a = [1.1, 2.2, 3.3];
  49. var b = [4.4, 5.5, 6.6];
  50. compareConcats(a, b);
  51. }
  52. },
  53. {
  54. name: "Var Arrays",
  55. body: function ()
  56. {
  57. var a = ["a", "b", "c"];
  58. var b = ["d", "e", "f"];
  59. compareConcats(a, b);
  60. }
  61. },
  62. {
  63. name: "intermixed Arrays",
  64. body: function ()
  65. {
  66. var a = [1.1, "b", 3];
  67. var b = [4, 5.5, "f"];
  68. compareConcats(a, b);
  69. }
  70. },
  71. {
  72. name: "concating spreadable objects",
  73. body: function ()
  74. {
  75. var a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
  76. var b = [1, 2, 3].concat(4, 5, { [Symbol.isConcatSpreadable]: true, 0: 6, 1: 7, 2: 8, "length" : 3 }, 9, 10);
  77. areEqual(a, b);
  78. // Test to confirm we Spread to nothing when length is not set
  79. var a = [1, 2, 3, 4, 5, 9, 10];
  80. var b = [1, 2, 3].concat(4, 5, { [Symbol.isConcatSpreadable]: true, 0: 6, 1: 7, 2: 8}, 9, 10);
  81. areEqual(a, b);
  82. }
  83. },
  84. {
  85. name: " concat with non-arrays",
  86. body: function ()
  87. {
  88. var a = [1.1, 2.1, 3.1];
  89. var b = 4.1;
  90. compareConcats(a, b);
  91. var a = [1, 2, 3];
  92. var b = 4;
  93. compareConcats(a, b);
  94. var a = [1, 2, 3];
  95. var b = "b";
  96. compareConcats(a, b);
  97. var a = [1, 2, 3];
  98. var b = true;
  99. compareConcats(a, b);
  100. }
  101. },
  102. {
  103. name: "override with constructors",
  104. body: function ()
  105. {
  106. var a = [1, 2, 3];
  107. var b = [4.4, 5.5, 6.6];
  108. var c = [Object, {}, undefined, Math.sin];
  109. for(var i = 0; i < c.length;i++)
  110. {
  111. a['constructor'] = c[i];
  112. compareConcats(a, b);
  113. }
  114. var o = [];
  115. o.constructor = function()
  116. {
  117. var a = new Array(100);
  118. a[0] = 10;
  119. return a;
  120. }
  121. areEqual([1, 2, 3], o.concat([1, 2, 3]));
  122. }
  123. },
  124. {
  125. name: "test ToBoolean on array[@@isConcatSpreadable]",
  126. body: function ()
  127. {
  128. function test(x, y, z) {
  129. var a = [x], b = [y, z];
  130. areEqual([x, y, z], a.concat(b), '[@@isConcatSpreadable] undefined');
  131. b[Symbol.isConcatSpreadable] = null;
  132. areEqual([x, [y, z]], a.concat(b), '[@@isConcatSpreadable]==null');
  133. b[Symbol.isConcatSpreadable] = false;
  134. areEqual([x, [y, z]], a.concat(b), '[@@isConcatSpreadable]==false');
  135. b[Symbol.isConcatSpreadable] = '';
  136. areEqual([x, [y, z]], a.concat(b), '[@@isConcatSpreadable]==\'\'');
  137. b[Symbol.isConcatSpreadable] = 0;
  138. areEqual([x, [y, z]], a.concat(b), '[@@isConcatSpreadable]==0');
  139. b[Symbol.isConcatSpreadable] = +0.0;
  140. areEqual([x, [y, z]], a.concat(b), '[@@isConcatSpreadable]==+0.0');
  141. b[Symbol.isConcatSpreadable] = -0.0;
  142. areEqual([x, [y, z]], a.concat(b), '[@@isConcatSpreadable]==-0.0');
  143. b[Symbol.isConcatSpreadable] = NaN;
  144. areEqual([x, [y, z]], a.concat(b), '[@@isConcatSpreadable]==NaN');
  145. b[Symbol.isConcatSpreadable] = undefined;
  146. areEqual([x, y, z], a.concat(b), '[@@isConcatSpreadable]==undefined');
  147. b[Symbol.isConcatSpreadable] = true;
  148. areEqual([x, y, z], a.concat(b), '[@@isConcatSpreadable]==true');
  149. b[Symbol.isConcatSpreadable] = 'abc';
  150. areEqual([x, y, z], a.concat(b), '[@@isConcatSpreadable]=\'abc\'');
  151. b[Symbol.isConcatSpreadable] = 0.1;
  152. areEqual([x, y, z], a.concat(b), '[@@isConcatSpreadable]==0.1');
  153. b[Symbol.isConcatSpreadable] = -0.1;
  154. areEqual([x, y, z], a.concat(b), '[@@isConcatSpreadable]==-0.1');
  155. b[Symbol.isConcatSpreadable] = Symbol();
  156. areEqual([x, y, z], a.concat(b), '[@@isConcatSpreadable]==Symbol()');
  157. b[Symbol.isConcatSpreadable] = {};
  158. areEqual([x, y, z], a.concat(b), '[@@isConcatSpreadable]=={}');
  159. delete b[Symbol.isConcatSpreadable];
  160. areEqual([x, y, z], a.concat(b), '[@@isConcatSpreadable] deleted');
  161. }
  162. test(1, 2, 3);
  163. test(1.1, 2.2, 3.3);
  164. test("a", "b", "c");
  165. test(1.1, "b", 3);
  166. test(4, 5.5, "f");
  167. test(undefined, NaN, function(){});
  168. }
  169. },
  170. {
  171. name: "test ToBoolean on object[@@isConcatSpreadable]",
  172. body: function ()
  173. {
  174. function test(x, y, z) {
  175. var a = [x], b = {'0':y, '1':z, 'length':2};
  176. areEqual([x, b], a.concat(b), '[@@isConcatSpreadable] undefined');
  177. b[Symbol.isConcatSpreadable] = null;
  178. areEqual([x, b], a.concat(b), '[@@isConcatSpreadable]==null');
  179. b[Symbol.isConcatSpreadable] = false;
  180. areEqual([x, b], a.concat(b), '[@@isConcatSpreadable]==false');
  181. b[Symbol.isConcatSpreadable] = '';
  182. areEqual([x, b], a.concat(b), '[@@isConcatSpreadable]==\'\'');
  183. b[Symbol.isConcatSpreadable] = 0;
  184. areEqual([x, b], a.concat(b), '[@@isConcatSpreadable]==0');
  185. b[Symbol.isConcatSpreadable] = +0.0;
  186. areEqual([x, b], a.concat(b), '[@@isConcatSpreadable]==+0.0');
  187. b[Symbol.isConcatSpreadable] = -0.0;
  188. areEqual([x, b], a.concat(b), '[@@isConcatSpreadable]==-0.0');
  189. b[Symbol.isConcatSpreadable] = NaN;
  190. areEqual([x, b], a.concat(b), '[@@isConcatSpreadable]==NaN');
  191. b[Symbol.isConcatSpreadable] = undefined;
  192. areEqual([x, b], a.concat(b), '[@@isConcatSpreadable]==undefined');
  193. b[Symbol.isConcatSpreadable] = true;
  194. areEqual([x, y, z], a.concat(b), '[@@isConcatSpreadable]==true');
  195. b[Symbol.isConcatSpreadable] = 'abc';
  196. areEqual([x, y, z], a.concat(b), '[@@isConcatSpreadable]==\'abc\'');
  197. b[Symbol.isConcatSpreadable] = 0.1;
  198. areEqual([x, y, z], a.concat(b), '[@@isConcatSpreadable]==0.1');
  199. b[Symbol.isConcatSpreadable] = -0.1;
  200. areEqual([x, y, z], a.concat(b), '[@@isConcatSpreadable]==-0.1');
  201. b[Symbol.isConcatSpreadable] = Symbol();
  202. areEqual([x, y, z], a.concat(b), '[@@isConcatSpreadable]==Symbol()');
  203. b[Symbol.isConcatSpreadable] = {};
  204. areEqual([x, y, z], a.concat(b), '[@@isConcatSpreadable]=={}');
  205. delete b[Symbol.isConcatSpreadable];
  206. areEqual([x, b], a.concat(b), '[@@isConcatSpreadable] deleted');
  207. }
  208. test(1, 2, 3);
  209. test(1.1, 2.2, 3.3);
  210. test("a", "b", "c");
  211. test(1.1, "b", 3);
  212. test(4, 5.5, "f");
  213. test(undefined, NaN, function(){});
  214. }
  215. },
  216. {
  217. name: "two arrays that may share the same type",
  218. body: function ()
  219. {
  220. function test(x, y, z) {
  221. var a = [x], b = [y, z], c = [y, z];
  222. areEqual([x, y, z], a.concat(b), 'b[@@isConcatSpreadable] undefined');
  223. areEqual([x, y, z], a.concat(c), 'c[@@isConcatSpreadable] undefined');
  224. b[Symbol.isConcatSpreadable] = false;
  225. areEqual([x, [y, z]], a.concat(b), 'b[@@isConcatSpreadable]==false');
  226. areEqual([x, y, z], a.concat(c), 'c[@@isConcatSpreadable] undefined');
  227. c[Symbol.isConcatSpreadable] = false;
  228. areEqual([x, [y, z]], a.concat(b), 'b[@@isConcatSpreadable]==false');
  229. areEqual([x, [y, z]], a.concat(c), 'c[@@isConcatSpreadable]==false');
  230. b[Symbol.isConcatSpreadable] = true;
  231. areEqual([x, y, z], a.concat(b), 'b[@@isConcatSpreadable]==true');
  232. areEqual([x, [y, z]], a.concat(c), 'c[@@isConcatSpreadable]==false');
  233. c[Symbol.isConcatSpreadable] = true;
  234. areEqual([x, y, z], a.concat(b), 'b[@@isConcatSpreadable]==true');
  235. areEqual([x, y, z], a.concat(c), 'c[@@isConcatSpreadable]==true');
  236. c[Symbol.isConcatSpreadable] = false;
  237. areEqual([x, y, z], a.concat(b), 'b[@@isConcatSpreadable]==true');
  238. areEqual([x, [y, z]], a.concat(c), 'c[@@isConcatSpreadable]==false');
  239. b[Symbol.isConcatSpreadable] = false;
  240. areEqual([x, [y, z]], a.concat(b), 'b[@@isConcatSpreadable]==false');
  241. areEqual([x, [y, z]], a.concat(c), 'c[@@isConcatSpreadable]==false');
  242. b[Symbol.isConcatSpreadable] = undefined;
  243. areEqual([x, y, z], a.concat(b), 'b[@@isConcatSpreadable]==undefined');
  244. areEqual([x, [y, z]], a.concat(c), 'c[@@isConcatSpreadable]==false');
  245. delete b[Symbol.isConcatSpreadable];
  246. areEqual([x, y, z], a.concat(b), 'b[@@isConcatSpreadable] deleted');
  247. areEqual([x, [y, z]], a.concat(c), 'c[@@isConcatSpreadable]==false');
  248. delete c[Symbol.isConcatSpreadable];
  249. areEqual([x, y, z], a.concat(b), 'b[@@isConcatSpreadable] deleted');
  250. areEqual([x, y, z], a.concat(c), 'c[@@isConcatSpreadable] deleted');
  251. }
  252. test(1, 2, 3);
  253. test(1.1, 2.2, 3.3);
  254. test("a", "b", "c");
  255. test(1.1, "b", 3);
  256. test(4, 5.5, "f");
  257. test(undefined, NaN, function(){});
  258. }
  259. },
  260. {
  261. name: "user-defined length",
  262. body: function ()
  263. {
  264. function test(a, b, c) {
  265. var args = (function() { return arguments; })(a, b, c);
  266. args[Symbol.isConcatSpreadable] = true;
  267. areEqual([a, b, c, a, b, c], [].concat(args, args), '['+a+', '+b+', '+c+', '+a+', '+b+', '+c+']');
  268. Object.defineProperty(args, "length", { value: 6 });
  269. areEqual([a, b, c, undefined, undefined, undefined], [].concat(args), '['+a+', '+b+', '+c+', undefined, undefined, undefined]');
  270. }
  271. test(1, 2, 3);
  272. test(1.1, 2.2, 3.3);
  273. test("a", "b", "c");
  274. test(1.1, "b", 3);
  275. test(4, 5.5, "f");
  276. test(undefined, NaN, function(){});
  277. }
  278. },
  279. {
  280. name: "concat a mix of user-constructed objects and arrays",
  281. body: function ()
  282. {
  283. class MyObj extends Object {}
  284. var a = new MyObj;
  285. a.length = 5;
  286. a[0] = 'a';
  287. a[2] = 'b';
  288. a[4] = 'c';
  289. var b = { length: 3, "0": "a", "1": "b", "2": "c" };
  290. class MyArray extends Array {}
  291. var c = new MyArray("a", "b", "c");
  292. var d = ['e', 'f', 'g'];
  293. a[Symbol.isConcatSpreadable] = true;
  294. d[Symbol.isConcatSpreadable] = false;
  295. areEqual(['a', undefined, 'b', undefined, 'c', b, 'a', 'b', 'c', ['e', 'f', 'g']], Array.prototype.concat.call(a, b, c, d));
  296. }
  297. },
  298. {
  299. name: "verify ToLength operation",
  300. body: function ()
  301. {
  302. var obj = {"length": {valueOf: null, toString: null}};
  303. obj[Symbol.isConcatSpreadable] = true;
  304. assert.throws(()=>Array.prototype.concat.call(obj), TypeError, '{valueOf: null, toString: null}', "Number expected");
  305. obj = {"length": {toString: function() { throw new Error('User-defined error in toString'); }, valueOf: null}};
  306. obj[Symbol.isConcatSpreadable] = true;
  307. assert.throws(()=>Array.prototype.concat.call(obj), Error, 'toString() throws', "User-defined error in toString");
  308. obj = {"length": {toString: function() { return 'string'; }, valueOf: null}};
  309. obj[Symbol.isConcatSpreadable] = true;
  310. areEqual([], [].concat(obj), ' toString() returns string');
  311. obj = {"length": {valueOf: function() { throw new Error('User-defined error in valueOf'); }, toString: null}};
  312. obj[Symbol.isConcatSpreadable] = true;
  313. assert.throws(()=>Array.prototype.concat.call(obj), Error, 'valueOf() throws', "User-defined error in valueOf");
  314. obj = {"length": {valueOf: function() { return 'string'; }, toString: null}};
  315. obj[Symbol.isConcatSpreadable] = true;
  316. areEqual([], [].concat(obj), 'toString() returns string');
  317. obj = { "length": -4294967294, "0": "a", "2": "b", "4": "c" };
  318. obj[Symbol.isConcatSpreadable] = true;
  319. areEqual([], [].concat(obj), 'ToLength clamps negative to zero');
  320. obj.length = -0.0;
  321. areEqual([], [].concat(obj), 'ToLength clamps negative to zero');
  322. obj.length = "-4294967294";
  323. areEqual([], [].concat(obj), 'ToLength clamps negative to zero');
  324. obj.length = "-0.0";
  325. areEqual([], [].concat(obj), 'ToLength clamps negative to zero');
  326. }
  327. },
  328. {
  329. name: "Getter of [@@isConcatSpreadable] throws",
  330. body: function ()
  331. {
  332. var obj = {};
  333. Object.defineProperty(obj, Symbol.isConcatSpreadable, {
  334. get: function() { throw Error('User-defined error in @@isConcatSpreadable getter'); }
  335. });
  336. assert.throws(()=>[].concat(obj), Error, '[].concat(obj)', "User-defined error in @@isConcatSpreadable getter");
  337. assert.throws(()=>Array.prototype.concat.call(obj), Error, 'Array.prototype.concat.call(obj)', "User-defined error in @@isConcatSpreadable getter");
  338. }
  339. },
  340. {
  341. name: "spread Function object",
  342. body: function ()
  343. {
  344. function test(arr) {
  345. var func = function(x, y, z){};
  346. areEqual([func], [].concat(func), 'Function object');
  347. func[Symbol.isConcatSpreadable] = true;
  348. areEqual([undefined, undefined, undefined], [].concat(func), 'func[Symbol.isConcatSpreadable] == true');
  349. func[Symbol.isConcatSpreadable] = false;
  350. areEqual([func], [].concat(func), 'func[Symbol.isConcatSpreadable] == false');
  351. func[Symbol.isConcatSpreadable] = true;
  352. areEqual([undefined, undefined, undefined], [].concat(func), 'func[Symbol.isConcatSpreadable] == true');
  353. func[0] = arr[0];
  354. func[1] = arr[1];
  355. func[2] = arr[2];
  356. areEqual(arr, [].concat(func), 'func[0..2] assigned');
  357. delete func[0];
  358. delete func[1];
  359. delete func[2];
  360. areEqual([undefined, undefined, undefined], [].concat(func), 'func[0..2] deleted');
  361. delete func[Symbol.isConcatSpreadable];
  362. areEqual([func], [].concat(func), 'func[Symbol.isConcatSpreadable] deleted');
  363. Function.prototype[Symbol.isConcatSpreadable] = true;
  364. areEqual([undefined, undefined, undefined], [].concat(func), 'Function.prototype[Symbol.isConcatSpreadable] == true');
  365. Function.prototype[Symbol.isConcatSpreadable] = false;
  366. areEqual([func], [].concat(func), 'Function.prototype[Symbol.isConcatSpreadable] == false');
  367. Function.prototype[0] = arr[0];
  368. Function.prototype[1] = arr[1];
  369. Function.prototype[2] = arr[2];
  370. areEqual([func], [].concat(func), 'Function.prototype[0..2] assigned');
  371. Function.prototype[Symbol.isConcatSpreadable] = true;
  372. areEqual(arr, [].concat(func), 'Function.prototype[Symbol.isConcatSpreadable] == true');
  373. delete Function.prototype[0];
  374. delete Function.prototype[1];
  375. delete Function.prototype[2];
  376. areEqual([undefined, undefined, undefined], [].concat(func), 'Function.prototype[0..2] deleted');
  377. delete Function.prototype[Symbol.isConcatSpreadable];
  378. areEqual([func], [].concat(func), 'Function.prototype[Symbol.isConcatSpreadable] deleted');
  379. }
  380. test([1, 2, 3]);
  381. test([1.1, 2.2, 3.3]);
  382. test(["a", "b", "c"]);
  383. test([2, NaN, function(){}]);
  384. }
  385. },
  386. {
  387. name: "spread Number, Boolean, and RegExp",
  388. body: function ()
  389. {
  390. function test(Type, initVal, arr) {
  391. var obj = new Type(initVal);
  392. areEqual([obj], [].concat(obj), Type.name+' obj');
  393. obj[Symbol.isConcatSpreadable] = true;
  394. areEqual([], [].concat(obj), Type.name+' obj[Symbol.isConcatSpreadable] == true');
  395. obj.length = arr.length;
  396. areEqual(new Array(arr.length), [].concat(obj), Type.name+' obj[length] assigned');
  397. for (var i = 0; i < arr.length; i++) {
  398. obj[i] = arr[i];
  399. }
  400. areEqual(arr, [].concat(obj), Type.name+' obj[0..length] assigned');
  401. obj[Symbol.isConcatSpreadable] = false;
  402. areEqual([obj], [].concat(obj), Type.name+' obj[Symbol.isConcatSpreadable] == false');
  403. obj[Symbol.isConcatSpreadable] = true;
  404. areEqual(arr, [].concat(obj), Type.name+' obj[Symbol.isConcatSpreadable] == true');
  405. for (var i = 0; i < arr.length; i++) {
  406. delete obj[i];
  407. }
  408. areEqual(new Array(arr.length), [].concat(obj), Type.name+' obj[0..length] deleted');
  409. delete obj.length;
  410. areEqual([], [].concat(obj), Type.name+' obj[length] deleted');
  411. delete obj[Symbol.isConcatSpreadable];
  412. areEqual([obj], [].concat(obj), Type.name+' obj[Symbol.isConcatSpreadable] deleted');
  413. Type.prototype[Symbol.isConcatSpreadable] = true;
  414. areEqual([], [].concat(obj), Type.name+'.prototype[Symbol.isConcatSpreadable] == true');
  415. Type.prototype.length = arr.length;
  416. areEqual(new Array(arr.length), [].concat(obj), Type.name+'.prototype[length] assigned');
  417. for (var i = 0; i < arr.length; i++) {
  418. Type.prototype[i] = arr[i];
  419. }
  420. areEqual(arr, [].concat(obj), Type.name+'.prototype[0..length] assigned');
  421. Type.prototype[Symbol.isConcatSpreadable] = false;
  422. areEqual([obj], [].concat(obj), Type.name+'.prototype[Symbol.isConcatSpreadable] == false');
  423. Type.prototype[Symbol.isConcatSpreadable] = true;
  424. areEqual(arr, [].concat(obj), Type.name+'.prototype[Symbol.isConcatSpreadable] == true');
  425. for (var i = 0; i < arr.length; i++) {
  426. delete Type.prototype[i];
  427. }
  428. areEqual(new Array(arr.length), [].concat(obj), Type.name+'.prototype[0..length] deleted');
  429. delete Type.prototype.length;
  430. areEqual([], [].concat(obj), Type.name+'.prototype[length] deleted');
  431. delete Type.prototype[Symbol.isConcatSpreadable];
  432. areEqual([obj], [].concat(obj), Type.name+'.prototype[Symbol.isConcatSpreadable] deleted');
  433. }
  434. test(Number, 0, [1, 2, 3]);
  435. test(Number, -0.1, [1.1, 2.2, 3.3]);
  436. test(Number, NaN, ["a", "b", "c"]);
  437. test(Number, 321, [1, "ab", 2.2, 2, NaN, 3, function(){ }]);
  438. test(Boolean, true, [1, 2, 3]);
  439. test(Boolean, false, [1.1, 2.2, 3.3]);
  440. test(Boolean, true, ["a", "b", "c"]);
  441. test(Boolean, false, [1, "ab", 2.2, 2, NaN, 3, function(){ }]);
  442. test(RegExp, /^/, [1, 2, 3]);
  443. test(RegExp, /abc/, [1.1, 2.2, 3.3]);
  444. test(RegExp, /(\d+)/, ["a", "b", "c"]);
  445. test(RegExp, /^\s*\S+\s*$/, [1, "ab", 2.2, 2, NaN, 3, function(){ }]);
  446. }
  447. },
  448. {
  449. name: "spread String object",
  450. body: function ()
  451. {
  452. function test() {
  453. var s = new String("abc");
  454. areEqual([s], [].concat(s), "string");
  455. s[Symbol.isConcatSpreadable] = true;
  456. areEqual(['a', 'b', 'c'], [].concat(s), "string s[Symbol.isConcatSpreadable] == true");
  457. s[Symbol.isConcatSpreadable] = false;
  458. areEqual([s], [].concat(s), "string s[Symbol.isConcatSpreadable] == false");
  459. s[Symbol.isConcatSpreadable] = true;
  460. areEqual(['a', 'b', 'c'], [].concat(s), "string s[Symbol.isConcatSpreadable] == true");
  461. delete s[Symbol.isConcatSpreadable];
  462. areEqual([s], [].concat(s), "string s[Symbol.isConcatSpreadable] deleted");
  463. String.prototype[Symbol.isConcatSpreadable] = true;
  464. areEqual(['a', 'b', 'c'], [].concat(s), "string.prototype[Symbol.isConcatSpreadable] == true");
  465. String.prototype[Symbol.isConcatSpreadable] = false;
  466. areEqual([s], [].concat(s), "string.prototype[Symbol.isConcatSpreadable] == false");
  467. String.prototype[Symbol.isConcatSpreadable] = true;
  468. areEqual(['a', 'b', 'c'], [].concat(s), "string.prototype[Symbol.isConcatSpreadable] == true");
  469. delete String.prototype[Symbol.isConcatSpreadable];
  470. areEqual([s], [].concat(s), "string.prototype[Symbol.isConcatSpreadable] deleted");
  471. }
  472. test();
  473. }
  474. },
  475. {
  476. name: "Revokable proxy revoked when retrieving [@@isConcatSpreadable]",
  477. body: function ()
  478. {
  479. // proxy revoked
  480. var obj = {};
  481. var pobj = Proxy.revocable(obj, {
  482. get: function(target, prop) {
  483. if (prop === Symbol.isConcatSpreadable) { pobj.revoke(); }
  484. return obj[prop];
  485. }
  486. });
  487. assert.throws(()=>[].concat(pobj.proxy), TypeError, 'proxy revoked', 'method is called on a revoked Proxy object');
  488. }
  489. },
  490. {
  491. name: "[@@isConcatSpreadable] getter altering array type",
  492. body: function ()
  493. {
  494. function test(arr, idx, elem) {
  495. var expected = arr.slice(0);
  496. expected[idx] = elem;
  497. Object.defineProperty(arr, Symbol.isConcatSpreadable, {
  498. get: function() {
  499. arr[idx] = elem;
  500. return true;
  501. }
  502. })
  503. areEqual(expected, [].concat(arr), 'expecting ['+expected+']');
  504. }
  505. test([1, 2, 3], 1, 'abc');
  506. test([1.1, 2.2, 3.3], 0, {});
  507. }
  508. },
  509. {
  510. name: "[@@isConcatSpreadable] getter altering binding",
  511. body: function ()
  512. {
  513. function test(arr, expected) {
  514. Object.defineProperty(arr, Symbol.isConcatSpreadable, {
  515. get: function() {
  516. arr = [];
  517. return true;
  518. }
  519. })
  520. areEqual(expected, Array.prototype.concat.call(arr, arr), 'expecting ['+expected+']');
  521. areEqual([], Array.prototype.concat.call(arr, arr), 'expecting []');
  522. }
  523. test([1, 2, 3], [1, 2, 3, 1, 2, 3]);
  524. test([1.1, 2.2, 3.3], [1.1, 2.2, 3.3, 1.1, 2.2, 3.3]);
  525. test(["a", "b", "c"], ["a", "b", "c", "a", "b", "c"]);
  526. test([1.1, "b", 3], [1.1, "b", 3, 1.1, "b", 3]);
  527. test([4, 5.5, "f"], [4, 5.5, "f", 4, 5.5, "f"]);
  528. var func = function() {};
  529. test([undefined, NaN, func], [undefined, NaN, func, undefined, NaN, func]);
  530. }
  531. },
  532. {
  533. name: "[@@isConcatSpreadable] getter changing array to ES5 array",
  534. body: function ()
  535. {
  536. function test(arr, idx, elem) {
  537. var expected = arr.slice(0);
  538. expected[idx] = elem;
  539. Object.defineProperty(arr, Symbol.isConcatSpreadable, {
  540. get: function() {
  541. Object.defineProperty(arr, idx, { 'get': function(){ return elem; } });
  542. return true;
  543. }
  544. })
  545. areEqual(expected, Array.prototype.concat.call(arr), 'expecting ['+arr+']');
  546. }
  547. test([1, 2, 3], 1, 'abc');
  548. test([1.1, 2.2, 3.3], 0, {});
  549. }
  550. },
  551. {
  552. name: "[@@isConcatSpreadable] getter setting illegal length property in object",
  553. body: function ()
  554. {
  555. function test(a) {
  556. var b = {"0":1, "1":2, "length": 2};
  557. Object.defineProperty(b, Symbol.isConcatSpreadable, {
  558. get: function() {
  559. b.length = 9007199254740989;
  560. return true;
  561. }
  562. });
  563. assert.throws(()=>a.concat(b), TypeError, a, "Illegal length and size specified for the array");
  564. }
  565. test([1, 2, 3]);
  566. test([1.1, 2.2, 3.3]);
  567. test(["a", "b", "c"]);
  568. test([1.1, "b", 3]);
  569. test([4, 5.5, "f"]);
  570. test([undefined, NaN, function(){}]);
  571. }
  572. },
  573. {
  574. name: "[@@isConcatSpreadable] should be called once when the destination array changes it's type",
  575. body: function ()
  576. {
  577. var obj1 = [21];
  578. var arr2 = [1];
  579. var arr2ICSCalled = 0;
  580. Object.defineProperty(arr2, Symbol.isConcatSpreadable, { get : function() { arr2ICSCalled++; dst[2] = {}; return false; } } );
  581. var dst = [1, 2, 3];
  582. var FakeConstructor = function () {
  583. return dst;
  584. };
  585. FakeConstructor[Symbol.species] = FakeConstructor;
  586. var arr = [2, 4];
  587. arr.constructor = FakeConstructor;
  588. var out = arr.concat(arr2, obj1);
  589. areEqual(1, arr2ICSCalled, 'isConcatSpreadable for arr2 should be called once');
  590. areEqual([2, 4, [1], 21], out);
  591. }
  592. },
  593. {
  594. name: "[@@isConcatSpreadable] the array 'arr2' should be spread to dest array, regardless of dest array changed to ES5 array during the IsConcatSpreadable call",
  595. body: function ()
  596. {
  597. var obj1 = [21, 22];
  598. var arr2 = [1, 2, 3];
  599. var first = true;
  600. var arr2ICSCalled = 0;
  601. Object.defineProperty(arr2, Symbol.isConcatSpreadable, { get : function() {
  602. arr2ICSCalled++;
  603. if (first) {
  604. // Changin the array to ES5 array.
  605. Object.defineProperty(dst, "2", { get : function() { return 1; } } );
  606. first = false;
  607. }
  608. return true;
  609. }
  610. } );
  611. var dst = [1, 2, 3];
  612. var FakeConstructor = function () {
  613. return dst;
  614. };
  615. FakeConstructor[Symbol.species] = FakeConstructor;
  616. var arr = [2, 4];
  617. arr.constructor = FakeConstructor;
  618. var out = arr.concat(arr2, obj1);
  619. areEqual(1, arr2ICSCalled, 'isConcatSpreadable for arr2 should be called once');
  620. areEqual([2,4,1,2,3,21,22], out, 'arr2 should be part of the spread');
  621. }
  622. },
  623. ];
  624. testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" });