import { expect, test } from "bun:test"; import { readFileSync } from "node:fs"; import { join } from "node:path"; import { createBrowserRunner } from "../src/web/runner/runner"; import { transpileTs } from "../src/server/transpile"; import { FIXTURES } from "./app"; const ex = (f: string) => readFileSync(join(FIXTURES, "sample/exercises/add", f), "utf8"); const js = createBrowserRunner("js"); test("the sample starter fails with expected and actual values", async () => { const r = await js.run(ex("starter.js"), ex("tests.js"), 5000); expect(r).toEqual([ { name: "adds 2 and 3", passed: false, message: "Expected values to be strictly equal", expected: "5", actual: "-1" }, { name: "adds -1 and 1", passed: false, message: "Expected values to be strictly equal", expected: "0", actual: "-2" }, ]); }); test("the sample exercise passes after editing", async () => { const edited = ex("starter.js").replace("a - b", "a + b"); expect((await js.run(edited, ex("tests.js"), 5000)).every((t) => t.passed)).toBe(true); }); test("syntax errors and missing exports are reported per test", async () => { const r = await js.run("export function add(a, b) { return a +", ex("tests.js"), 5000); expect(r.length).toBe(2); expect(r[0]!.passed).toBe(false); expect(r[0]!.message).toContain("did not load"); const r2 = await js.run("export const nope = 1", ex("tests.js"), 5000); expect(r2[0]!.message).toContain("TypeError"); }); test("an endless loop is killed by the timeout", async () => { const t0 = Date.now(); const r = await js.run("export function add() { while (true) {} }", ex("tests.js"), 400); expect(Date.now() - t0).toBeLessThan(3000); expect(r.map((x) => x.passed)).toEqual([false, false]); expect(r[0]!.message).toContain("Timed out"); }); test("network access is blocked inside the worker", async () => { const tests = `export const tests = [{ name: "net", run: async (m, assert) => assert.equal(await m.probe(), "blocked") }];`; const code = `export async function probe() { try { await fetch("http://127.0.0.1:1/"); return "open"; } catch (e) { return String(e.message).includes("turned off") ? "blocked" : "other " + e.message; } }`; expect((await js.run(code, tests, 3000))[0]!.passed).toBe(true); }); test("deepEqual compares structures", async () => { const tests = `export const tests = [ { name: "same", run: (m, a) => a.deepEqual(m.v(), { x: [1, 2], y: { z: null } }) }, { name: "diff", run: (m, a) => a.deepEqual(m.v(), { x: [1, 3] }) }, ];`; const r = await js.run("export const v = () => ({ x: [1, 2], y: { z: null } })", tests, 3000); expect(r[0]!.passed).toBe(true); expect(r[1]).toMatchObject({ passed: false, expected: "{x: [1, 3]}", actual: "{x: [1, 2], y: {z: null}}" }); }); test("TypeScript exercises are transpiled first", async () => { const runner = createBrowserRunner("ts", { transpile: async (c) => transpileTs(c) }); const tests = `import type { X } from "./x"; export const tests = [{ name: "typed", run: (m: any, a: any) => a.equal(m.add(1, 2), 3) }];`; const r = await runner.run("export function add(a: number, b: number): number { return a + b; }", tests, 3000); expect(r).toEqual([{ name: "typed", passed: true }]); });