From bd008fe1530f938b47e29a10eec2d0430180cbf5 Mon Sep 17 00:00:00 2001 From: Alexander Fedyashov Date: Mon, 24 Apr 2017 21:49:29 +0300 Subject: [PATCH 1/2] test(typings): additional tests for typings and fixes --- src/addons/Confirm/Confirm.d.ts | 2 ++ src/addons/Portal/Portal.js | 6 ++-- src/addons/Select/Select.d.ts | 1 + src/collections/Form/FormCheckbox.d.ts | 3 ++ src/collections/Form/FormRadio.d.ts | 3 ++ src/collections/Table/TableFooter.d.ts | 1 + src/elements/List/ListIcon.d.ts | 2 ++ src/modules/Popup/Popup.d.ts | 35 +++++++++++++++++++ src/views/Statistic/StatisticGroup.d.ts | 2 +- test/specs/addons/Portal/Portal-test.js | 3 ++ .../Form/FormFieldCheckbox-test.js | 4 ++- .../collections/Form/FormFieldRadio-test.js | 4 ++- test/specs/commonTests/hasValidTypings.js | 15 +++++--- test/specs/commonTests/isConformant.js | 1 + test/specs/commonTests/tsHelpers.js | 15 ++++++-- test/specs/modules/Popup/Popup-test.js | 1 + 16 files changed, 86 insertions(+), 12 deletions(-) diff --git a/src/addons/Confirm/Confirm.d.ts b/src/addons/Confirm/Confirm.d.ts index 839df925a4..0aeefe28f5 100644 --- a/src/addons/Confirm/Confirm.d.ts +++ b/src/addons/Confirm/Confirm.d.ts @@ -2,6 +2,8 @@ import * as React from 'react'; import { ModalProps } from '../../modules/Modal'; export interface ConfirmProps extends ModalProps { + [key: string]: any; + /** The cancel button text. */ cancelButton?: any; diff --git a/src/addons/Portal/Portal.js b/src/addons/Portal/Portal.js index 9740947b10..0118a4a70e 100644 --- a/src/addons/Portal/Portal.js +++ b/src/addons/Portal/Portal.js @@ -64,12 +64,12 @@ class Portal extends Component { /** The node where the portal should mount. */ mountNode: PropTypes.any, - /** Milliseconds to wait before closing on mouse leave */ - mouseLeaveDelay: PropTypes.number, - /** Milliseconds to wait before opening on mouse over */ mouseEnterDelay: PropTypes.number, + /** Milliseconds to wait before closing on mouse leave */ + mouseLeaveDelay: PropTypes.number, + /** * Called when a close event happens * diff --git a/src/addons/Select/Select.d.ts b/src/addons/Select/Select.d.ts index 65d30fed71..4e01a9f8c2 100644 --- a/src/addons/Select/Select.d.ts +++ b/src/addons/Select/Select.d.ts @@ -7,6 +7,7 @@ import { default as DropdownItem } from '../../modules/Dropdown/DropdownItem'; import { default as DropdownMenu } from '../../modules/Dropdown/DropdownMenu'; export interface SelectProps extends DropdownProps { + [key: string]: any; } interface SelectComponent extends React.StatelessComponent { diff --git a/src/collections/Form/FormCheckbox.d.ts b/src/collections/Form/FormCheckbox.d.ts index 453467878b..b337cdb753 100644 --- a/src/collections/Form/FormCheckbox.d.ts +++ b/src/collections/Form/FormCheckbox.d.ts @@ -11,6 +11,9 @@ export interface FormCheckboxProps extends FormFieldProps, CheckboxProps { /** A FormField control prop. */ control?: any; + + /** HTML input type, either checkbox or radio. */ + type?: 'checkbox' | 'radio'; } declare const FormCheckbox: React.StatelessComponent; diff --git a/src/collections/Form/FormRadio.d.ts b/src/collections/Form/FormRadio.d.ts index 4f95ab812d..1f3fcdc84d 100644 --- a/src/collections/Form/FormRadio.d.ts +++ b/src/collections/Form/FormRadio.d.ts @@ -11,6 +11,9 @@ export interface FormRadioProps extends FormFieldProps, RadioProps { /** A FormField control prop. */ control?: any; + + /** HTML input type, either checkbox or radio. */ + type?: 'checkbox' | 'radio'; } declare const FormRadio: React.StatelessComponent; diff --git a/src/collections/Table/TableFooter.d.ts b/src/collections/Table/TableFooter.d.ts index 73695e4dc9..7cda502e24 100644 --- a/src/collections/Table/TableFooter.d.ts +++ b/src/collections/Table/TableFooter.d.ts @@ -2,6 +2,7 @@ import * as React from 'react'; import { TableHeaderProps } from './TableHeader'; export interface TableFooterProps extends TableHeaderProps { + [key: string]: any; } declare const TableFooter: React.StatelessComponent; diff --git a/src/elements/List/ListIcon.d.ts b/src/elements/List/ListIcon.d.ts index 12795bbf0b..9445be1af0 100644 --- a/src/elements/List/ListIcon.d.ts +++ b/src/elements/List/ListIcon.d.ts @@ -4,6 +4,8 @@ import { SemanticVERTICALALIGNMENTS } from '../..'; import { IconProps } from '../Icon'; export interface ListIconProps extends IconProps { + [key: string]: any; + /** Additional classes. */ className?: string; diff --git a/src/modules/Popup/Popup.d.ts b/src/modules/Popup/Popup.d.ts index 4f51547cd2..74f2a96b87 100644 --- a/src/modules/Popup/Popup.d.ts +++ b/src/modules/Popup/Popup.d.ts @@ -40,6 +40,38 @@ export interface PopupProps extends PortalProps { /** Event triggering the popup. */ on?: 'hover' | 'click' | 'focus'; + /** + * Called when a close event happens. + * + * @param {SyntheticEvent} event - React's original SyntheticEvent. + * @param {object} data - All props. + */ + onClose?: (event: React.MouseEvent, data: PopupProps) => void; + + /** + * Called when the portal is mounted on the DOM. + * + * @param {null} + * @param {object} data - All props. + */ + onMount?: (nothing: null, data: PopupProps) => void; + + /** + * Called when an open event happens. + * + * @param {SyntheticEvent} event - React's original SyntheticEvent. + * @param {object} data - All props. + */ + onOpen?: (event: React.MouseEvent, data: PopupProps) => void; + + /** + * Called when the portal is unmounted from the DOM. + * + * @param {null} + * @param {object} data - All props. + */ + onUnmount?: (nothing: null, data: PopupProps) => void; + /** Position for the popover. */ position?: 'top left' | 'top right' | 'bottom right' | 'bottom left' | @@ -52,6 +84,9 @@ export interface PopupProps extends PortalProps { /** Custom Popup style. */ style?: Object; + /** Element to be rendered in-place where the popup is defined. */ + trigger?: React.ReactNode; + /** Popup width. */ wide?: boolean | 'very'; } diff --git a/src/views/Statistic/StatisticGroup.d.ts b/src/views/Statistic/StatisticGroup.d.ts index 4d0b177378..9ba9b71cbe 100644 --- a/src/views/Statistic/StatisticGroup.d.ts +++ b/src/views/Statistic/StatisticGroup.d.ts @@ -1,7 +1,7 @@ import * as React from 'react'; import { SemanticCOLORS, SemanticWIDTHS } from '../..'; -import { StatisticSizeProp } from './Statictic'; +import { StatisticSizeProp } from './Statistic'; export interface StatisticGroupProps { [key: string]: any; diff --git a/test/specs/addons/Portal/Portal-test.js b/test/specs/addons/Portal/Portal-test.js index 7d0f3d342b..1c87a3c8e5 100644 --- a/test/specs/addons/Portal/Portal-test.js +++ b/test/specs/addons/Portal/Portal-test.js @@ -2,6 +2,7 @@ import PropTypes from 'prop-types' import React from 'react' import { unmountComponentAtNode } from 'react-dom' +import * as common from 'test/specs/commonTests' import { domEvent, sandbox } from 'test/utils' import Portal from 'src/addons/Portal/Portal' @@ -25,6 +26,8 @@ describe('Portal', () => { if (attachTo) document.body.removeChild(attachTo) }) + common.hasValidTypings(Portal) + it('propTypes.children should be required', () => { Portal.propTypes.children.should.equal(PropTypes.node.isRequired) }) diff --git a/test/specs/collections/Form/FormFieldCheckbox-test.js b/test/specs/collections/Form/FormFieldCheckbox-test.js index fab5295107..430cd6b5b9 100644 --- a/test/specs/collections/Form/FormFieldCheckbox-test.js +++ b/test/specs/collections/Form/FormFieldCheckbox-test.js @@ -5,7 +5,9 @@ import Checkbox from 'src/modules/Checkbox/Checkbox' import * as common from 'test/specs/commonTests' describe('FormCheckbox', () => { - common.isConformant(FormCheckbox) + common.isConformant(FormCheckbox, { + ignoredProps: ['type'], + }) it('renders a FormField with a Checkbox control', () => { shallow() diff --git a/test/specs/collections/Form/FormFieldRadio-test.js b/test/specs/collections/Form/FormFieldRadio-test.js index 592d4c9b63..4e9704582a 100644 --- a/test/specs/collections/Form/FormFieldRadio-test.js +++ b/test/specs/collections/Form/FormFieldRadio-test.js @@ -5,7 +5,9 @@ import FormRadio from 'src/collections/Form/FormRadio' import * as common from 'test/specs/commonTests' describe('FormRadio', () => { - common.isConformant(FormRadio) + common.isConformant(FormRadio, { + ignoredProps: ['type'], + }) it('renders a FormField with a Radio control', () => { shallow() diff --git a/test/specs/commonTests/hasValidTypings.js b/test/specs/commonTests/hasValidTypings.js index 7a64a07064..97b96aa7f4 100644 --- a/test/specs/commonTests/hasValidTypings.js +++ b/test/specs/commonTests/hasValidTypings.js @@ -5,6 +5,7 @@ import componentInfo from './componentInfo' import { getNodes, getInterfaces, + hasAnySignature, requireTs, } from './tsHelpers' @@ -14,6 +15,7 @@ import { * @param {Object} [extractedInfo={}] * @param {Object} [extractedInfo._meta={}] The meta information about Component * @param {Object} [options={}] + * @param {array} [options.ignoredProps=[]] Props that will be ignored in tests. * @param {Object} [options.requiredProps={}] Props required to render Component without errors or warnings. */ export default (Component, extractedInfo, options = {}) => { @@ -22,7 +24,7 @@ export default (Component, extractedInfo, options = {}) => { filenameWithoutExt, filePath, } = extractedInfo || _.find(componentInfo, i => i.constructorName === Component.prototype.constructor.name) - const { requiredProps } = options + const { ignoredProps = [], requiredProps } = options const tsFile = filenameWithoutExt + '.d.ts' const tsContent = requireTs(path.join(path.dirname(filePath), tsFile)) @@ -50,18 +52,23 @@ export default (Component, extractedInfo, options = {}) => { }) describe('props', () => { - const { props: interfaceProps } = interfaceObject + const { props } = interfaceObject + + it('has any signature', () => { + hasAnySignature(tsNodes).should.to.equal(true) + }) it('are correctly defined', () => { const componentPropTypes = _.get(Component, 'propTypes') const componentProps = _.keys(componentPropTypes) + const interfaceProps = _.without(_.map(props, 'name'), ...ignoredProps) - componentProps.should.to.deep.equal(_.map(interfaceProps, 'name')) + componentProps.should.to.deep.equal(interfaceProps) }) it('only necessary are required', () => { const componentRequired = _.keys(requiredProps) - const interfaceRequired = _.filter(interfaceProps, ['required', true]) + const interfaceRequired = _.filter(props, ['required', true]) componentRequired.should.to.deep.equal(_.map(interfaceRequired, 'name')) }) diff --git a/test/specs/commonTests/isConformant.js b/test/specs/commonTests/isConformant.js index 9c49b78f4f..e790db743f 100644 --- a/test/specs/commonTests/isConformant.js +++ b/test/specs/commonTests/isConformant.js @@ -14,6 +14,7 @@ import { consoleUtil, sandbox, syntheticEvent } from 'test/utils' * Assert Component conforms to guidelines that are applicable to all components. * @param {React.Component|Function} Component A component that should conform. * @param {Object} [options={}] + * @param {array} [options.ignoredProps=[]] Props that will be ignored in typings tests. * @param {Object} [options.eventTargets={}] Map of events and the child component to target. * @param {Object} [options.requiredProps={}] Props required to render Component without errors or warnings. */ diff --git a/test/specs/commonTests/tsHelpers.js b/test/specs/commonTests/tsHelpers.js index d54f68e5ab..1d6988a053 100644 --- a/test/specs/commonTests/tsHelpers.js +++ b/test/specs/commonTests/tsHelpers.js @@ -6,11 +6,12 @@ import { SyntaxKind, } from 'typescript' +const isAnyKeyword = ({ kind }) => kind === SyntaxKind.AnyKeyword +const isIndexSignature = ({ kind }) => kind === SyntaxKind.IndexSignature const isInterface = ({ kind }) => kind === SyntaxKind.InterfaceDeclaration - const isExportModifier = ({ kind }) => kind === SyntaxKind.ExportKeyword - const isPropertySignature = ({ kind }) => kind === SyntaxKind.PropertySignature +const isStringKeyword = ({ kind }) => kind === SyntaxKind.StringKeyword const getProps = members => { const props = _.filter(members, isPropertySignature) @@ -47,6 +48,16 @@ export const getInterfaces = nodes => { })) } +export const hasAnySignature = nodes => { + const signatures = _.filter(nodes, isIndexSignature) + + return _.some(signatures, ({ parameters, type: rightType }) => { + const { name: { text }, type } = _.head(parameters) + + return isAnyKeyword(rightType) && isStringKeyword(type) && text === 'key' + }) +} + export const requireTs = tsPath => { try { return require(`!raw-loader!../../../src/${tsPath}`) diff --git a/test/specs/modules/Popup/Popup-test.js b/test/specs/modules/Popup/Popup-test.js index 08eb220cc5..14a0a2696c 100644 --- a/test/specs/modules/Popup/Popup-test.js +++ b/test/specs/modules/Popup/Popup-test.js @@ -36,6 +36,7 @@ describe('Popup', () => { }) common.hasSubComponents(Popup, [PopupHeader, PopupContent]) + common.hasValidTypings(Popup) // Heads up! // From a720687ca3f1b70fb9c26df5b1b69dd2cd60eaaa Mon Sep 17 00:00:00 2001 From: Alexander Fedyashov Date: Fri, 28 Apr 2017 18:54:14 +0300 Subject: [PATCH 2/2] test(typings): update option name --- test/specs/collections/Form/FormFieldCheckbox-test.js | 2 +- test/specs/collections/Form/FormFieldRadio-test.js | 2 +- test/specs/commonTests/hasValidTypings.js | 6 +++--- test/specs/commonTests/isConformant.js | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test/specs/collections/Form/FormFieldCheckbox-test.js b/test/specs/collections/Form/FormFieldCheckbox-test.js index 430cd6b5b9..985e6f9c58 100644 --- a/test/specs/collections/Form/FormFieldCheckbox-test.js +++ b/test/specs/collections/Form/FormFieldCheckbox-test.js @@ -6,7 +6,7 @@ import * as common from 'test/specs/commonTests' describe('FormCheckbox', () => { common.isConformant(FormCheckbox, { - ignoredProps: ['type'], + ignoredTypingsProps: ['type'], }) it('renders a FormField with a Checkbox control', () => { diff --git a/test/specs/collections/Form/FormFieldRadio-test.js b/test/specs/collections/Form/FormFieldRadio-test.js index 4e9704582a..ec31b57aa2 100644 --- a/test/specs/collections/Form/FormFieldRadio-test.js +++ b/test/specs/collections/Form/FormFieldRadio-test.js @@ -6,7 +6,7 @@ import * as common from 'test/specs/commonTests' describe('FormRadio', () => { common.isConformant(FormRadio, { - ignoredProps: ['type'], + ignoredTypingsProps: ['type'], }) it('renders a FormField with a Radio control', () => { diff --git a/test/specs/commonTests/hasValidTypings.js b/test/specs/commonTests/hasValidTypings.js index 97b96aa7f4..866c34f17c 100644 --- a/test/specs/commonTests/hasValidTypings.js +++ b/test/specs/commonTests/hasValidTypings.js @@ -15,7 +15,7 @@ import { * @param {Object} [extractedInfo={}] * @param {Object} [extractedInfo._meta={}] The meta information about Component * @param {Object} [options={}] - * @param {array} [options.ignoredProps=[]] Props that will be ignored in tests. + * @param {array} [options.ignoredTypingsProps=[]] Props that will be ignored in tests. * @param {Object} [options.requiredProps={}] Props required to render Component without errors or warnings. */ export default (Component, extractedInfo, options = {}) => { @@ -24,7 +24,7 @@ export default (Component, extractedInfo, options = {}) => { filenameWithoutExt, filePath, } = extractedInfo || _.find(componentInfo, i => i.constructorName === Component.prototype.constructor.name) - const { ignoredProps = [], requiredProps } = options + const { ignoredTypingsProps = [], requiredProps } = options const tsFile = filenameWithoutExt + '.d.ts' const tsContent = requireTs(path.join(path.dirname(filePath), tsFile)) @@ -61,7 +61,7 @@ export default (Component, extractedInfo, options = {}) => { it('are correctly defined', () => { const componentPropTypes = _.get(Component, 'propTypes') const componentProps = _.keys(componentPropTypes) - const interfaceProps = _.without(_.map(props, 'name'), ...ignoredProps) + const interfaceProps = _.without(_.map(props, 'name'), ...ignoredTypingsProps) componentProps.should.to.deep.equal(interfaceProps) }) diff --git a/test/specs/commonTests/isConformant.js b/test/specs/commonTests/isConformant.js index e790db743f..d97e5024e6 100644 --- a/test/specs/commonTests/isConformant.js +++ b/test/specs/commonTests/isConformant.js @@ -14,7 +14,7 @@ import { consoleUtil, sandbox, syntheticEvent } from 'test/utils' * Assert Component conforms to guidelines that are applicable to all components. * @param {React.Component|Function} Component A component that should conform. * @param {Object} [options={}] - * @param {array} [options.ignoredProps=[]] Props that will be ignored in typings tests. + * @param {array} [options.ignoredTypingsProps=[]] Props that will be ignored in typings tests. * @param {Object} [options.eventTargets={}] Map of events and the child component to target. * @param {Object} [options.requiredProps={}] Props required to render Component without errors or warnings. */