import { ContentResolver } from "./ContentResolver"; import { Store } from "../data/Store"; import { createTestRenderer, act } from "../util/test/createTestRenderer"; import assert from "assert"; import { bind } from "./bind"; import { createAccessorModelProxy } from "../data/createAccessorModelProxy"; import { Prop } from "./Prop"; import { stringTemplate } from "../data/StringTemplate"; interface TestModel { user: { name: string; age: number; active: boolean; }; } describe("ContentResolver", () => { it("resolves content based on params", async () => { let widget = (
} />
); let store = new Store({ data: { name: "Test", }, }); const component = await createTestRenderer(store, widget); let tree = component.toJSON(); assert.deepEqual(tree, { type: "div", props: {}, children: [ { type: "span", props: {}, children: ["Test"], }, ], }); }); it("re-resolves when params change", async () => { let resolveCount = 0; let widget = (
{ resolveCount++; return ; }} />
); let store = new Store({ data: { value: 1, }, }); const component = await createTestRenderer(store, widget); let tree = component.toJSON(); assert.deepEqual(tree, { type: "div", props: {}, children: [ { type: "span", props: {}, children: ["1"], }, ], }); assert.equal(resolveCount, 1); await act(async () => { store.set("value", 2); }); tree = component.toJSON(); assert.deepEqual(tree, { type: "div", props: {}, children: [ { type: "span", props: {}, children: ["2"], }, ], }); assert.equal(resolveCount, 2); }); it("does not re-resolve if params are unchanged", async () => { let resolveCount = 0; let widget = (
{ resolveCount++; return ; }} />
); let store = new Store({ data: { value: 1, other: "a", }, }); const component = await createTestRenderer(store, widget); assert.equal(resolveCount, 1); // Change unrelated data store.set("other", "b"); component.toJSON(); assert.equal(resolveCount, 1); }); it("supports literal values in params", async () => { let receivedParams: any = null; let widget = (
{ receivedParams = params; return ; }} />
); let store = new Store(); const component = await createTestRenderer(store, widget); let tree = component.toJSON(); assert.deepEqual(tree, { type: "div", props: {}, children: [ { type: "span", props: {}, children: ["test: 42"], }, ], }); assert.equal(receivedParams.count, 42); assert.equal(receivedParams.name, "test"); }); it("supports mode=prepend", async () => { let widget = (
Prepended}> Original
); let store = new Store(); const component = await createTestRenderer(store, widget); let tree = component.toJSON(); assert.deepEqual(tree, { type: "div", props: {}, children: [ { type: "span", props: {}, children: ["Prepended"], }, { type: "span", props: {}, children: ["Original"], }, ], }); }); it("supports mode=append", async () => { let widget = (
Appended}> Original
); let store = new Store(); const component = await createTestRenderer(store, widget); let tree = component.toJSON(); assert.deepEqual(tree, { type: "div", props: {}, children: [ { type: "span", props: {}, children: ["Original"], }, { type: "span", props: {}, children: ["Appended"], }, ], }); }); it("supports mode=replace (default)", async () => { let widget = (
Replaced}> Original
); let store = new Store(); const component = await createTestRenderer(store, widget); let tree = component.toJSON(); assert.deepEqual(tree, { type: "div", props: {}, children: [ { type: "span", props: {}, children: ["Replaced"], }, ], }); }); it("handles async resolution with Promise", async () => { let resolvePromise: ((value: any) => void) | null = null; let widget = (
{ return new Promise((resolve) => { resolvePromise = resolve; }); }} />
); let store = new Store({ data: { id: 1, loading: false, }, }); const component = await createTestRenderer(store, widget); // Initially loading should be true assert.equal(store.get("loading"), true); // Resolve the promise resolvePromise!(Loaded Content); // Wait for promise resolution await new Promise((r) => setTimeout(r, 10)); // After resolution, loading should be false assert.equal(store.get("loading"), false); let tree = component.toJSON(); assert.deepEqual(tree, { type: "div", props: {}, children: [ { type: "span", props: {}, children: ["Loaded Content"], }, ], }); }); it("supports typed params with accessor chains", async () => { const model = createAccessorModelProxy(); // This test verifies type inference works correctly. // AccessorChain resolves to T in the onResolve params. let widget = (
{ // TypeScript infers types from AccessorChain: // params.name is string (from AccessorChain) // params.age is number (from AccessorChain) // params.active is boolean (from AccessorChain) const name: string = params.name; const age: number = params.age; const active: boolean = params.active; return ; }} />
); let store = new Store({ data: { user: { name: "John", age: 30, active: true, }, }, }); const component = await createTestRenderer(store, widget); let tree = component.toJSON(); assert.deepEqual(tree, { type: "div", props: {}, children: [ { type: "span", props: {}, children: ["John is 30 years old and active"], }, ], }); }); it("infers types from literal params", async () => { // When using literal values, TypeScript can infer their types let widget = (
{ // params.count is inferred as number // params.label is inferred as string const count: number = params.count; const label: string = params.label; return ; }} />
); let store = new Store(); const component = await createTestRenderer(store, widget); let tree = component.toJSON(); assert.deepEqual(tree, { type: "div", props: {}, children: [ { type: "span", props: {}, children: ["42 items"], }, ], }); }); it("supports simple Prop params (non-structured)", async () => { const model = createAccessorModelProxy(); // Test using a single AccessorChain as params instead of an object let widget = (
{ // name should be typed as string (from AccessorChain) const typedName: string = name; return ; }} />
); let store = new Store({ data: { user: { name: "Alice", age: 25, active: true, }, }, }); const component = await createTestRenderer(store, widget); let tree = component.toJSON(); assert.deepEqual(tree, { type: "div", props: {}, children: [ { type: "span", props: {}, children: ["Hello, Alice!"], }, ], }); }); it("supports simple bind() as params", async () => { let widget = (
{ return ; }} />
); let store = new Store({ data: { count: 42, }, }); const component = await createTestRenderer(store, widget); let tree = component.toJSON(); assert.deepEqual(tree, { type: "div", props: {}, children: [ { type: "span", props: {}, children: ["Count: 42"], }, ], }); }); it("resolves Selector | null to string | null", async () => { const text: string | null = "Hello {user.name}"; let widget = (
{ // params.text should be string | null const typedText: string | null = params.text; // @ts-expect-error - params.text should not be 'any' or broader type const shouldFail: number = params.text; return ; }} />
); let store = new Store({ data: { user: { name: "World", age: 30, active: true }, }, }); const component = await createTestRenderer(store, widget); let tree = component.toJSON(); assert.deepEqual(tree, { type: "div", props: {}, children: [{ type: "span", props: {}, children: ["Hello World"] }], }); }); it("resolves Prop to string (not any)", async () => { const model = createAccessorModelProxy(); // When params is typed as Prop, onResolve should receive string (not any) // This tests that the union type Prop correctly extracts T const typedParam: Prop = model.user.name; let widget = (
{ // name should be typed as string, not any const typedName: string = name; return ; }} />
); let store = new Store({ data: { user: { name: "Bob", age: 35, active: false, }, }, }); const component = await createTestRenderer(store, widget); let tree = component.toJSON(); assert.deepEqual(tree, { type: "div", props: {}, children: [ { type: "span", props: {}, children: ["Hello, Bob!"], }, ], }); }); });