Skip to content

Commit 1ccf969

Browse files
feat: drop support for function tests and unawaited promises (#406)
* feat: drop support for function tests and unawaited promises () => assert(...) will simply be evaluated, the function will no longer get called. new Promise(...) will not be awaited, but await new Promise(...) will be. This is much closer to how normal javascript and normal tests behave. * fix: ignore returned Promises in python tests
1 parent abecbd9 commit 1ccf969

File tree

4 files changed

+52
-92
lines changed

4 files changed

+52
-92
lines changed

packages/dom-evaluator/src/dom-test-evaluator.ts

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -129,31 +129,10 @@ export class DOMTestEvaluator implements TestEvaluator {
129129
this.#runTest = async function (rawTest: string): Promise<Fail | Pass> {
130130
this.#proxyConsole.on();
131131
try {
132-
let test;
133-
try {
134-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
135-
test = await eval(`${opts.hooks?.beforeEach ?? ""}
136-
${rawTest}`);
137-
} catch (err) {
138-
if (
139-
err instanceof SyntaxError &&
140-
err.message.includes(
141-
"await is only valid in async functions and the top level bodies of modules",
142-
)
143-
) {
144-
const iifeTest = createAsyncIife(rawTest);
145-
// There's no need to assign this to 'test', since it replaces that
146-
// functionality.
147-
await eval(iifeTest);
148-
} else {
149-
throw err;
150-
}
151-
}
132+
const test = createAsyncIife(rawTest);
152133

153-
if (typeof test === "function") {
154-
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
155-
await test();
156-
}
134+
await eval(`${opts.hooks?.beforeEach ?? ""}
135+
${test}`);
157136

158137
return { pass: true, ...this.#proxyConsole.flush() };
159138
} catch (err) {

packages/python-evaluator/src/python-test-evaluator.ts

Lines changed: 31 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -112,35 +112,36 @@ input = __fake_input
112112

113113
try {
114114
eval(opts.hooks?.beforeEach ?? "");
115-
// Eval test string to get the dummy input and actual test
116-
const evaluatedTestString = await new Promise<unknown>(
117-
(resolve, reject) => {
118-
try {
119-
const test: unknown = eval(testString);
120-
resolve(test);
121-
} catch (err) {
122-
const isUsingTopLevelAwait =
123-
err instanceof SyntaxError &&
124-
err.message.includes(
125-
"await is only valid in async functions and the top level bodies of modules",
126-
);
127-
128-
if (isUsingTopLevelAwait) {
129-
const iifeTest = createAsyncIife(testString);
130-
131-
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
132-
eval(iifeTest).then(resolve).catch(reject);
133-
} else {
134-
// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
135-
reject(err);
136-
}
137-
}
138-
},
139-
);
140-
141-
// If the test string does not evaluate to an object, then we assume that
142-
// it's a standard JS test and any assertions have already passed.
143-
if (typeof evaluatedTestString !== "object") {
115+
// Eval test string to get the test property
116+
117+
let evaluatedTestString;
118+
119+
try {
120+
evaluatedTestString = eval(testString) as EvaluatedTeststring;
121+
} catch (err) {
122+
const isUsingTopLevelAwait =
123+
err instanceof SyntaxError &&
124+
err.message.includes(
125+
"await is only valid in async functions and the top level bodies of modules",
126+
);
127+
128+
if (isUsingTopLevelAwait) {
129+
const iifeTest = createAsyncIife(testString);
130+
131+
await eval(iifeTest);
132+
} else {
133+
throw err;
134+
}
135+
}
136+
137+
// If the test string does not evaluate to an object, then we assume
138+
// that it's a standard JS test and any assertions have already passed.
139+
// Finally, if it is a Promise, that should have been awaited so we
140+
// ignore it.
141+
if (
142+
typeof evaluatedTestString !== "object" ||
143+
evaluatedTestString instanceof Promise
144+
) {
144145
// Execute afterEach hook if it exists
145146
if (opts.hooks?.afterEach) eval(opts.hooks.afterEach);
146147

@@ -153,7 +154,7 @@ input = __fake_input
153154
);
154155
}
155156

156-
const { test } = evaluatedTestString as EvaluatedTeststring;
157+
const { test } = evaluatedTestString;
157158

158159
// Evaluates the learner's code so that any variables they define are
159160
// available to the test.

packages/tests/dom-test-evaluator.test.ts

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -83,21 +83,5 @@ describe("DOMTestEvaluator", () => {
8383

8484
expect(result).toStrictEqual({ pass: true });
8585
});
86-
87-
it("should await tests that return promises", async () => {
88-
const test = `new Promise((resolve) => setTimeout(resolve, 1))
89-
.then(() => { assert.equal(1,2)});`;
90-
91-
const result = await evaluator.runTest(test);
92-
expect(result).toStrictEqual({
93-
err: {
94-
message: "expected 1 to equal 2",
95-
expected: 2,
96-
actual: 1,
97-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
98-
stack: expect.stringMatching("AssertionError: expected"),
99-
},
100-
});
101-
});
10286
});
10387
});

packages/tests/integration-tests/index.test.ts

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -536,26 +536,24 @@ for(let i = 0; i < 3; i++) {
536536
expect(result).toMatchObject({ err: { actual: 1 } });
537537
});
538538

539-
// The old api wasn't consistent: only DOM tests supported async tests, but
540-
// this needs to be maintained while it's deprecated.
541-
it.runIf(type === "dom")(
542-
"should support `async () => {}` style tests",
543-
async () => {
544-
const result = await page.evaluate(async (type) => {
545-
const runner = await window.FCCTestRunner.createTestRunner({
546-
type,
547-
});
539+
it("should not await tests that return promises", async () => {
540+
const result = await page.evaluate(async (type) => {
541+
const runner = await window.FCCTestRunner.createTestRunner({
542+
type,
543+
});
548544

549-
return runner.runTest(`
550-
async () => {
551-
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
552-
await delay(10);
553-
assert.equal(1, 2);
554-
}`);
555-
}, type);
556-
expect(result).toMatchObject({ err: { actual: 1 } });
557-
},
558-
);
545+
return runner.runTest(`
546+
new Promise((resolve) => {
547+
setTimeout(() => {
548+
console.log("This should not be awaited");
549+
resolve();
550+
}, 10);
551+
});
552+
`);
553+
}, type);
554+
// No logs should be returned, as the test should not be awaited
555+
expect(result).toEqual({ pass: true });
556+
});
559557

560558
it("should fail the test if the afterEach hook throws an error", async () => {
561559
const result = await page.evaluate(async (type) => {
@@ -936,9 +934,7 @@ const getFive = () => 5;
936934
source,
937935
type: "dom",
938936
});
939-
return runner.runTest(
940-
"async () => await document.getElementById('audio').play()",
941-
);
937+
return runner.runTest("await document.getElementById('audio').play()");
942938
}, source);
943939

944940
// If it were unable to play, it would throw "play() failed because the

0 commit comments

Comments
 (0)