| 
									
										
										
										
											2022-01-18 19:39:36 +01:00
										 |  |  | // Because you can't easily load modules directly we load them via here and check
 | 
					
						
							|  |  |  | // if they passed by checking the result
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-27 01:54:47 +01:00
										 |  |  | function validTestModule(filename) { | 
					
						
							| 
									
										
										
										
											2022-01-18 19:39:36 +01:00
										 |  |  |     if (!filename.endsWith(".mjs") || !filename.startsWith("./")) { | 
					
						
							|  |  |  |         throw new ExpectationError( | 
					
						
							| 
									
										
										
										
											2022-01-27 01:54:47 +01:00
										 |  |  |             `Expected module name to start with './' and end with '.mjs' but got '${filename}'` | 
					
						
							| 
									
										
										
										
											2022-01-18 19:39:36 +01:00
										 |  |  |         ); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-01-27 01:54:47 +01:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2022-01-18 19:39:36 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-27 02:44:03 +01:00
										 |  |  | function expectModulePassed(filename, options = undefined) { | 
					
						
							| 
									
										
										
										
											2022-01-27 01:54:47 +01:00
										 |  |  |     validTestModule(filename); | 
					
						
							| 
									
										
										
										
											2022-01-18 19:39:36 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     let moduleLoaded = false; | 
					
						
							|  |  |  |     let moduleResult = null; | 
					
						
							|  |  |  |     let thrownError = null; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-27 02:44:03 +01:00
										 |  |  |     import(filename, options) | 
					
						
							| 
									
										
										
										
											2022-01-18 19:39:36 +01:00
										 |  |  |         .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; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-27 01:54:47 +01:00
										 |  |  | 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.` | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-18 19:39:36 +01:00
										 |  |  | 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"); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2022-01-27 02:44:03 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     test("can call expectModulePassed with options", () => { | 
					
						
							|  |  |  |         expectModulePassed("./single-const-export.mjs", { key: "value" }); | 
					
						
							|  |  |  |         expectModulePassed("./single-const-export.mjs", { key1: "value1", key2: "value2" }); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2022-01-18 19:39:36 +01:00
										 |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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"); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2022-01-27 01:54:47 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     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'" | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2022-01-27 02:44:03 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     test("can import with (useless) assertions", () => { | 
					
						
							|  |  |  |         expectModulePassed("./import-with-assertions.mjs"); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2022-08-31 20:31:02 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     test("namespace has expected ordering", () => { | 
					
						
							|  |  |  |         expectModulePassed("./namespace-order.mjs"); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2022-09-01 22:55:02 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     test("can have multiple star imports even from the same file", () => { | 
					
						
							|  |  |  |         expectModulePassed("./multiple-star-imports.mjs"); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2022-09-01 23:13:45 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     test("can export namespace via binding", () => { | 
					
						
							|  |  |  |         expectModulePassed("./re-export-namespace-via-binding.mjs"); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2022-09-01 23:48:57 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     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"); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2022-09-02 00:46:37 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     test("exporting anonymous function", () => { | 
					
						
							|  |  |  |         expectModulePassed("./anon-func-decl-default-export.mjs"); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2022-12-20 22:09:57 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-22 17:30:15 +12:00
										 |  |  |     test.xfailIf( | 
					
						
							|  |  |  |         isBytecodeInterpreterEnabled(), | 
					
						
							|  |  |  |         "can have top level using declarations which trigger at the end of running a module", | 
					
						
							|  |  |  |         () => { | 
					
						
							|  |  |  |             expectModulePassed("./top-level-dispose.mjs"); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2023-05-28 00:08:52 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     test("can export default a RegExp", () => { | 
					
						
							|  |  |  |         const result = expectModulePassed("./default-regexp-export.mjs"); | 
					
						
							|  |  |  |         expect(result.default).toBeInstanceOf(RegExp); | 
					
						
							|  |  |  |         expect(result.default.toString()).toBe(/foo/.toString()); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2022-01-18 19:39:36 +01:00
										 |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | describe("loops", () => { | 
					
						
							|  |  |  |     test("import and export from own file", () => { | 
					
						
							|  |  |  |         expectModulePassed("./loop-self.mjs"); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     test("import something which imports a cycle", () => { | 
					
						
							|  |  |  |         expectModulePassed("./loop-entry.mjs"); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | }); | 
					
						
							| 
									
										
										
										
											2022-08-29 22:12:25 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 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 | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | }); | 
					
						
							| 
									
										
										
										
											2022-11-15 01:39:07 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | describe("scoping in modules", () => { | 
					
						
							|  |  |  |     test("functions within functions", () => { | 
					
						
							|  |  |  |         expectModulePassed("./function-in-function.mjs"); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | }); |