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

Improve utils coverage #11

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
6 changes: 3 additions & 3 deletions packages/material-ui-utils/src/HTMLElementType.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
export default function HTMLElementType(
props: { [key: string]: any },
props: { [key: string]: unknown },
propName: string,
componentName: string,
location: string,
propFullName: string,
) {
): Error | null {
if (process.env.NODE_ENV === 'production') {
return null;
}
Expand All @@ -16,7 +16,7 @@ export default function HTMLElementType(
return null;
}

if (propValue && propValue.nodeType !== 1) {
if (propValue && (propValue as any).nodeType !== 1) {
return new Error(
`Invalid ${location} \`${safePropName}\` supplied to \`${componentName}\`. ` +
`Expected an HTMLElement.`,
Expand Down
4 changes: 1 addition & 3 deletions packages/material-ui-utils/src/chainPropTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,11 @@ import * as PropTypes from 'prop-types';
export default function chainPropTypes(
propType1: PropTypes.Validator<unknown>,
propType2: PropTypes.Validator<unknown>,
): PropTypes.Requireable<unknown> {
): PropTypes.Validator<unknown> {
Copy link
Owner

@oliviertassinari oliviertassinari Aug 31, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about this one, it should create an issue on

const elementAcceptingRef = chainPropTypes(PropTypes.element, acceptingRef);
elementAcceptingRef.isRequired = chainPropTypes(PropTypes.element.isRequired, acceptingRef);

but at the same time, this looks completely safe:

var foo = () => null;
foo.isRequired = () => null;

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PropTypes.Requireable<unknown> means there already is a isRequred which is not true. We only assign that later.

if (process.env.NODE_ENV === 'production') {
// @ts-ignore
return () => null;
}

// @ts-ignore
return function validate(...args) {
return propType1(...args) || propType2(...args);
};
Expand Down
16 changes: 7 additions & 9 deletions packages/material-ui-utils/src/deepmerge.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export function isPlainObject(item: unknown) {
export function isPlainObject(item: unknown): item is Record<keyof any, unknown> {
return item && typeof item === 'object' && item.constructor === Object;
}

Expand All @@ -7,10 +7,10 @@ export interface DeepmergeOptions {
}

export default function deepmerge<T>(
target: Partial<T>,
source: Partial<T>,
target: T,
source: unknown,
options: DeepmergeOptions = { clone: true },
) {
): T {
const output = options.clone ? { ...target } : target;

if (isPlainObject(target) && isPlainObject(source)) {
Expand All @@ -20,13 +20,11 @@ export default function deepmerge<T>(
return;
}

// @ts-ignore
if (isPlainObject(source[key]) && key in target) {
// @ts-ignore
output[key] = deepmerge(target[key], source[key], options);
// Since `output` is a clone of `target` and we have narrowed `target` in this block we can cast to the same type.
(output as Record<keyof any, unknown>)[key] = deepmerge(target[key], source[key], options);
} else {
// @ts-ignore
output[key] = source[key];
(output as Record<keyof any, unknown>)[key] = source[key];
}
});
}
Expand Down
11 changes: 7 additions & 4 deletions packages/material-ui-utils/src/elementAcceptingRef.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import PropTypes from 'prop-types';
import chainPropTypes from './chainPropTypes';

function isClassComponent(elementType: any) {
function isClassComponent(elementType: Function) {
// elementType.prototype?.isReactComponent
const { prototype = {} } = elementType;

return Boolean(prototype.isReactComponent);
}

function acceptingRef(
props: { [key: string]: any },
props: { [key: string]: unknown },
propName: string,
componentName: string,
location: string,
Expand All @@ -24,7 +24,7 @@ function acceptingRef(

let warningHint;

const elementType = element.type;
const elementType: unknown = (element as any).type;
/**
* Blacklisting instead of whitelisting
*
Expand All @@ -49,7 +49,10 @@ function acceptingRef(
return null;
}

const elementAcceptingRef = chainPropTypes(PropTypes.element, acceptingRef);
const elementAcceptingRef = chainPropTypes(
PropTypes.element,
acceptingRef,
) as PropTypes.Requireable<unknown>;
elementAcceptingRef.isRequired = chainPropTypes(PropTypes.element.isRequired, acceptingRef);

export default elementAcceptingRef;
4 changes: 2 additions & 2 deletions packages/material-ui-utils/src/elementTypeAcceptingRef.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import PropTypes from 'prop-types';
import chainPropTypes from './chainPropTypes';

function isClassComponent(elementType: { prototype?: Record<keyof any, any> }) {
function isClassComponent(elementType: Function) {
// elementType.prototype?.isReactComponent
const { prototype = {} } = elementType;
const { prototype } = elementType;

return Boolean(prototype.isReactComponent);
}
Expand Down
48 changes: 25 additions & 23 deletions packages/material-ui-utils/src/exactProp.test.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,35 @@
import { expect } from 'chai';
import exactProp, { specialProperty } from './exactProp';
import * as PropTypes from 'prop-types';
import exactProp from './exactProp';

describe('exactProp()', () => {
const exactPropTypes = exactProp({
bar: {},
beforeEach(() => {
PropTypes.resetWarningCache();
});

it('should have the right shape', () => {
expect(typeof exactProp).to.equal('function');
expect(typeof exactPropTypes).to.equal('object');
it('should return null for supported props', () => {
const props = {
bar: false,
};
const propTypes = {
bar: PropTypes.bool,
};

expect(() => {
PropTypes.checkPropTypes(exactProp(propTypes), props, 'props', 'Component');
}).not.toErrorDev();
});

describe('exactPropTypes', () => {
it('should return null for supported props', () => {
const props = {
bar: false,
};
const result = exactPropTypes[specialProperty](props);
expect(result).to.equal(null);
});
it('should return an error for unsupported props', () => {
const props = {
foo: false,
};
const propTypes = {
bar: PropTypes.bool,
};

it('should return an error for unsupported props', () => {
const props = {
foo: true,
};
const result = exactPropTypes[specialProperty](props);
expect(result.message).to.match(
/The following props are not supported: `foo`. Please remove them/,
);
});
expect(() => {
PropTypes.checkPropTypes(exactProp(propTypes), props, 'props', 'Component');
}).toErrorDev('The following props are not supported: `foo`. Please remove them');
});
});
6 changes: 3 additions & 3 deletions packages/material-ui-utils/src/exactProp.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { ValidationMap } from 'prop-types';
// This module is based on https://github.com/airbnb/prop-types-exact repository.
// However, in order to reduce the number of dependencies and to remove some extra safe checks
// the module was forked.

// Only exported for test purposes.
export const specialProperty = 'exact-prop: \u200b';
const specialProperty = 'exact-prop: \u200b';

export default function exactProp(propTypes: any) {
export default function exactProp<T>(propTypes: ValidationMap<T>): ValidationMap<T> {
if (process.env.NODE_ENV === 'production') {
return propTypes;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/material-ui-utils/src/formatMuiErrorMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Use `MuiError` from `@material-ui/utils/macros/MuiError.macro` instead.
* @param {number} code
*/
export default function formatMuiErrorMessage(code: number) {
export default function formatMuiErrorMessage(code: number): string {
// Apply babel-plugin-transform-template-literals in loose mode
// loose mode is safe iff we're concatenating primitives
// see https://babeljs.io/docs/en/babel-plugin-transform-template-literals#loose
Expand Down
27 changes: 10 additions & 17 deletions packages/material-ui-utils/src/getDisplayName.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,16 @@ import { ForwardRef, Memo } from 'react-is';
// Simplified polyfill for IE 11 support
// https://github.com/JamesMGreene/Function.name/blob/58b314d4a983110c3682f1228f845d39ccca1817/Function.name.js#L3
const fnNameMatchRegex = /^\s*function(?:\s|\s*\/\*.*\*\/\s*)+([^(\s/]*)\s*/;
export function getFunctionName(fn: any) {
export function getFunctionName(fn: Function): string {
const match = `${fn}`.match(fnNameMatchRegex);
const name = match && match[1];
return name || '';
}

/**
* @param {function} Component
* @param {string} fallback
* @returns {string | undefined}
*/
function getFunctionComponentName(Component: any, fallback = '') {
function getFunctionComponentName(
Component: React.FunctionComponent | React.ComponentClass,
fallback = '',
) {
return Component.displayName || Component.name || getFunctionName(Component) || fallback;
}

Expand All @@ -30,11 +28,8 @@ function getWrappedName(outerType: any, innerType: any, wrapperName: string) {
* cherry-pick from
* https://github.com/facebook/react/blob/769b1f270e1251d9dbdce0fcbd9e92e502d059b8/packages/shared/getComponentName.js
* originally forked from recompose/getDisplayName with added IE 11 support
*
* @param {React.ElementType} Component
* @returns {string | undefined}
*/
export default function getDisplayName(Component: React.ElementType) {
export default function getDisplayName(Component: React.ElementType): string | undefined {
if (Component == null) {
return undefined;
}
Expand All @@ -47,15 +42,13 @@ export default function getDisplayName(Component: React.ElementType) {
return getFunctionComponentName(Component, 'Component');
}

// TypeScript can't have components as objects but they exist in the form of `memo` or `Suspense`
if (typeof Component === 'object') {
// @ts-ignore
switch (Component.$$typeof) {
switch ((Component as any).$$typeof) {
case ForwardRef:
// @ts-ignore
return getWrappedName(Component, Component.render, 'ForwardRef');
return getWrappedName(Component, (Component as any).render, 'ForwardRef');
case Memo:
// @ts-ignore
return getWrappedName(Component, Component.type, 'memo');
return getWrappedName(Component, (Component as any).type, 'memo');
default:
return undefined;
}
Expand Down