summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibJS/Tests/modules/basic-modules.js
blob: d0806c5e823d1ff84b24522d50f7945a81b8fb3b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
// 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");
    });
});

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
        );
    });
});