dynamic-module-functionality.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. //-------------------------------------------------------------------------------------------------------
  2. // Copyright (C) Microsoft. All rights reserved.
  3. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
  4. //-------------------------------------------------------------------------------------------------------
  5. // ES6 Module functionality tests -- verifies functionality of import and export statements
  6. WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");
  7. function testScript(source, message, shouldFail = false, explicitAsync = false) {
  8. message += " (script)";
  9. let testfunc = () => testRunner.LoadScript(source, undefined, shouldFail, explicitAsync);
  10. if (shouldFail) {
  11. let caught = false;
  12. assert.throws(testfunc, SyntaxErrr, message);
  13. assert.isTrue(caught, `Expected error not thrown: ${message}`);
  14. } else {
  15. assert.doesNotThrow(testfunc, message);
  16. }
  17. }
  18. function testModuleScript(source, message, shouldFail = false, explicitAsync = false) {
  19. message += " (module)";
  20. let testfunc = () => testRunner.LoadModule(source, 'samethread', shouldFail, explicitAsync);
  21. if (shouldFail) {
  22. let caught = false;
  23. // We can't use assert.throws here because the SyntaxError used to construct the thrown error
  24. // is from a different context so it won't be strictly equal to our SyntaxError.
  25. try {
  26. testfunc();
  27. } catch(e) {
  28. caught = true;
  29. // Compare toString output of SyntaxError and other context SyntaxError constructor.
  30. assert.areEqual(e.constructor.toString(), SyntaxError.toString(), message);
  31. }
  32. assert.isTrue(caught, `Expected error not thrown: ${message}`);
  33. } else {
  34. assert.doesNotThrow(testfunc, message);
  35. }
  36. }
  37. function testDynamicImport(importFunc, thenFunc, catchFunc, _asyncEnter, _asyncExit) {
  38. var promise = importFunc();
  39. assert.isTrue(promise instanceof Promise);
  40. promise.then((v)=>{
  41. _asyncEnter();
  42. thenFunc(v);
  43. _asyncExit();
  44. }).catch((err)=>{
  45. _asyncEnter();
  46. catchFunc(err);
  47. _asyncExit();
  48. });
  49. }
  50. function testDoubleDynamicImport(importFunc, secondFunc, thirdFunc, catchFunc, _asyncEnter, _asyncExit) {
  51. let promise = importFunc();
  52. assert.isTrue(promise instanceof Promise);
  53. promise.then((v)=>{
  54. _asyncEnter();
  55. let secondPromise = secondFunc(v);
  56. secondPromise.then((v2)=>{
  57. _asyncEnter();
  58. thirdFunc(v2);
  59. _asyncExit();
  60. });
  61. _asyncExit();
  62. }).catch((err)=>{
  63. _asyncEnter();
  64. catchFunc(err);
  65. _asyncExit();
  66. });
  67. }
  68. var tests = [
  69. {
  70. name: "Runtime evaluation of module specifier",
  71. body: function () {
  72. let functionBody =
  73. `testDynamicImport(
  74. ()=>{
  75. var getName = ()=>{ return 'ModuleSimpleExport'; };
  76. return import( getName() + '.js');
  77. },
  78. (v)=>{
  79. assert.areEqual('ModuleSimpleExport', v.ModuleSimpleExport_foo(), 'Failed to import ModuleSimpleExport_foo from ModuleSimpleExport.js');
  80. },
  81. (err)=>{ assert.fail(err.message); }, _asyncEnter, _asyncExit
  82. )`;
  83. testScript(functionBody, "Test importing a simple exported function", false, true);
  84. testModuleScript(functionBody, "Test importing a simple exported function", false, true);
  85. }
  86. },
  87. {
  88. name: "Validate a simple module export",
  89. body: function () {
  90. let functionBody =
  91. `testDynamicImport(
  92. ()=>{ return import('ModuleSimpleExport.js'); },
  93. (v)=>{ assert.areEqual('ModuleSimpleExport', v.ModuleSimpleExport_foo(), 'Failed to import ModuleSimpleExport_foo from ModuleSimpleExport.js'); },
  94. (err)=>{ assert.fail(err.message); }, _asyncEnter, _asyncExit
  95. )`;
  96. testScript(functionBody, "Test importing a simple exported function", false, true);
  97. testModuleScript(functionBody, "Test importing a simple exported function", false, true);
  98. }
  99. },
  100. {
  101. name: "Validate importing from multiple modules",
  102. body: function () {
  103. let functionBody =
  104. `testDynamicImport(
  105. ()=>import('ModuleComplexExports.js'),
  106. (v)=>{
  107. assert.areEqual('foo', v.foo2(), 'Failed to import foo2 from ModuleComplexExports.js');
  108. },
  109. (err)=>{ assert.fail(err.message); }, _asyncEnter, _asyncExit
  110. )`;
  111. testScript(functionBody, "Test importing from multiple modules", false, true);
  112. testModuleScript(functionBody, "Test importing from multiple modules", false, true);
  113. }
  114. },
  115. {
  116. name: "Validate a variety of more complex exports",
  117. body: function () {
  118. let functionBody =
  119. `testDynamicImport(
  120. ()=>import('ModuleComplexExports.js'),
  121. (v)=>{
  122. assert.areEqual('foo', v.foo(), 'Failed to import foo from ModuleComplexExports.js');
  123. assert.areEqual('foo', v.foo2(), 'Failed to import foo2 from ModuleComplexExports.js');
  124. assert.areEqual('bar', v.bar(), 'Failed to import bar from ModuleComplexExports.js');
  125. assert.areEqual('bar', v.bar2(), 'Failed to import bar2 from ModuleComplexExports.js');
  126. assert.areEqual('let2', v.let2, 'Failed to import let2 from ModuleComplexExports.js');
  127. assert.areEqual('let3', v.let3, 'Failed to import let3 from ModuleComplexExports.js');
  128. assert.areEqual('let2', v.let4, 'Failed to import let4 from ModuleComplexExports.js');
  129. assert.areEqual('let3', v.let5, 'Failed to import let5 from ModuleComplexExports.js');
  130. assert.areEqual('const2', v.const2, 'Failed to import const2 from ModuleComplexExports.js');
  131. assert.areEqual('const3', v.const3, 'Failed to import const3 from ModuleComplexExports.js');
  132. assert.areEqual('const2', v.const4, 'Failed to import const4 from ModuleComplexExports.js');
  133. assert.areEqual('const3', v.const5, 'Failed to import const5 from ModuleComplexExports.js');
  134. assert.areEqual('var2', v.var2, 'Failed to import var2 from ModuleComplexExports.js');
  135. assert.areEqual('var3', v.var3, 'Failed to import var3 from ModuleComplexExports.js');
  136. assert.areEqual('var2', v.var4, 'Failed to import var4 from ModuleComplexExports.js');
  137. assert.areEqual('var3', v.var5, 'Failed to import var5 from ModuleComplexExports.js');
  138. assert.areEqual('class2', v.class2.static_member(), 'Failed to import class2 from ModuleComplexExports.js');
  139. assert.areEqual('class2', new v.class2().member(), 'Failed to create intance of class2 from ModuleComplexExports.js');
  140. assert.areEqual('class2', v.class3.static_member(), 'Failed to import class3 from ModuleComplexExports.js');
  141. assert.areEqual('class2', new v.class3().member(), 'Failed to create intance of class3 from ModuleComplexExports.js');
  142. assert.areEqual('class4', v.class4.static_member(), 'Failed to import class4 from ModuleComplexExports.js');
  143. assert.areEqual('class4', new v.class4().member(), 'Failed to create intance of class4 from ModuleComplexExports.js');
  144. assert.areEqual('class4', v.class5.static_member(), 'Failed to import class4 from ModuleComplexExports.js');
  145. assert.areEqual('class4', new v.class5().member(), 'Failed to create intance of class4 from ModuleComplexExports.js');
  146. assert.areEqual('default', v.default(), 'Failed to import default from ModuleComplexExports.js');
  147. assert.areEqual('ModuleComplexExports', v.function, 'Failed to import v.function from ModuleComplexExports.js');
  148. assert.areEqual('ModuleComplexExports', v.export, 'Failed to import v.export from ModuleComplexExports.js');
  149. },
  150. (err)=>{ assert.fail(err.message); }, _asyncEnter, _asyncExit);
  151. `;
  152. testScript(functionBody, "Test importing a variety of exports", false, true);
  153. testModuleScript(functionBody, "Test importing a variety of exports", false, true);
  154. }
  155. },
  156. {
  157. name: "Exporting module changes exported value",
  158. body: function () {
  159. let functionBody =
  160. `testDynamicImport(
  161. ()=>import('ModuleComplexExports.js'),
  162. (v)=>{
  163. v.reset();
  164. assert.areEqual('before', v.target(), 'Failed to import target from ModuleComplexExports.js');
  165. assert.areEqual('ok', v.changeTarget(), 'Failed to import changeTarget from ModuleComplexExports.js');
  166. assert.areEqual('after', v.target(), 'changeTarget failed to change export value');
  167. },
  168. (err)=>{ assert.fail(err.message); }, _asyncEnter, _asyncExit);
  169. `;
  170. testScript(functionBody, "Changing exported value", false, true);
  171. testModuleScript(functionBody, "Changing exported value", false, true);
  172. }
  173. },
  174. {
  175. name: "Simple re-export forwards import to correct slot",
  176. body: function () {
  177. let functionBody =
  178. `testDynamicImport(
  179. ()=>import('ModuleSimpleReexport.js'),
  180. (v)=>{
  181. assert.areEqual('ModuleSimpleExport', v.ModuleSimpleExport_foo(), 'Failed to import ModuleSimpleExport_foo from ModuleSimpleReexport.js');
  182. },
  183. (err)=>{ assert.fail(err.message); }, _asyncEnter, _asyncExit);
  184. `;
  185. testScript(functionBody, "Simple re-export from one module to another", false, true);
  186. testModuleScript(functionBody, "Simple re-export from one module to another", false, true);
  187. }
  188. },
  189. {
  190. name: "Renamed re-export and dynamic import",
  191. body: function () {
  192. let functionBody =
  193. `testDynamicImport(
  194. ()=>import('ModuleComplexReexports.js'),
  195. (v)=>{
  196. assert.areEqual('bar', v.ModuleComplexReexports_foo(), 'Failed to import ModuleComplexReexports_foo from ModuleComplexReexports.js');
  197. },
  198. (err)=>{ assert.fail(err.message); }, _asyncEnter, _asyncExit);
  199. `;
  200. testScript(functionBody, "Rename already renamed re-export", false, true);
  201. testModuleScript(functionBody, "Rename already renamed re-export", false, true);
  202. }
  203. },
  204. {
  205. name: "Explicit export/import to default binding",
  206. body: function () {
  207. let functionBody =
  208. `testDynamicImport(
  209. ()=>import('ModuleDefaultExport1.js'),
  210. (v)=>{
  211. assert.areEqual('ModuleDefaultExport1', v.default(), 'Failed to import default from ModuleDefaultExport1.js');
  212. },
  213. (err)=>{ assert.fail(err.message); }, _asyncEnter, _asyncExit);
  214. `;
  215. testScript(functionBody, "Explicitly export and import a local name to the default binding", false, true);
  216. testModuleScript(functionBody, "Explicitly export and import a local name to the default binding", false, true);
  217. }
  218. },
  219. {
  220. name: "Explicit import of default binding",
  221. body: function () {
  222. let functionBody =
  223. `testDynamicImport(
  224. ()=>import('ModuleDefaultExport2.js'),
  225. (v)=>{
  226. assert.areEqual('ModuleDefaultExport2', v.default(), 'Failed to import default from ModuleDefaultExport2.js');
  227. },
  228. (err)=>{ assert.fail(err.message); }, _asyncEnter, _asyncExit);
  229. `;
  230. testScript(functionBody, "Explicitly import the default export binding", false, true);
  231. testModuleScript(functionBody, "Explicitly import the default export binding", false, true);
  232. }
  233. },
  234. {
  235. name: "Exporting module changes value of default export",
  236. body: function () {
  237. let functionBody =
  238. `testDynamicImport(
  239. ()=>import('ModuleDefaultExport3.js'),
  240. (v)=>{
  241. assert.areEqual(2, v.default, 'Failed to import default from ModuleDefaultExport3.js');
  242. },
  243. (err)=>{ assert.fail(err.message); }, _asyncEnter, _asyncExit);
  244. `;
  245. testScript(functionBody, "Exported value incorrectly bound", false, true);
  246. testModuleScript(functionBody, "Exported value incorrectly bound", false, true);
  247. functionBody =
  248. `testDynamicImport(
  249. ()=>import('ModuleDefaultExport4.js'),
  250. (v)=>{
  251. assert.areEqual(1, v.default, 'Failed to import default from ModuleDefaultExport4.js');
  252. },
  253. (err)=>{ assert.fail(err.message); }, _asyncEnter, _asyncExit);
  254. `;
  255. testScript(functionBody, "Exported value incorrectly bound", false, true);
  256. testModuleScript(functionBody, "Exported value incorrectly bound", false, true);
  257. }
  258. },
  259. {
  260. name: "Import bindings used in a nested function",
  261. body: function () {
  262. let functionBody =
  263. `function test(func) {
  264. assert.areEqual('ModuleDefaultExport2', func(), 'Failed to import default from ModuleDefaultExport2.js');
  265. }
  266. testDynamicImport(
  267. ()=>import('ModuleDefaultExport2.js'),
  268. (v)=>test(v.default),
  269. (err)=>{ assert.fail(err.message); }, _asyncEnter, _asyncExit);
  270. `;
  271. testScript(functionBody, "Failed to find imported name correctly in nested function", false, true);
  272. testModuleScript(functionBody, "Failed to find imported name correctly in nested function", false, true);
  273. }
  274. },
  275. {
  276. name: "Exported name may be any keyword",
  277. body: function () {
  278. let functionBody =
  279. `testDynamicImport(
  280. ()=>import('ModuleComplexExports.js'),
  281. (v)=>{
  282. assert.areEqual('ModuleComplexExports', v.export, 'Failed to import export from ModuleDefaultExport2.js');
  283. assert.areEqual('ModuleComplexExports', v.function, 'Failed to import function from ModuleDefaultExport2.js');
  284. },
  285. (err)=>{ assert.fail(err.message); }, _asyncEnter, _asyncExit);
  286. `;
  287. testScript(functionBody, "Exported name may be a keyword (import binding must be binding identifier)", false, true);
  288. testModuleScript(functionBody, "Exported name may be a keyword (import binding must be binding identifier)", false, true);
  289. }
  290. },
  291. {
  292. name: "Odd case of 'export { as as as }; import { as as as };'",
  293. body: function () {
  294. let functionBody =
  295. `testDynamicImport(
  296. ()=>import('ModuleComplexExports.js'),
  297. (v)=>{
  298. assert.areEqual('as', v.as(), 'String "as" is not reserved word');
  299. },
  300. (err)=>{ assert.fail(err.message); }, _asyncEnter, _asyncExit);
  301. `;
  302. testScript(functionBody, "Test 'import { as as as}'", false, true);
  303. testModuleScript(functionBody, "Test 'import { as as as}'", false, true);
  304. }
  305. },
  306. {
  307. name: "Typeof a module export",
  308. body: function () {
  309. let functionBody =
  310. `testDynamicImport(
  311. ()=>import('ModuleDefaultExport2.js'),
  312. (v)=>{
  313. assert.areEqual('function', typeof v.default, 'typeof default export from ModuleDefaultExport2.js is function');
  314. },
  315. (err)=>{ assert.fail(err.message); }, _asyncEnter, _asyncExit);
  316. `;
  317. testScript(functionBody, "Typeof a module export", false, true);
  318. testModuleScript(functionBody, "Typeof a module export", false, true);
  319. }
  320. },
  321. {
  322. name: "Circular module dependency",
  323. body: function () {
  324. let functionBody =
  325. `testDynamicImport(
  326. ()=>import('ModuleCircularFoo.js'),
  327. (v)=>{
  328. v.reset();
  329. assert.areEqual(2, v.circular_foo(), 'This function calls between both modules in the circular dependency incrementing a counter in each');
  330. assert.areEqual(4, v.rexportbar(), 'Second call originates in the other module but still increments the counter twice');
  331. },
  332. (err)=>{ assert.fail(err.message); }, _asyncEnter, _asyncExit);
  333. `;
  334. testScript(functionBody, "Circular module dependency", false, true);
  335. testModuleScript(functionBody, "Circular module dependency", false, true);
  336. }
  337. },
  338. {
  339. name: "Implicitly re-exporting an import binding (import { foo } from ''; export { foo };)",
  340. body: function () {
  341. let functionBody =
  342. `testDynamicImport(
  343. ()=>import('ModuleComplexReexports.js'),
  344. (v)=>{
  345. assert.areEqual('foo', v.foo(), 'Simple implicit re-export');
  346. assert.areEqual('foo', v.baz(), 'Renamed export imported and renamed during implicit re-export');
  347. assert.areEqual('foo', v.localfoo(), 'Export renamed as import and implicitly re-exported');
  348. assert.areEqual('foo', v.bar(), 'Renamed export renamed as import and renamed again during implicit re-exported');
  349. assert.areEqual('foo', v.localfoo2(), 'Renamed export renamed as import and implicitly re-exported');
  350. assert.areEqual('foo', v.bar2(), 'Renamed export renamed as import and renamed again during implicit re-export');
  351. },
  352. (err)=>{ assert.fail(err.message); }, _asyncEnter, _asyncExit);
  353. `;
  354. testScript(functionBody, "Implicitly re-exporting an import binding (import { foo } from ''; export { foo };)", false, true);
  355. testModuleScript(functionBody, "Implicitly re-exporting an import binding (import { foo } from ''; export { foo };)", false, true);
  356. }
  357. },
  358. {
  359. name: "Validate a simple module export inside eval()",
  360. body: function () {
  361. let functionBody =
  362. `testDynamicImport(
  363. ()=>{ return eval("import('ModuleSimpleExport.js')"); },
  364. (v)=>{ assert.areEqual('ModuleSimpleExport', v.ModuleSimpleExport_foo(), 'Failed to import ModuleSimpleExport_foo from ModuleSimpleExport.js'); },
  365. (err)=>{ assert.fail(err.message); }, _asyncEnter, _asyncExit
  366. )`;
  367. testScript(functionBody, "Test importing a simple exported function", false, true);
  368. testModuleScript(functionBody, "Test importing a simple exported function", false, true);
  369. }
  370. },
  371. {
  372. name: "Test dynamic import of an un-parsed module from a module",
  373. body: function () {
  374. let functionBody =
  375. `testDoubleDynamicImport(
  376. ()=>{ return import('testDynamicImportfromModule.js'); },
  377. (v)=>{ return v.foo; },
  378. (v2)=>{ assert.areEqual(true, v.foo.success, "Failed to load module dynamicly from module");},
  379. (err)=>{ assert.fail(err.message); }, _asyncEnter, _asyncExit
  380. )`;
  381. testModuleScript(functionBody, "Test dynamic import of an un-parsed module from a module", false, true);
  382. //note exclusion of testScript case intentional - running the code from a script loads the module
  383. //then the test from Module uses the one loaded by the script - do not add testScript here
  384. }
  385. },
  386. {
  387. name : "Test 'new import()' throws - Bug Issue 5797",
  388. body: function() {
  389. assert.throws(()=>{eval('new import("ModuleSimpleExport.js")')}, SyntaxError);
  390. }
  391. },
  392. {
  393. name : "Test that import() always gives different promise objects - Bug Issue #5795",
  394. body: function () {
  395. WScript.RegisterModuleSource("testModule", "export const a = 5;");
  396. let functionBody =
  397. `testDynamicImport(function () {
  398. const first = import ('ModuleSimpleExport.js');
  399. const second = import ('ModuleSimpleExport.js');
  400. assert.isTrue(first !== second, 'import() should not return the same promise');
  401. return Promise.all([first, second]).then ((results) => ({first, second, results}));
  402. }, function (imports) {
  403. assert.isTrue(imports.first !== imports.second, 'import() should not return the same promise after resolution');
  404. assert.isTrue(imports.results[0] === imports.results[1], 'import() should return the same namespace object');
  405. }, function (e) {
  406. print ("Test should not throw, threw " + e);
  407. }, _asyncEnter, _asyncExit);`;
  408. testScript(functionBody, "Test that import() always gives different promise objects", false, true);
  409. testModuleScript(functionBody, "Test that import() always gives different promise objects", false, true);
  410. }
  411. }
  412. ];
  413. testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" });