import { describe, it, expect } from "vitest";
import React from "react";
import { render, screen } from "@testing-library/react";
import { z } from "zod";
import type { Spec } from "@json-render/core";
import { defineDirective, resolvePropValue } from "@json-render/core";
import {
JSONUIProvider,
Renderer,
type ComponentRenderProps,
} from "./renderer";
function Text({ element }: ComponentRenderProps<{ text: unknown }>) {
const value = element.props.text;
return {value == null ? "" : String(value)};
}
const registry = { Text };
const doubleDirective = defineDirective({
name: "$double",
description: "Double a numeric value.",
schema: z.object({ $double: z.unknown() }),
resolve(value, ctx) {
const resolved = resolvePropValue(value.$double, ctx);
return (resolved as number) * 2;
},
});
const upperDirective = defineDirective({
name: "$upper",
schema: z.object({ $upper: z.unknown() }),
resolve(value, ctx) {
const resolved = resolvePropValue(value.$upper, ctx);
return String(resolved).toUpperCase();
},
});
describe("directives in React renderer", () => {
it("resolves a custom directive in rendered props", () => {
const spec: Spec = {
root: "main",
elements: {
main: {
type: "Text",
props: { text: { $upper: "hello" } },
children: [],
},
},
};
render(
,
);
expect(screen.getByTestId("text").textContent).toBe("HELLO");
});
it("resolves a directive that reads from state", () => {
const spec: Spec = {
root: "main",
elements: {
main: {
type: "Text",
props: { text: { $double: { $state: "/count" } } },
children: [],
},
},
state: { count: 7 },
};
render(
,
);
expect(screen.getByTestId("text").textContent).toBe("14");
});
it("renders without directives (backward compat)", () => {
const spec: Spec = {
root: "main",
elements: {
main: {
type: "Text",
props: { text: "plain" },
children: [],
},
},
};
render(
,
);
expect(screen.getByTestId("text").textContent).toBe("plain");
});
});