ModuleTests.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664
  1. using Jint.Native;
  2. using Jint.Runtime;
  3. using Jint.Runtime.Modules;
  4. namespace Jint.Tests.Runtime;
  5. public class ModuleTests
  6. {
  7. private readonly Engine _engine;
  8. public ModuleTests()
  9. {
  10. _engine = new Engine();
  11. }
  12. [Fact]
  13. public void ShouldExportNamed()
  14. {
  15. _engine.Modules.Add("my-module", "export const value = 'exported value';");
  16. var ns = _engine.Modules.Import("my-module");
  17. Assert.Equal("exported value", ns.Get("value").AsString());
  18. }
  19. [Fact]
  20. public void ShouldExportNamedListRenamed()
  21. {
  22. _engine.Modules.Add("my-module", "const value1 = 1; const value2 = 2; export { value1 as renamed1, value2 as renamed2 }");
  23. var ns = _engine.Modules.Import("my-module");
  24. Assert.Equal(1, ns.Get("renamed1").AsInteger());
  25. Assert.Equal(2, ns.Get("renamed2").AsInteger());
  26. }
  27. [Fact]
  28. public void ShouldExportDefault()
  29. {
  30. _engine.Modules.Add("my-module", "export default 'exported value';");
  31. var ns = _engine.Modules.Import("my-module");
  32. Assert.Equal("exported value", ns.Get("default").AsString());
  33. }
  34. [Fact]
  35. public void ShouldExportAll()
  36. {
  37. _engine.Modules.Add("module1", "export const value = 'exported value';");
  38. _engine.Modules.Add("module2", "export * from 'module1';");
  39. var ns = _engine.Modules.Import("module2");
  40. Assert.Equal("exported value", ns.Get("value").AsString());
  41. }
  42. [Fact]
  43. public void ShouldImportNamed()
  44. {
  45. _engine.Modules.Add("imported-module", "export const value = 'exported value';");
  46. _engine.Modules.Add("my-module", "import { value } from 'imported-module'; export const exported = value;");
  47. var ns = _engine.Modules.Import("my-module");
  48. Assert.Equal("exported value", ns.Get("exported").AsString());
  49. }
  50. [Fact]
  51. public void ShouldImportRenamed()
  52. {
  53. _engine.Modules.Add("imported-module", "export const value = 'exported value';");
  54. _engine.Modules.Add("my-module", "import { value as renamed } from 'imported-module'; export const exported = renamed;");
  55. var ns = _engine.Modules.Import("my-module");
  56. Assert.Equal("exported value", ns.Get("exported").AsString());
  57. }
  58. [Fact]
  59. public void ShouldImportDefault()
  60. {
  61. _engine.Modules.Add("imported-module", "export default 'exported value';");
  62. _engine.Modules.Add("my-module", "import imported from 'imported-module'; export const exported = imported;");
  63. var ns = _engine.Modules.Import("my-module");
  64. Assert.Equal("exported value", ns.Get("exported").AsString());
  65. }
  66. [Fact]
  67. public void ShouldImportAll()
  68. {
  69. _engine.Modules.Add("imported-module", "export const value = 'exported value';");
  70. _engine.Modules.Add("my-module", "import * as imported from 'imported-module'; export const exported = imported.value;");
  71. var ns = _engine.Modules.Import("my-module");
  72. Assert.Equal("exported value", ns.Get("exported").AsString());
  73. }
  74. [Fact]
  75. public void ShouldImportDynamically()
  76. {
  77. var received = false;
  78. _engine.Modules.Add("imported-module", builder => builder.ExportFunction("signal", () => received = true));
  79. _engine.Modules.Add("my-module", "import('imported-module').then(ns => { ns.signal(); });");
  80. _engine.Modules.Import("my-module");
  81. Assert.True(received);
  82. }
  83. [Fact]
  84. public void ShouldPropagateParseError()
  85. {
  86. _engine.Modules.Add("imported", "export const invalid;");
  87. _engine.Modules.Add("my-module", "import { invalid } from 'imported';");
  88. var exc = Assert.Throws<JavaScriptException>(() => _engine.Modules.Import("my-module"));
  89. Assert.Equal("Error while loading module: error in module 'imported': Line 1: Missing initializer in const declaration", exc.Message);
  90. Assert.Equal("imported", exc.Location.Source);
  91. }
  92. [Fact]
  93. public void ShouldPropagateLinkError()
  94. {
  95. _engine.Modules.Add("imported", "export invalid;");
  96. _engine.Modules.Add("my-module", "import { value } from 'imported';");
  97. var exc = Assert.Throws<JavaScriptException>(() => _engine.Modules.Import("my-module"));
  98. Assert.Equal("Error while loading module: error in module 'imported': Line 1: Unexpected identifier", exc.Message);
  99. Assert.Equal("imported", exc.Location.Source);
  100. }
  101. [Fact]
  102. public void ShouldPropagateExecuteError()
  103. {
  104. _engine.Modules.Add("my-module", "throw new Error('imported successfully');");
  105. var exc = Assert.Throws<JavaScriptException>(() => _engine.Modules.Import("my-module"));
  106. Assert.Equal("imported successfully", exc.Message);
  107. Assert.Equal("my-module", exc.Location.Source);
  108. }
  109. [Fact]
  110. public void ShouldPropagateThrowStatementThroughJavaScriptImport()
  111. {
  112. _engine.Modules.Add("imported-module", "throw new Error('imported successfully');");
  113. _engine.Modules.Add("my-module", "import 'imported-module';");
  114. var exc = Assert.Throws<JavaScriptException>(() => _engine.Modules.Import("my-module"));
  115. Assert.Equal("imported successfully", exc.Message);
  116. }
  117. [Fact]
  118. public void ShouldAddModuleFromJsValue()
  119. {
  120. _engine.Modules.Add("my-module", builder => builder.ExportValue("value", JsString.Create("hello world")));
  121. var ns = _engine.Modules.Import("my-module");
  122. Assert.Equal("hello world", ns.Get("value").AsString());
  123. }
  124. [Fact]
  125. public void ShouldAddModuleFromClrInstance()
  126. {
  127. _engine.Modules.Add("imported-module", builder => builder.ExportObject("value", new ImportedClass
  128. {
  129. Value = "instance value"
  130. }));
  131. _engine.Modules.Add("my-module", "import { value } from 'imported-module'; export const exported = value.value;");
  132. var ns = _engine.Modules.Import("my-module");
  133. Assert.Equal("instance value", ns.Get("exported").AsString());
  134. }
  135. [Fact]
  136. public void ShouldAllowInvokeUserDefinedClass()
  137. {
  138. _engine.Modules.Add("user", "export class UserDefined { constructor(v) { this._v = v; } hello(c) { return `hello ${this._v}${c}`; } }");
  139. var ctor = _engine.Modules.Import("user").Get("UserDefined");
  140. var instance = _engine.Construct(ctor, JsString.Create("world"));
  141. var result = instance.GetMethod("hello").Call(instance, JsString.Create("!"));
  142. Assert.Equal("hello world!", result);
  143. }
  144. [Fact]
  145. public void ShouldAddModuleFromClrType()
  146. {
  147. _engine.Modules.Add("imported-module", builder => builder.ExportType<ImportedClass>());
  148. _engine.Modules.Add("my-module", "import { ImportedClass } from 'imported-module'; export const exported = new ImportedClass().value;");
  149. var ns = _engine.Modules.Import("my-module");
  150. Assert.Equal("hello world", ns.Get("exported").AsString());
  151. }
  152. [Fact]
  153. public void ShouldAddModuleFromClrFunction()
  154. {
  155. var received = new List<string>();
  156. _engine.Modules.Add("imported-module", builder => builder
  157. .ExportFunction("act_noargs", () => received.Add("act_noargs"))
  158. .ExportFunction("act_args", args => received.Add($"act_args:{args[0].AsString()}"))
  159. .ExportFunction("fn_noargs", () =>
  160. {
  161. received.Add("fn_noargs");
  162. return "ret";
  163. })
  164. .ExportFunction("fn_args", args =>
  165. {
  166. received.Add($"fn_args:{args[0].AsString()}");
  167. return "ret";
  168. })
  169. );
  170. _engine.Modules.Add("my-module", @"
  171. import * as fns from 'imported-module';
  172. export const result = [fns.act_noargs(), fns.act_args('ok'), fns.fn_noargs(), fns.fn_args('ok')];");
  173. var ns = _engine.Modules.Import("my-module");
  174. Assert.Equal(new[]
  175. {
  176. "act_noargs",
  177. "act_args:ok",
  178. "fn_noargs",
  179. "fn_args:ok"
  180. }, received.ToArray());
  181. Assert.Equal(new[]
  182. {
  183. "undefined",
  184. "undefined",
  185. "ret",
  186. "ret"
  187. }, ns.Get("result").AsArray().Select(x => x.ToString()).ToArray());
  188. }
  189. private class ImportedClass
  190. {
  191. public string Value { get; set; } = "hello world";
  192. }
  193. [Fact]
  194. public void ShouldAllowExportMultipleImports()
  195. {
  196. _engine.Modules.Add("@mine/import1", builder => builder.ExportValue("value1", JsNumber.Create(1)));
  197. _engine.Modules.Add("@mine/import2", builder => builder.ExportValue("value2", JsNumber.Create(2)));
  198. _engine.Modules.Add("@mine", "export * from '@mine/import1'; export * from '@mine/import2'");
  199. _engine.Modules.Add("app", "import { value1, value2 } from '@mine'; export const result = `${value1} ${value2}`");
  200. var ns = _engine.Modules.Import("app");
  201. Assert.Equal("1 2", ns.Get("result").AsString());
  202. }
  203. [Fact]
  204. public void ShouldAllowNamedStarExport()
  205. {
  206. _engine.Modules.Add("imported-module", builder => builder.ExportValue("value1", 5));
  207. _engine.Modules.Add("my-module", "export * as ns from 'imported-module';");
  208. var ns = _engine.Modules.Import("my-module");
  209. Assert.Equal(5, ns.Get("ns").Get("value1").AsNumber());
  210. }
  211. [Fact]
  212. public void ShouldAllowChaining()
  213. {
  214. _engine.Modules.Add("dependent-module", "export const dependency = 1;");
  215. _engine.Modules.Add("my-module", builder => builder
  216. .AddSource("import { dependency } from 'dependent-module';")
  217. .AddSource("export const output = dependency + 1;")
  218. .ExportValue("num", JsNumber.Create(-1))
  219. );
  220. var ns = _engine.Modules.Import("my-module");
  221. Assert.Equal(2, ns.Get("output").AsInteger());
  222. Assert.Equal(-1, ns.Get("num").AsInteger());
  223. }
  224. [Fact]
  225. public void ShouldImportOnlyOnce()
  226. {
  227. var called = 0;
  228. _engine.Modules.Add("imported-module", builder => builder.ExportFunction("count", args => called++));
  229. _engine.Modules.Add("my-module", "import { count } from 'imported-module'; count();");
  230. _engine.Modules.Import("my-module");
  231. _engine.Modules.Import("my-module");
  232. Assert.Equal(1, called);
  233. }
  234. [Fact]
  235. public void ShouldAllowSelfImport()
  236. {
  237. _engine.Modules.Add("my-globals", "export const globals = { counter: 0 };");
  238. _engine.Modules.Add("my-module", @"
  239. import { globals } from 'my-globals';
  240. import {} from 'my-module';
  241. globals.counter++;
  242. export const count = globals.counter;
  243. ");
  244. var ns = _engine.Modules.Import("my-module");
  245. Assert.Equal(1, ns.Get("count").AsInteger());
  246. }
  247. [Fact]
  248. public void ShouldAllowCyclicImport()
  249. {
  250. // https://tc39.es/ecma262/#sec-example-cyclic-module-record-graphs
  251. _engine.Modules.Add("B", "import { a } from 'A'; export const b = 'b';");
  252. _engine.Modules.Add("A", "import { b } from 'B'; export const a = 'a';");
  253. var nsA = _engine.Modules.Import("A");
  254. var nsB = _engine.Modules.Import("B");
  255. Assert.Equal("a", nsA.Get("a").AsString());
  256. Assert.Equal("b", nsB.Get("b").AsString());
  257. }
  258. [Fact]
  259. public void ShouldSupportConstraints()
  260. {
  261. var engine = new Engine(opts => opts.TimeoutInterval(TimeSpan.FromTicks(1)));
  262. engine.Modules.Add("sleep", builder => builder.ExportFunction("sleep", () => Thread.Sleep(100)));
  263. engine.Modules.Add("my-module", "import { sleep } from 'sleep'; for(var i = 0; i < 100; i++) { sleep(); } export const result = 'ok';");
  264. Assert.Throws<TimeoutException>(() => engine.Modules.Import("my-module"));
  265. }
  266. [Fact]
  267. public void CanLoadModuleImportsFromFiles()
  268. {
  269. var engine = new Engine(options => options.EnableModules(GetBasePath()));
  270. engine.Modules.Add("my-module", "import { User } from './modules/user.js'; export const user = new User('John', 'Doe');");
  271. var ns = engine.Modules.Import("my-module");
  272. Assert.Equal("John Doe", ns["user"].Get("name").AsString());
  273. }
  274. [Fact]
  275. public void CanImportFromFile()
  276. {
  277. var engine = new Engine(options => options.EnableModules(GetBasePath()));
  278. var ns = engine.Modules.Import("./modules/format-name.js");
  279. var result = engine.Invoke(ns.Get("formatName"), "John", "Doe").AsString();
  280. Assert.Equal("John Doe", result);
  281. }
  282. [Fact]
  283. public void CanImportFromFileWithSpacesInPath()
  284. {
  285. var engine = new Engine(options => options.EnableModules(GetBasePath()));
  286. var ns = engine.Modules.Import("./dir with spaces/format name.js");
  287. var result = engine.Invoke(ns.Get("formatName"), "John", "Doe").AsString();
  288. Assert.Equal("John Doe", result);
  289. }
  290. [Fact]
  291. public void CanReuseModule()
  292. {
  293. const string Code = "export function formatName(firstName, lastName) {\r\n return `${firstName} ${lastName}`;\r\n}";
  294. var module = Engine.PrepareModule(Code);
  295. for (var i = 0; i < 5; i++)
  296. {
  297. var engine = new Engine();
  298. engine.Modules.Add("__main__", x => x.AddModule(module));
  299. var ns = engine.Modules.Import("__main__");
  300. var result = engine.Invoke(ns.Get("formatName"), "John" + i, "Doe").AsString();
  301. Assert.Equal($"John{i} Doe", result);
  302. }
  303. }
  304. [Fact]
  305. public void EngineExecutePassesSourceForModuleResolving()
  306. {
  307. var moduleLoader = new EnforceRelativeModuleLoader(new Dictionary<string, string>()
  308. {
  309. ["file:///folder/my-module.js"] = "export const value = 'myModuleConst'"
  310. });
  311. var engine = new Engine(options => options.EnableModules(moduleLoader));
  312. var code = @"
  313. (async () => {
  314. const { value } = await import('./my-module.js');
  315. log(value);
  316. })();
  317. ";
  318. List<string> logStatements = new List<string>();
  319. engine.SetValue("log", logStatements.Add);
  320. engine.Execute(code, source: "file:///folder/main.js");
  321. engine.Advanced.ProcessTasks();
  322. Assert.Collection(
  323. logStatements,
  324. s => Assert.Equal("myModuleConst", s));
  325. }
  326. [Fact]
  327. public void EngineExecuteUsesScriptSourceForSource()
  328. {
  329. var moduleLoader = new EnforceRelativeModuleLoader(new Dictionary<string, string>()
  330. {
  331. ["file:///folder/my-module.js"] = "export const value = 'myModuleConst'"
  332. });
  333. var engine = new Engine(options => options.EnableModules(moduleLoader));
  334. var code = @"
  335. (async () => {
  336. const { value } = await import('./my-module.js');
  337. log(value);
  338. })();
  339. ";
  340. List<string> logStatements = new List<string>();
  341. engine.SetValue("log", logStatements.Add);
  342. var script = Engine.PrepareScript(code, source: "file:///folder/main.js");
  343. engine.Execute(script);
  344. engine.Advanced.ProcessTasks();
  345. Assert.Collection(
  346. logStatements,
  347. s => Assert.Equal("myModuleConst", s));
  348. }
  349. [Fact]
  350. public void EngineEvaluatePassesSourceForModuleResolving()
  351. {
  352. var moduleLoader = new EnforceRelativeModuleLoader(new Dictionary<string, string>()
  353. {
  354. ["file:///folder/my-module.js"] = "export const value = 'myModuleConst'"
  355. });
  356. var engine = new Engine(options => options.EnableModules(moduleLoader));
  357. var code = @"
  358. (async () => {
  359. const { value } = await import('./my-module.js');
  360. log(value);
  361. })();
  362. ";
  363. List<string> logStatements = new List<string>();
  364. engine.SetValue("log", logStatements.Add);
  365. engine.Evaluate(code, source: "file:///folder/main.js");
  366. engine.Advanced.ProcessTasks();
  367. Assert.Collection(
  368. logStatements,
  369. s => Assert.Equal("myModuleConst", s));
  370. }
  371. [Fact]
  372. public void EngineEvaluateUsesScriptSourceForSource()
  373. {
  374. var moduleLoader = new EnforceRelativeModuleLoader(new Dictionary<string, string>()
  375. {
  376. ["file:///folder/my-module.js"] = "export const value = 'myModuleConst'"
  377. });
  378. var engine = new Engine(options => options.EnableModules(moduleLoader));
  379. var code = @"
  380. (async () => {
  381. const { value } = await import('./my-module.js');
  382. log(value);
  383. })();
  384. ";
  385. List<string> logStatements = new List<string>();
  386. engine.SetValue("log", logStatements.Add);
  387. var script = Engine.PrepareScript(code, source: "file:///folder/main.js");
  388. engine.Evaluate(script);
  389. engine.Advanced.ProcessTasks();
  390. Assert.Collection(
  391. logStatements,
  392. s => Assert.Equal("myModuleConst", s));
  393. }
  394. private sealed class EnforceRelativeModuleLoader : IModuleLoader
  395. {
  396. private readonly IReadOnlyDictionary<string, string> _modules;
  397. public EnforceRelativeModuleLoader(IReadOnlyDictionary<string, string> modules)
  398. {
  399. _modules = modules;
  400. }
  401. public ResolvedSpecifier Resolve(string referencingModuleLocation, ModuleRequest moduleRequest)
  402. {
  403. Assert.False(string.IsNullOrEmpty(referencingModuleLocation), "Referencing module location is null or empty");
  404. var target = new Uri(new Uri(referencingModuleLocation, UriKind.Absolute), moduleRequest.Specifier);
  405. Assert.True(_modules.ContainsKey(target.ToString()), $"Resolve was called with unexpected module request, {moduleRequest.Specifier} relative to {referencingModuleLocation}");
  406. return new ResolvedSpecifier(moduleRequest, target.ToString(), target, SpecifierType.Bare);
  407. }
  408. public Module LoadModule(Engine engine, ResolvedSpecifier resolved)
  409. {
  410. Assert.NotNull(resolved.Uri);
  411. var source = resolved.Uri.ToString();
  412. Assert.True(_modules.TryGetValue(source, out var script), $"Resolved module does not exist: {source}");
  413. return ModuleFactory.BuildSourceTextModule(engine, Engine.PrepareModule(script, source));
  414. }
  415. }
  416. private static string GetBasePath()
  417. {
  418. var assemblyDirectory = new DirectoryInfo(AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory);
  419. var current = assemblyDirectory;
  420. var binDirectory = $"{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}";
  421. while (current is not null)
  422. {
  423. if (current.FullName.Contains(binDirectory) || current.Name == "bin")
  424. {
  425. current = current.Parent;
  426. continue;
  427. }
  428. var testDirectory = current.GetDirectories("Jint.Tests").FirstOrDefault();
  429. if (testDirectory == null)
  430. {
  431. current = current.Parent;
  432. continue;
  433. }
  434. // found it
  435. current = testDirectory;
  436. break;
  437. }
  438. if (current is null)
  439. {
  440. throw new NullReferenceException($"Could not find tests base path, assemblyPath: {assemblyDirectory}");
  441. }
  442. return Path.Combine(current.FullName, "Runtime", "Scripts");
  443. }
  444. [Fact]
  445. public void ModuleBuilderWithCustomModuleLoaderLoadsModulesProperly()
  446. {
  447. var engine = new Engine(o => o.EnableModules(new LocationResolveOnlyModuleLoader((_, moduleRequest) =>
  448. {
  449. var result = moduleRequest.Specifier;
  450. if (moduleRequest.Specifier == "../library1/builder_module.js")
  451. {
  452. result = "library1/builder_module.js";
  453. }
  454. return result;
  455. })));
  456. var logStatements = new List<string>();
  457. engine.SetValue("log", logStatements.Add);
  458. engine.Modules.Add("library1/builder_module.js",
  459. builder => builder.AddSource("export const value = 'builder_module_const'; log('builder_module')"));
  460. engine.Modules.Add("library2/entry_point_module.js",
  461. builder => builder.AddSource("import * as m from '../library1/builder_module.js'; log('entry_point_module')"));
  462. engine.Modules.Import("library2/entry_point_module.js");
  463. Assert.Collection(
  464. logStatements,
  465. s => Assert.Equal("builder_module", s),
  466. s => Assert.Equal("entry_point_module", s));
  467. }
  468. [Fact]
  469. public void ModuleBuilderPassesReferencingModuleLocationToModuleLoader()
  470. {
  471. var engine = new Engine(o => o.EnableModules(new LocationResolveOnlyModuleLoader((referencingModuleLocation, moduleRequest) =>
  472. {
  473. var result = moduleRequest.Specifier;
  474. if (moduleRequest.Specifier == "../library1/builder_module.js")
  475. {
  476. Assert.Equal("library2/entry_point_module.js", referencingModuleLocation);
  477. result = "library1/builder_module.js";
  478. }
  479. return result;
  480. })));
  481. var logStatements = new List<string>();
  482. engine.SetValue("log", logStatements.Add);
  483. engine.Modules.Add("library1/builder_module.js",
  484. builder => builder.AddSource("export const value = 'builder_module_const'; log('builder_module')"));
  485. engine.Modules.Add("library2/entry_point_module.js",
  486. builder => builder.AddSource("import * as m from '../library1/builder_module.js'; log('entry_point_module')"));
  487. engine.Modules.Import("library2/entry_point_module.js");
  488. Assert.Collection(
  489. logStatements,
  490. s => Assert.Equal("builder_module", s),
  491. s => Assert.Equal("entry_point_module", s));
  492. }
  493. /// <summary>
  494. /// Custom <see cref="ModuleLoader"/> implementation which is only responsible to
  495. /// resolve the correct module location (see <see cref="Resolve"/>). Modules
  496. /// must be registered using <see cref="Jint.Engine.ModuleOperations"/> (e.g.
  497. /// by using <see cref="Engine.ModuleOperations.Add(string,string)"/>)
  498. /// </summary>
  499. private sealed class LocationResolveOnlyModuleLoader : ModuleLoader
  500. {
  501. public delegate string ResolveHandler(string referencingModuleLocation, ModuleRequest moduleRequest);
  502. private readonly ResolveHandler _resolveHandler;
  503. public LocationResolveOnlyModuleLoader(ResolveHandler resolveHandler)
  504. {
  505. _resolveHandler = resolveHandler ?? throw new ArgumentNullException(nameof(resolveHandler));
  506. }
  507. public override ResolvedSpecifier Resolve(string referencingModuleLocation, ModuleRequest moduleRequest)
  508. {
  509. return new ResolvedSpecifier(
  510. moduleRequest,
  511. Key: _resolveHandler(referencingModuleLocation, moduleRequest),
  512. Uri: null,
  513. SpecifierType.RelativeOrAbsolute
  514. );
  515. }
  516. protected override string LoadModuleContents(Engine engine, ResolvedSpecifier resolved)
  517. => throw new InvalidOperationException();
  518. }
  519. [Fact]
  520. public void EngineShouldTransmitSourceModuleForModuleLoader()
  521. {
  522. var engine = new Engine(o => o.EnableModules(new ModuleLoaderForEngineShouldTransmitSourceModuleForModuleLoaderTest()));
  523. var logs = new List<string>();
  524. engine.SetValue("log", logs.Add);
  525. engine.Modules.Import($"code/lib/module.js");
  526. Assert.Collection(logs,
  527. s => Assert.Equal("code/execute.js", s),
  528. s => Assert.Equal("code/lib/module.js", s));
  529. }
  530. public class ModuleLoaderForEngineShouldTransmitSourceModuleForModuleLoaderTest : ModuleLoader
  531. {
  532. public override ResolvedSpecifier Resolve(string referencingModuleLocation, ModuleRequest moduleRequest)
  533. {
  534. var moduleSpec = moduleRequest.Specifier;
  535. // to resolve this statement requires information about source module
  536. if (moduleSpec == "../execute.js")
  537. {
  538. Assert.True(!string.IsNullOrEmpty(referencingModuleLocation), "module loader cannot resolve referensing module - has no referencing module location");
  539. moduleSpec = $"code/execute.js";
  540. }
  541. return new ResolvedSpecifier(
  542. moduleRequest,
  543. moduleSpec,
  544. Uri: null,
  545. SpecifierType.RelativeOrAbsolute
  546. );
  547. }
  548. protected override string LoadModuleContents(Engine engine, ResolvedSpecifier resolved)
  549. {
  550. if (resolved.Key == $"code/lib/module.js")
  551. return $"import * as m from '../execute.js'; log('code/lib/module.js')";
  552. if (resolved.Key == $"code/execute.js")
  553. {
  554. return $"log('code/execute.js')";
  555. }
  556. throw new NotImplementedException(); // no need in this test
  557. }
  558. }
  559. }