import { test, expect } from "bun:test" import { isValue, toValue } from "#reef" test("isValue - recognizes valid Value objects", () => { expect(isValue({ type: 'number', value: 42 })).toBe(true) expect(isValue({ type: 'number', value: 0 })).toBe(true) expect(isValue({ type: 'string', value: 'hello' })).toBe(true) expect(isValue({ type: 'string', value: '' })).toBe(true) expect(isValue({ type: 'boolean', value: true })).toBe(true) expect(isValue({ type: 'boolean', value: false })).toBe(true) expect(isValue({ type: 'null', value: null })).toBe(true) expect(isValue({ type: 'array', value: [] })).toBe(true) expect(isValue({ type: 'array', value: [toValue(1), toValue(2)] })).toBe(true) expect(isValue({ type: 'dict', value: new Map() })).toBe(true) expect(isValue({ type: 'regex', value: /test/ })).toBe(true) expect(isValue({ type: 'function', value: '', params: [], defaults: {}, body: 0, variadic: false, named: false, parentScope: null as any })).toBe(true) expect(isValue({ type: 'native', value: '', fn: (() => { }) as any })).toBe(true) }) test("isValue - rejects primitives", () => { expect(isValue(42)).toBe(false) expect(isValue(0)).toBe(false) expect(isValue('hello')).toBe(false) expect(isValue('')).toBe(false) expect(isValue(true)).toBe(false) expect(isValue(false)).toBe(false) expect(isValue(null)).toBe(false) expect(isValue(undefined)).toBe(false) }) test("isValue - rejects plain objects", () => { expect(isValue({})).toBe(false) expect(isValue({ foo: 'bar' })).toBe(false) expect(isValue({ type: 'number' })).toBe(false) expect(isValue({ value: 42 })).toBe(false) }) test("isValue - rejects arrays and functions", () => { expect(isValue([])).toBe(false) expect(isValue([1, 2, 3])).toBe(false) expect(isValue(() => { })).toBe(false) expect(isValue(function () { })).toBe(false) }) test("isValue - rejects other object types", () => { expect(isValue(new Date())).toBe(false) expect(isValue(/regex/)).toBe(false) expect(isValue(new Map())).toBe(false) expect(isValue(new Set())).toBe(false) }) test("isValue - used by toValue to detect already-converted values", () => { const value = toValue(42) expect(isValue(value)).toBe(true) const result = toValue(value) expect(result).toBe(value) }) test("isValue - edge cases with type and value properties", () => { expect(isValue({ type: 'number', value: 42, extra: 'data' })).toBe(true) expect(isValue({ type: null, value: 42 })).toBe(false) expect(isValue({ type: 'number', value: undefined })).toBe(true) expect(isValue({ type: 'number', val: 42 })).toBe(false) expect(isValue({ typ: 'number', value: 42 })).toBe(false) }) test("isValue - rejects objects with invalid type values", () => { expect(isValue({ type: 'text', value: 'Bob' })).toBe(false) expect(isValue({ type: 'email', value: 'test@example.com' })).toBe(false) expect(isValue({ type: 'password', value: 'secret' })).toBe(false) expect(isValue({ type: 'checkbox', value: true })).toBe(false) expect(isValue({ type: 'custom', value: 123 })).toBe(false) expect(isValue({ type: 'unknown', value: null })).toBe(false) }) test("toValue - correctly handles HTML input props", async () => { const { VM, toBytecode, toValue } = await import("#reef") const bytecode = toBytecode([["HALT"]]) const vm = new VM(bytecode) const inputProps = { type: 'text', value: 'Bob' } const converted = toValue(inputProps, vm) expect(converted.type).toBe('dict') expect(converted.value.get('type')).toEqual({ type: 'string', value: 'text' }) expect(converted.value.get('value')).toEqual({ type: 'string', value: 'Bob' }) }) test("toValue - converts wrapped Reef functions back to original Value", async () => { const { VM, toBytecode, fnFromValue } = await import("#reef") // Create a Reef function const bytecode = toBytecode([ ["MAKE_FUNCTION", ["x"], ".body"], ["STORE", "add1"], ["JUMP", ".end"], [".body:"], ["LOAD", "x"], ["PUSH", 1], ["ADD"], ["RETURN"], [".end:"], ["HALT"] ]) const vm = new VM(bytecode) await vm.run() const reefFunction = vm.scope.get("add1")! expect(reefFunction.type).toBe("function") // Convert to JS function const jsFunction = fnFromValue(reefFunction, vm) expect(typeof jsFunction).toBe("function") // Convert back to Value - should return the original Reef function const backToValue = toValue(jsFunction) expect(backToValue).toBe(reefFunction) // Same reference expect(backToValue.type).toBe("function") }) test("fromValue - converts native function back to original JS function", async () => { const { VM, toBytecode, toValue, fromValue } = await import("#reef") const bytecode = toBytecode([["HALT"]]) const vm = new VM(bytecode) // Create a native JS function const originalFn = (x: number, y: number) => x * y // Convert to Value (wraps it as a native function) const nativeValue = toValue(originalFn, vm) expect(nativeValue.type).toBe("native") // Convert back to JS - should get the original function const convertedFn = fromValue(nativeValue, vm) expect(typeof convertedFn).toBe("function") // Verify it's the same function expect(convertedFn).toBe(originalFn) // Verify it works correctly expect(convertedFn(3, 4)).toBe(12) }) test("fromValue - native function roundtrip preserves functionality", async () => { const { VM, toBytecode, toValue, fromValue } = await import("#reef") const bytecode = toBytecode([["HALT"]]) const vm = new VM(bytecode) // Create a native function with state let callCount = 0 const countingFn = (n: number) => { callCount++ return n * callCount } // Roundtrip through Value const nativeValue = toValue(countingFn, vm) const roundtrippedFn = fromValue(nativeValue, vm) // Verify it maintains state across calls expect(roundtrippedFn(10)).toBe(10) // 10 * 1 expect(roundtrippedFn(10)).toBe(20) // 10 * 2 expect(roundtrippedFn(10)).toBe(30) // 10 * 3 expect(callCount).toBe(3) }) test("fromValue - async native function roundtrip", async () => { const { VM, toBytecode, toValue, fromValue } = await import("#reef") const bytecode = toBytecode([["HALT"]]) const vm = new VM(bytecode) const asyncFn = async (x: number, y: number) => { await new Promise(resolve => setTimeout(resolve, 1)) return x + y } const nativeValue = toValue(asyncFn, vm) expect(nativeValue.type).toBe("native") const roundtrippedFn = fromValue(nativeValue, vm) const result = await roundtrippedFn(5, 7) expect(result).toBe(12) }) test("toValue - throws helpful error when converting function without VM", () => { function myFunction(x: number) { return x * 2 } expect(() => toValue(myFunction)).toThrow(/can't toValue\(\) function without a vm/) expect(() => toValue(myFunction)).toThrow(/Function: myFunction/) expect(() => toValue(myFunction)).toThrow(/Source:/) expect(() => toValue(myFunction)).toThrow(/Called from:/) }) test("toValue - error message shows function name from binding", () => { const anonymousFn = (x: number) => x * 2 expect(() => toValue(anonymousFn)).toThrow(/Function: anonymousFn/) expect(() => toValue(anonymousFn)).toThrow(/Source:/) }) test("toValue - error when function is nested in object without VM", () => { const obj = { name: "test", handler: (x: number) => x * 2 } expect(() => toValue(obj)).toThrow(/can't toValue\(\) function without a vm/) expect(() => toValue(obj)).toThrow(/Function: handler/) }) test("toValue - error when function is nested in array without VM", () => { const arr = [1, 2, (x: number) => x * 2] expect(() => toValue(arr)).toThrow(/can't toValue\(\) function without a vm/) }) test("fromValue - throws helpful error when converting function without VM", async () => { const { Scope, fromValue } = await import("#reef") const reefFunction = { type: 'function' as const, params: ['x', 'y'], defaults: {}, body: 10, parentScope: new Scope(), variadic: false, named: false, value: '' as const } expect(() => fromValue(reefFunction)).toThrow(/VM is required for function conversion/) expect(() => fromValue(reefFunction)).toThrow(/Function params: \[x, y\]/) expect(() => fromValue(reefFunction)).toThrow(/Function body at instruction: 10/) expect(() => fromValue(reefFunction)).toThrow(/Called from:/) })