ModuleTests.cs 24 KB

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