import { describe, it, expect } from "vitest";
import { render, screen, fireEvent, waitFor } from "@solidjs/testing-library";
import type { Spec } from "@json-render/core";
import {
JSONUIProvider,
Renderer,
type ComponentRenderProps,
} from "./renderer";
import { useStateStore } from "./contexts/state";
// DO NOT destructure props — Solid requires proxy access
function Button(props: ComponentRenderProps<{ label: string }>) {
return (
);
}
function Text(props: ComponentRenderProps<{ text: unknown }>) {
const display = () => {
const v = props.element.props.text;
return typeof v === "string" ? v : JSON.stringify(v);
};
return {display()};
}
let probeGetSnapshot: (() => Record) | undefined;
function StateProbe() {
const ctx = useStateStore();
probeGetSnapshot = ctx.getSnapshot;
return ;
}
const registry = {
Button,
Text,
};
function getProbeState(): Record {
return probeGetSnapshot!();
}
describe("chained actions: live $state resolution (#141)", () => {
it("setState after pushState sees the post-push value via $state", async () => {
probeGetSnapshot = undefined;
const spec: Spec = {
state: { items: ["initial"], observed: "not yet set" },
root: "main",
elements: {
main: {
type: "Button",
props: { label: "Add Item" },
on: {
press: [
{
action: "pushState",
params: { statePath: "/items", value: "new-item" },
},
{
action: "setState",
params: {
statePath: "/observed",
value: { $state: "/items" },
},
},
],
},
},
},
};
function App() {
return (
);
}
render(() => );
fireEvent.click(screen.getByTestId("btn"));
await waitFor(() => {
const state = getProbeState();
expect(state.items).toEqual(["initial", "new-item"]);
expect(state.observed).toEqual(["initial", "new-item"]);
});
});
it("multiple pushState + setState chain resolves correctly", async () => {
probeGetSnapshot = undefined;
const spec: Spec = {
state: { items: [], snapshot: null },
root: "main",
elements: {
main: {
type: "Button",
props: { label: "Go" },
on: {
press: [
{
action: "pushState",
params: { statePath: "/items", value: "a" },
},
{
action: "pushState",
params: { statePath: "/items", value: "b" },
},
{
action: "setState",
params: {
statePath: "/snapshot",
value: { $state: "/items" },
},
},
],
},
},
},
};
function App() {
return (
);
}
render(() => );
fireEvent.click(screen.getByTestId("btn"));
await waitFor(() => {
const state = getProbeState();
expect(state.items).toEqual(["a", "b"]);
expect(state.snapshot).toEqual(["a", "b"]);
});
});
it("setState reading a path mutated by an earlier setState sees fresh value", async () => {
probeGetSnapshot = undefined;
const spec: Spec = {
state: { counter: 0, counterCopy: -1 },
root: "main",
elements: {
main: {
type: "Button",
props: { label: "Go" },
on: {
press: [
{
action: "setState",
params: { statePath: "/counter", value: 42 },
},
{
action: "setState",
params: {
statePath: "/counterCopy",
value: { $state: "/counter" },
},
},
],
},
},
},
};
function App() {
return (
);
}
render(() => );
fireEvent.click(screen.getByTestId("btn"));
await waitFor(() => {
const state = getProbeState();
expect(state.counter).toBe(42);
expect(state.counterCopy).toBe(42);
});
});
});