ModuleTests.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. #if(NET6_0_OR_GREATER)
  2. using System;
  3. using System.IO;
  4. using System.Reflection;
  5. #endif
  6. using System;
  7. using System.Collections.Generic;
  8. using System.Linq;
  9. using Jint.Native;
  10. using Jint.Runtime;
  11. using Xunit;
  12. namespace Jint.Tests.Runtime;
  13. public class ModuleTests
  14. {
  15. private readonly Engine _engine;
  16. public ModuleTests()
  17. {
  18. _engine = new Engine();
  19. }
  20. [Fact]
  21. public void ShouldExportNamed()
  22. {
  23. _engine.AddModule("my-module", @"export const value = 'exported value';");
  24. var ns = _engine.ImportModule("my-module");
  25. Assert.Equal("exported value", ns.Get("value").AsString());
  26. }
  27. [Fact]
  28. public void ShouldExportNamedListRenamed()
  29. {
  30. _engine.AddModule("my-module", @"const value1 = 1; const value2 = 2; export { value1 as renamed1, value2 as renamed2 }");
  31. var ns = _engine.ImportModule("my-module");
  32. Assert.Equal(1, ns.Get("renamed1").AsInteger());
  33. Assert.Equal(2, ns.Get("renamed2").AsInteger());
  34. }
  35. [Fact]
  36. public void ShouldExportDefault()
  37. {
  38. _engine.AddModule("my-module", @"export default 'exported value';");
  39. var ns = _engine.ImportModule("my-module");
  40. Assert.Equal("exported value", ns.Get("default").AsString());
  41. }
  42. [Fact]
  43. public void ShouldExportAll()
  44. {
  45. _engine.AddModule("module1", @"export const value = 'exported value';");
  46. _engine.AddModule("module2", @"export * from 'module1';");
  47. var ns = _engine.ImportModule("module2");
  48. Assert.Equal("exported value", ns.Get("value").AsString());
  49. }
  50. [Fact]
  51. public void ShouldImportNamed()
  52. {
  53. _engine.AddModule("imported-module", @"export const value = 'exported value';");
  54. _engine.AddModule("my-module", @"import { value } from 'imported-module'; export const exported = value;");
  55. var ns = _engine.ImportModule("my-module");
  56. Assert.Equal("exported value", ns.Get("exported").AsString());
  57. }
  58. [Fact]
  59. public void ShouldImportRenamed()
  60. {
  61. _engine.AddModule("imported-module", @"export const value = 'exported value';");
  62. _engine.AddModule("my-module", @"import { value as renamed } from 'imported-module'; export const exported = renamed;");
  63. var ns = _engine.ImportModule("my-module");
  64. Assert.Equal("exported value", ns.Get("exported").AsString());
  65. }
  66. [Fact]
  67. public void ShouldImportDefault()
  68. {
  69. _engine.AddModule("imported-module", @"export default 'exported value';");
  70. _engine.AddModule("my-module", @"import imported from 'imported-module'; export const exported = imported;");
  71. var ns = _engine.ImportModule("my-module");
  72. Assert.Equal("exported value", ns.Get("exported").AsString());
  73. }
  74. [Fact]
  75. public void ShouldImportAll()
  76. {
  77. _engine.AddModule("imported-module", @"export const value = 'exported value';");
  78. _engine.AddModule("my-module", @"import * as imported from 'imported-module'; export const exported = imported.value;");
  79. var ns = _engine.ImportModule("my-module");
  80. Assert.Equal("exported value", ns.Get("exported").AsString());
  81. }
  82. [Fact]
  83. public void ShouldImportDynamically()
  84. {
  85. var received = false;
  86. _engine.AddModule("imported-module", builder => builder.ExportFunction("signal", () => received = true));
  87. _engine.AddModule("my-module", @"import('imported-module').then(ns => { ns.signal(); });");
  88. _engine.ImportModule("my-module");
  89. _engine.RunAvailableContinuations();
  90. Assert.True(received);
  91. }
  92. [Fact]
  93. public void ShouldPropagateParseError()
  94. {
  95. _engine.AddModule("imported", @"export const invalid;");
  96. _engine.AddModule("my-module", @"import { invalid } from 'imported';");
  97. var exc = Assert.Throws<JavaScriptException>(() => _engine.ImportModule("my-module"));
  98. Assert.Equal("Error while loading module: error in module 'imported': Line 1: Missing initializer in const declaration", exc.Message);
  99. }
  100. [Fact]
  101. public void ShouldPropagateLinkError()
  102. {
  103. _engine.AddModule("imported", @"export invalid;");
  104. _engine.AddModule("my-module", @"import { value } from 'imported';");
  105. var exc = Assert.Throws<JavaScriptException>(() => _engine.ImportModule("my-module"));
  106. Assert.Equal("Error while loading module: error in module 'imported': Line 1: Unexpected identifier", exc.Message);
  107. Assert.Equal("my-module", exc.Location.Source);
  108. }
  109. [Fact]
  110. public void ShouldPropagateExecuteError()
  111. {
  112. _engine.AddModule("my-module", @"throw new Error('imported successfully');");
  113. var exc = Assert.Throws<JavaScriptException>(() => _engine.ImportModule("my-module"));
  114. Assert.Equal("imported successfully", exc.Message);
  115. Assert.Equal("my-module", exc.Location.Source);
  116. }
  117. [Fact]
  118. public void ShouldPropagateThrowStatementThroughJavaScriptImport()
  119. {
  120. _engine.AddModule("imported-module", @"throw new Error('imported successfully');");
  121. _engine.AddModule("my-module", @"import 'imported-module';");
  122. var exc = Assert.Throws<JavaScriptException>(() => _engine.ImportModule("my-module"));
  123. Assert.Equal("imported successfully", exc.Message);
  124. }
  125. [Fact]
  126. public void ShouldAddModuleFromJsValue()
  127. {
  128. _engine.AddModule("my-module", builder => builder.ExportValue("value", JsString.Create("hello world")));
  129. var ns = _engine.ImportModule("my-module");
  130. Assert.Equal("hello world", ns.Get("value").AsString());
  131. }
  132. [Fact]
  133. public void ShouldAddModuleFromClrInstance()
  134. {
  135. _engine.AddModule("imported-module", builder => builder.ExportObject("value", new ImportedClass { Value = "instance value" }));
  136. _engine.AddModule("my-module", @"import { value } from 'imported-module'; export const exported = value.value;");
  137. var ns = _engine.ImportModule("my-module");
  138. Assert.Equal("instance value", ns.Get("exported").AsString());
  139. }
  140. [Fact]
  141. public void ShouldAllowInvokeUserDefinedClass()
  142. {
  143. _engine.AddModule("user", "export class UserDefined { constructor(v) { this._v = v; } hello(c) { return `hello ${this._v}${c}`; } }");
  144. var ctor = _engine.ImportModule("user").Get("UserDefined");
  145. var instance = _engine.Construct(ctor, JsString.Create("world"));
  146. var result = instance.GetMethod("hello").Call(instance, JsString.Create("!"));
  147. Assert.Equal("hello world!", result);
  148. }
  149. [Fact]
  150. public void ShouldAddModuleFromClrType()
  151. {
  152. _engine.AddModule("imported-module", builder => builder.ExportType<ImportedClass>());
  153. _engine.AddModule("my-module", @"import { ImportedClass } from 'imported-module'; export const exported = new ImportedClass().value;");
  154. var ns = _engine.ImportModule("my-module");
  155. Assert.Equal("hello world", ns.Get("exported").AsString());
  156. }
  157. [Fact]
  158. public void ShouldAddModuleFromClrFunction()
  159. {
  160. var received = new List<string>();
  161. _engine.AddModule("imported-module", builder => builder
  162. .ExportFunction("act_noargs", () => received.Add("act_noargs"))
  163. .ExportFunction("act_args", args => received.Add($"act_args:{args[0].AsString()}"))
  164. .ExportFunction("fn_noargs", () =>
  165. {
  166. received.Add("fn_noargs");
  167. return "ret";
  168. })
  169. .ExportFunction("fn_args", args =>
  170. {
  171. received.Add($"fn_args:{args[0].AsString()}");
  172. return "ret";
  173. })
  174. );
  175. _engine.AddModule("my-module", @"
  176. import * as fns from 'imported-module';
  177. export const result = [fns.act_noargs(), fns.act_args('ok'), fns.fn_noargs(), fns.fn_args('ok')];");
  178. var ns = _engine.ImportModule("my-module");
  179. Assert.Equal(new[] { "act_noargs", "act_args:ok", "fn_noargs", "fn_args:ok" }, received.ToArray());
  180. Assert.Equal(new[] { "undefined", "undefined", "ret", "ret" }, ns.Get("result").AsArray().Select(x => x.ToString()).ToArray());
  181. }
  182. private class ImportedClass
  183. {
  184. public string Value { get; set; } = "hello world";
  185. }
  186. [Fact]
  187. public void ShouldAllowExportMultipleImports()
  188. {
  189. _engine.AddModule("@mine/import1", builder => builder.ExportValue("value1", JsNumber.Create(1)));
  190. _engine.AddModule("@mine/import2", builder => builder.ExportValue("value2", JsNumber.Create(2)));
  191. _engine.AddModule("@mine", "export * from '@mine/import1'; export * from '@mine/import2'");
  192. _engine.AddModule("app", @"import { value1, value2 } from '@mine'; export const result = `${value1} ${value2}`");
  193. var ns = _engine.ImportModule("app");
  194. Assert.Equal("1 2", ns.Get("result").AsString());
  195. }
  196. [Fact]
  197. public void ShouldAllowNamedStarExport()
  198. {
  199. _engine.AddModule("imported-module", builder => builder.ExportValue("value1", 5));
  200. _engine.AddModule("my-module", "export * as ns from 'imported-module';");
  201. var ns = _engine.ImportModule("my-module");
  202. Assert.Equal(5, ns.Get("ns").Get("value1").AsNumber());
  203. }
  204. [Fact]
  205. public void ShouldAllowChaining()
  206. {
  207. _engine.AddModule("dependent-module", "export const dependency = 1;");
  208. _engine.AddModule("my-module", builder => builder
  209. .AddSource("import { dependency } from 'dependent-module';")
  210. .AddSource("export const output = dependency + 1;")
  211. .ExportValue("num", JsNumber.Create(-1))
  212. );
  213. var ns = _engine.ImportModule("my-module");
  214. Assert.Equal(2, ns.Get("output").AsInteger());
  215. Assert.Equal(-1, ns.Get("num").AsInteger());
  216. }
  217. [Fact]
  218. public void ShouldImportOnlyOnce()
  219. {
  220. var called = 0;
  221. _engine.AddModule("imported-module", builder => builder.ExportFunction("count", args => called++));
  222. _engine.AddModule("my-module", @"import { count } from 'imported-module'; count();");
  223. _engine.ImportModule("my-module");
  224. _engine.ImportModule("my-module");
  225. Assert.Equal(1, called);
  226. }
  227. [Fact]
  228. public void ShouldAllowSelfImport()
  229. {
  230. _engine.AddModule("my-globals", @"export const globals = { counter: 0 };");
  231. _engine.AddModule("my-module", @"
  232. import { globals } from 'my-globals';
  233. import {} from 'my-module';
  234. globals.counter++;
  235. export const count = globals.counter;
  236. ");
  237. var ns= _engine.ImportModule("my-module");
  238. Assert.Equal(1, ns.Get("count").AsInteger());
  239. }
  240. [Fact]
  241. public void ShouldAllowCyclicImport()
  242. {
  243. // https://tc39.es/ecma262/#sec-example-cyclic-module-record-graphs
  244. _engine.AddModule("B", @"import { a } from 'A'; export const b = 'b';");
  245. _engine.AddModule("A", @"import { b } from 'B'; export const a = 'a';");
  246. var nsA = _engine.ImportModule("A");
  247. var nsB = _engine.ImportModule("B");
  248. Assert.Equal("a", nsA.Get("a").AsString());
  249. Assert.Equal("b", nsB.Get("b").AsString());
  250. }
  251. [Fact]
  252. public void ShouldSupportConstraints()
  253. {
  254. var engine = new Engine(opts => opts.TimeoutInterval(TimeSpan.FromTicks(1)));
  255. engine.AddModule("my-module", @"for(var i = 0; i < 100000; i++) { } export const result = 'ok';");
  256. Assert.Throws<TimeoutException>(() => engine.ImportModule("my-module"));
  257. }
  258. #if(NET6_0_OR_GREATER)
  259. [Fact]
  260. public void CanLoadModuleImportsFromFiles()
  261. {
  262. var engine = new Engine(options => options.EnableModules(GetBasePath()));
  263. engine.AddModule("my-module", "import { User } from './modules/user.js'; export const user = new User('John', 'Doe');");
  264. var ns = engine.ImportModule("my-module");
  265. Assert.Equal("John Doe", ns["user"].Get("name").AsString());
  266. }
  267. [Fact]
  268. public void CanImportFromFile()
  269. {
  270. var engine = new Engine(options => options.EnableModules(GetBasePath()));
  271. var ns = engine.ImportModule("./modules/format-name.js");
  272. var result = engine.Invoke(ns.Get("formatName"), "John", "Doe").AsString();
  273. Assert.Equal("John Doe", result);
  274. }
  275. private static string GetBasePath()
  276. {
  277. var assemblyPath = new Uri(typeof(ModuleTests).GetTypeInfo().Assembly.Location).LocalPath;
  278. var assemblyDirectory = new FileInfo(assemblyPath).Directory;
  279. return Path.Combine(
  280. assemblyDirectory?.Parent?.Parent?.Parent?.FullName ?? throw new NullReferenceException("Could not find tests base path"),
  281. "Runtime",
  282. "Scripts");
  283. }
  284. #endif
  285. }