From da61c1de50a59438c4aefa8a3b95839960fb730b Mon Sep 17 00:00:00 2001 From: Chris Wanstrath Date: Sun, 26 Oct 2025 08:24:58 -0700 Subject: [PATCH] isValue() --- src/index.ts | 2 +- src/value.ts | 6 +++- tests/value.test.ts | 82 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 tests/value.test.ts diff --git a/src/index.ts b/src/index.ts index 119f0e1..1fab29d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,5 +13,5 @@ export { wrapNative, isWrapped, type ParamInfo, extractParamInfo, getOriginalFun export { OpCode } from "./opcode" export { Scope } from "./scope" export type { Value, TypeScriptFunction, NativeFunction } from "./value" -export { toValue, toString, toNumber, fromValue, toNull } from "./value" +export { isValue, toValue, toString, toNumber, fromValue, toNull } from "./value" export { VM } from "./vm" \ No newline at end of file diff --git a/src/value.ts b/src/value.ts index 47fe7f9..af1930e 100644 --- a/src/value.ts +++ b/src/value.ts @@ -36,11 +36,15 @@ export type FunctionDef = { named: boolean } +export function isValue(v: any): boolean { + return !!(v && typeof v === 'object' && v.type && 'value' in v) +} + export function toValue(v: any): Value /* throws */ { if (v === null || v === undefined) return { type: 'null', value: null } - if (v && typeof v === 'object' && 'type' in v && 'value' in v) + if (isValue(v)) return v as Value if (Array.isArray(v)) diff --git a/tests/value.test.ts b/tests/value.test.ts new file mode 100644 index 0000000..193e087 --- /dev/null +++ b/tests/value.test.ts @@ -0,0 +1,82 @@ +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", () => { + // extra 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) +})