Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Card] Fix TypeScript not recognizing "component" prop #20179

Merged
merged 4 commits into from
Mar 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 58 additions & 9 deletions packages/material-ui/src/CardHeader/CardHeader.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,28 @@ import * as React from 'react';
import { TypographyProps } from '../Typography';
import { OverridableComponent, OverrideProps } from '../OverridableComponent';

export interface CardHeaderTypeMap<P = {}, D extends React.ElementType = 'div'> {
props: P & {
export interface CardHeaderTypeMap<
Props = {},
DefaultComponent extends React.ElementType = 'div',
TitleTypographyComponent extends React.ElementType = 'span',
SubheaderTypographyComponent extends React.ElementType = 'span'
> {
props: Props & {
action?: React.ReactNode;
avatar?: React.ReactNode;
disableTypography?: boolean;
subheader?: React.ReactNode;
subheaderTypographyProps?: Partial<TypographyProps>;
subheaderTypographyProps?: TypographyProps<
SubheaderTypographyComponent,
{ component?: SubheaderTypographyComponent }
>;
title?: React.ReactNode;
titleTypographyProps?: Partial<TypographyProps>;
titleTypographyProps?: TypographyProps<
TitleTypographyComponent,
{ component?: TitleTypographyComponent }
>;
};
defaultComponent: D;
defaultComponent: DefaultComponent;
classKey: CardHeaderClassKey;
}
/**
Expand All @@ -25,13 +36,51 @@ export interface CardHeaderTypeMap<P = {}, D extends React.ElementType = 'div'>
*
* - [CardHeader API](https://material-ui.com/api/card-header/)
*/
declare const CardHeader: OverridableComponent<CardHeaderTypeMap>;
declare const CardHeader: OverridableCardHeader;

export interface OverridableCardHeader extends OverridableComponent<CardHeaderTypeMap> {
<
DefaultComponent extends React.ElementType = CardHeaderTypeMap['defaultComponent'],
Props = {},
TitleTypographyComponent extends React.ElementType = 'span',
SubheaderTypographyComponent extends React.ElementType = 'span'
>(
props: CardHeaderPropsWithComponent<
DefaultComponent,
Props,
TitleTypographyComponent,
SubheaderTypographyComponent
>,
): JSX.Element;
}

export type CardHeaderClassKey = 'root' | 'avatar' | 'action' | 'content' | 'title' | 'subheader';

export type CardHeaderProps<
D extends React.ElementType = CardHeaderTypeMap['defaultComponent'],
P = {}
> = OverrideProps<CardHeaderTypeMap<P, D>, D>;
DefaultComponent extends React.ElementType = CardHeaderTypeMap['defaultComponent'],
Props = {},
TitleTypographyComponent extends React.ElementType = 'span',
SubheaderTypographyComponent extends React.ElementType = 'span'
> = OverrideProps<
CardHeaderTypeMap<
Props,
DefaultComponent,
TitleTypographyComponent,
SubheaderTypographyComponent
>,
DefaultComponent
>;

export type CardHeaderPropsWithComponent<
DefaultComponent extends React.ElementType = CardHeaderTypeMap['defaultComponent'],
Props = {},
TitleTypographyComponent extends React.ElementType = 'span',
SubheaderTypographyComponent extends React.ElementType = 'span'
> = { component?: DefaultComponent } & CardHeaderProps<
DefaultComponent,
Props,
TitleTypographyComponent,
SubheaderTypographyComponent
>;

export default CardHeader;
299 changes: 299 additions & 0 deletions packages/material-ui/src/CardHeader/CardHeader.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
import * as React from 'react';
import CardHeader, { CardHeaderProps, CardHeaderTypeMap } from '@material-ui/core/CardHeader';

const CustomComponent: React.FC<{ stringProp: string; numberProp: number }> = () => <div />;

type DefaultComponent = CardHeaderTypeMap['defaultComponent'];

interface ComponentProp {
component?: React.ElementType;
}

function createElementBasePropMixedTest() {
React.createElement<CardHeaderProps<DefaultComponent, ComponentProp>>(CardHeader);
React.createElement<CardHeaderProps<DefaultComponent, ComponentProp>>(CardHeader, {
component: 'div',
});
// ExpectError: type system should be demanding the required props of "CustomComponent"
React.createElement<CardHeaderProps<DefaultComponent, ComponentProp>>(CardHeader, {
component: CustomComponent,
});
// $ExpectError
React.createElement<CardHeaderProps<DefaultComponent, ComponentProp>>(CardHeader, {
// This test shouldn't fail but does; stringProp & numberProp are required props of CustomComponent
component: CustomComponent,
stringProp: '',
numberProp: 0,
});
React.createElement<CardHeaderProps>(CardHeader, {
disableTypography: true,
});
// $ExpectError
React.createElement<CardHeaderProps<DefaultComponent, {}, React.ElementType>>(CardHeader, {
unknownProp: 'shouldNotWork',
});
// $ExpectError
React.createElement<CardHeaderProps>(CardHeader, {
disableTypography: 'hello',
});
// $ExpectError
React.createElement<CardHeaderProps>(CardHeader, {
disableTypography: 1,
});
// $ExpectError
React.createElement<CardHeaderProps<any, ComponentProp>>(CardHeader, {
component: 'incorrectElement',
});
}

function createElementTypographyTest() {
React.createElement<CardHeaderProps>(CardHeader, {
titleTypographyProps: {
align: 'center',
},
});
// $ExpectError
React.createElement<CardHeaderProps>(CardHeader, {
titleTypographyProps: {
align: 'incorrectAlign',
},
});
React.createElement<CardHeaderProps>(CardHeader, {
titleTypographyProps: {
variant: 'body1',
},
});
// $ExpectError
React.createElement<CardHeaderProps>(CardHeader, {
titleTypographyProps: {
variant: 123,
},
});
React.createElement<CardHeaderProps<DefaultComponent, {}, React.ElementType>>(CardHeader, {
titleTypographyProps: {
component: 'div',
},
});
// ExpectError: This is expected to err; the type system should catch required props from "CustomComponent".
React.createElement<CardHeaderProps<DefaultComponent, {}, React.ElementType>>(CardHeader, {
titleTypographyProps: {
component: CustomComponent,
},
});
React.createElement<CardHeaderProps<DefaultComponent, {}, React.ElementType>>(CardHeader, {
titleTypographyProps: {
component: CustomComponent,
stringProp: '',
numberProp: 0,
},
});
// ExpectError: This is expected to err; the type system should catch the props type mismatch
// from "CustomComponent" props.
React.createElement<CardHeaderProps<DefaultComponent, {}, React.ElementType>>(CardHeader, {
titleTypographyProps: {
component: CustomComponent,
stringProp: 0,
numberProp: '',
},
});
// ExpectError: This is expected to err; the type system is welcoming unknown props.
React.createElement<CardHeaderProps<DefaultComponent, {}, React.ElementType>>(CardHeader, {
titleTypographyProps: {
unknownProp: 'shouldNotWork',
},
});
// $ExpectError
React.createElement<CardHeaderProps<DefaultComponent, {}, React.ElementType>>(CardHeader, {
titleTypographyProps: {
component: 'incorrectComponent',
},
});
// $ExpectError
React.createElement<CardHeaderProps>(CardHeader, {
titleTypographyProps: true,
});
}

function componentPropTest() {
<CardHeader component="div" />;
<CardHeader component={CustomComponent} stringProp="string" numberProp={1} />;
// $ExpectError
<CardHeader component="incorrectComponent" />;
// $ExpectError
<CardHeader component={CustomComponent} />;
}

function mixedCardHeaderComponentAndTypographyTest() {
<CardHeader component="div" titleTypographyProps={{ component: 'a', href: 'href' }} />;
<CardHeader component="div" subheaderTypographyProps={{ component: 'a', href: 'href' }} />;
<CardHeader
component={CustomComponent}
stringProp="string"
numberProp={1}
titleTypographyProps={{ component: CustomComponent, stringProp: 'stringProp', numberProp: 2 }}
/>;
<CardHeader
component={CustomComponent}
stringProp="string"
numberProp={1}
titleTypographyProps={{ component: CustomComponent, stringProp: 'stringProp', numberProp: 2 }}
subheaderTypographyProps={{
component: CustomComponent,
stringProp: 'stringProp',
numberProp: 2,
}}
/>;
// $ExpectError
<CardHeader component="incorrectComponent" />;
// $ExpectError
<CardHeader component={CustomComponent} />;
<CardHeader
component={CustomComponent}
stringProp="string"
numberProp={1}
// $ExpectError
titleTypographyProps={{ component: CustomComponent, stringProp: 'stringProp' }}
subheaderTypographyProps={{
component: CustomComponent,
stringProp: 'stringProp',
numberProp: 2,
}}
/>;
// $ExpectError
<CardHeader
component={CustomComponent}
stringProp="string"
numberProp={1}
titleTypographyProps={{ component: CustomComponent, stringProp: 'stringProp' }}
subheaderTypographyProps={{ component: CustomComponent, stringProp: 'stringProp' }}
/>;
<CardHeader
// $ExpectError
component="incorrectComponent"
stringProp="string"
numberProp={1}
titleTypographyProps={{ component: CustomComponent, stringProp: 'stringProp', numberProp: 2 }}
subheaderTypographyProps={{
component: CustomComponent,
stringProp: 'stringProp',
numberProp: 2,
}}
/>;
}

function titleTypographyPropsTest() {
// $ExpectError
<CardHeader titleTypographyProps={{ component: 'incorrectComponent' }} />;
<CardHeader titleTypographyProps={{ component: 'a', href: 'href' }} />;
<CardHeader
titleTypographyProps={{ component: CustomComponent, stringProp: 'stringProp', numberProp: 2 }}
/>;
<CardHeader titleTypographyProps={{ variant: 'h1' }} />;
<CardHeader titleTypographyProps={{ align: 'left' }} />;
<CardHeader
titleTypographyProps={{
color: 'primary',
display: 'block',
gutterBottom: true,
noWrap: true,
variantMapping: { h1: 'h1' },
}}
/>;
// $ExpectError
<CardHeader
titleTypographyProps={{
component: CustomComponent,
stringProp: 'stringProp',
numberProp: '',
}}
/>;
// $ExpectError
<CardHeader titleTypographyProps={{ component: CustomComponent, numberProp: 2 }} />;
<CardHeader
titleTypographyProps={{
component: 'a',
// ExpectError: This is expected to err; the type system is welcoming unknown props.
propThatDoesntExist: 'shouldNotWork',
}}
/>;
}

function subheaderTypographyPropsTest() {
<CardHeader subheaderTypographyProps={{ component: 'a', href: 'href' }} />;
<CardHeader
subheaderTypographyProps={{
component: CustomComponent,
stringProp: 'stringProp',
numberProp: 2,
}}
/>;
<CardHeader subheaderTypographyProps={{ variant: 'h1' }} />;
<CardHeader subheaderTypographyProps={{ align: 'left' }} />;
<CardHeader
subheaderTypographyProps={{
color: 'primary',
display: 'block',
gutterBottom: true,
noWrap: true,
variantMapping: { h1: 'h1' },
}}
/>;
<CardHeader
subheaderTypographyProps={{
component: 'a',
// ExpectError: This is expected to err; the type system is welcoming unknown props.
propThatDoesntExist: 'shouldNotWork',
}}
/>;
// $ExpectError
<CardHeader subheaderTypographyProps={{ component: 'incorrectComponent' }} />;
// $ExpectError
<CardHeader subheaderTypographyProps={{ component: CustomComponent, numberProp: 2 }} />;
}

function mixedTypographyPropsTest() {
<CardHeader
titleTypographyProps={{ component: 'a', href: 'href' }}
subheaderTypographyProps={{ component: 'a', href: 'href' }}
/>;
<CardHeader
titleTypographyProps={{ component: CustomComponent, stringProp: 'stringProp', numberProp: 2 }}
subheaderTypographyProps={{
component: CustomComponent,
stringProp: 'stringProp',
numberProp: 2,
}}
/>;
// $ExpectError
<CardHeader
titleTypographyProps={{ component: 'incorrectComponent' }}
subheaderTypographyProps={{ component: 'incorrectComponent' }}
/>;
<CardHeader
titleTypographyProps={{
component: 'a',
// ExpectError: This is expected to err; the type system is welcoming unknown props.
propThatDoesntExist: 'shouldNotWork',
}}
subheaderTypographyProps={{
component: 'a',
// ExpectError: This is expected to err; the type system is welcoming unknown props.
propThatDoesntExist: 'shouldNotWork',
}}
/>;
// $ExpectError
<CardHeader
titleTypographyProps={{ component: CustomComponent, numberProp: 2 }}
subheaderTypographyProps={{ component: CustomComponent, numberProp: 2 }}
/>;
<CardHeader
// $ExpectError
titleTypographyProps={{ component: CustomComponent, numberProp: 2 }}
subheaderTypographyProps={{ component: CustomComponent, numberProp: 2, stringProp: 'yada' }}
/>;
<CardHeader
titleTypographyProps={{ component: CustomComponent, numberProp: 2, stringProp: 'yada' }}
// $ExpectError
subheaderTypographyProps={{ component: CustomComponent, numberProp: 2 }}
/>;
}