Skip to content
This repository has been archived by the owner on Mar 4, 2020. It is now read-only.

Commit

Permalink
feat(test): add shorthand property conformance tests (#112)
Browse files Browse the repository at this point in the history
* add conformance tests for shorthand properties

* add shorthand tests for Button

* reset default icon position (needs to be addressed later)

* changelog

* remove unnecessary functionality

* remove unnecessary functionality

* reuse Icon's shorthand functionality

* renaming

* introduce checks in a separate module
  • Loading branch information
kuzhelov authored Aug 22, 2018
1 parent 62ac7b5 commit 0949379
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 109 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Fix components generation script @kuzhelov ([#105](https://github.com/stardust-ui/react/pull/105))
- Reactivate tests for `Text` @kuzhelov ([#104](https://github.com/stardust-ui/react/pull/104))
- Fix Button icon color @levithomason ([#102](https://github.com/stardust-ui/react/pull/102))
- Fix `icon` shorthand property for Button @kuzhelov ([#112](https://github.com/stardust-ui/react/pull/112))

### Features
- Add Menu `iconOnly`, MenuItem `iconOnly` and `icon` props @miroslavstastny ([#73](https://github.com/stardust-ui/react/pull/73))
Expand Down
2 changes: 1 addition & 1 deletion src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class Button extends UIComponent<any, any> {
fluid: PropTypes.bool,

/** Button can have an icon. */
icon: customPropTypes.some([PropTypes.bool, PropTypes.string]),
icon: customPropTypes.itemShorthand,

/** An icon button can format an Icon to appear before or after the button */
iconPosition: PropTypes.oneOf(['before', 'after']),
Expand Down
123 changes: 123 additions & 0 deletions test/specs/commonTests/implementsShorthandProp-SUI.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import * as _ from 'lodash'
import { shallow } from 'enzyme'
import * as React from 'react'

import { createShorthand } from 'src/lib'
import { consoleUtil } from 'test/utils'
import { noDefaultClassNameFromProp } from './classNameHelpers'
import helpers from './commonHelpers'

const shorthandComponentName = ShorthandComponent => {
if (typeof ShorthandComponent === 'string') return ShorthandComponent
return (
_.get(ShorthandComponent, '_meta.name') ||
ShorthandComponent.displayName ||
ShorthandComponent.name
)
}

/**
* Assert that a Component correctly implements a shorthand prop.
*
* @param {function} Component The component to test.
* @param {object} options
* @param {string} options.propKey The name of the shorthand prop.
* @param {string|function} options.ShorthandComponent The component that should be rendered from the shorthand value.
* @param {boolean} [options.alwaysPresent] Whether or not the shorthand exists by default.
* @param {boolean} [options.assertExactMatch] Selects an assertion method, `contain` will be used if true.
* @param {boolean} [options.generateKey=false] Whether or not automatic key generation is allowed for the shorthand component.
* @param {function} options.mapValueToProps A function that maps a primitive value to the Component props.
* @param {Object} [options.requiredProps={}] Props required to render the component.
* @param {Object} [options.shorthandDefaultProps] Default props for the shorthand component.
* @param {Object} [options.shorthandOverrideProps] Override props for the shorthand component.
*/
export default (Component, options: any = {}) => {
const {
alwaysPresent,
assertExactMatch = true,
generateKey = false,
mapValueToProps,
propKey,
ShorthandComponent,
shorthandDefaultProps = {},
shorthandOverrideProps = {},
requiredProps = {},
} = options
const { assertRequired } = helpers('implementsShorthandProp', Component)
const assertMethod = assertExactMatch ? 'contain' : 'containMatchingElement'

describe(`${propKey} shorthand prop (common)`, () => {
assertRequired(Component, 'a `Component`')
assertRequired(_.isPlainObject(options), 'an `options` object')
assertRequired(propKey, 'a `propKey`')
assertRequired(ShorthandComponent, 'a `ShorthandComponent`')

const name = shorthandComponentName(ShorthandComponent)
const assertValidShorthand = value => {
const expectedShorthandElement = createShorthand(ShorthandComponent, mapValueToProps, value, {
defaultProps: shorthandDefaultProps,
overrideProps: shorthandOverrideProps,
generateKey,
})
const element = React.createElement(Component, { ...requiredProps, [propKey]: value })
const wrapper = shallow(element)

wrapper.should[assertMethod](expectedShorthandElement)

// Enzyme's .key() method is not consistent with React for elements with
// no key (`undefined` vs `null`), so use the underlying element instead
// Will fail if more than one element of this type is found
const shorthandElement = wrapper.find(ShorthandComponent).getElement()
expect(shorthandElement.key).to.equal(expectedShorthandElement.key, "key doesn't match")
}

if (alwaysPresent || (Component.defaultProps && Component.defaultProps[propKey])) {
it(`has default ${name} when not defined`, () => {
shallow(<Component {...requiredProps} />).should.have.descendants(name)
})
} else {
noDefaultClassNameFromProp(Component, propKey, [], options)

it(`has no ${name} when not defined`, () => {
shallow(<Component {...requiredProps} />).should.not.have.descendants(name)
})
}

if (!alwaysPresent) {
it(`has no ${name} when null`, () => {
shallow(
React.createElement(Component, { ...requiredProps, [propKey]: null }),
).should.not.have.descendants(ShorthandComponent)
})
}

it(`renders a ${name} from strings`, () => {
consoleUtil.disableOnce()
assertValidShorthand('string')
})

it(`renders a ${name} from numbers`, () => {
consoleUtil.disableOnce()
assertValidShorthand(123)
})

// the Input maps shorthand to `type`
// React uses the default prop ('text') in place of type={0}
if (propKey !== 'input') {
it(`renders a ${name} from number 0`, () => {
consoleUtil.disableOnce()
assertValidShorthand(0)
})
}

it(`renders a ${name} from a props object`, () => {
consoleUtil.disableOnce()
assertValidShorthand(mapValueToProps('foo'))
})

it(`renders a ${name} from elements`, () => {
consoleUtil.disableOnce()
assertValidShorthand(<ShorthandComponent />)
})
})
}
142 changes: 36 additions & 106 deletions test/specs/commonTests/implementsShorthandProp.tsx
Original file line number Diff line number Diff line change
@@ -1,123 +1,53 @@
import * as _ from 'lodash'
import { shallow } from 'enzyme'
import * as React from 'react'
import { mount } from './isConformant'

import { createShorthand } from 'src/lib'
import { consoleUtil } from 'test/utils'
import { noDefaultClassNameFromProp } from './classNameHelpers'
import helpers from './commonHelpers'

const shorthandComponentName = ShorthandComponent => {
if (typeof ShorthandComponent === 'string') return ShorthandComponent
return (
_.get(ShorthandComponent, '_meta.name') ||
ShorthandComponent.displayName ||
ShorthandComponent.name
)
type ShorthandTestOptions = {
mapsValueToProp?: string
}

/**
* Assert that a Component correctly implements a shorthand prop.
*
* @param {function} Component The component to test.
* @param {object} options
* @param {string} options.propKey The name of the shorthand prop.
* @param {string|function} options.ShorthandComponent The component that should be rendered from the shorthand value.
* @param {boolean} [options.alwaysPresent] Whether or not the shorthand exists by default.
* @param {boolean} [options.assertExactMatch] Selects an assertion method, `contain` will be used if true.
* @param {boolean} [options.generateKey=false] Whether or not automatic key generation is allowed for the shorthand component.
* @param {function} options.mapValueToProps A function that maps a primitive value to the Component props.
* @param {Object} [options.requiredProps={}] Props required to render the component.
* @param {Object} [options.shorthandDefaultProps] Default props for the shorthand component.
* @param {Object} [options.shorthandOverrideProps] Override props for the shorthand component.
*/
export default (Component, options: any = {}) => {
const {
alwaysPresent,
assertExactMatch = true,
generateKey = false,
mapValueToProps,
propKey,
ShorthandComponent,
shorthandDefaultProps = {},
shorthandOverrideProps = {},
requiredProps = {},
} = options
const { assertRequired } = helpers('implementsShorthandProp', Component)
const assertMethod = assertExactMatch ? 'contain' : 'containMatchingElement'

describe(`${propKey} shorthand prop (common)`, () => {
assertRequired(Component, 'a `Component`')
assertRequired(_.isPlainObject(options), 'an `options` object')
assertRequired(propKey, 'a `propKey`')
assertRequired(ShorthandComponent, 'a `ShorthandComponent`')
const DefaultShorthandTestOptions: ShorthandTestOptions = {
mapsValueToProp: 'content',
}

const name = shorthandComponentName(ShorthandComponent)
const assertValidShorthand = value => {
const expectedShorthandElement = createShorthand(ShorthandComponent, mapValueToProps, value, {
defaultProps: shorthandDefaultProps,
overrideProps: shorthandOverrideProps,
generateKey,
export default Component => {
return function implementsShorthandProp(
shorthandPropertyName: string,
ShorthandComponent: React.ComponentType,
options: ShorthandTestOptions = DefaultShorthandTestOptions,
) {
const { mapsValueToProp } = options

describe(`shorthand property for '${ShorthandComponent.displayName}'`, () => {
test(`is defined`, () => {
expect(Component.propTypes[shorthandPropertyName]).toBeTruthy()
})
const element = React.createElement(Component, { ...requiredProps, [propKey]: value })
const wrapper = shallow(element)

wrapper.should[assertMethod](expectedShorthandElement)
test(`string value is handled as ${
ShorthandComponent.displayName
}'s ${mapsValueToProp}`, () => {
const props = { [shorthandPropertyName]: 'some value' }
const wrapper = mount(<Component {...props} />)

// Enzyme's .key() method is not consistent with React for elements with
// no key (`undefined` vs `null`), so use the underlying element instead
// Will fail if more than one element of this type is found
const shorthandElement = wrapper.find(ShorthandComponent).getElement()
expect(shorthandElement.key).to.equal(expectedShorthandElement.key, "key doesn't match")
}

if (alwaysPresent || (Component.defaultProps && Component.defaultProps[propKey])) {
it(`has default ${name} when not defined`, () => {
shallow(<Component {...requiredProps} />).should.have.descendants(name)
const shorthandComponentProps = wrapper.find(ShorthandComponent.displayName).props()
expect(shorthandComponentProps[mapsValueToProp]).toEqual('some value')
})
} else {
noDefaultClassNameFromProp(Component, propKey, [], options)

it(`has no ${name} when not defined`, () => {
shallow(<Component {...requiredProps} />).should.not.have.descendants(name)
})
}
test(`object value is spread as ${ShorthandComponent.displayName}'s props`, () => {
const ShorthandValue = { foo: 'foo value', bar: 'bar value' }

if (!alwaysPresent) {
it(`has no ${name} when null`, () => {
shallow(
React.createElement(Component, { ...requiredProps, [propKey]: null }),
).should.not.have.descendants(ShorthandComponent)
})
}
const props = { [shorthandPropertyName]: ShorthandValue }
const wrapper = mount(<Component {...props} />)

it(`renders a ${name} from strings`, () => {
consoleUtil.disableOnce()
assertValidShorthand('string')
})
const shorthandComponentProps = wrapper.find(ShorthandComponent.displayName).props()

it(`renders a ${name} from numbers`, () => {
consoleUtil.disableOnce()
assertValidShorthand(123)
})
const allShorthandPropertiesArePassedToShorthandComponent = Object.keys(
ShorthandValue,
).every(
propertyName => ShorthandValue[propertyName] === shorthandComponentProps[propertyName],
)

// the Input maps shorthand to `type`
// React uses the default prop ('text') in place of type={0}
if (propKey !== 'input') {
it(`renders a ${name} from number 0`, () => {
consoleUtil.disableOnce()
assertValidShorthand(0)
expect(allShorthandPropertiesArePassedToShorthandComponent).toBe(true)
})
}

it(`renders a ${name} from a props object`, () => {
consoleUtil.disableOnce()
assertValidShorthand(mapValueToProps('foo'))
})

it(`renders a ${name} from elements`, () => {
consoleUtil.disableOnce()
assertValidShorthand(<ShorthandComponent />)
})
})
}
}
2 changes: 1 addition & 1 deletion test/specs/commonTests/isConformant.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import helpers from './commonHelpers'
import * as stardust from 'src/'
import { felaRenderer } from 'src/lib'

const mount = (node, options?) => {
export const mount = (node, options?) => {
return enzymeMount(
<ThemeProvider theme={{ renderer: felaRenderer }}>{node}</ThemeProvider>,
options,
Expand Down
8 changes: 7 additions & 1 deletion test/specs/components/Button/Button-test.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import * as React from 'react'

import { isConformant, handlesAccessibility } from 'test/specs/commonTests'
import { isConformant, handlesAccessibility, implementsShorthandProp } from 'test/specs/commonTests'
import { getTestingRenderedComponent, mountWithProvider } from 'test/utils'

import Button from 'src/components/Button/Button'
import Icon from 'src/components/Icon/Icon'

import { MenuBehavior } from 'src/lib/accessibility'

const buttonImplementsShorthandProp = implementsShorthandProp(Button)

describe('Button', () => {
isConformant(Button)
buttonImplementsShorthandProp('icon', Icon, { mapsValueToProp: 'name' })

handlesAccessibility(Button, {
defaultRootRole: 'button',
accessibilityOverride: MenuBehavior,
Expand Down

0 comments on commit 0949379

Please sign in to comment.