diff --git a/src/component/conditional.ts b/src/component/conditional.ts index 8cec2e9..bf1e1cb 100644 --- a/src/component/conditional.ts +++ b/src/component/conditional.ts @@ -1 +1,26 @@ -export {}; \ No newline at end of file +import type { UElement, UNode, UniversalProps } from "@alkdev/ujsx"; +import type { CallResult } from "../schema/edge.js"; + +export interface ConditionalProps { + test: ((results: Record) => boolean) | string; + else?: UNode; +} + +function Conditional( + props: ConditionalProps & { children?: UNode[] }, +): UElement { + const { test, else: elseProp, children } = props; + const elementProps: UniversalProps = { test: test as UniversalProps["test"] }; + if (elseProp !== undefined) { + elementProps.else = elseProp; + } + return { + type: "conditional", + props: elementProps, + children: children ?? [], + }; +} + +Conditional.displayName = "Conditional"; + +export { Conditional }; \ No newline at end of file diff --git a/src/component/index.ts b/src/component/index.ts index 71b81bf..cd39cf2 100644 --- a/src/component/index.ts +++ b/src/component/index.ts @@ -1,3 +1,5 @@ +export { Conditional } from "./conditional.js"; +export type { ConditionalProps } from "./conditional.js"; export { Operation } from "./operation.js"; export type { OperationProps } from "./operation.js"; export { Parallel } from "./parallel.js"; diff --git a/test/component/conditional.test.ts b/test/component/conditional.test.ts new file mode 100644 index 0000000..eb75168 --- /dev/null +++ b/test/component/conditional.test.ts @@ -0,0 +1,86 @@ +import { describe, it, expect } from "vitest"; +import { Conditional } from "@/component/index.js"; +import type { ConditionalProps } from "@/component/index.js"; +import type { UElement, UNode } from "@alkdev/ujsx"; +import type { CallResult } from "@/schema/index.js"; + +describe("Conditional", () => { + it("produces UElement with type conditional", () => { + const el = Conditional({ + test: "operationName", + } as ConditionalProps & { children?: UNode[] }); + expect(el.type).toBe("conditional"); + }); + + it("stores test as string", () => { + const el = Conditional({ + test: "fetch-data", + } as ConditionalProps & { children?: UNode[] }); + expect((el as UElement).props.test).toBe("fetch-data"); + }); + + it("stores test as function", () => { + const testFn = (results: Record) => + results["fetch-data"]?.status === "completed"; + const el = Conditional({ + test: testFn, + } as ConditionalProps & { children?: UNode[] }); + expect((el as UElement).props.test).toBe(testFn); + }); + + it("children are the then-branch", () => { + const thenChild: UElement = { + type: "operation", + props: { name: "process" }, + children: [], + }; + const el = Conditional({ + test: "check", + children: [thenChild], + } as ConditionalProps & { children?: UNode[] }); + expect(el.children).toEqual([thenChild]); + }); + + it("else prop is the alternative branch", () => { + const elseChild: UElement = { + type: "operation", + props: { name: "fallback" }, + children: [], + }; + const el = Conditional({ + test: "check", + else: elseChild, + } as ConditionalProps & { children?: UNode[] }); + expect((el as UElement).props.else).toBe(elseChild); + }); + + it("omits else prop when not provided", () => { + const el = Conditional({ + test: "check", + } as ConditionalProps & { children?: UNode[] }); + expect((el as UElement).props.else).toBeUndefined(); + }); + + it("defaults children to empty array", () => { + const el = Conditional({ + test: "check", + } as ConditionalProps & { children?: UNode[] }); + expect(el.children).toEqual([]); + }); + + it("is a valid UElement (has type, props, children)", () => { + const el = Conditional({ + test: (results: Record) => true, + } as ConditionalProps & { children?: UNode[] }); + expect(el).toHaveProperty("type"); + expect(el).toHaveProperty("props"); + expect(el).toHaveProperty("children"); + expect(typeof el.type).toBe("string"); + expect(typeof el.props).toBe("object"); + expect(Array.isArray(el.children)).toBe(true); + }); + + it("displayName is Conditional", () => { + expect(Conditional.displayName).toBe("Conditional"); + }); +}); \ No newline at end of file