From d117cf621f939c6789d8e00978bebaa7896af676 Mon Sep 17 00:00:00 2001 From: Alexander Fedyashov Date: Wed, 8 Nov 2017 21:51:50 +0200 Subject: [PATCH 1/2] feat(ElementType): new wrapper for all elements --- src/elements/Container/Container.js | 22 ++-- src/elements/Image/Image.js | 44 ++++---- src/elements/List/List.js | 34 +++--- src/elements/List/ListContent.js | 24 +++-- src/elements/List/ListDescription.js | 24 +++-- src/elements/List/ListHeader.js | 24 +++-- src/elements/List/ListIcon.js | 2 +- src/elements/List/ListItem.js | 36 ++++--- src/elements/List/ListList.js | 30 +++--- src/lib/ElementType.js | 34 ++++++ src/lib/computeElementType.js | 37 +++++++ src/lib/getUnhandledProps.js | 6 +- src/lib/index.js | 2 + src/lib/withComputedType.js | 17 +++ src/views/Advertisement/Advertisement.js | 22 ++-- src/views/Item/ItemImage.js | 4 +- test/specs/commonTests/classNameHelpers.js | 58 +++++++--- test/specs/commonTests/componentInfo.js | 9 +- test/specs/commonTests/hasUIClassName.js | 13 ++- test/specs/commonTests/hasValidTypings.js | 7 +- .../commonTests/implementsClassNameProps.js | 101 ++++++++++++++---- .../commonTests/implementsCommonProps.js | 50 +++++++-- .../commonTests/implementsShorthandProp.js | 12 ++- test/specs/commonTests/isConformant.js | 43 ++++---- test/specs/commonTests/rendersChildren.js | 84 ++++++++++++--- test/specs/elements/Image/Image-test.js | 26 +++-- test/specs/elements/List/List-test.js | 36 +++---- test/specs/elements/List/ListContent-test.js | 11 +- test/specs/elements/List/ListIcon-test.js | 8 +- test/specs/elements/List/ListItem-test.js | 60 ++++++----- test/specs/elements/List/ListList-test.js | 8 ++ test/specs/lib/ElementType-test.js | 39 +++++++ test/specs/lib/computeElementType-test.js | 34 ++++++ .../views/Advertisement/Advertisement-test.js | 2 +- test/specs/views/Item/ItemImage-test.js | 9 +- 35 files changed, 697 insertions(+), 275 deletions(-) create mode 100644 src/lib/ElementType.js create mode 100644 src/lib/computeElementType.js create mode 100644 src/lib/withComputedType.js create mode 100644 test/specs/lib/ElementType-test.js create mode 100644 test/specs/lib/computeElementType-test.js diff --git a/src/elements/Container/Container.js b/src/elements/Container/Container.js index 683e36051d..561de0355f 100644 --- a/src/elements/Container/Container.js +++ b/src/elements/Container/Container.js @@ -5,18 +5,19 @@ import React from 'react' import { childrenUtils, customPropTypes, - getElementType, + ElementType, getUnhandledProps, META, SUI, useKeyOnly, useTextAlignProp, + withComputedType, } from '../../lib' /** * A container limits content to a maximum width. */ -function Container(props) { +const InnerContainer = (props) => { const { children, className, @@ -33,8 +34,7 @@ function Container(props) { 'container', className, ) - const rest = getUnhandledProps(Container, props) - const ElementType = getElementType(Container, props) + const rest = getUnhandledProps(InnerContainer, props, { passAs: true }) return ( @@ -43,12 +43,7 @@ function Container(props) { ) } -Container._meta = { - name: 'Container', - type: META.TYPES.ELEMENT, -} - -Container.propTypes = { +InnerContainer.propTypes = { /** An element type to render as (string or function). */ as: customPropTypes.as, @@ -71,4 +66,11 @@ Container.propTypes = { textAlign: PropTypes.oneOf(SUI.TEXT_ALIGNMENTS), } +const Container = withComputedType()(InnerContainer) + +Container._meta = { + name: 'Container', + type: META.TYPES.ELEMENT, +} + export default Container diff --git a/src/elements/Image/Image.js b/src/elements/Image/Image.js index 686a093e20..7211623114 100644 --- a/src/elements/Image/Image.js +++ b/src/elements/Image/Image.js @@ -7,7 +7,7 @@ import { childrenUtils, createShorthandFactory, customPropTypes, - getElementType, + ElementType, getUnhandledProps, META, SUI, @@ -15,6 +15,7 @@ import { useKeyOrValueAndKey, useValueAndKey, useVerticalAlignProp, + withComputedType, } from '../../lib' import Dimmer from '../../modules/Dimmer' import Label from '../Label/Label' @@ -25,8 +26,9 @@ import ImageGroup from './ImageGroup' * An image is a graphic representation of something. * @see Icon */ -function Image(props) { +const InnerImage = (props) => { const { + as, alt, avatar, bordered, @@ -50,7 +52,6 @@ function Image(props) { src, verticalAlign, width, - wrapped, ui, } = props @@ -72,22 +73,19 @@ function Image(props) { 'image', className, ) - const rest = getUnhandledProps(Image, props) - const ElementType = getElementType(Image, props, () => { - if (!_.isNil(dimmer) || !_.isNil(label) || !_.isNil(wrapped) || !childrenUtils.isNil(children)) return 'div' - }) + const rest = getUnhandledProps(InnerImage, props) if (!childrenUtils.isNil(children)) { - return {children} + return {children} } if (!childrenUtils.isNil(content)) { - return {content} + return {content} } - const rootProps = { ...rest, className: classes } + const rootProps = { ...rest, as, className: classes } const imgTagProps = { alt, src, height, width } - if (ElementType === 'img') return + if (as === 'img') return return ( @@ -98,14 +96,7 @@ function Image(props) { ) } -Image.Group = ImageGroup - -Image._meta = { - name: 'Image', - type: META.TYPES.ELEMENT, -} - -Image.propTypes = { +InnerImage.propTypes = { /** An element type to render as (string or function). */ as: customPropTypes.as, @@ -197,11 +188,24 @@ Image.propTypes = { wrapped: PropTypes.bool, } -Image.defaultProps = { +InnerImage.defaultProps = { as: 'img', ui: true, } +const computeType = ({ children, dimmer, label, wrapped }) => { + if (!_.isNil(dimmer) || !_.isNil(label) || !_.isNil(wrapped) || !childrenUtils.isNil(children)) return 'div' +} + +const Image = withComputedType(computeType)(InnerImage) + +Image.Group = ImageGroup + +Image._meta = { + name: 'Image', + type: META.TYPES.ELEMENT, +} + Image.create = createShorthandFactory(Image, value => ({ src: value })) export default Image diff --git a/src/elements/List/List.js b/src/elements/List/List.js index 6fe4815e9c..ad72ffdb4c 100644 --- a/src/elements/List/List.js +++ b/src/elements/List/List.js @@ -6,7 +6,7 @@ import React, { Component } from 'react' import { childrenUtils, customPropTypes, - getElementType, + ElementType, getUnhandledProps, META, SUI, @@ -14,6 +14,7 @@ import { useKeyOrValueAndKey, useValueAndKey, useVerticalAlignProp, + withComputedType, } from '../../lib' import ListContent from './ListContent' import ListDescription from './ListDescription' @@ -25,7 +26,7 @@ import ListList from './ListList' /** * A list groups related content. */ -class List extends Component { +class InnerList extends Component { static propTypes = { /** An element type to render as (string or function). */ as: customPropTypes.as, @@ -96,18 +97,6 @@ class List extends Component { verticalAlign: PropTypes.oneOf(SUI.VERTICAL_ALIGNMENTS), } - static _meta = { - name: 'List', - type: META.TYPES.ELEMENT, - } - - static Content = ListContent - static Description = ListDescription - static Header = ListHeader - static Icon = ListIcon - static Item = ListItem - static List = ListList - handleItemOverrides = predefinedProps => ({ onClick: (e, itemProps) => { _.invoke(predefinedProps, 'onClick', e, itemProps) @@ -154,8 +143,7 @@ class List extends Component { 'list', className, ) - const rest = getUnhandledProps(List, this.props) - const ElementType = getElementType(List, this.props) + const rest = getUnhandledProps(InnerList, this.props, { passAs: true }) if (!childrenUtils.isNil(children)) { return {children} @@ -173,4 +161,18 @@ class List extends Component { } } +const List = withComputedType()(InnerList) + +List._meta = { + name: 'List', + type: META.TYPES.ELEMENT, +} + +List.Content = ListContent +List.Description = ListDescription +List.Header = ListHeader +List.Icon = ListIcon +List.Item = ListItem +List.List = ListList + export default List diff --git a/src/elements/List/ListContent.js b/src/elements/List/ListContent.js index 337b83d41d..8733ecc54e 100644 --- a/src/elements/List/ListContent.js +++ b/src/elements/List/ListContent.js @@ -6,12 +6,13 @@ import { childrenUtils, createShorthandFactory, customPropTypes, - getElementType, + ElementType, getUnhandledProps, META, SUI, useValueAndKey, useVerticalAlignProp, + withComputedType, } from '../../lib' import ListDescription from './ListDescription' import ListHeader from './ListHeader' @@ -19,7 +20,7 @@ import ListHeader from './ListHeader' /** * A list item can contain a content. */ -function ListContent(props) { +const InnerListContent = (props) => { const { children, className, @@ -36,8 +37,7 @@ function ListContent(props) { 'content', className, ) - const rest = getUnhandledProps(ListContent, props) - const ElementType = getElementType(ListContent, props) + const rest = getUnhandledProps(InnerListContent, props, { passAs: true }) if (!childrenUtils.isNil(children)) return {children} @@ -50,13 +50,7 @@ function ListContent(props) { ) } -ListContent._meta = { - name: 'ListContent', - parent: 'List', - type: META.TYPES.ELEMENT, -} - -ListContent.propTypes = { +InnerListContent.propTypes = { /** An element type to render as (string or function). */ as: customPropTypes.as, @@ -82,6 +76,14 @@ ListContent.propTypes = { verticalAlign: PropTypes.oneOf(SUI.VERTICAL_ALIGNMENTS), } +const ListContent = withComputedType()(InnerListContent) + +ListContent._meta = { + name: 'ListContent', + parent: 'List', + type: META.TYPES.ELEMENT, +} + ListContent.create = createShorthandFactory(ListContent, content => ({ content })) export default ListContent diff --git a/src/elements/List/ListDescription.js b/src/elements/List/ListDescription.js index b80ef96cd4..5824a72349 100644 --- a/src/elements/List/ListDescription.js +++ b/src/elements/List/ListDescription.js @@ -6,19 +6,19 @@ import { childrenUtils, createShorthandFactory, customPropTypes, - getElementType, + ElementType, getUnhandledProps, META, + withComputedType, } from '../../lib' /** * A list item can contain a description. */ -function ListDescription(props) { +const InnerListDescription = (props) => { const { children, className, content } = props const classes = cx(className, 'description') - const rest = getUnhandledProps(ListDescription, props) - const ElementType = getElementType(ListDescription, props) + const rest = getUnhandledProps(InnerListDescription, props, { passAs: true }) return ( @@ -27,13 +27,7 @@ function ListDescription(props) { ) } -ListDescription._meta = { - name: 'ListDescription', - parent: 'List', - type: META.TYPES.ELEMENT, -} - -ListDescription.propTypes = { +InnerListDescription.propTypes = { /** An element type to render as (string or function). */ as: customPropTypes.as, @@ -47,6 +41,14 @@ ListDescription.propTypes = { content: customPropTypes.contentShorthand, } +const ListDescription = withComputedType()(InnerListDescription) + +ListDescription._meta = { + name: 'ListDescription', + parent: 'List', + type: META.TYPES.ELEMENT, +} + ListDescription.create = createShorthandFactory(ListDescription, content => ({ content })) export default ListDescription diff --git a/src/elements/List/ListHeader.js b/src/elements/List/ListHeader.js index 77a97da048..ddf837cdd1 100644 --- a/src/elements/List/ListHeader.js +++ b/src/elements/List/ListHeader.js @@ -6,19 +6,19 @@ import { childrenUtils, createShorthandFactory, customPropTypes, - getElementType, + ElementType, getUnhandledProps, META, + withComputedType, } from '../../lib' /** * A list item can contain a header. */ -function ListHeader(props) { +const InnerListHeader = (props) => { const { children, className, content } = props const classes = cx('header', className) - const rest = getUnhandledProps(ListHeader, props) - const ElementType = getElementType(ListHeader, props) + const rest = getUnhandledProps(InnerListHeader, props, { passAs: true }) return ( @@ -27,13 +27,7 @@ function ListHeader(props) { ) } -ListHeader._meta = { - name: 'ListHeader', - parent: 'List', - type: META.TYPES.ELEMENT, -} - -ListHeader.propTypes = { +InnerListHeader.propTypes = { /** An element type to render as (string or function). */ as: customPropTypes.as, @@ -47,6 +41,14 @@ ListHeader.propTypes = { content: customPropTypes.contentShorthand, } +const ListHeader = withComputedType()(InnerListHeader) + +ListHeader._meta = { + name: 'ListHeader', + parent: 'List', + type: META.TYPES.ELEMENT, +} + ListHeader.create = createShorthandFactory(ListHeader, content => ({ content })) export default ListHeader diff --git a/src/elements/List/ListIcon.js b/src/elements/List/ListIcon.js index 4d89c8014e..2fe132c9ed 100644 --- a/src/elements/List/ListIcon.js +++ b/src/elements/List/ListIcon.js @@ -14,7 +14,7 @@ import Icon from '../Icon/Icon' /** * A list item can contain an icon. */ -function ListIcon(props) { +const ListIcon = (props) => { const { className, verticalAlign } = props const classes = cx( useVerticalAlignProp(verticalAlign), diff --git a/src/elements/List/ListItem.js b/src/elements/List/ListItem.js index 63b87f596a..d6a5ccb681 100644 --- a/src/elements/List/ListItem.js +++ b/src/elements/List/ListItem.js @@ -7,10 +7,11 @@ import { childrenUtils, createShorthandFactory, customPropTypes, - getElementType, + ElementType, getUnhandledProps, META, useKeyOnly, + withComputedType, } from '../../lib' import Image from '../../elements/Image' import ListContent from './ListContent' @@ -21,7 +22,7 @@ import ListIcon from './ListIcon' /** * A list item can contain a set of items. */ -class ListItem extends Component { +class InnerListItem extends Component { static propTypes = { /** An element type to render as (string or function). */ as: customPropTypes.as, @@ -80,12 +81,6 @@ class ListItem extends Component { value: PropTypes.string, } - static _meta = { - name: 'ListItem', - parent: 'List', - type: META.TYPES.ELEMENT, - } - handleClick = (e) => { const { disabled } = this.props @@ -94,6 +89,7 @@ class ListItem extends Component { render() { const { + as, active, children, className, @@ -106,19 +102,18 @@ class ListItem extends Component { value, } = this.props - const ElementType = getElementType(ListItem, this.props) const classes = cx( useKeyOnly(active, 'active'), useKeyOnly(disabled, 'disabled'), - useKeyOnly(ElementType !== 'li', 'item'), + useKeyOnly(as !== 'li', 'item'), className, ) - const rest = getUnhandledProps(ListItem, this.props) - const valueProp = ElementType === 'li' ? { value } : { 'data-value': value } + const rest = getUnhandledProps(InnerListItem, this.props, { passAs: true }) + const valueProp = as === 'li' ? { value } : { 'data-value': value } if (!childrenUtils.isNil(children)) { return ( - + {children} ) @@ -130,7 +125,7 @@ class ListItem extends Component { // See description of `content` prop for explanation about why this is necessary. if (!isValidElement(content) && _.isPlainObject(content)) { return ( - + {iconElement || imageElement} {ListContent.create(content, { header, description })} @@ -139,9 +134,10 @@ class ListItem extends Component { const headerElement = ListHeader.create(header) const descriptionElement = ListDescription.create(description) + if (iconElement || imageElement) { return ( - + {iconElement || imageElement} {(content || headerElement || descriptionElement) && ( @@ -155,7 +151,7 @@ class ListItem extends Component { } return ( - + {headerElement} {descriptionElement} {content} @@ -164,6 +160,14 @@ class ListItem extends Component { } } +const ListItem = withComputedType()(InnerListItem) + +ListItem._meta = { + name: 'ListItem', + parent: 'List', + type: META.TYPES.ELEMENT, +} + ListItem.create = createShorthandFactory(ListItem, content => ({ content })) export default ListItem diff --git a/src/elements/List/ListList.js b/src/elements/List/ListList.js index aa0c7e518d..aa688b1f6f 100644 --- a/src/elements/List/ListList.js +++ b/src/elements/List/ListList.js @@ -5,39 +5,33 @@ import React from 'react' import { childrenUtils, customPropTypes, - getElementType, + ElementType, getUnhandledProps, META, useKeyOnly, + withComputedType, } from '../../lib' /** * A list can contain a sub list. */ -function ListList(props) { - const { children, className, content } = props +const InnerListList = (props) => { + const { as, children, className, content } = props - const rest = getUnhandledProps(ListList, props) - const ElementType = getElementType(ListList, props) + const rest = getUnhandledProps(InnerListList, props) const classes = cx( - useKeyOnly(ElementType !== 'ul' && ElementType !== 'ol', 'list'), + useKeyOnly(as !== 'ul' && as !== 'ol', 'list'), className, ) return ( - + {childrenUtils.isNil(children) ? content : children} ) } -ListList._meta = { - name: 'ListList', - parent: 'List', - type: META.TYPES.ELEMENT, -} - -ListList.propTypes = { +InnerListList.propTypes = { /** An element type to render as (string or function). */ as: customPropTypes.as, @@ -51,4 +45,12 @@ ListList.propTypes = { content: customPropTypes.contentShorthand, } +const ListList = withComputedType()(InnerListList) + +ListList._meta = { + name: 'ListList', + parent: 'List', + type: META.TYPES.ELEMENT, +} + export default ListList diff --git a/src/lib/ElementType.js b/src/lib/ElementType.js new file mode 100644 index 0000000000..047cfd5a16 --- /dev/null +++ b/src/lib/ElementType.js @@ -0,0 +1,34 @@ +import PropTypes from 'prop-types' +import React, { Component } from 'react' + +import Ref from '../addons/Ref' +import * as customPropTypes from './customPropTypes' + +export default class ElementType extends Component { + static propTypes = { + /** An element type to render as (string or function). */ + as: customPropTypes.as, + + /** + * Called when component did mount, returns an inner DOM node. + * + * @param {HTMLElement} node - Referred node. + */ + innerRef: PropTypes.func, + } + + render() { + const { as: Element, innerRef, ...rest } = this.props + + // Heads up! We should handle common situations to gain performance. + // We don't need to when Element is string or we deal with our component. + if (typeof Element === 'string') return + if (Element._meta) return + + return ( + + + + ) + } +} diff --git a/src/lib/computeElementType.js b/src/lib/computeElementType.js new file mode 100644 index 0000000000..1f51ae9667 --- /dev/null +++ b/src/lib/computeElementType.js @@ -0,0 +1,37 @@ +/** + * Returns a createElement() type based on the passed props. + * Useful for calculating what type a component should render as. + * + * @param {object} props A ReactElement props object + * @param {object} defaultProps A default ReactElement props object + * @param {function} [computeType] A function that returns a default element type. + * @returns {string|function} A ReactElement type + */ +const computeElementType = (props, defaultProps = {}, computeType) => { + const { as, href } = props + + // ---------------------------------------- + // user defined "as" element type + + if (as && as !== defaultProps.as) return props.as + + // ---------------------------------------- + // computed default element type + + if (computeType) { + const computedDefault = computeType(props) + if (computedDefault) return computedDefault + } + + // ---------------------------------------- + // infer anchor links + + if (href) return 'a' + + // ---------------------------------------- + // use defaultProp or 'div' + + return defaultProps.as || 'div' +} + +export default computeElementType diff --git a/src/lib/getUnhandledProps.js b/src/lib/getUnhandledProps.js index 1a4d199541..72cb7ee1d5 100644 --- a/src/lib/getUnhandledProps.js +++ b/src/lib/getUnhandledProps.js @@ -3,14 +3,18 @@ * Useful for getting and spreading unknown props from the user. * @param {function} Component A function or ReactClass. * @param {object} props A ReactElement props object + * @param {Object} [options={}] + * @param {object|function} [options.passAs=false] Forces a pass of `as` prop to unhandled props * @returns {{}} A shallow copy of the prop object */ -const getUnhandledProps = (Component, props) => { +const getUnhandledProps = (Component, props, options = {}) => { // Note that `handledProps` are generated automatically during build with `babel-plugin-transform-react-handled-props` const { handledProps = [] } = Component + const { passAs = false } = options return Object.keys(props).reduce((acc, prop) => { if (prop === 'childKey') return acc + if (prop === 'as' && passAs) acc[prop] = props[prop] if (handledProps.indexOf(prop) === -1) acc[prop] = props[prop] return acc }, {}) diff --git a/src/lib/index.js b/src/lib/index.js index 5601f842e3..4c605e038a 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -19,6 +19,7 @@ export { debug, makeDebugger, } from './debug' +export ElementType from './ElementType' export eventStack from './eventStack' export * from './factories' @@ -43,3 +44,4 @@ export normalizeOffset from './normalizeOffset' export normalizeTransitionDuration from './normalizeTransitionDuration' export { default as objectDiff } from './objectDiff' export shallowEqual from './shallowEqual' +export withComputedType from './withComputedType' diff --git a/src/lib/withComputedType.js b/src/lib/withComputedType.js new file mode 100644 index 0000000000..c90d04f111 --- /dev/null +++ b/src/lib/withComputedType.js @@ -0,0 +1,17 @@ +import React from 'react' +import computeElementType from './computeElementType' + +const withComputedType = (computeType = null) => (ChildComponent) => { + const WithComputedType = props => ( + + ) + WithComputedType.originalComponent = ChildComponent + + return WithComputedType +} + + +export default withComputedType diff --git a/src/views/Advertisement/Advertisement.js b/src/views/Advertisement/Advertisement.js index 87310fea3d..8b5890ec27 100644 --- a/src/views/Advertisement/Advertisement.js +++ b/src/views/Advertisement/Advertisement.js @@ -5,16 +5,17 @@ import React from 'react' import { childrenUtils, customPropTypes, - getElementType, + ElementType, getUnhandledProps, META, useKeyOnly, + withComputedType, } from '../../lib' /** * An ad displays third-party promotional content. */ -function Advertisement(props) { +function InnerAdvertisement(props) { const { centered, children, @@ -32,8 +33,7 @@ function Advertisement(props) { 'ad', className, ) - const rest = getUnhandledProps(Advertisement, props) - const ElementType = getElementType(Advertisement, props) + const rest = getUnhandledProps(InnerAdvertisement, props, { passAs: true }) return ( @@ -42,12 +42,7 @@ function Advertisement(props) { ) } -Advertisement._meta = { - name: 'Advertisement', - type: META.TYPES.VIEW, -} - -Advertisement.propTypes = { +InnerAdvertisement.propTypes = { /** An element type to render as (string or function). */ as: customPropTypes.as, @@ -85,4 +80,11 @@ Advertisement.propTypes = { } +const Advertisement = withComputedType()(InnerAdvertisement) + +Advertisement._meta = { + name: 'Advertisement', + type: META.TYPES.VIEW, +} + export default Advertisement diff --git a/src/views/Item/ItemImage.js b/src/views/Item/ItemImage.js index 3d753df83e..564d96ca86 100644 --- a/src/views/Item/ItemImage.js +++ b/src/views/Item/ItemImage.js @@ -1,9 +1,11 @@ +import PropTypes from 'prop-types' import React from 'react' import { createShorthandFactory, getUnhandledProps, META, + SUI, } from '../../lib' import Image from '../../elements/Image' @@ -25,7 +27,7 @@ ItemImage._meta = { ItemImage.propTypes = { /** An image may appear at different sizes. */ - size: Image.propTypes.size, + size: PropTypes.oneOf(SUI.SIZES), } ItemImage.create = createShorthandFactory(ItemImage, src => ({ src })) diff --git a/test/specs/commonTests/classNameHelpers.js b/test/specs/commonTests/classNameHelpers.js index a987ce788d..7d97f43974 100644 --- a/test/specs/commonTests/classNameHelpers.js +++ b/test/specs/commonTests/classNameHelpers.js @@ -8,8 +8,17 @@ export const classNamePropValueBeforePropName = (Component, propKey, propValues, propValues.forEach((propVal) => { it(`adds "${propVal} ${className}" to className`, () => { - shallow(createElement(Component, { ...requiredProps, [propKey]: propVal })) - .should.have.className(`${propVal} ${className}`) + // Heads up! It's a temporary solution until all other components will be wrapped with + // the new component and withComputedType HOC + // TODO: Remove this after all components will be updated + try { + shallow(createElement(Component, { ...requiredProps, [propKey]: propVal })) + .should.have.className(`${propVal} ${className}`) + } catch (e) { + shallow(createElement(Component, { ...requiredProps, [propKey]: propVal })) + .dive() + .should.have.className(`${propVal} ${className}`) + } }) }) } @@ -20,13 +29,26 @@ export const noClassNameFromBoolProps = (Component, propKey, propValues, options _.each([true, false], bool => it(`does not add any className when ${bool}`, () => { consoleUtil.disableOnce() - const wrapper = shallow(createElement(Component, { ...requiredProps, [propKey]: bool })) + // Heads up! It's a temporary solution until all other components will be wrapped with + // the new component and withComputedType HOC + // TODO: Remove this after all components will be updated + try { + const wrapper = shallow(createElement(Component, { ...requiredProps, [propKey]: bool })) - wrapper.should.not.have.className(className) - wrapper.should.not.have.className('true') - wrapper.should.not.have.className('false') + wrapper.should.not.have.className(className) + wrapper.should.not.have.className('true') + wrapper.should.not.have.className('false') - propValues.forEach(propVal => wrapper.should.not.have.className(propVal.toString())) + propValues.forEach(propVal => wrapper.should.not.have.className(propVal.toString())) + } catch (e) { + const wrapper = shallow(createElement(Component, { ...requiredProps, [propKey]: bool })).dive() + + wrapper.should.not.have.className(className) + wrapper.should.not.have.className('true') + wrapper.should.not.have.className('false') + + propValues.forEach(propVal => wrapper.should.not.have.className(propVal.toString())) + } })) } @@ -40,11 +62,23 @@ export const noDefaultClassNameFromProp = (Component, propKey, propValues, optio if (propKey in requiredProps) return it('is not included in className when not defined', () => { - const wrapper = shallow() - wrapper.should.not.have.className(className) + // Heads up! It's a temporary solution until all other components will be wrapped with + // the new component and withComputedType HOC + // TODO: Remove this after all components will be updated + try { + const wrapper = shallow() + wrapper.should.not.have.className(className) + + // ensure that none of the prop option values are in className + // SUI classes ought to be built up using a declarative component API + propValues.forEach(propValue => wrapper.should.not.have.className(propValue.toString())) + } catch (e) { + const wrapper = shallow().dive() + wrapper.should.not.have.className(className) - // ensure that none of the prop option values are in className - // SUI classes ought to be built up using a declarative component API - propValues.forEach(propValue => wrapper.should.not.have.className(propValue.toString())) + // ensure that none of the prop option values are in className + // SUI classes ought to be built up using a declarative component API + propValues.forEach(propValue => wrapper.should.not.have.className(propValue.toString())) + } }) } diff --git a/test/specs/commonTests/componentInfo.js b/test/specs/commonTests/componentInfo.js index df24a848a3..f90ff10945 100644 --- a/test/specs/commonTests/componentInfo.js +++ b/test/specs/commonTests/componentInfo.js @@ -24,7 +24,7 @@ const componentInfo = componentCtx.keys().map((key) => { ].join(' ')) } - const { _meta, prototype } = Component + const { _meta, originalComponent } = Component if (!_meta) { throwError([ @@ -33,7 +33,6 @@ const componentInfo = componentCtx.keys().map((key) => { ].join('\n')) } - const constructorName = prototype.constructor.name const filePath = key const filename = path.basename(key) const filenameWithoutExt = path.basename(key, '.js') @@ -51,12 +50,12 @@ const componentInfo = componentCtx.keys().map((key) => { return { _meta, Component, - constructorName, componentClassName, - subComponentName, - filePath, filename, filenameWithoutExt, + filePath, + originalComponent, + subComponentName, } }) diff --git a/test/specs/commonTests/hasUIClassName.js b/test/specs/commonTests/hasUIClassName.js index 102d4ff3e8..993f02aa36 100644 --- a/test/specs/commonTests/hasUIClassName.js +++ b/test/specs/commonTests/hasUIClassName.js @@ -14,7 +14,16 @@ export default (Component, options = {}) => { it('has the "ui" className', () => { assertRequired(Component, 'a `Component`') - shallow() - .should.have.className('ui') + // Heads up! It's a temporary solution until all other components will be wrapped with + // the new component and withComputedType HOC + // TODO: Remove this after all components will be updated + try { + shallow() + .should.have.className('ui') + } catch (e) { + shallow() + .dive() + .should.have.className('ui') + } }) } diff --git a/test/specs/commonTests/hasValidTypings.js b/test/specs/commonTests/hasValidTypings.js index 2aade00817..8bf63fc43d 100644 --- a/test/specs/commonTests/hasValidTypings.js +++ b/test/specs/commonTests/hasValidTypings.js @@ -35,7 +35,8 @@ export default (Component, extractedInfo, options = {}) => { _meta: { name: componentName }, filenameWithoutExt, filePath, - } = extractedInfo || _.find(componentInfo, i => i.constructorName === Component.prototype.constructor.name) + originalComponent, + } = extractedInfo || _.find(componentInfo, i => i._meta.name === Component._meta.name) const { ignoredTypingsProps = [], requiredProps } = options const tsFile = `${filenameWithoutExt}.d.ts` @@ -71,7 +72,7 @@ export default (Component, extractedInfo, options = {}) => { }) it('are correctly defined', () => { - const componentPropTypes = _.get(Component, 'propTypes') + const componentPropTypes = _.get(originalComponent || Component, 'propTypes') const componentProps = _.keys(componentPropTypes) const interfaceProps = _.without(_.map(props, 'name'), ...ignoredTypingsProps) @@ -116,7 +117,7 @@ export default (Component, extractedInfo, options = {}) => { describe('shorthands', () => { const { shorthands } = interfaceObject - const componentPropTypes = _.get(Component, 'propTypes') + const componentPropTypes = _.get(originalComponent || Component, 'propTypes') const componentShorthands = _.pickBy(componentPropTypes, isShorthand) _.forEach(componentShorthands, (propType, propName) => { diff --git a/test/specs/commonTests/implementsClassNameProps.js b/test/specs/commonTests/implementsClassNameProps.js index 9f199d6f5e..68aa94cbae 100644 --- a/test/specs/commonTests/implementsClassNameProps.js +++ b/test/specs/commonTests/implementsClassNameProps.js @@ -50,16 +50,35 @@ export const propKeyOnlyToClassName = (Component, propKey, options = {}) => { noDefaultClassNameFromProp(Component, propKey, [], options) it('adds prop name to className', () => { - shallow(createElement(Component, { ...requiredProps, [propKey]: true })) - .should.have.className(className) + consoleUtil.disableOnce() + // Heads up! It's a temporary solution until all other components will be wrapped with + // the new component and withComputedType HOC + // TODO: Remove this after all components will be updated + try { + shallow(createElement(Component, { ...requiredProps, [propKey]: true })) + .should.have.className(className) + } catch (e) { + shallow(createElement(Component, { ...requiredProps, [propKey]: true })) + .dive() + .should.have.className(className) + } }) it('does not add prop value to className', () => { consoleUtil.disableOnce() - const value = 'foo-bar-baz' - shallow(createElement(Component, { ...requiredProps, [propKey]: value })) - .should.not.have.className(value) + + // Heads up! It's a temporary solution until all other components will be wrapped with + // the new component and withComputedType HOC + // TODO: Remove this after all components will be updated + try { + shallow(createElement(Component, { ...requiredProps, [propKey]: value })) + .should.not.have.className(value) + } catch (e) { + shallow(createElement(Component, { ...requiredProps, [propKey]: value })) + .dive() + .should.not.have.className(value) + } }) }) } @@ -89,20 +108,44 @@ export const propKeyOrValueAndKeyToClassName = (Component, propKey, propValues, }) it('adds only the name to className when true', () => { - shallow(createElement(Component, { ...requiredProps, [propKey]: true })) - .should.have.className(className) + // Heads up! It's a temporary solution until all other components will be wrapped with + // the new component and withComputedType HOC + // TODO: Remove this after all components will be updated + try { + shallow(createElement(Component, { ...requiredProps, [propKey]: true })) + .should.have.className(className) + } catch (e) { + shallow(createElement(Component, { ...requiredProps, [propKey]: true })) + .dive() + .should.have.className(className) + } }) it('adds no className when false', () => { - const wrapper = shallow(createElement(Component, { ...requiredProps, [propKey]: false })) - - wrapper.should.not.have.className(className) - wrapper.should.not.have.className('true') - wrapper.should.not.have.className('false') - - _.each(propValues, (propVal) => { - wrapper.should.not.have.className(propVal) - }) + // Heads up! It's a temporary solution until all other components will be wrapped with + // the new component and withComputedType HOC + // TODO: Remove this after all components will be updated + try { + const wrapper = shallow(createElement(Component, { ...requiredProps, [propKey]: false })) + + wrapper.should.not.have.className(className) + wrapper.should.not.have.className('true') + wrapper.should.not.have.className('false') + + _.each(propValues, (propVal) => { + wrapper.should.not.have.className(propVal) + }) + } catch (e) { + const wrapper = shallow(createElement(Component, { ...requiredProps, [propKey]: false })).dive() + + wrapper.should.not.have.className(className) + wrapper.should.not.have.className('true') + wrapper.should.not.have.className('false') + + _.each(propValues, (propVal) => { + wrapper.should.not.have.className(propVal) + }) + } }) }) } @@ -129,8 +172,17 @@ export const propValueOnlyToClassName = (Component, propKey, propValues, options it('adds prop value to className', () => { propValues.forEach((propValue) => { - shallow(createElement(Component, { ...requiredProps, [propKey]: propValue })) - .should.have.className(propValue) + // Heads up! It's a temporary solution until all other components will be wrapped with + // the new component and withComputedType HOC + // TODO: Remove this after all components will be updated + try { + shallow(createElement(Component, { ...requiredProps, [propKey]: propValue })) + .should.have.className(propValue) + } catch (e) { + shallow(createElement(Component, { ...requiredProps, [propKey]: propValue })) + .dive() + .should.have.className(propValue) + } }) }) @@ -138,8 +190,17 @@ export const propValueOnlyToClassName = (Component, propKey, propValues, options consoleUtil.disableOnce() propValues.forEach((propValue) => { - shallow(createElement(Component, { ...requiredProps, [propKey]: propValue })) - .should.not.have.className(propKey) + // Heads up! It's a temporary solution until all other components will be wrapped with + // the new component and withComputedType HOC + // TODO: Remove this after all components will be updated + try { + shallow(createElement(Component, { ...requiredProps, [propKey]: propValue })) + .should.not.have.className(propKey) + } catch (e) { + shallow(createElement(Component, { ...requiredProps, [propKey]: propValue })) + .dive() + .should.not.have.className(propKey) + } }) }) }) diff --git a/test/specs/commonTests/implementsCommonProps.js b/test/specs/commonTests/implementsCommonProps.js index 9ee1235e7f..78f27d10ce 100644 --- a/test/specs/commonTests/implementsCommonProps.js +++ b/test/specs/commonTests/implementsCommonProps.js @@ -160,6 +160,7 @@ export const implementsLabelProp = (Component, options = {}) => { * Assert that a Component correctly implements the "only" prop. * @param {React.Component|Function} Component The component to test. * @param {String} propKey A props key. + * @param {Array} propValues An array of possible values. */ export const implementsMultipleProp = (Component, propKey, propValues) => { const { assertRequired } = helpers('propKeyAndValueToClassName', Component) @@ -207,18 +208,36 @@ export const implementsTextAlignProp = (Component, alignments = SUI.TEXT_ALIGNME alignments.forEach((propVal) => { if (propVal === 'justified') { it('adds "justified" without "aligned" to className', () => { - shallow() - .should.have.className('justified') - - shallow() - .should.not.have.className('aligned') + const wrapper = shallow() + + // Heads up! It's a temporary solution until all other components will be wrapped with + // the new component and withComputedType HOC + // TODO: Remove this after all components will be updated + try { + wrapper.should.have.className('justified') + wrapper.should.not.have.className('aligned') + } catch (e) { + wrapper.dive().should.have.className('justified') + wrapper.dive().should.not.have.className('aligned') + } }) - } else { - it(`adds "${propVal} aligned" to className`, () => { + + return + } + + it(`adds "${propVal} aligned" to className`, () => { + // Heads up! It's a temporary solution until all other components will be wrapped with + // the new component and withComputedType HOC + // TODO: Remove this after all components will be updated + try { shallow() .should.have.className(`${propVal} ${'aligned'}`) - }) - } + } catch (e) { + shallow() + .dive() + .should.have.className(`${propVal} ${'aligned'}`) + } + }) }) }) } @@ -242,8 +261,17 @@ export const implementsVerticalAlignProp = (Component, alignments = SUI.VERTICAL alignments.forEach((propVal) => { it(`adds "${propVal} aligned" to className`, () => { - shallow() - .should.have.className(`${propVal} ${'aligned'}`) + // Heads up! It's a temporary solution until all other components will be wrapped with + // the new component and withComputedType HOC + // TODO: Remove this after all components will be updated + try { + shallow() + .should.have.className(`${propVal} ${'aligned'}`) + } catch (e) { + shallow() + .dive() + .should.have.className(`${propVal} ${'aligned'}`) + } }) }) }) diff --git a/test/specs/commonTests/implementsShorthandProp.js b/test/specs/commonTests/implementsShorthandProp.js index 28eac09618..170ce4c2d3 100644 --- a/test/specs/commonTests/implementsShorthandProp.js +++ b/test/specs/commonTests/implementsShorthandProp.js @@ -53,7 +53,17 @@ export default (Component, options = {}) => { }) const element = createElement(Component, { ...requiredProps, [propKey]: value }) - shallow(element).should[assertMethod](shorthandElement) + // Heads up! It's a temporary solution until all other components will be wrapped with + // the new component and withComputedType HOC + // TODO: Remove this after all components will be updated + try { + shallow(element) + .should[assertMethod](shorthandElement) + } catch (e) { + shallow(element) + .dive() + .should[assertMethod](shorthandElement) + } } if (alwaysPresent || (Component.defaultProps && Component.defaultProps[propKey])) { diff --git a/test/specs/commonTests/isConformant.js b/test/specs/commonTests/isConformant.js index 890400e9bf..afd59c5981 100644 --- a/test/specs/commonTests/isConformant.js +++ b/test/specs/commonTests/isConformant.js @@ -22,36 +22,34 @@ export default (Component, options = {}) => { const { eventTargets = {}, requiredProps = {}, rendersPortal = false } = options const { throwError } = helpers('isConformant', Component) - // tests depend on Component constructor names, enforce them - if (!Component.prototype.constructor.name) { + if (!Component._meta) { throwError([ - 'Component is not a named function. This should help identify it:', - `static _meta = ${JSON.stringify(Component._meta, null, 2)}`, + 'Component does not define _meta. This should help identify it:', `Rendered:\n${ReactDOMServer.renderToStaticMarkup()}`, ].join('\n')) } // extract componentInfo for this component - const extractedInfo = _.find(componentInfo, i => i.constructorName === Component.prototype.constructor.name) + const extractedInfo = _.find(componentInfo, i => i._meta.name === Component._meta.name) const { _meta, - constructorName, componentClassName, filenameWithoutExt, } = extractedInfo + const componentName = _meta.name // ---------------------------------------- // Class and file name // ---------------------------------------- - it(`constructor name matches filename "${constructorName}"`, () => { - constructorName.should.equal(filenameWithoutExt) + it(`"${componentName}" in meta matches filename`, () => { + componentName.should.equal(filenameWithoutExt) }) // ---------------------------------------- // Is exported or private // ---------------------------------------- - // detect components like: semanticUIReact.H1 - const isTopLevelAPIProp = _.has(semanticUIReact, constructorName) + // detect components like: semanticUIReact.Header + const isTopLevelAPIProp = _.has(semanticUIReact, componentName) // detect sub components like: semanticUIReact.Form.Field (ie FormField component) // Build a path by following _meta.parents to the root: @@ -71,17 +69,17 @@ export default (Component, options = {}) => { // find the apiPath in the semanticUIReact object const isSubComponent = _.isFunction(_.get(semanticUIReact, apiPath)) - if (META.isPrivate(constructorName)) { + if (META.isPrivate(componentName)) { it('is not exported as a component nor sub component', () => { expect(isTopLevelAPIProp).to.equal( false, - `"${constructorName}" is private (starts with "_").` + + `"${componentName}" is private (starts with "_").` + ' It cannot be exposed on the top level API', ) expect(isSubComponent).to.equal( false, - `"${constructorName}" is private (starts with "_").` + + `"${componentName}" is private (starts with "_").` + ' It cannot be a static prop of another component (sub-component)', ) }) @@ -89,7 +87,7 @@ export default (Component, options = {}) => { // require all components to be exported at the top level it('is exported at the top level', () => { expect(isTopLevelAPIProp).to.equal(true, [ - `"${constructorName}" must be exported at top level.`, + `"${componentName}" must be exported at top level.`, 'Export it in `src/index.js`.', ].join(' ')) }) @@ -97,11 +95,10 @@ export default (Component, options = {}) => { if (_meta.parent) { it('is a static component on its parent', () => { - expect(isSubComponent).to.equal( - true, - `\`${constructorName}\` is a child component (has a _meta.parent).` + - ` It must be a static prop of its parent \`${_meta.parent}\``, - ) + expect(isSubComponent).to.equal(true, [ + `"${componentName}" is a child component (has a _meta.parent).`, + `It must be a static prop of its parent "${_meta.parent}"`, + ].join(' ')) }) } @@ -241,10 +238,10 @@ export default (Component, options = {}) => { // // ^ was not called once on "blur" - const leftPad = ' '.repeat(constructorName.length + listenerName.length + 3) + const leftPad = ' '.repeat(componentName.length + listenerName.length + 3) handlerSpy.calledOnce.should.equal(true, - `<${constructorName} ${listenerName}={${handlerName}} />\n` + + `<${componentName} ${listenerName}={${handlerName}} />\n` + `${leftPad} ^ was not called once on "${eventName}".` + 'You may need to hoist your event handlers up to the root element.\n', ) @@ -259,7 +256,7 @@ export default (Component, options = {}) => { // Components should return the event first, then any data handlerSpy.calledWithMatch(...expectedArgs).should.equal(true, [ - `<${constructorName} ${listenerName}={${handlerName}} />\n`, + `<${componentName} ${listenerName}={${handlerName}} />\n`, `${leftPad} ^ ${errorMessage}`, 'It was called with args:', JSON.stringify(handlerSpy.args, null, 2), @@ -287,7 +284,7 @@ export default (Component, options = {}) => { if (_.has(_meta, 'parent')) { describe('parent', () => { it('matches some component name', () => { - expect(_.map(semanticUIReact, c => c.prototype.constructor.name)).to.contain(_meta.parent) + expect(_.map(semanticUIReact, '_meta.name')).to.contain(_meta.parent) }) }) } diff --git a/test/specs/commonTests/rendersChildren.js b/test/specs/commonTests/rendersChildren.js index e68b80c623..910f4124d0 100644 --- a/test/specs/commonTests/rendersChildren.js +++ b/test/specs/commonTests/rendersChildren.js @@ -19,19 +19,49 @@ export default (Component, options = {}) => { describe('children (common)', () => { it('renders child text', () => { const text = faker.hacker.phrase() - shallow(createElement(Component, requiredProps, text)) - .should.contain.text(text) + // Heads up! It's a temporary solution until all other components will be wrapped with + // the new component and withComputedType HOC + // TODO: Remove this after all components will be updated + try { + shallow(createElement(Component, requiredProps, text)) + .should.contain.text(text) + } catch (e) { + shallow(createElement(Component, requiredProps, text)) + .dive() + .dive() + .should.contain.text(text) + } }) it('renders child components', () => { const child =
- shallow(createElement(Component, requiredProps, child)) - .should.contain(child) + // Heads up! It's a temporary solution until all other components will be wrapped with + // the new component and withComputedType HOC + // TODO: Remove this after all components will be updated + try { + shallow(createElement(Component, requiredProps, child)) + .should.contain(child) + } catch (e) { + shallow(createElement(Component, requiredProps, child)) + .dive() + .dive() + .should.contain(child) + } }) it('renders child number with 0 value', () => { - shallow(createElement(Component, requiredProps, 0)) - .should.contain.text('0') + // Heads up! It's a temporary solution until all other components will be wrapped with + // the new component and withComputedType HOC + // TODO: Remove this after all components will be updated + try { + shallow(createElement(Component, requiredProps, 0)) + .should.contain.text('0') + } catch (e) { + shallow(createElement(Component, requiredProps, 0)) + .dive() + .dive() + .should.contain.text('0') + } }) }) @@ -39,19 +69,49 @@ export default (Component, options = {}) => { describe('content (common)', () => { it('renders child text', () => { const text = faker.hacker.phrase() - shallow(createElement(Component, { ...requiredProps, content: text })) - .should.contain.text(text) + // Heads up! It's a temporary solution until all other components will be wrapped with + // the new component and withComputedType HOC + // TODO: Remove this after all components will be updated + try { + shallow(createElement(Component, { ...requiredProps, content: text })) + .should.contain.text(text) + } catch (e) { + shallow(createElement(Component, { ...requiredProps, content: text })) + .dive() + .dive() + .should.contain.text(text) + } }) it('renders child components', () => { const child =
- shallow(createElement(Component, { ...requiredProps, content: child })) - .should.contain(child) + // Heads up! It's a temporary solution until all other components will be wrapped with + // the new component and withComputedType HOC + // TODO: Remove this after all components will be updated + try { + shallow(createElement(Component, { ...requiredProps, content: child })) + .should.contain(child) + } catch (e) { + shallow(createElement(Component, { ...requiredProps, content: child })) + .dive() + .dive() + .should.contain(child) + } }) it('renders child number with 0 value', () => { - shallow(createElement(Component, { ...requiredProps, content: 0 })) - .should.contain.text('0') + // Heads up! It's a temporary solution until all other components will be wrapped with + // the new component and withComputedType HOC + // TODO: Remove this after all components will be updated + try { + shallow(createElement(Component, { ...requiredProps, content: 0 })) + .should.contain.text('0') + } catch (e) { + shallow(createElement(Component, { ...requiredProps, content: 0 })) + .dive() + .dive() + .should.contain.text('0') + } }) }) } diff --git a/test/specs/elements/Image/Image-test.js b/test/specs/elements/Image/Image-test.js index 14c2cfc200..7c3a75c7c9 100644 --- a/test/specs/elements/Image/Image-test.js +++ b/test/specs/elements/Image/Image-test.js @@ -38,31 +38,42 @@ describe('Image', () => { common.propValueOnlyToClassName(Image, 'size', SUI.SIZES) - it('renders an img tag', () => { - shallow() - .type() - .should.equal('img') + describe('as', () => { + it('renders an img tag', () => { + shallow() + .dive() + .dive() + .type() + .should.equal('img') + }) }) describe('href', () => { it('renders an a tag', () => { shallow() + .dive() + .dive() .type() .should.equal('a') }) }) describe('ui', () => { - it('is true by default', () => { - Image.defaultProps.should.have.any.keys('ui') - Image.defaultProps.ui.should.equal(true) + it('adds the "ui" className by default', () => { + shallow() + .dive() + .should.have.className('ui') }) + it('adds the "ui" className when true', () => { shallow() + .dive() .should.have.className('ui') }) + it('removes the "ui" className when false', () => { shallow() + .dive() .should.not.have.className('ui') }) }) @@ -71,6 +82,7 @@ describe('Image', () => { it('applies all img attribute props to the img tag', () => { const props = { src: 'http://g.co', alt: 'alt text', width: 10, height: '10' } const img = shallow() + .dive() .find('img') _.each(props, (val, key) => { diff --git a/test/specs/elements/List/List-test.js b/test/specs/elements/List/List-test.js index 5d3d6980bf..8f66528808 100644 --- a/test/specs/elements/List/List-test.js +++ b/test/specs/elements/List/List-test.js @@ -38,11 +38,6 @@ describe('List', () => { const items = ['Name', 'Status', 'Notes'] describe('onItemClick', () => { - it('can be omitted', () => { - const click = () => shallow().simulate('click') - expect(click).to.not.throw() - }) - it('is called with (e, itemProps) when clicked', () => { const onClick = sandbox.spy() const onItemClick = sandbox.spy() @@ -51,10 +46,9 @@ describe('List', () => { const callbackData = { content: 'Notes', 'data-foo': 'bar' } const itemProps = { key: 'notes', content: 'Notes', 'data-foo': 'bar', onClick } - shallow() - .find('ListItem') + mount() + .find(ListItem) .first() - .shallow() .simulate('click', event) onClick.should.have.been.calledOnce() @@ -67,29 +61,29 @@ describe('List', () => { describe('role', () => { it('is accessibile with no items', () => { - const wrapper = shallow() - - wrapper.should.have.prop('role', 'list') + shallow() + .dive() + .should.have.prop('role', 'list') }) it('is accessibile with items', () => { - const wrapper = shallow() - - wrapper.should.have.prop('role', 'list') + shallow() + .dive() + .should.have.prop('role', 'list') }) }) describe('shorthand', () => { - it('renders empty tr with no shorthand', () => { - const wrapper = shallow() - - wrapper.find('ListItem').should.have.lengthOf(0) + it('renders empty list with no shorthand', () => { + shallow() + .dive() + .should.not.have.descendants(ListItem) }) it('renders the items', () => { - const wrapper = shallow() - - wrapper.find('ListItem').should.have.lengthOf(items.length) + shallow() + .dive() + .should.have.exactly(3).descendants(ListItem) }) }) }) diff --git a/test/specs/elements/List/ListContent-test.js b/test/specs/elements/List/ListContent-test.js index d4ea3b03be..2b71a2fae3 100644 --- a/test/specs/elements/List/ListContent-test.js +++ b/test/specs/elements/List/ListContent-test.js @@ -2,6 +2,8 @@ import faker from 'faker' import React from 'react' import ListContent from 'src/elements/List/ListContent' +import ListDescription from 'src/elements/List/ListDescription' +import ListHeader from 'src/elements/List/ListHeader' import { SUI } from 'src/lib' import * as common from 'test/specs/commonTests' @@ -10,7 +12,6 @@ describe('ListContent', () => { common.rendersChildren(ListContent) common.implementsCreateMethod(ListContent) - common.implementsVerticalAlignProp(ListContent) common.propKeyAndValueToClassName(ListContent, 'floated', SUI.FLOATS) @@ -23,11 +24,11 @@ describe('ListContent', () => { } it('renders content without wrapping ListContent', () => { - const wrapper = shallow() + const wrapper = shallow().dive() - wrapper.find('ListHeader').should.have.prop('content', baseProps.header) - wrapper.find('ListDescription').should.have.prop('content', baseProps.description) - wrapper.should.contain.text(baseProps.content) + wrapper.find(ListHeader).should.have.prop('content', baseProps.header) + wrapper.find(ListDescription).should.have.prop('content', baseProps.description) + wrapper.dive().should.contain.text(baseProps.content) }) }) }) diff --git a/test/specs/elements/List/ListIcon-test.js b/test/specs/elements/List/ListIcon-test.js index 7c1811c230..010d59d226 100644 --- a/test/specs/elements/List/ListIcon-test.js +++ b/test/specs/elements/List/ListIcon-test.js @@ -8,8 +8,10 @@ describe('ListIcon', () => { common.isConformant(ListIcon) common.implementsVerticalAlignProp(ListIcon) - it('returns Icon component', () => { - shallow() - .should.have.descendants(Icon) + describe('children', () => { + it('returns Icon component', () => { + shallow() + .should.have.descendants(Icon) + }) }) }) diff --git a/test/specs/elements/List/ListItem-test.js b/test/specs/elements/List/ListItem-test.js index 87d6fbeb53..46fa25e73f 100644 --- a/test/specs/elements/List/ListItem-test.js +++ b/test/specs/elements/List/ListItem-test.js @@ -2,6 +2,8 @@ import faker from 'faker' import _ from 'lodash' import React from 'react' +import Image from 'src/elements/Image/Image' +import ListIcon from 'src/elements/List/ListIcon' import ListItem from 'src/elements/List/ListItem' import ListContent from 'src/elements/List/ListContent' import * as common from 'test/specs/commonTests' @@ -26,20 +28,19 @@ describe('ListItem', () => { const onClick = sandbox.spy() const event = { target: null } const props = { onClick, 'data-foo': 'bar' } + const wrapper = mount() - shallow() - .simulate('click', event) - + wrapper.simulate('click', event) onClick.should.have.been.calledOnce() - onClick.should.have.been.calledWithExactly(event, props) + onClick.should.have.been.calledWithMatch(event, props) }) it('is not called when is disabled', () => { const onClick = sandbox.spy() + const wrapper = mount() - shallow() - .simulate('click') - onClick.should.have.callCount(0) + wrapper.simulate('click') + onClick.should.have.not.been.called() }) }) @@ -67,9 +68,9 @@ describe('ListItem', () => { } it('renders without wrapping ListContent', () => { - const wrapper = shallow() - - wrapper.find('ListContent').should.have.lengthOf(0) + shallow() + .dive() + .find('ListContent').should.have.lengthOf(0) }) it('renders without wrapping ListContent when content passed as element', () => { @@ -80,43 +81,54 @@ describe('ListItem', () => { }) it('renders wrapping ListContent when content passed as props', () => { - const wrapper = shallow() - - wrapper.find('ListContent').should.have.lengthOf(1) + shallow() + .dive() + .should.have.exactly(1).descendants(ListContent) }) _.each(baseProps, (value, key) => { it(`renders wrapping ListContent when icon and ${key} present`, () => { - const wrapper = shallow() + const wrapper = shallow().dive() - wrapper.find('ListIcon').should.have.lengthOf(1) - wrapper.find('ListContent').should.have.lengthOf(1) + wrapper.should.have.exactly(1).descendants(ListIcon) + wrapper.should.have.exactly(1).descendants(ListContent) }) it(`renders wrapping ListContent when image and ${key} present`, () => { - const wrapper = shallow() + const wrapper = shallow().dive() - wrapper.find('Image').should.have.lengthOf(1) - wrapper.find('ListContent').should.have.lengthOf(1) + wrapper.should.have.exactly(1).descendants(Image) + wrapper.should.have.exactly(1).descendants(ListContent) }) }) }) describe('role', () => { - it('adds role=listitem', () => { + it('adds role="listitem"', () => { shallow() + .dive() .should.have.prop('role', 'listitem') }) - it('adds role=listitem with children', () => { - shallow(
Test
) + + it('adds role="listitem" with children', () => { + shallow( + +
Test
+
, + ) + .dive() .should.have.prop('role', 'listitem') }) - it('adds role=listitem with content', () => { + + it('adds role="listitem" with content', () => { shallow(} />) + .dive() .should.have.prop('role', 'listitem') }) - it('adds role=listitem with icon', () => { + + it('adds role="listitem" with icon', () => { shallow() + .dive() .should.have.prop('role', 'listitem') }) }) diff --git a/test/specs/elements/List/ListList-test.js b/test/specs/elements/List/ListList-test.js index 19d2b69bc6..702b0ddfe1 100644 --- a/test/specs/elements/List/ListList-test.js +++ b/test/specs/elements/List/ListList-test.js @@ -8,13 +8,21 @@ describe('ListList', () => { common.rendersChildren(ListList) describe('list', () => { + it('defined by default', () => { + shallow() + .dive() + .should.have.className('list') + }) + it('omitted when rendered as `ol`', () => { shallow() + .dive() .should.not.have.className('list') }) it('omitted when rendered as `ul`', () => { shallow() + .dive() .should.not.have.className('list') }) }) diff --git a/test/specs/lib/ElementType-test.js b/test/specs/lib/ElementType-test.js new file mode 100644 index 0000000000..da07a99cf5 --- /dev/null +++ b/test/specs/lib/ElementType-test.js @@ -0,0 +1,39 @@ +import React from 'react' + +import Ref from 'src/addons/Ref' +import Container from 'src/elements/Container' +import { ElementType } from 'src/lib' +import { sandbox } from 'test/utils' + +describe('ElementType', () => { + describe('children', () => { + it('adds ref to a string element', () => { + const innerRef = sandbox.spy() + const wrapper = mount() + + wrapper.childAt(0).type().should.equal('a') + innerRef.should.have.been.calledOnce('a') + innerRef.should.have.been.calledWithMatch({ tagName: 'A' }) + }) + + it('adds innerRef to our element', () => { + const innerRef = () => {} + const wrapper = mount() + const child = wrapper.childAt(0) + + child.type().should.equal(Container) + child.should.have.prop('innerRef', innerRef) + }) + + it('wraps with Ref a third-part element', () => { + const Element = () =>
+ const innerRef = () => {} + const wrapper = mount() + const child = wrapper.childAt(0) + + child.type().should.equal(Ref) + child.should.have.prop('innerRef', innerRef) + child.childAt(0).type().should.equal(Element) + }) + }) +}) diff --git a/test/specs/lib/computeElementType-test.js b/test/specs/lib/computeElementType-test.js new file mode 100644 index 0000000000..820c4213a6 --- /dev/null +++ b/test/specs/lib/computeElementType-test.js @@ -0,0 +1,34 @@ +import faker from 'faker' +import computeElementType from 'src/lib/computeElementType' + +describe('computeElementType', () => { + it('returns user defined "as" element type', () => { + computeElementType({ as: 'button' }) + .should.equal('button') + }) + + it('returns computed default element type', () => { + computeElementType({}, {}, () => 'button') + .should.equal('button') + }) + + it('returns default element type when compute failed', () => { + computeElementType({}, {}, () => false) + .should.equal('div') + }) + + it('returns "a" when has a "href" prop', () => { + computeElementType({ href: faker.internet.url() }) + .should.equal('a') + }) + + it('returns "as" from defaultProps', () => { + computeElementType({}, { as: 'button' }) + .should.equal('button') + }) + + it('returns default element type', () => { + computeElementType({}) + .should.equal('div') + }) +}) diff --git a/test/specs/views/Advertisement/Advertisement-test.js b/test/specs/views/Advertisement/Advertisement-test.js index f6f4bf826f..c4c5dfe2e2 100644 --- a/test/specs/views/Advertisement/Advertisement-test.js +++ b/test/specs/views/Advertisement/Advertisement-test.js @@ -23,7 +23,7 @@ describe('Advertisement', () => { 'netboard', 'half page', 'square', 'small square', - ]) + ], { requiredProps }) it('renders a
by default', () => { shallow() diff --git a/test/specs/views/Item/ItemImage-test.js b/test/specs/views/Item/ItemImage-test.js index 066fa503cf..2abcdd6cae 100644 --- a/test/specs/views/Item/ItemImage-test.js +++ b/test/specs/views/Item/ItemImage-test.js @@ -1,14 +1,17 @@ import React from 'react' +import Image from 'src/elements/Image/Image' import ItemImage from 'src/views/Item/ItemImage' import * as common from 'test/specs/commonTests' describe('ItemImage', () => { common.implementsCreateMethod(ItemImage) - it('renders Image component', () => { - shallow() - .should.have.descendants('Image') + describe('children', () => { + it('renders Image component', () => { + mount() + .should.have.descendants(Image) + }) }) it('is wrapped without ui', () => { From 93cde4dda118023604813d187627e3918dc59cdb Mon Sep 17 00:00:00 2001 From: Alexander Fedyashov Date: Sun, 17 Dec 2017 09:46:15 +0200 Subject: [PATCH 2/2] fix(Image): fix merge problems --- src/elements/Image/Image.js | 6 ++---- test/specs/elements/Image/Image-test.js | 3 +++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/elements/Image/Image.js b/src/elements/Image/Image.js index 847f54710f..15dbab7822 100644 --- a/src/elements/Image/Image.js +++ b/src/elements/Image/Image.js @@ -31,7 +31,6 @@ const imageProps = ['alt', 'height', 'src', 'srcSet', 'width'] const InnerImage = (props) => { const { as, - alt, avatar, bordered, centered, @@ -51,7 +50,6 @@ const InnerImage = (props) => { size, spaced, verticalAlign, - wrapped, ui, } = props @@ -83,9 +81,9 @@ const InnerImage = (props) => { return {content} } - if (as === 'img') return + if (as === 'img') return return ( - + {Dimmer.create(dimmer)} {Label.create(label)} diff --git a/test/specs/elements/Image/Image-test.js b/test/specs/elements/Image/Image-test.js index 25187d9a33..379e6a9543 100644 --- a/test/specs/elements/Image/Image-test.js +++ b/test/specs/elements/Image/Image-test.js @@ -69,6 +69,7 @@ describe('Image', () => { it(`passes "${propName}" to the img tag when wrapped`, () => { shallow() + .dive() .find('img') .should.have.prop(propName, 'foo') }) @@ -98,6 +99,8 @@ describe('Image', () => { describe('wrapped', () => { it('renders an div tag when true', () => { shallow() + .dive() + .dive() .type() .should.equal('div') })