// Because you can't easily load modules directly we load them via here and check // if they passed by checking the result function validTestModule(filename) { if (!filename.endsWith(".mjs") || !filename.startsWith("./")) { throw new ExpectationError( `Expected module name to start with './' and end with '.mjs' but got '${filename}'` ); } } function expectModulePassed(filename, options = undefined) { validTestModule(filename); let moduleLoaded = false; let moduleResult = null; let thrownError = null; import(filename, options) .then(result => { moduleLoaded = true; moduleResult = result; expect(moduleResult).toHaveProperty("passed", true); }) .catch(error => { thrownError = error; }); runQueuedPromiseJobs(); if (thrownError) { throw thrownError; } expect(moduleLoaded).toBeTrue(); return moduleResult; } function expectedModuleToThrowSyntaxError(filename, message) { validTestModule(filename); let moduleLoaded = false; let thrownError = null; import(filename) .then(() => { moduleLoaded = true; }) .catch(error => { thrownError = error; }); runQueuedPromiseJobs(); if (thrownError) { expect(() => { throw thrownError; }).toThrowWithMessage(SyntaxError, message); } else { throw new ExpectationError( `Expected module: '${filename}' to fail to load with a syntax error but did not throw.` ); } } describe("testing behavior", () => { // To ensure the other tests are interpreter correctly we first test the underlying // mechanisms so these tests don't use expectModulePassed. test("can load a module", () => { let passed = false; let error = null; import("./empty.mjs") .then(() => { passed = true; }) .catch(err => { error = err; }); runQueuedPromiseJobs(); if (error) throw error; expect(passed).toBeTrue(); }); test("can load a module twice", () => { let passed = false; let error = null; import("./empty.mjs") .then(() => { passed = true; }) .catch(err => { error = err; }); runQueuedPromiseJobs(); if (error) throw error; expect(passed).toBeTrue(); }); test("can retrieve exported value", () => { async function getValue(filename) { const imported = await import(filename); expect(imported).toHaveProperty("passed", true); } let passed = false; let error = null; getValue("./single-const-export.mjs") .then(obj => { passed = true; }) .catch(err => { error = err; }); runQueuedPromiseJobs(); if (error) throw error; expect(passed).toBeTrue(); }); test("expectModulePassed works", () => { expectModulePassed("./single-const-export.mjs"); }); test("can call expectModulePassed with options", () => { expectModulePassed("./single-const-export.mjs", { key: "value" }); expectModulePassed("./single-const-export.mjs", { key1: "value1", key2: "value2" }); }); }); describe("in- and exports", () => { test("variable and lexical declarations", () => { const result = expectModulePassed("./basic-export-types.mjs"); expect(result).not.toHaveProperty("default", null); expect(result).toHaveProperty("constValue", 1); expect(result).toHaveProperty("letValue", 2); expect(result).toHaveProperty("varValue", 3); expect(result).toHaveProperty("namedConstValue", 1 + 3); expect(result).toHaveProperty("namedLetValue", 2 + 3); expect(result).toHaveProperty("namedVarValue", 3 + 3); }); test("default exports", () => { const result = expectModulePassed("./module-with-default.mjs"); expect(result).toHaveProperty("defaultValue"); expect(result.default).toBe(result.defaultValue); }); test("declaration exports which can be used in the module it self", () => { expectModulePassed("./declarations-tests.mjs"); }); test("string '*' is not a full namespace import", () => { expectModulePassed("./string-import-names.mjs"); }); test("can combine string and default exports", () => { expectModulePassed("./string-import-namespace.mjs"); }); test("can re export string names", () => { expectModulePassed("./string-import-namespace-indirect.mjs"); }); test("re exporting all-but-default does not export a default value", () => { expectedModuleToThrowSyntaxError( "./indirect-export-without-default.mjs", "Invalid or ambiguous export entry 'default'" ); }); test("can import with (useless) assertions", () => { expectModulePassed("./import-with-assertions.mjs"); }); test("namespace has expected ordering", () => { expectModulePassed("./namespace-order.mjs"); }); test("can have multiple star imports even from the same file", () => { expectModulePassed("./multiple-star-imports.mjs"); }); test("can export namespace via binding", () => { expectModulePassed("./re-export-namespace-via-binding.mjs"); }); test("import variable before import statement behaves as undefined and non mutable variable", () => { expectModulePassed("./accessing-var-import-before-decl.mjs"); }); test("import lexical binding before import statement behaves as initialized but non mutable binding", () => { expectModulePassed("./accessing-lex-import-before-decl.mjs"); }); test("exporting anonymous function", () => { expectModulePassed("./anon-func-decl-default-export.mjs"); }); test("can have top level using declarations which trigger at the end of running a module", () => { expectModulePassed("./top-level-dispose.mjs"); }); }); describe("loops", () => { test("import and export from own file", () => { expectModulePassed("./loop-self.mjs"); }); test("import something which imports a cycle", () => { expectModulePassed("./loop-entry.mjs"); }); }); describe("failing modules cascade", () => { let failingModuleError = "Left-hand side of postfix"; test("importing a file with a SyntaxError results in a SyntaxError", () => { expectedModuleToThrowSyntaxError("./failing.mjs", failingModuleError); }); test("importing a file without a syntax error which imports a file with a syntax error fails", () => { expectedModuleToThrowSyntaxError("./importing-failing-module.mjs", failingModuleError); }); test("importing a file which re exports a file with a syntax error fails", () => { expectedModuleToThrowSyntaxError("./exporting-from-failing.mjs", failingModuleError); }); test("importing a file re exports nothing from a file with a syntax error fails", () => { expectedModuleToThrowSyntaxError( "./exporting-nothing-from-failing.mjs", failingModuleError ); }); }); describe("scoping in modules", () => { test("functions within functions", () => { expectModulePassed("./function-in-function.mjs"); }); });