module-functionality.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  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 testModuleScript(source, message, shouldFail) {
  8. let testfunc = () => WScript.LoadModule(source, 'samethread');
  9. if (shouldFail) {
  10. let caught = false;
  11. // We can't use assert.throws here because the SyntaxError used to construct the thrown error
  12. // is from a different context so it won't be strictly equal to our SyntaxError.
  13. try {
  14. testfunc();
  15. } catch(e) {
  16. caught = true;
  17. // Compare toString output of SyntaxError and other context SyntaxError constructor.
  18. assert.areEqual(e.constructor.toString(), SyntaxError.toString(), message);
  19. }
  20. assert.isTrue(caught, `Expected error not thrown: ${message}`);
  21. } else {
  22. assert.doesNotThrow(testfunc, message);
  23. }
  24. }
  25. var tests = [
  26. {
  27. name: "Validate a simple module export",
  28. body: function () {
  29. let functionBody =
  30. `import { ModuleSimpleExport_foo } from 'ModuleSimpleExport.js';
  31. assert.areEqual('ModuleSimpleExport', ModuleSimpleExport_foo(), 'Failed to import ModuleSimpleExport_foo from ModuleSimpleExport.js');`;
  32. testModuleScript(functionBody, "Test importing a simple exported function", false);
  33. }
  34. },
  35. {
  36. name: "Validate importing from multiple modules",
  37. body: function () {
  38. let functionBody =
  39. `import { ModuleSimpleExport_foo } from 'ModuleSimpleExport.js';
  40. assert.areEqual('ModuleSimpleExport', ModuleSimpleExport_foo(), 'Failed to import ModuleSimpleExport_foo from ModuleSimpleExport.js');
  41. import { foo2 } from 'ModuleComplexExports.js';
  42. assert.areEqual('foo', foo2(), 'Failed to import foo2 from ModuleComplexExports.js');`;
  43. testModuleScript(functionBody, "Test importing from multiple modules", false);
  44. }
  45. },
  46. {
  47. name: "Validate a variety of more complex exports",
  48. body: function () {
  49. let functionBody =
  50. `import { foo, foo2 } from 'ModuleComplexExports.js';
  51. assert.areEqual('foo', foo(), 'Failed to import foo from ModuleComplexExports.js');
  52. assert.areEqual('foo', foo2(), 'Failed to import foo2 from ModuleComplexExports.js');
  53. import { bar, bar2 } from 'ModuleComplexExports.js';
  54. assert.areEqual('bar', bar(), 'Failed to import bar from ModuleComplexExports.js');
  55. assert.areEqual('bar', bar2(), 'Failed to import bar2 from ModuleComplexExports.js');
  56. import { let2, let3, let4, let5 } from 'ModuleComplexExports.js';
  57. assert.areEqual('let2', let2, 'Failed to import let2 from ModuleComplexExports.js');
  58. assert.areEqual('let3', let3, 'Failed to import let3 from ModuleComplexExports.js');
  59. assert.areEqual('let2', let4, 'Failed to import let4 from ModuleComplexExports.js');
  60. assert.areEqual('let3', let5, 'Failed to import let5 from ModuleComplexExports.js');
  61. import { const2, const3, const4, const5 } from 'ModuleComplexExports.js';
  62. assert.areEqual('const2', const2, 'Failed to import const2 from ModuleComplexExports.js');
  63. assert.areEqual('const3', const3, 'Failed to import const3 from ModuleComplexExports.js');
  64. assert.areEqual('const2', const4, 'Failed to import const4 from ModuleComplexExports.js');
  65. assert.areEqual('const3', const5, 'Failed to import const5 from ModuleComplexExports.js');
  66. import { var2, var3, var4, var5 } from 'ModuleComplexExports.js';
  67. assert.areEqual('var2', var2, 'Failed to import var2 from ModuleComplexExports.js');
  68. assert.areEqual('var3', var3, 'Failed to import var3 from ModuleComplexExports.js');
  69. assert.areEqual('var2', var4, 'Failed to import var4 from ModuleComplexExports.js');
  70. assert.areEqual('var3', var5, 'Failed to import var5 from ModuleComplexExports.js');
  71. import { class2, class3, class4, class5 } from 'ModuleComplexExports.js';
  72. assert.areEqual('class2', class2.static_member(), 'Failed to import class2 from ModuleComplexExports.js');
  73. assert.areEqual('class2', new class2().member(), 'Failed to create intance of class2 from ModuleComplexExports.js');
  74. assert.areEqual('class2', class3.static_member(), 'Failed to import class3 from ModuleComplexExports.js');
  75. assert.areEqual('class2', new class3().member(), 'Failed to create intance of class3 from ModuleComplexExports.js');
  76. assert.areEqual('class4', class4.static_member(), 'Failed to import class4 from ModuleComplexExports.js');
  77. assert.areEqual('class4', new class4().member(), 'Failed to create intance of class4 from ModuleComplexExports.js');
  78. assert.areEqual('class4', class5.static_member(), 'Failed to import class4 from ModuleComplexExports.js');
  79. assert.areEqual('class4', new class5().member(), 'Failed to create intance of class4 from ModuleComplexExports.js');
  80. import _default from 'ModuleComplexExports.js';
  81. assert.areEqual('default', _default(), 'Failed to import default from ModuleComplexExports.js');
  82. `;
  83. testModuleScript(functionBody, "Test importing a variety of exports", false);
  84. }
  85. },
  86. {
  87. name: "Import an export as a different binding identifier",
  88. body: function () {
  89. let functionBody =
  90. `import { ModuleSimpleExport_foo as foo3 } from 'ModuleSimpleExport.js';
  91. assert.areEqual('ModuleSimpleExport', foo3(), 'Failed to import ModuleSimpleExport_foo from ModuleSimpleExport.js');
  92. import { foo2 as foo4 } from 'ModuleComplexExports.js';
  93. assert.areEqual('foo', foo4(), 'Failed to import foo4 from ModuleComplexExports.js');`;
  94. testModuleScript(functionBody, "Test importing as different binding identifiers", false);
  95. }
  96. },
  97. {
  98. name: "Import the same export under multiple local binding identifiers",
  99. body: function () {
  100. let functionBody =
  101. `import { ModuleSimpleExport_foo as foo3, ModuleSimpleExport_foo as foo4 } from 'ModuleSimpleExport.js';
  102. assert.areEqual('ModuleSimpleExport', foo3(), 'Failed to import ModuleSimpleExport_foo from ModuleSimpleExport.js');
  103. assert.areEqual('ModuleSimpleExport', foo4(), 'Failed to import ModuleSimpleExport_foo from ModuleSimpleExport.js');
  104. assert.isTrue(foo3 === foo4, 'Export has the same value even if rebound');`;
  105. testModuleScript(functionBody, "Test importing the same export under multiple binding identifier", false);
  106. }
  107. },
  108. {
  109. name: "Exporting module changes exported value",
  110. body: function () {
  111. let functionBody =
  112. `import { target, changeTarget } from 'ModuleComplexExports.js';
  113. assert.areEqual('before', target(), 'Failed to import target from ModuleComplexExports.js');
  114. assert.areEqual('ok', changeTarget(), 'Failed to import changeTarget from ModuleComplexExports.js');
  115. assert.areEqual('after', target(), 'changeTarget failed to change export value');`;
  116. testModuleScript(functionBody, "Changing exported value", false);
  117. }
  118. },
  119. {
  120. name: "Simple re-export forwards import to correct slot",
  121. body: function () {
  122. let functionBody =
  123. `import { ModuleSimpleExport_foo } from 'ModuleSimpleReexport.js';
  124. assert.areEqual('ModuleSimpleExport', ModuleSimpleExport_foo(), 'Failed to import ModuleSimpleExport_foo from ModuleSimpleReexport.js');`;
  125. testModuleScript(functionBody, "Simple re-export from one module to another", false);
  126. }
  127. },
  128. {
  129. name: "Import of renamed re-export forwards import to correct slot",
  130. body: function () {
  131. let functionBody =
  132. `import { ModuleSimpleExport_foo as ModuleSimpleExport_baz } from 'ModuleSimpleReexport.js';
  133. assert.areEqual('ModuleSimpleExport', ModuleSimpleExport_baz(), 'Failed to import ModuleSimpleExport_foo from ModuleSimpleReexport.js');`;
  134. testModuleScript(functionBody, "Rename simple re-export", false);
  135. }
  136. },
  137. {
  138. name: "Renamed re-export and renamed import",
  139. body: function () {
  140. let functionBody =
  141. `import { ModuleComplexReexports_foo as ModuleComplexReexports_baz } from 'ModuleComplexReexports.js';
  142. assert.areEqual('bar', ModuleComplexReexports_baz(), 'Failed to import ModuleComplexReexports_foo from ModuleComplexReexports.js');`;
  143. testModuleScript(functionBody, "Rename already renamed re-export", false);
  144. }
  145. },
  146. {
  147. name: "Explicit export/import to default binding",
  148. body: function () {
  149. let functionBody =
  150. `import { default as baz } from 'ModuleDefaultExport1.js';
  151. assert.areEqual('ModuleDefaultExport1', baz(), 'Failed to import default from ModuleDefaultExport1.js');`;
  152. testModuleScript(functionBody, "Explicitly export and import a local name to the default binding", false);
  153. }
  154. },
  155. {
  156. name: "Explicit import of default binding",
  157. body: function () {
  158. let functionBody =
  159. `import { default as baz } from 'ModuleDefaultExport2.js';
  160. assert.areEqual('ModuleDefaultExport2', baz(), 'Failed to import default from ModuleDefaultExport2.js');`;
  161. testModuleScript(functionBody, "Explicitly import the default export binding", false);
  162. }
  163. },
  164. {
  165. name: "Implicitly re-export default export",
  166. body: function () {
  167. let functionBody =
  168. `import baz from 'ModuleDefaultReexport.js';
  169. assert.areEqual('ModuleDefaultExport1', baz(), 'Failed to import default from ModuleDefaultReexport.js');`;
  170. testModuleScript(functionBody, "Implicitly re-export the default export binding", false);
  171. }
  172. },
  173. {
  174. name: "Implicitly re-export default export and rename the imported binding",
  175. body: function () {
  176. let functionBody =
  177. `import { default as baz } from 'ModuleDefaultReexport.js';
  178. assert.areEqual('ModuleDefaultExport1', baz(), 'Failed to import default from ModuleDefaultReexport.js');
  179. import { not_default as bat } from 'ModuleDefaultReexport.js';
  180. assert.areEqual('ModuleDefaultExport2', bat(), 'Failed to import not_default from ModuleDefaultReexport.js');`;
  181. testModuleScript(functionBody, "Implicitly re-export the default export binding and rename the import binding", false);
  182. }
  183. },
  184. {
  185. name: "Exporting module changes value of default export",
  186. body: function () {
  187. let functionBody =
  188. `import ModuleDefaultExport3_default from 'ModuleDefaultExport3.js';
  189. assert.areEqual(2, ModuleDefaultExport3_default, 'Failed to import default from ModuleDefaultExport3.js');
  190. import ModuleDefaultExport4_default from 'ModuleDefaultExport4.js';
  191. assert.areEqual(1, ModuleDefaultExport4_default, 'Failed to import not_default from ModuleDefaultExport4.js');`;
  192. testModuleScript(functionBody, "Exported value incorrectly bound", false);
  193. }
  194. },
  195. {
  196. name: "Import bindings used in a nested function",
  197. body: function () {
  198. let functionBody =
  199. `function test() {
  200. assert.areEqual('ModuleDefaultExport2', foo(), 'Failed to import default from ModuleDefaultExport2.js');
  201. }
  202. test();
  203. import foo from 'ModuleDefaultExport2.js';
  204. test();`;
  205. testModuleScript(functionBody, "Failed to find imported name correctly in nested function", false);
  206. }
  207. },
  208. {
  209. name: "Exported name may be any keyword",
  210. body: function () {
  211. let functionBody =
  212. `import { export as baz } from 'ModuleComplexExports.js';
  213. assert.areEqual('ModuleComplexExports', baz, 'Failed to import export from ModuleDefaultExport2.js');
  214. import { function as bat } from 'ModuleComplexExports.js';
  215. assert.areEqual('ModuleComplexExports', bat, 'Failed to import function from ModuleDefaultExport2.js');`;
  216. testModuleScript(functionBody, "Exported name may be a keyword (import binding must be binding identifier)", false);
  217. }
  218. },
  219. {
  220. name: "Import binding of a keyword-named export may not be a keyword unless it is bound to a different binding identifier",
  221. body: function () {
  222. let functionBody = `import { export } from 'ModuleComplexExports.js';`;
  223. testModuleScript(functionBody, "Import binding must be binding identifier even if export name is not (export)", true);
  224. functionBody = `import { function } from 'ModuleComplexExports.js';`;
  225. testModuleScript(functionBody, "Import binding must be binding identifier even if export name is not (function)", true);
  226. functionBody = `import { switch } from 'ModuleComplexReexports.js';`;
  227. testModuleScript(functionBody, "Import binding must be binding identifier even if re-export name is not (switch)", true);
  228. }
  229. },
  230. {
  231. name: "Exported name may be any keyword testing re-exports",
  232. body: function () {
  233. let functionBody =
  234. `import { switch as baz } from 'ModuleComplexReexports.js';
  235. assert.areEqual('ModuleComplexExports', baz, 'Failed to import switch from ModuleComplexReexports.js');`;
  236. testModuleScript(functionBody, "Exported name may be a keyword including re-epxort chains", false);
  237. }
  238. },
  239. {
  240. name: "Odd case of 'export { as as as }; import { as as as };'",
  241. body: function () {
  242. let functionBody =
  243. `import { as as as } from 'ModuleComplexExports.js';
  244. assert.areEqual('as', as(), 'String "as" is not reserved word');`;
  245. testModuleScript(functionBody, "Test 'import { as as as}'", false);
  246. }
  247. },
  248. {
  249. name: "Typeof a module export",
  250. body: function () {
  251. let functionBody =
  252. `import _default from 'ModuleDefaultExport2.js';
  253. assert.areEqual('function', typeof _default, 'typeof default export from ModuleDefaultExport2.js is function');`;
  254. WScript.LoadModule(functionBody, 'samethread');
  255. }
  256. },
  257. {
  258. name: "Circular module dependency",
  259. body: function () {
  260. let functionBody =
  261. `import { circular_foo } from 'ModuleCircularFoo.js';
  262. assert.areEqual(2, circular_foo(), 'This function calls between both modules in the circular dependency incrementing a counter in each');
  263. import { circular_bar } from 'ModuleCircularBar.js';
  264. assert.areEqual(4, circular_bar(), 'Second call originates in the other module but still increments the counter twice');`;
  265. WScript.LoadModule(functionBody, 'samethread');
  266. }
  267. },
  268. {
  269. name: "Implicitly re-exporting an import binding (import { foo } from ''; export { foo };)",
  270. body: function () {
  271. let functionBody =
  272. `import { foo, baz, localfoo, bar, localfoo2, bar2, bar2 as bar3 } from 'ModuleComplexReexports.js';
  273. assert.areEqual('foo', foo(), 'Simple implicit re-export');
  274. assert.areEqual('foo', baz(), 'Renamed export imported and renamed during implicit re-export');
  275. assert.areEqual('foo', localfoo(), 'Export renamed as import and implicitly re-exported');
  276. assert.areEqual('foo', bar(), 'Renamed export renamed as import and renamed again during implicit re-exported');
  277. assert.areEqual('foo', localfoo2(), 'Renamed export renamed as import and implicitly re-exported');
  278. assert.areEqual('foo', bar2(), 'Renamed export renamed as import and renamed again during implicit re-export');
  279. assert.areEqual('foo', bar3(), 'Renamed export renamed as import renamed during implicit re-export and renamed in final import');`;
  280. WScript.LoadModule(functionBody, 'samethread');
  281. }
  282. },
  283. {
  284. name: "Nested function in module function body which captures exported symbol doesn't create empty frame object",
  285. body: function() {
  286. let functionBody =
  287. `function foo() { };
  288. export { foo };
  289. function bar() { foo(); };`;
  290. WScript.LoadModule(functionBody, 'samethread');
  291. }
  292. },
  293. ];
  294. testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" });