//------------------------------------------------------------------------------------------------------- // Copyright (C) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. //------------------------------------------------------------------------------------------------------- // ES6 Module functionality tests -- verifies functionality of import and export statements WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js"); function testModuleScript(source, message, shouldFail) { let testfunc = () => WScript.LoadModule(source, 'samethread'); if (shouldFail) { let caught = false; // We can't use assert.throws here because the SyntaxError used to construct the thrown error // is from a different context so it won't be strictly equal to our SyntaxError. try { testfunc(); } catch(e) { caught = true; // Compare toString output of SyntaxError and other context SyntaxError constructor. assert.areEqual(e.constructor.toString(), SyntaxError.toString(), message); } assert.isTrue(caught, `Expected error not thrown: ${message}`); } else { assert.doesNotThrow(testfunc, message); } } var tests = [ { name: "Validate a simple module export", body: function () { let functionBody = `import { ModuleSimpleExport_foo } from 'ModuleSimpleExport.js'; assert.areEqual('ModuleSimpleExport', ModuleSimpleExport_foo(), 'Failed to import ModuleSimpleExport_foo from ModuleSimpleExport.js');`; testModuleScript(functionBody, "Test importing a simple exported function", false); } }, { name: "Validate importing from multiple modules", body: function () { let functionBody = `import { ModuleSimpleExport_foo } from 'ModuleSimpleExport.js'; assert.areEqual('ModuleSimpleExport', ModuleSimpleExport_foo(), 'Failed to import ModuleSimpleExport_foo from ModuleSimpleExport.js'); import { foo2 } from 'ModuleComplexExports.js'; assert.areEqual('foo', foo2(), 'Failed to import foo2 from ModuleComplexExports.js');`; testModuleScript(functionBody, "Test importing from multiple modules", false); } }, { name: "Validate a variety of more complex exports", body: function () { let functionBody = `import { foo, foo2 } from 'ModuleComplexExports.js'; assert.areEqual('foo', foo(), 'Failed to import foo from ModuleComplexExports.js'); assert.areEqual('foo', foo2(), 'Failed to import foo2 from ModuleComplexExports.js'); import { bar, bar2 } from 'ModuleComplexExports.js'; assert.areEqual('bar', bar(), 'Failed to import bar from ModuleComplexExports.js'); assert.areEqual('bar', bar2(), 'Failed to import bar2 from ModuleComplexExports.js'); import { let2, let3, let4, let5 } from 'ModuleComplexExports.js'; assert.areEqual('let2', let2, 'Failed to import let2 from ModuleComplexExports.js'); assert.areEqual('let3', let3, 'Failed to import let3 from ModuleComplexExports.js'); assert.areEqual('let2', let4, 'Failed to import let4 from ModuleComplexExports.js'); assert.areEqual('let3', let5, 'Failed to import let5 from ModuleComplexExports.js'); import { const2, const3, const4, const5 } from 'ModuleComplexExports.js'; assert.areEqual('const2', const2, 'Failed to import const2 from ModuleComplexExports.js'); assert.areEqual('const3', const3, 'Failed to import const3 from ModuleComplexExports.js'); assert.areEqual('const2', const4, 'Failed to import const4 from ModuleComplexExports.js'); assert.areEqual('const3', const5, 'Failed to import const5 from ModuleComplexExports.js'); import { var2, var3, var4, var5 } from 'ModuleComplexExports.js'; assert.areEqual('var2', var2, 'Failed to import var2 from ModuleComplexExports.js'); assert.areEqual('var3', var3, 'Failed to import var3 from ModuleComplexExports.js'); assert.areEqual('var2', var4, 'Failed to import var4 from ModuleComplexExports.js'); assert.areEqual('var3', var5, 'Failed to import var5 from ModuleComplexExports.js'); import { class2, class3, class4, class5 } from 'ModuleComplexExports.js'; assert.areEqual('class2', class2.static_member(), 'Failed to import class2 from ModuleComplexExports.js'); assert.areEqual('class2', new class2().member(), 'Failed to create intance of class2 from ModuleComplexExports.js'); assert.areEqual('class2', class3.static_member(), 'Failed to import class3 from ModuleComplexExports.js'); assert.areEqual('class2', new class3().member(), 'Failed to create intance of class3 from ModuleComplexExports.js'); assert.areEqual('class4', class4.static_member(), 'Failed to import class4 from ModuleComplexExports.js'); assert.areEqual('class4', new class4().member(), 'Failed to create intance of class4 from ModuleComplexExports.js'); assert.areEqual('class4', class5.static_member(), 'Failed to import class4 from ModuleComplexExports.js'); assert.areEqual('class4', new class5().member(), 'Failed to create intance of class4 from ModuleComplexExports.js'); import _default from 'ModuleComplexExports.js'; assert.areEqual('default', _default(), 'Failed to import default from ModuleComplexExports.js'); `; testModuleScript(functionBody, "Test importing a variety of exports", false); } }, { name: "Import an export as a different binding identifier", body: function () { let functionBody = `import { ModuleSimpleExport_foo as foo3 } from 'ModuleSimpleExport.js'; assert.areEqual('ModuleSimpleExport', foo3(), 'Failed to import ModuleSimpleExport_foo from ModuleSimpleExport.js'); import { foo2 as foo4 } from 'ModuleComplexExports.js'; assert.areEqual('foo', foo4(), 'Failed to import foo4 from ModuleComplexExports.js');`; testModuleScript(functionBody, "Test importing as different binding identifiers", false); } }, { name: "Import the same export under multiple local binding identifiers", body: function () { let functionBody = `import { ModuleSimpleExport_foo as foo3, ModuleSimpleExport_foo as foo4 } from 'ModuleSimpleExport.js'; assert.areEqual('ModuleSimpleExport', foo3(), 'Failed to import ModuleSimpleExport_foo from ModuleSimpleExport.js'); assert.areEqual('ModuleSimpleExport', foo4(), 'Failed to import ModuleSimpleExport_foo from ModuleSimpleExport.js'); assert.isTrue(foo3 === foo4, 'Export has the same value even if rebound');`; testModuleScript(functionBody, "Test importing the same export under multiple binding identifier", false); } }, { name: "Exporting module changes exported value", body: function () { let functionBody = `import { target, changeTarget } from 'ModuleComplexExports.js'; assert.areEqual('before', target(), 'Failed to import target from ModuleComplexExports.js'); assert.areEqual('ok', changeTarget(), 'Failed to import changeTarget from ModuleComplexExports.js'); assert.areEqual('after', target(), 'changeTarget failed to change export value');`; testModuleScript(functionBody, "Changing exported value", false); } }, { name: "Simple re-export forwards import to correct slot", body: function () { let functionBody = `import { ModuleSimpleExport_foo } from 'ModuleSimpleReexport.js'; assert.areEqual('ModuleSimpleExport', ModuleSimpleExport_foo(), 'Failed to import ModuleSimpleExport_foo from ModuleSimpleReexport.js');`; testModuleScript(functionBody, "Simple re-export from one module to another", false); } }, { name: "Import of renamed re-export forwards import to correct slot", body: function () { let functionBody = `import { ModuleSimpleExport_foo as ModuleSimpleExport_baz } from 'ModuleSimpleReexport.js'; assert.areEqual('ModuleSimpleExport', ModuleSimpleExport_baz(), 'Failed to import ModuleSimpleExport_foo from ModuleSimpleReexport.js');`; testModuleScript(functionBody, "Rename simple re-export", false); } }, { name: "Renamed re-export and renamed import", body: function () { let functionBody = `import { ModuleComplexReexports_foo as ModuleComplexReexports_baz } from 'ModuleComplexReexports.js'; assert.areEqual('bar', ModuleComplexReexports_baz(), 'Failed to import ModuleComplexReexports_foo from ModuleComplexReexports.js');`; testModuleScript(functionBody, "Rename already renamed re-export", false); } }, { name: "Explicit export/import to default binding", body: function () { let functionBody = `import { default as baz } from 'ModuleDefaultExport1.js'; assert.areEqual('ModuleDefaultExport1', baz(), 'Failed to import default from ModuleDefaultExport1.js');`; testModuleScript(functionBody, "Explicitly export and import a local name to the default binding", false); } }, { name: "Explicit import of default binding", body: function () { let functionBody = `import { default as baz } from 'ModuleDefaultExport2.js'; assert.areEqual('ModuleDefaultExport2', baz(), 'Failed to import default from ModuleDefaultExport2.js');`; testModuleScript(functionBody, "Explicitly import the default export binding", false); } }, { name: "Implicitly re-export default export", body: function () { let functionBody = `import baz from 'ModuleDefaultReexport.js'; assert.areEqual('ModuleDefaultExport1', baz(), 'Failed to import default from ModuleDefaultReexport.js');`; testModuleScript(functionBody, "Implicitly re-export the default export binding", false); } }, { name: "Implicitly re-export default export and rename the imported binding", body: function () { let functionBody = `import { default as baz } from 'ModuleDefaultReexport.js'; assert.areEqual('ModuleDefaultExport1', baz(), 'Failed to import default from ModuleDefaultReexport.js'); import { not_default as bat } from 'ModuleDefaultReexport.js'; assert.areEqual('ModuleDefaultExport2', bat(), 'Failed to import not_default from ModuleDefaultReexport.js');`; testModuleScript(functionBody, "Implicitly re-export the default export binding and rename the import binding", false); } }, { name: "Exporting module changes value of default export", body: function () { let functionBody = `import ModuleDefaultExport3_default from 'ModuleDefaultExport3.js'; assert.areEqual(2, ModuleDefaultExport3_default, 'Failed to import default from ModuleDefaultExport3.js'); import ModuleDefaultExport4_default from 'ModuleDefaultExport4.js'; assert.areEqual(1, ModuleDefaultExport4_default, 'Failed to import not_default from ModuleDefaultExport4.js');`; testModuleScript(functionBody, "Exported value incorrectly bound", false); } }, { name: "Import bindings used in a nested function", body: function () { let functionBody = `function test() { assert.areEqual('ModuleDefaultExport2', foo(), 'Failed to import default from ModuleDefaultExport2.js'); } test(); import foo from 'ModuleDefaultExport2.js'; test();`; testModuleScript(functionBody, "Failed to find imported name correctly in nested function", false); } }, { name: "Exported name may be any keyword", body: function () { let functionBody = `import { export as baz } from 'ModuleComplexExports.js'; assert.areEqual('ModuleComplexExports', baz, 'Failed to import export from ModuleDefaultExport2.js'); import { function as bat } from 'ModuleComplexExports.js'; assert.areEqual('ModuleComplexExports', bat, 'Failed to import function from ModuleDefaultExport2.js');`; testModuleScript(functionBody, "Exported name may be a keyword (import binding must be binding identifier)", false); } }, { name: "Import binding of a keyword-named export may not be a keyword unless it is bound to a different binding identifier", body: function () { let functionBody = `import { export } from 'ModuleComplexExports.js';`; testModuleScript(functionBody, "Import binding must be binding identifier even if export name is not (export)", true); functionBody = `import { function } from 'ModuleComplexExports.js';`; testModuleScript(functionBody, "Import binding must be binding identifier even if export name is not (function)", true); functionBody = `import { switch } from 'ModuleComplexReexports.js';`; testModuleScript(functionBody, "Import binding must be binding identifier even if re-export name is not (switch)", true); } }, { name: "Exported name may be any keyword testing re-exports", body: function () { let functionBody = `import { switch as baz } from 'ModuleComplexReexports.js'; assert.areEqual('ModuleComplexExports', baz, 'Failed to import switch from ModuleComplexReexports.js');`; testModuleScript(functionBody, "Exported name may be a keyword including re-epxort chains", false); } }, { name: "Odd case of 'export { as as as }; import { as as as };'", body: function () { let functionBody = `import { as as as } from 'ModuleComplexExports.js'; assert.areEqual('as', as(), 'String "as" is not reserved word');`; testModuleScript(functionBody, "Test 'import { as as as}'", false); } }, { name: "Typeof a module export", body: function () { let functionBody = `import _default from 'ModuleDefaultExport2.js'; assert.areEqual('function', typeof _default, 'typeof default export from ModuleDefaultExport2.js is function');`; WScript.LoadModule(functionBody, 'samethread'); } }, { name: "Circular module dependency", body: function () { let functionBody = `import { circular_foo } from 'ModuleCircularFoo.js'; assert.areEqual(2, circular_foo(), 'This function calls between both modules in the circular dependency incrementing a counter in each'); import { circular_bar } from 'ModuleCircularBar.js'; assert.areEqual(4, circular_bar(), 'Second call originates in the other module but still increments the counter twice');`; WScript.LoadModule(functionBody, 'samethread'); } }, { name: "Implicitly re-exporting an import binding (import { foo } from ''; export { foo };)", body: function () { let functionBody = `import { foo, baz, localfoo, bar, localfoo2, bar2, bar2 as bar3 } from 'ModuleComplexReexports.js'; assert.areEqual('foo', foo(), 'Simple implicit re-export'); assert.areEqual('foo', baz(), 'Renamed export imported and renamed during implicit re-export'); assert.areEqual('foo', localfoo(), 'Export renamed as import and implicitly re-exported'); assert.areEqual('foo', bar(), 'Renamed export renamed as import and renamed again during implicit re-exported'); assert.areEqual('foo', localfoo2(), 'Renamed export renamed as import and implicitly re-exported'); assert.areEqual('foo', bar2(), 'Renamed export renamed as import and renamed again during implicit re-export'); assert.areEqual('foo', bar3(), 'Renamed export renamed as import renamed during implicit re-export and renamed in final import');`; WScript.LoadModule(functionBody, 'samethread'); } }, { name: "Nested function in module function body which captures exported symbol doesn't create empty frame object", body: function() { let functionBody = `function foo() { }; export { foo }; function bar() { foo(); };`; WScript.LoadModule(functionBody, 'samethread'); } }, ]; testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" });