import * as React from "react"; import { SlotChildren, template, useSlot, type Slot } from "../src"; import { COMPONENT_TYPE, SLOT_NAME, SLOT_TYPE_IDENTIFIER, TEMPLATE_TYPE_IDENTIFIER, } from "../src/constants"; import { CreateSlot, CreateTemplate, SlotComponent } from "../src/types"; test("Slot Type", () => { expectTypeOf().toEqualTypeOf<{ value: ["default", {}] }>(); expectTypeOf>().toEqualTypeOf<{ value: ["foo", {}] }>(); expectTypeOf>().toEqualTypeOf<{ value: ["default", { foo: boolean }]; }>(); expectTypeOf>().toEqualTypeOf<{ value: ["default", { foo?: boolean }]; }>(); expectTypeOf>().toEqualTypeOf<{ value: ["foo", { foo: boolean }]; }>(); // undefined as the first argument should be the same as omitting the TName expectTypeOf>().toEqualTypeOf(); expectTypeOf>().toEqualTypeOf< Slot<{ foo: string }> >(); // TProps can be any expectTypeOf>().toEqualTypeOf<{ value: ["foo", any] }>(); expectTypeOf>().toEqualTypeOf<{ value: [string, any] }>(); // any in the first argument should transform to string expectTypeOf>().toEqualTypeOf<{ value: [string, {}] }>(); expectTypeOf>().toEqualTypeOf<{ value: [string, any] }>(); // Distributivity should work on both TName and TProps expectTypeOf< Slot<"foo" | "bar", { foo: string } | { bar: string }> >().toEqualTypeOf<{ value: | ["foo", { foo: string } | { bar: string }] | ["bar", { foo: string } | { bar: string }]; }>(); // Disallowed props should be omitted expectTypeOf< Slot<{ children: any; key?: string; ref: undefined; as?: unknown; thisIsOk: any; }> >().toEqualTypeOf>(); expectTypeOf< Slot< string, { children: any; key?: string; ref: undefined; as?: unknown; thisIsOk: any; } > >().toEqualTypeOf>(); }); test("SlotChildren", () => { type Literals = string | number | boolean | null | undefined; type Func = [T] extends [unknown] ? (props: T) => React.ReactNode : never; type TypeOrIterable = T | Iterable>; type TemplateComponentLikeElement< TName extends string, TProps extends {}, > = React.ReactElement< { children?: React.ReactNode | ((props: TProps) => React.ReactNode); as?: React.ElementType; }, { (props: any): any; [SLOT_NAME]: TName; [COMPONENT_TYPE]: typeof TEMPLATE_TYPE_IDENTIFIER; } >; type TemplateAsSlotComponentLikeElement< TName extends string, TProps extends {}, > = React.ReactElement< { children?: React.ReactNode; as?: SlotComponent; }, { (props: any): any; [SLOT_NAME]: TName; [COMPONENT_TYPE]: typeof TEMPLATE_TYPE_IDENTIFIER; } >; // SlotChildren with any should have sensible defaults expectTypeOf< TypeOrIterable< | Literals | Func | TemplateComponentLikeElement | TemplateAsSlotComponentLikeElement | React.ReactElement<{ "slot-name": string }> > >().toEqualTypeOf>(); expectTypeOf< TypeOrIterable> >().not.toMatchTypeOf>(); // SlotChildren without type arguments should be the same as SlotChildren expectTypeOf>().toEqualTypeOf(); expectTypeOf< TypeOrIterable< | Literals | Func | TemplateComponentLikeElement | TemplateAsSlotComponentLikeElement | React.ReactElement<{ "slot-name": string }> > >().toEqualTypeOf>>(); expectTypeOf< TypeOrIterable< | Literals | Func<{}> | TemplateComponentLikeElement<"default", {}> | TemplateAsSlotComponentLikeElement<"default", {}> | React.ReactElement<{ "slot-name": "default" }> > >().toEqualTypeOf>(); expectTypeOf< TypeOrIterable< | Literals | Func<{ foo: string }> | TemplateComponentLikeElement<"default", { foo: string }> | TemplateAsSlotComponentLikeElement<"default", { foo: string }> | React.ReactElement<{ "slot-name": "default" }> > >().toEqualTypeOf>>(); expectTypeOf< TypeOrIterable< | TemplateComponentLikeElement<"name", { foo: string }> | TemplateAsSlotComponentLikeElement<"name", { foo: string }> | React.ReactElement<{ "slot-name": "name" }> > >().toEqualTypeOf>>(); // Distributivity test expectTypeOf< TypeOrIterable< | TemplateComponentLikeElement<"name", { foo: string }> | TemplateAsSlotComponentLikeElement<"name", { foo: string }> | React.ReactElement<{ "slot-name": "name" }> | Literals | Func<{ bar: string }> | TemplateComponentLikeElement<"default", { bar: string }> | TemplateAsSlotComponentLikeElement<"default", { bar: string }> | React.ReactElement<{ "slot-name": "default" }> | TemplateComponentLikeElement<"noProps", {}> | TemplateAsSlotComponentLikeElement<"noProps", {}> | React.ReactElement<{ "slot-name": "noProps" }> > >().toEqualTypeOf< SlotChildren< Slot<"name", { foo: string }> | Slot<{ bar: string }> | Slot<"noProps"> > >(); // Distributivity through Slot test expectTypeOf< TypeOrIterable< | TemplateComponentLikeElement<"name", { bar: string } | { foo: string }> | TemplateAsSlotComponentLikeElement< "name", { bar: string } | { foo: string } > | React.ReactElement<{ "slot-name": "name" }> | Literals | Func<{ bar: string } | { foo: string }> | TemplateComponentLikeElement< "default", { bar: string } | { foo: string } > | TemplateAsSlotComponentLikeElement< "default", { bar: string } | { foo: string } > | React.ReactElement<{ "slot-name": "default" }> > >().toEqualTypeOf< SlotChildren> >(); // Should merge duplicate names expectTypeOf< TypeOrIterable< | TemplateComponentLikeElement< "name", { foo: string } | { bar: string } | { baz: string } > | TemplateAsSlotComponentLikeElement< "name", { foo: string } | { bar: string } | { baz: string } > | React.ReactElement<{ "slot-name": "name" }> > >().toEqualTypeOf< SlotChildren< | Slot<"name", { foo: string }> | Slot<"name", { bar: string }> | Slot<"name", { baz: string }> > >(); // ReactNode extends children with default slot expectTypeOf().toMatchTypeOf>(); }); test("Template", () => { expectTypeOf>().toEqualTypeOf< CreateTemplate >(); expectTypeOf(template.name[COMPONENT_TYPE]).toEqualTypeOf( TEMPLATE_TYPE_IDENTIFIER, ); expectTypeOf(template.name[SLOT_NAME]).toEqualTypeOf(); // no error ; No error; {(props: any) => "No error"}; // @ts-expect-error Extra prop {(props: any) => null}; // @ts-expect-error Type Arg must be the same as in `as` prop href="/test" as="a"> {(props: any) => "No error"} ; // no error ; // @ts-expect-error All required props must be provided null} />; null}> No error ; // No error children is optional null} />; // @ts-expect-error `as` element doesn't have children null}> ERROR ; // No error ; // CreateTemplate removes undefined (important when children is optional and doing `CreateTemplate) const testTemplate = template as CreateTemplate< | undefined | SlotChildren< | Slot | Slot<"named", { foo: string }> | Slot<"optionalArg", { optionalArg?: string }> > >; // No error ; ; ; // @ts-expect-error Should only access known keys ; No error; {(props) => { expectTypeOf(props).toEqualTypeOf({}); return "No error"; }} ; // No error No error; {(props) => { expectTypeOf(props).toEqualTypeOf<{ foo: string }>(); return "No error"; }} ; // No error {(props) => { expectTypeOf(props).toEqualTypeOf<{ optionalArg?: string }>(); return "No error"; }} ; // @ts-expect-error Extra prop {(props) => null}; // @ts-expect-error Type Arg must be the same as in `as` prop href="/test" as="a"> {(props: any) => "No error"} ; // no error ; // @ts-expect-error All required props must be provided null} />; null}> No error ; // No error children is optional null} />; // @ts-expect-error `as` element doesn't have children null}> ERROR ; // No error. Slot props is the same as testTemplate props >>("children" as any).slot .default } >; // No error. Slot props extends testTemplate props >>( "children" as any, ).slot.default } bar="bar" >; // @ts-expect-error If template has more required props, the extra props must be specified on the element >>( "children" as any, ).slot.default } >; // No error, bar is not required >>( "children" as any, ).slot.default } // No error on children > Children ; // @ts-expect-error Template props doesn't extends slot props >>("children" as any).slot .default } >; >>("children" as any).slot .default } // No error. Template props can be overridden foo="string" > {/* @ts-expect-error 'Slot as template' children cannot be a function */} {() => null} ; // No error. {} extends {optionalArg?: string} >>("children" as any).slot.default} >; }); test("Slot", () => { expectTypeOf>().toEqualTypeOf>(); expectTypeOf( useSlot("children").slot.anyKey[COMPONENT_TYPE], ).toEqualTypeOf(SLOT_TYPE_IDENTIFIER); // All the different call signatures on basic slot should all work useSlot("children").slot.anyKey(); useSlot("children").slot.anyKey(null); useSlot("children").slot.anyKey(null, {}); useSlot("children").slot.anyKey(null, {}, 1); const AnyKey = useSlot("children").slot.anyKey; children; const slotTest = useSlot< SlotChildren< | Slot<"named", { prop1: string; prop2?: string }> | Slot<{ prop1?: string; prop2?: string }> > >("children" as any).slot; // @ts-expect-error Must provide first two arguments when at least one prop is not optional slotTest.named(); // @ts-expect-error Must provide first two arguments when at least one prop is not optional slotTest.named(null); // No error. All props are optional slotTest.default(); // @ts-expect-error Extra prop provided slotTest.named(null, { prop1: "foo", prop2: "bar", prop3: "string" }); // @ts-expect-error Must provide all required props ; // No error. Accepts children children; });