Initial implementation of jrx - JSX factory for json-render

JSX factory that compiles JSX trees into json-render Spec JSON.
Framework-agnostic custom jsx-runtime, no React dependency at runtime.

- jsx/jsxs/Fragment via jsxImportSource: "jrx"
- render() flattens JrxNode tree into { root, elements, state? } Spec
- Auto key generation (type-N) with explicit key override
- Full feature parity: visible, on, repeat, watch as reserved props
- Function components via component() or plain functions
- @json-render/core as peer dependency

Co-authored-by: Ona <no-reply@ona.com>
This commit is contained in:
Kenneth Nym
2026-02-27 00:31:52 +00:00
commit f36256dda9
17 changed files with 2019 additions and 0 deletions

166
src/spec-validator.test.tsx Normal file
View File

@@ -0,0 +1,166 @@
/**
* Ported from json-render's core/src/spec-validator.test.ts.
*
* Runs @json-render/core's validateSpec against Specs produced by jrx
* to prove structural correctness.
*/
import { describe, it, expect } from "bun:test";
import { validateSpec } from "@json-render/core";
import { render } from "./render";
import {
Stack,
Card,
Text,
Button,
Badge,
List,
ListItem,
Select,
} from "./test-components";
describe("validateSpec on jrx-produced specs", () => {
it("validates a simple single-element spec", () => {
const spec = render(<Text text="hello" />);
const result = validateSpec(spec);
expect(result.valid).toBe(true);
expect(result.issues).toHaveLength(0);
});
it("validates a parent-child spec", () => {
const spec = render(
<Stack>
<Text text="hello" />
</Stack>,
);
const result = validateSpec(spec);
expect(result.valid).toBe(true);
expect(result.issues).toHaveLength(0);
});
it("validates a deep tree", () => {
const spec = render(
<Stack>
<Card title="A">
<Text text="1" />
<Text text="2" />
</Card>
<Button label="Go" />
</Stack>,
);
const result = validateSpec(spec);
expect(result.valid).toBe(true);
expect(result.issues).toHaveLength(0);
});
it("validates spec with visible at element level (not in props)", () => {
const spec = render(
<Text text="conditional" visible={{ $state: "/show" }} />,
);
const result = validateSpec(spec);
expect(result.issues.some((i) => i.code === "visible_in_props")).toBe(false);
});
it("validates spec with on at element level (not in props)", () => {
const spec = render(
<Button label="Click" on={{ press: { action: "submit" } }} />,
);
const result = validateSpec(spec);
expect(result.issues.some((i) => i.code === "on_in_props")).toBe(false);
});
it("validates spec with repeat at element level (not in props)", () => {
const spec = render(
<Stack repeat={{ statePath: "/items" }}>
<Text text="item" />
</Stack>,
);
const result = validateSpec(spec);
expect(result.issues.some((i) => i.code === "repeat_in_props")).toBe(false);
});
it("validates spec with watch at element level (not in props)", () => {
const spec = render(
<Select
label="Country"
watch={{ "/form/country": { action: "loadCities" } }}
/>,
);
const result = validateSpec(spec);
expect(result.issues.some((i) => i.code === "watch_in_props")).toBe(false);
});
it("no orphaned elements in jrx output", () => {
const spec = render(
<Stack>
<Text text="A" />
<Text text="B" />
</Stack>,
);
const result = validateSpec(spec, { checkOrphans: true });
expect(result.issues.some((i) => i.code === "orphaned_element")).toBe(false);
});
it("no missing children in jrx output", () => {
const spec = render(
<Stack>
<Card title="X">
<Text text="nested" />
<Badge text="tag" />
</Card>
<Button label="action" />
</Stack>,
);
const result = validateSpec(spec);
expect(result.issues.some((i) => i.code === "missing_child")).toBe(false);
expect(result.valid).toBe(true);
});
it("validates spec with state", () => {
const spec = render(<Text text="stateful" />, {
state: { count: 0, items: ["a", "b"] },
});
const result = validateSpec(spec);
expect(result.valid).toBe(true);
});
it("validates spec with all features combined", () => {
const spec = render(
<Stack>
<Text text="header" visible={{ $state: "/showHeader" }} />
<List repeat={{ statePath: "/items", key: "id" }}>
<ListItem
title={{ $item: "name" }}
on={{ press: { action: "selectItem" } }}
/>
</List>
<Select
label="Country"
watch={{ "/country": { action: "loadCities" } }}
/>
<Button
label="Submit"
on={{
press: [
{ action: "validateForm" },
{ action: "submitForm" },
],
}}
/>
</Stack>,
{ state: { showHeader: true, items: [], country: "" } },
);
const result = validateSpec(spec);
expect(result.valid).toBe(true);
expect(result.issues).toHaveLength(0);
for (const el of Object.values(spec.elements)) {
const props = el.props as Record<string, unknown>;
expect(props.visible).toBeUndefined();
expect(props.on).toBeUndefined();
expect(props.repeat).toBeUndefined();
expect(props.watch).toBeUndefined();
}
});
});