ArrayTests.cs 11 KB

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