Skip to content

Commit

Permalink
fix(propfunctionprops): prevent 'undefined', 'null', 'never' conversi…
Browse files Browse the repository at this point in the history
…on to PropFnInterface (#5363)

* fix(propfunctionprops): prevent 'undefined', 'null', 'never' conversion to PropFnInterface

fix #4946

* improve PropFunctionProps readability and edge-cases

* fix never becoming undefined

* fixup! fix never becoming undefined

* refine tests

---------

Co-authored-by: Miško Hevery <[email protected]>
  • Loading branch information
maiieul and mhevery authored Nov 10, 2023
1 parent df012ea commit 665b800
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 7 deletions.
2 changes: 1 addition & 1 deletion packages/docs/src/routes/api/qwik/api.json
Original file line number Diff line number Diff line change
Expand Up @@ -1624,7 +1624,7 @@
}
],
"kind": "TypeAlias",
"content": "```typescript\nexport type PropFunctionProps<PROPS extends {}> = {\n [K in keyof PROPS]: NonNullable<PROPS[K]> extends (...args: infer ARGS) => infer RET ? PropFnInterface<ARGS, Awaited<RET>> : PROPS[K];\n};\n```\n**References:** [PropFnInterface](#propfninterface)",
"content": "```typescript\nexport type PropFunctionProps<PROPS extends {}> = {\n [K in keyof PROPS]: PROPS[K] extends undefined ? PROPS[K] : PROPS[K] extends ((...args: infer ARGS) => infer RET) | undefined ? PropFnInterface<ARGS, Awaited<RET>> : PROPS[K];\n};\n```\n**References:** [PropFnInterface](#propfninterface)",
"editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/core/component/component.public.ts",
"mdFile": "qwik.propfunctionprops.md"
},
Expand Down
6 changes: 3 additions & 3 deletions packages/docs/src/routes/api/qwik/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2123,9 +2123,9 @@ export type PropFunction<T extends Function = (...args: any[]) => any> =
```typescript
export type PropFunctionProps<PROPS extends {}> = {
[K in keyof PROPS]: NonNullable<PROPS[K]> extends (
...args: infer ARGS
) => infer RET
[K in keyof PROPS]: PROPS[K] extends undefined
? PROPS[K]
: PROPS[K] extends ((...args: infer ARGS) => infer RET) | undefined
? PropFnInterface<ARGS, Awaited<RET>>
: PROPS[K];
};
Expand Down
2 changes: 1 addition & 1 deletion packages/qwik/src/core/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1401,7 +1401,7 @@ export type PropFunction<T extends Function = (...args: any[]) => any> = T exten

// @public (undocumented)
export type PropFunctionProps<PROPS extends {}> = {
[K in keyof PROPS]: NonNullable<PROPS[K]> extends (...args: infer ARGS) => infer RET ? PropFnInterface<ARGS, Awaited<RET>> : PROPS[K];
[K in keyof PROPS]: PROPS[K] extends undefined ? PROPS[K] : PROPS[K] extends ((...args: infer ARGS) => infer RET) | undefined ? PropFnInterface<ARGS, Awaited<RET>> : PROPS[K];
};

// @public
Expand Down
4 changes: 3 additions & 1 deletion packages/qwik/src/core/component/component.public.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,9 @@ export const isQwikComponent = (component: any): component is Component<any> =>

/** @public */
export type PropFunctionProps<PROPS extends {}> = {
[K in keyof PROPS]: NonNullable<PROPS[K]> extends (...args: infer ARGS) => infer RET
[K in keyof PROPS]: PROPS[K] extends undefined
? PROPS[K]
: PROPS[K] extends ((...args: infer ARGS) => infer RET) | undefined
? PropFnInterface<ARGS, Awaited<RET>>
: PROPS[K];
};
Expand Down
57 changes: 56 additions & 1 deletion packages/qwik/src/core/component/component.unit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import { createDOM } from '../../testing/library';
import { expectDOM } from '../../testing/expect-dom';
import { inlinedQrl } from '../qrl/qrl';
import { useStylesQrl } from '../use/use-styles';
import { type PropsOf, component$ } from './component.public';
import { type PropsOf, component$, type PropFunctionProps } from './component.public';
import { useStore } from '../use/use-store.public';
import { useLexicalScope } from '../use/use-lexical-scope.public';
import { describe, test } from 'vitest';
import type { InputHTMLAttributes } from '../render/jsx/types/jsx-generated';
import type { QwikIntrinsicElements } from '../render/jsx/types/jsx-qwik-elements';

describe('q-component', () => {
/**
Expand Down Expand Up @@ -113,6 +115,59 @@ describe('q-component', () => {
`
);
});

test('types work as expected', () => {
const Input1 = component$<InputHTMLAttributes<HTMLInputElement>>((props) => {
return <input {...props} />;
});

const Input2 = component$((props: PropFunctionProps<InputHTMLAttributes<HTMLInputElement>>) => {
return <input {...props} />;
});

const Input3 = component$((props: Partial<InputHTMLAttributes<HTMLInputElement>>) => {
return <input {...props} />;
});

const Input4 = component$(
(props: Partial<PropFunctionProps<InputHTMLAttributes<HTMLInputElement>>>) => {
return <input {...props} />;
}
);

type Input5Props = {
type: 'text' | 'number';
} & Partial<InputHTMLAttributes<HTMLInputElement>>;

const Input5 = component$<Input5Props>(({ type, ...props }) => {
return <input type={type} {...props} />;
});

type Input6Props = {
type: 'text' | 'number';
} & QwikIntrinsicElements['input'];

const Input6 = component$<Input6Props>(({ type, ...props }) => {
return (
<div>
<input type={type} {...props} />
</div>
);
});

component$(() => {
return (
<>
<Input1 value="1" />
<Input2 value="2" />
<Input3 value="3" />
<Input4 value="4" />
<Input5 value="5" type="text" />
<Input6 value="6" type="number" />
</>
);
});
});
});

/////////////////////////////////////////////////////////////////////////////
Expand Down

0 comments on commit 665b800

Please sign in to comment.