1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 18:17:44 +00:00

LibJS: Implement ImportCall and HostImportModuleDynamically

This allows us to load modules from scripts.
This can be dangerous as it can load arbitrary files. Because of that it
fails and throws by default. Currently, only js and JavaScriptTestRunner
enable the default hook.

This also adds tests to test-js which test module code. Because we
form a spec perspective can't "enter" a module this is the easiest way
to run tests without having to modify test-js to have special cases for
modules.
To specify modules in test-js we use the extension '.mjs' this is to
ensure the files are not executed. We do still want to lint these files
so the prettier scripts have changed to look for '.mjs' files as well.
This commit is contained in:
davidot 2022-01-18 19:39:36 +01:00 committed by Linus Groh
parent 023968a489
commit 7cbf4b90e8
17 changed files with 416 additions and 5 deletions

View file

@ -0,0 +1,144 @@
// Because you can't easily load modules directly we load them via here and check
// if they passed by checking the result
function expectModulePassed(filename) {
if (!filename.endsWith(".mjs") || !filename.startsWith("./")) {
throw new ExpectationError(
"Expected module name to start with './' " +
"and end with '.mjs' but got '" +
filename +
"'"
);
}
async function getModule() {
return import(filename);
}
let moduleLoaded = false;
let moduleResult = null;
let thrownError = null;
getModule()
.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;
}
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");
});
});
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");
});
});
describe("loops", () => {
test("import and export from own file", () => {
expectModulePassed("./loop-self.mjs");
});
test("import something which imports a cycle", () => {
expectModulePassed("./loop-entry.mjs");
});
});