ArrayTests.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. using Jint.Native;
  2. using Jint.Runtime.Interop;
  3. namespace Jint.Tests.Runtime;
  4. public class ArrayTests
  5. {
  6. private readonly Engine _engine;
  7. public ArrayTests()
  8. {
  9. _engine = new Engine()
  10. .SetValue("log", new Action<object>(Console.WriteLine))
  11. .SetValue("assert", new Action<bool>(Assert.True))
  12. .SetValue("equal", new Action<object, object>(Assert.Equal));
  13. }
  14. [Fact]
  15. public void ArrayPrototypeToStringWithArray()
  16. {
  17. var result = _engine.Evaluate("Array.prototype.toString.call([1,2,3]);").AsString();
  18. Assert.Equal("1,2,3", result);
  19. }
  20. [Fact]
  21. public void ArrayPrototypeToStringWithNumber()
  22. {
  23. var result = _engine.Evaluate("Array.prototype.toString.call(1);").AsString();
  24. Assert.Equal("[object Number]", result);
  25. }
  26. [Fact]
  27. public void ArrayPrototypeToStringWithObject()
  28. {
  29. var result = _engine.Evaluate("Array.prototype.toString.call({});").AsString();
  30. Assert.Equal("[object Object]", result);
  31. }
  32. [Fact]
  33. public void ArrayPrototypeJoinWithCircularReference()
  34. {
  35. var result = _engine.Evaluate("Array.prototype.join.call((c = [1, 2, 3, 4], b = [1, 2, 3, 4], b[1] = c, c[1] = b, c))").AsString();
  36. Assert.Equal("1,1,,3,4,3,4", result);
  37. }
  38. [Fact]
  39. public void ArrayPrototypeToLocaleStringWithCircularReference()
  40. {
  41. var result = _engine.Evaluate("Array.prototype.toLocaleString.call((c = [1, 2, 3, 4], b = [1, 2, 3, 4], b[1] = c, c[1] = b, c))").AsString();
  42. Assert.Equal("1,1,,3,4,3,4", result);
  43. }
  44. [Fact]
  45. public void EmptyStringKey()
  46. {
  47. var result = _engine.Evaluate("var x=[];x[\"\"]=8;x[\"\"];").AsNumber();
  48. Assert.Equal(8, result);
  49. }
  50. [Fact]
  51. public void LargeArraySize()
  52. {
  53. const string code = @"
  54. let arr = [];
  55. for (let i = 0; i < 10000; i++) arr.push(i);
  56. for (let i=0;i<10000;i++) arr.splice(0, 1);
  57. ";
  58. var engine = new Engine();
  59. engine.Execute(code);
  60. }
  61. [Fact]
  62. public void ArrayLengthFromInitialState()
  63. {
  64. var engine = new Engine();
  65. var array = new JsArray(engine);
  66. var length = (int) array.Length;
  67. Assert.Equal(0, length);
  68. }
  69. [Fact]
  70. public void ArraySortIsStable()
  71. {
  72. const string code = @"
  73. var items = [
  74. { name: 'Edward', value: 0 },
  75. { name: 'Sharpe', value: 0 },
  76. { name: 'And', value: 0 },
  77. { name: 'The', value: 1 },
  78. { name: 'Magnetic', value: 0 },
  79. { name: 'Zeros', value: 0 }
  80. ];
  81. // sort by value
  82. function compare(a, b) {
  83. return a.value - b.value;
  84. }
  85. var a = items.sort();
  86. assert(a[0].name == 'Edward');
  87. assert(a[1].name == 'Sharpe');
  88. assert(a[2].name == 'And');
  89. assert(a[3].name == 'The');
  90. assert(a[4].name == 'Magnetic');
  91. assert(a[5].name == 'Zeros');
  92. var a = items.sort(compare);
  93. assert(a[0].name == 'Edward');
  94. assert(a[1].name == 'Sharpe');
  95. assert(a[2].name == 'And');
  96. assert(a[3].name == 'Magnetic');
  97. assert(a[4].name == 'Zeros');
  98. assert(a[5].name == 'The');
  99. ";
  100. _engine.Execute(code);
  101. }
  102. [Fact]
  103. public void ExtendingArrayAndInstanceOf()
  104. {
  105. const string script = @"
  106. class MyArr extends Array {
  107. constructor(...args) {
  108. super(...args);
  109. }
  110. }";
  111. _engine.Execute(script);
  112. _engine.Evaluate("const a = new MyArr(1,2);");
  113. Assert.True(_engine.Evaluate("a instanceof MyArr").AsBoolean());
  114. }
  115. [Fact]
  116. public void IteratorShouldBeConvertibleToArray()
  117. {
  118. Assert.Equal("hello;again", _engine.Evaluate("Array.from(['hello', 'again'].values()).join(';')"));
  119. Assert.Equal("hello;another", _engine.Evaluate("Array.from(new Map([['hello', 'world'], ['another', 'value']]).keys()).join(';')"));
  120. }
  121. [Fact]
  122. public void ArrayFromShouldNotFlattenInputArray()
  123. {
  124. Assert.Equal("a;b", _engine.Evaluate("[...['a', 'b']].join(';')"));
  125. Assert.Equal("0,a;1,b", _engine.Evaluate("[...['a', 'b'].entries()].join(';')"));
  126. Assert.Equal("0,c;1,d", _engine.Evaluate("Array.from(['c', 'd'].entries()).join(';')"));
  127. Assert.Equal("0,e;1,f", _engine.Evaluate("Array.from([[0, 'e'],[1, 'f']]).join(';')"));
  128. }
  129. [Fact]
  130. public void ArrayEntriesShouldReturnKeyValuePairs()
  131. {
  132. Assert.Equal("0,hello,1,world", _engine.Evaluate("Array.from(['hello', 'world'].entries()).join()"));
  133. Assert.Equal("0,hello;1,world", _engine.Evaluate("Array.from(['hello', 'world'].entries()).join(';')"));
  134. Assert.Equal("0,;1,1;2,5", _engine.Evaluate("Array.from([,1,5,].entries()).join(';')"));
  135. }
  136. [Fact]
  137. public void IteratorsShouldHaveIteratorSymbol()
  138. {
  139. _engine.Execute("assert(!!['hello'].values()[Symbol.iterator])");
  140. _engine.Execute("assert(!!new Map([['hello', 'world']]).keys()[Symbol.iterator])");
  141. }
  142. [Fact]
  143. public void ArraySortDoesNotCrashInDebugMode()
  144. {
  145. var engine = new Engine(o =>
  146. {
  147. o.DebugMode(true);
  148. });
  149. engine.SetValue("equal", new Action<object, object>(Assert.Equal));
  150. const string code = @"
  151. var items = [5,2,4,1];
  152. items.sort((a,b) => a - b);
  153. equal('1,2,4,5', items.join());
  154. ";
  155. engine.Execute(code);
  156. }
  157. [Fact]
  158. public void ArrayConstructorFromHoles()
  159. {
  160. _engine.Evaluate("var a = Array(...[,,]);");
  161. Assert.True(_engine.Evaluate("\"0\" in a").AsBoolean());
  162. Assert.True(_engine.Evaluate("\"1\" in a").AsBoolean());
  163. Assert.Equal("undefinedundefined", _engine.Evaluate("'' + a[0] + a[1]"));
  164. }
  165. [Fact]
  166. public void ArrayIsSubclassable()
  167. {
  168. _engine.Evaluate("class C extends Array {}");
  169. _engine.Evaluate("var c = new C();");
  170. Assert.True(_engine.Evaluate("c.map(Boolean) instanceof C").AsBoolean());
  171. }
  172. [Fact]
  173. public void HasProperIteratorPrototypeChain()
  174. {
  175. const string Script = @"
  176. // Iterator instance
  177. var iterator = [][Symbol.iterator]();
  178. // %ArrayIteratorPrototype%
  179. var proto1 = Object.getPrototypeOf(iterator);
  180. // %IteratorPrototype%
  181. var proto2 = Object.getPrototypeOf(proto1);";
  182. var engine = new Engine();
  183. engine.Execute(Script);
  184. Assert.True(engine.Evaluate("proto2.hasOwnProperty(Symbol.iterator)").AsBoolean());
  185. Assert.True(engine.Evaluate("!proto1.hasOwnProperty(Symbol.iterator)").AsBoolean());
  186. Assert.True(engine.Evaluate("!iterator.hasOwnProperty(Symbol.iterator)").AsBoolean());
  187. Assert.True(engine.Evaluate("iterator[Symbol.iterator]() === iterator").AsBoolean());
  188. }
  189. [Fact]
  190. public void ArrayFrom()
  191. {
  192. const string Script = @"
  193. // Array.from -> Get -> [[Get]]
  194. var get = [];
  195. var p = new Proxy({length: 2, 0: '', 1: ''}, { get: function(o, k) { get.push(k); return o[k]; }});
  196. Array.from(p);";
  197. var engine = new Engine();
  198. engine.Execute(Script);
  199. Assert.True(engine.Evaluate("get[0] === Symbol.iterator").AsBoolean());
  200. Assert.Equal("length,0,1", engine.Evaluate("get.slice(1) + ''").AsString());
  201. }
  202. [Fact]
  203. public void ArrayFromStringUsingMapping()
  204. {
  205. var engine = new Engine();
  206. var array = engine.Evaluate("Array.from('fff', (s) => Number.parseInt(s, 16))").AsArray();
  207. Assert.Equal((uint) 3, array.Length);
  208. Assert.Equal((uint) 15, array[0]);
  209. Assert.Equal((uint) 15, array[1]);
  210. Assert.Equal((uint) 15, array[2]);
  211. }
  212. [Fact]
  213. public void Iteration()
  214. {
  215. const string Script = @"
  216. // Array.prototype methods -> Get -> [[Get]]
  217. var methods = ['copyWithin', 'every', 'fill', 'filter', 'find', 'findIndex', 'forEach',
  218. 'indexOf', 'join', 'lastIndexOf', 'map', 'reduce', 'reduceRight', 'some'];
  219. var get;
  220. var p = new Proxy({length: 2, 0: '', 1: ''}, { get: function(o, k) { get.push(k); return o[k]; }});
  221. for(var i = 0; i < methods.length; i+=1) {
  222. get = [];
  223. Array.prototype[methods[i]].call(p, Function());
  224. var actual = get + '';
  225. var expected = (
  226. methods[i] === 'fill' ? ""length"" :
  227. methods[i] === 'every' ? ""length,0"" :
  228. methods[i] === 'lastIndexOf' || methods[i] === 'reduceRight' ? ""length,1,0"" :
  229. ""length,0,1"");
  230. if (actual !== expected) {
  231. throw methods[i] + ': ' + actual + ' !== ' + expected;
  232. }
  233. }
  234. return true;";
  235. var engine = new Engine();
  236. Assert.True(engine.Evaluate(Script).AsBoolean());
  237. }
  238. [Fact]
  239. public void Concat()
  240. {
  241. const string Script = @"
  242. // Array.prototype.concat -> Get -> [[Get]]
  243. var get = [];
  244. var arr = [1];
  245. arr.constructor = void undefined;
  246. var p = new Proxy(arr, { get: function(o, k) { get.push(k); return o[k]; }});
  247. Array.prototype.concat.call(p,p);";
  248. var engine = new Engine();
  249. engine.Execute(Script);
  250. Assert.Equal("constructor", engine.Evaluate("get[0]"));
  251. Assert.True(engine.Evaluate("get[1] === Symbol.isConcatSpreadable").AsBoolean());
  252. Assert.Equal("length", engine.Evaluate("get[2]"));
  253. Assert.Equal("0", engine.Evaluate("get[3]"));
  254. Assert.True(engine.Evaluate("get[4] === get[1] && get[5] === get[2] && get[6] === get[3]").AsBoolean());
  255. Assert.Equal(7, engine.Evaluate("get.length"));
  256. }
  257. [Fact]
  258. public void ConcatHandlesHolesCorrectly()
  259. {
  260. const string Code = """
  261. function colors(specifier) {
  262. var n = specifier.length / 6 | 0, colors = new Array(n), i = 0;
  263. while (i < n) colors[i] = "#" + specifier.slice(i * 6, ++i * 6);
  264. return colors;
  265. }
  266. new Array(3).concat("d8b365f5f5f55ab4ac","a6611adfc27d80cdc1018571").map(colors);
  267. """;
  268. var engine = new Engine();
  269. var a = engine.Evaluate(Code).AsArray();
  270. a.Length.Should().Be(5);
  271. a[0].Should().Be(JsValue.Undefined);
  272. a[1].Should().Be(JsValue.Undefined);
  273. a[2].Should().Be(JsValue.Undefined);
  274. a[3].Should().BeOfType<JsArray>().Which.Should().ContainInOrder("#d8b365", "#f5f5f5", "#5ab4ac");
  275. a[4].Should().BeOfType<JsArray>().Which.Should().ContainInOrder("#a6611a", "#dfc27d", "#80cdc1", "#018571");
  276. }
  277. [Fact]
  278. public void Shift()
  279. {
  280. const string Script = @"
  281. // Array.prototype.shift -> Get -> [[Get]]
  282. var get = [];
  283. var p = new Proxy([0,1,2,3], { get: function(o, k) { get.push(k); return o[k]; }});
  284. Array.prototype.shift.call(p);
  285. return get + '' === ""length,0,1,2,3"";";
  286. var engine = new Engine();
  287. Assert.True(engine.Evaluate(Script).AsBoolean());
  288. }
  289. [Fact]
  290. public void ShouldBeAbleToInitFromArray()
  291. {
  292. var engine = new Engine();
  293. var propertyDescriptors = new JsArray(engine, [1]).GetOwnProperties().ToArray();
  294. Assert.Equal(2, propertyDescriptors.Length);
  295. Assert.Equal("0", propertyDescriptors[0].Key);
  296. Assert.Equal(1, propertyDescriptors[0].Value.Value);
  297. Assert.Equal("length", propertyDescriptors[1].Key);
  298. Assert.Equal(1, propertyDescriptors[1].Value.Value);
  299. }
  300. [Fact]
  301. public void ArrayFromSortTest()
  302. {
  303. var item1 = new KeyValuePair<string, string>("Id1", "0020");
  304. var item2 = new KeyValuePair<string, string>("Id2", "0001");
  305. var engine = new Engine();
  306. engine.SetValue("Root", new { Inner = new { Items = new[] { item1, item2 } } });
  307. var result = engine.Evaluate("Array.from(Root.Inner.Items).sort((a, b) => a.Value === '0001' ? -1 : 1)").AsArray();
  308. var enumerableResult = result
  309. .Select(x => (KeyValuePair<string, string>) ((IObjectWrapper) x).Target)
  310. .ToList();
  311. enumerableResult.Should().HaveCount(2);
  312. enumerableResult[0].Key.Should().Be(item2.Key);
  313. enumerableResult[1].Key.Should().Be(item1.Key);
  314. }
  315. }