import { expect } from "bun:test" import { toValue, fromValue, type Value, toBytecode, run, type Bytecode } from "#reef" import { isEqual } from "../src/value" declare module "bun:test" { interface Matchers { /** * Run bytecode and assert that the result equals a JavaScript value after conversion via toValue() * @example expect(bytecode).toEqualValue(42) * @example expect("PUSH 5\nPUSH 3\nADD").toEqualValue(8) * @example expect([["PUSH", 42]]).toEqualValue(42) */ toEqualValue(expected: any): Promise /** * Run bytecode and assert that the result is null * @example expect(bytecode).toBeNull() * @example expect("PUSH null").toBeNull() */ toBeNull(): Promise /** * Run bytecode and assert that the result is a boolean with the expected value * @example expect(bytecode).toBeBoolean(true) * @example expect("PUSH true").toBeBoolean(true) */ toBeBoolean(expected: boolean): Promise /** * Run bytecode and assert that the result is a number with the expected value * @example expect(bytecode).toBeNumber(42) * @example expect("PUSH 42").toBeNumber(42) */ toBeNumber(expected: number): Promise /** * Run bytecode and assert that the result is a string with the expected value * @example expect(bytecode).toBeString("hello") * @example expect("PUSH \"hello\"").toBeString("hello") */ toBeString(expected: string): Promise /** * Run bytecode and assert that the result is an array with the expected values * @example expect(bytecode).toBeArray([1, 2, 3]) */ toBeArray(expected: any[]): Promise /** * Run bytecode and assert that the result is a dict with the expected key-value pairs * @example expect(bytecode).toBeDict({ x: 10, y: 20 }) */ toBeDict(expected: Record): Promise /** * Run bytecode and assert that the result is a function (Reef or native) * @example expect(bytecode).toBeFunction() */ toBeFunction(): Promise /** * Run bytecode and assert that the result is truthy according to ReefVM semantics * (only null and false are falsy) * @example expect(bytecode).toBeTruthy() */ toBeTruthy(): Promise /** * Run bytecode and assert that the result is falsy according to ReefVM semantics * (only null and false are falsy) * @example expect(bytecode).toBeFalsy() */ toBeFalsy(): Promise } } expect.extend({ async toEqualValue(this: void, received: unknown, expected: any) { const bytecode = typeof received === "string" || Array.isArray(received) ? toBytecode(received as string | any[]) : received as Bytecode const result = await run(bytecode) const expectedValue = toValue(expected) const pass = isEqual(result, expectedValue) return { pass, message: () => pass ? `Expected value NOT to equal ${formatValue(expectedValue)}, but it did` : `Expected value to equal ${formatValue(expectedValue)}, but received ${formatValue(result)}`, } }, async toBeNull(this: void, received: unknown) { const bytecode = typeof received === "string" || Array.isArray(received) ? toBytecode(received as string | any[]) : received as Bytecode const result = await run(bytecode) const pass = result.type === "null" return { pass, message: () => pass ? `Expected value NOT to be null, but it was` : `Expected value to be null, but received ${formatValue(result)}`, } }, async toBeBoolean(this: void, received: unknown, expected: boolean) { const bytecode = typeof received === "string" || Array.isArray(received) ? toBytecode(received as string | any[]) : received as Bytecode const result = await run(bytecode) const pass = result.type === "boolean" && result.value === expected return { pass, message: () => pass ? `Expected value NOT to be boolean ${expected}, but it was` : `Expected value to be boolean ${expected}, but received ${formatValue(result)}`, } }, async toBeNumber(this: void, received: unknown, expected: number) { const bytecode = typeof received === "string" || Array.isArray(received) ? toBytecode(received as string | any[]) : received as Bytecode const result = await run(bytecode) const pass = result.type === "number" && result.value === expected return { pass, message: () => pass ? `Expected value NOT to be number ${expected}, but it was` : `Expected value to be number ${expected}, but received ${formatValue(result)}`, } }, async toBeString(this: void, received: unknown, expected: string) { const bytecode = typeof received === "string" || Array.isArray(received) ? toBytecode(received as string | any[]) : received as Bytecode const result = await run(bytecode) const pass = result.type === "string" && result.value === expected return { pass, message: () => pass ? `Expected value NOT to be string "${expected}", but it was` : `Expected value to be string "${expected}", but received ${formatValue(result)}`, } }, async toBeArray(this: void, received: unknown, expected: any[]) { const bytecode = typeof received === "string" || Array.isArray(received) ? toBytecode(received as string | any[]) : received as Bytecode const result = await run(bytecode) const expectedValue = toValue(expected) const pass = result.type === "array" && isEqual(result, expectedValue) return { pass, message: () => pass ? `Expected value NOT to be array ${formatValue(expectedValue)}, but it was` : `Expected value to be array ${formatValue(expectedValue)}, but received ${formatValue(result)}`, } }, async toBeDict(this: void, received: unknown, expected: Record) { const bytecode = typeof received === "string" || Array.isArray(received) ? toBytecode(received as string | any[]) : received as Bytecode const result = await run(bytecode) const expectedValue = toValue(expected) const pass = result.type === "dict" && isEqual(result, expectedValue) return { pass, message: () => pass ? `Expected value NOT to be dict ${formatValue(expectedValue)}, but it was` : `Expected value to be dict ${formatValue(expectedValue)}, but received ${formatValue(result)}`, } }, async toBeFunction(this: void, received: unknown) { const bytecode = typeof received === "string" || Array.isArray(received) ? toBytecode(received as string | any[]) : received as Bytecode const result = await run(bytecode) const pass = result.type === "function" || result.type === "native" return { pass, message: () => pass ? `Expected value NOT to be a function, but it was` : `Expected value to be a function, but received ${formatValue(result)}`, } }, async toBeTruthy(this: void, received: unknown) { const bytecode = typeof received === "string" || Array.isArray(received) ? toBytecode(received as string | any[]) : received as Bytecode const result = await run(bytecode) // ReefVM semantics: only null and false are falsy const pass = !(result.type === "null" || (result.type === "boolean" && !result.value)) return { pass, message: () => pass ? `Expected value NOT to be truthy, but it was: ${formatValue(result)}` : `Expected value to be truthy, but received ${formatValue(result)}`, } }, async toBeFalsy(this: void, received: unknown) { const bytecode = typeof received === "string" || Array.isArray(received) ? toBytecode(received as string | any[]) : received as Bytecode const result = await run(bytecode) // ReefVM semantics: only null and false are falsy const pass = result.type === "null" || (result.type === "boolean" && !result.value) return { pass, message: () => pass ? `Expected value NOT to be falsy, but it was: ${formatValue(result)}` : `Expected value to be falsy, but received ${formatValue(result)}`, } }, }) function formatValue(value: Value): string { switch (value.type) { case "null": return "null" case "boolean": case "number": return String(value.value) case "string": return `"${value.value}"` case "array": return `[${value.value.map(formatValue).join(", ")}]` case "dict": { const entries = Array.from(value.value.entries()) .map(([k, v]) => `${k}: ${formatValue(v)}`) .join(", ") return `{${entries}}` } case "regex": return String(value.value) case "function": case "native": return "" default: return String(value) } }