import { describe, expect, test } from "vitest"; import { safeJsonStringify } from "#src/json-safe-stringify"; describe("safeJsonStringify", () => { test("serializes a plain record", () => { expect(safeJsonStringify({ toolName: "write", allowed: true })).toBe( '{"toolName":"write","allowed":true}', ); }); test("returns undefined for a value JSON cannot represent", () => { expect(safeJsonStringify(undefined)).toBe(undefined); expect(safeJsonStringify(() => "noop")).toBe(undefined); }); describe("Error values", () => { test("expands an Error into name, message, and stack", () => { const error = new Error("boom"); error.stack = "Error: boom\n at somewhere"; expect(safeJsonStringify({ error })).toBe( '{"error":{"name":"Error","message":"boom","stack":"Error: boom\\n at somewhere"}}', ); }); test("expands an Error at the root", () => { const error = new TypeError("bad type"); error.stack = "trace"; expect(safeJsonStringify(error)).toBe( '{"name":"TypeError","message":"bad type","stack":"trace"}', ); }); }); describe("bigint values", () => { test("stringifies a bigint rather than throwing", () => { expect(safeJsonStringify({ size: 9007199254740993n })).toBe( '{"size":"9007199254740993"}', ); }); }); describe("circular references", () => { test("replaces a self-reference with the [Circular] marker", () => { const node: Record = { name: "root" }; node.self = node; expect(safeJsonStringify(node)).toBe( '{"name":"root","self":"[Circular]"}', ); }); test("replaces a mutual reference cycle with the [Circular] marker", () => { const parent: Record = { kind: "parent" }; const child: Record = { kind: "child", parent }; parent.child = child; expect(safeJsonStringify(parent)).toBe( '{"kind":"parent","child":{"kind":"child","parent":"[Circular]"}}', ); }); test("also marks a repeated non-cyclic reference, since seen entries are never released", () => { const shared = { id: 1 }; expect(safeJsonStringify({ first: shared, second: shared })).toBe( '{"first":{"id":1},"second":"[Circular]"}', ); }); }); });