Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(factories): support functions #2790

Merged
merged 1 commit into from
May 11, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 30 additions & 18 deletions src/lib/factories.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,22 @@ export function createShorthand(Component, mapValueToProps, val, options = {}) {

const valIsString = _.isString(val)
const valIsNumber = _.isNumber(val)

const isReactElement = isValidElement(val)
const isPropsObject = _.isPlainObject(val)
const isPrimitiveValue = valIsString || valIsNumber || _.isArray(val)
const valIsFunction = _.isFunction(val)
const valIsReactElement = isValidElement(val)
const valIsPropsObject = _.isPlainObject(val)
const valIsPrimitiveValue = valIsString || valIsNumber || _.isArray(val)

// unhandled type return null
/* eslint-disable no-console */
if (!isReactElement && !isPropsObject && !isPrimitiveValue) {
if (!valIsFunction && !valIsReactElement && !valIsPropsObject && !valIsPrimitiveValue) {
if (process.env.NODE_ENV !== 'production') {
console.error([
'Shorthand value must be a string|number|array|object|ReactElement.',
' Use null|undefined|boolean for none',
` Received ${typeof val}.`,
].join(''))
console.error(
[
'Shorthand value must be a string|number|array|object|ReactElement|function.',
' Use null|undefined|boolean for none',
` Received ${typeof val}.`,
].join(''),
)
}
return null
}
Expand All @@ -51,21 +53,28 @@ export function createShorthand(Component, mapValueToProps, val, options = {}) {
const { defaultProps = {} } = options

// User's props
const usersProps = (isReactElement && val.props)
|| (isPropsObject && val)
|| (isPrimitiveValue && mapValueToProps(val))
const usersProps =
(valIsReactElement && val.props) ||
(valIsPropsObject && val) ||
(valIsPrimitiveValue && mapValueToProps(val))

// Override props
let { overrideProps = {} } = options
overrideProps = _.isFunction(overrideProps) ? overrideProps({ ...defaultProps, ...usersProps }) : overrideProps
overrideProps = _.isFunction(overrideProps)
? overrideProps({ ...defaultProps, ...usersProps })
: overrideProps

// Merge props
/* eslint-disable react/prop-types */
const props = { ...defaultProps, ...usersProps, ...overrideProps }

// Merge className
if (defaultProps.className || overrideProps.className || usersProps.className) {
const mergedClassesNames = cx(defaultProps.className, overrideProps.className, usersProps.className)
const mergedClassesNames = cx(
defaultProps.className,
overrideProps.className,
usersProps.className,
)
props.className = _.uniq(mergedClassesNames.split(' ')).join(' ')
}

Expand All @@ -91,17 +100,20 @@ export function createShorthand(Component, mapValueToProps, val, options = {}) {
props.key = val
}
}
/* eslint-enable react/prop-types */

// ----------------------------------------
// Create Element
// ----------------------------------------

// Clone ReactElements
if (isReactElement) return cloneElement(val, props)
if (valIsReactElement) return cloneElement(val, props)

// Create ReactElements from built up props
if (isPrimitiveValue || isPropsObject) return <Component {...props} />
if (valIsPrimitiveValue || valIsPropsObject) return <Component {...props} />

// Call functions with args similar to createElement()
if (valIsFunction) return val(Component, props, props.children)
/* eslint-enable react/prop-types */
}

// ============================================================
Expand Down
146 changes: 88 additions & 58 deletions test/specs/lib/factories-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,9 @@ const itMergesClassNames = (classNameSource, extraClassName, shorthandConfig) =>
const defaultProps = { className: 'default' }
const overrideProps = { className: 'override' }

shallow(getShorthand({ defaultProps, overrideProps, ...shorthandConfig }))
.should.have.same.className(`default override ${extraClassName}`)
shallow(
getShorthand({ defaultProps, overrideProps, ...shorthandConfig }),
).should.have.same.className(`default override ${extraClassName}`)
})
}

Expand Down Expand Up @@ -170,61 +171,49 @@ describe('factories', () => {

describe('key', () => {
it('is not consumed', () => {
getShorthand({ value: { key: 123 } })
.props.should.have.property('key')
getShorthand({ value: { key: 123 } }).props.should.have.property('key')
})

describe('on an element', () => {
it('works with a string', () => {
getShorthand({ value: <div key='foo' /> })
.should.have.property('key', 'foo')
getShorthand({ value: <div key='foo' /> }).should.have.property('key', 'foo')
})

it('works with a number', () => {
getShorthand({ value: <div key={123} /> })
.should.have.property('key', '123')
getShorthand({ value: <div key={123} /> }).should.have.property('key', '123')
})

it('works with falsy values', () => {
getShorthand({ value: <div key={null} /> })
.should.have.property('key', 'null')
getShorthand({ value: <div key={null} /> }).should.have.property('key', 'null')

getShorthand({ value: <div key={0} /> })
.should.have.property('key', '0')
getShorthand({ value: <div key={0} /> }).should.have.property('key', '0')

getShorthand({ value: <div key='' /> })
.should.have.property('key', '')
getShorthand({ value: <div key='' /> }).should.have.property('key', '')
})
})

describe('on an object', () => {
it('works with a string', () => {
getShorthand({ value: { key: 'foo' } })
.should.have.property('key', 'foo')
getShorthand({ value: { key: 'foo' } }).should.have.property('key', 'foo')
})

it('works with a number', () => {
getShorthand({ value: { key: 123 } })
.should.have.property('key', '123')
getShorthand({ value: { key: 123 } }).should.have.property('key', '123')
})

it('works with falsy values', () => {
getShorthand({ value: { key: null } })
.should.have.property('key', 'null')
getShorthand({ value: { key: null } }).should.have.property('key', 'null')

getShorthand({ value: { key: 0 } })
.should.have.property('key', '0')
getShorthand({ value: { key: 0 } }).should.have.property('key', '0')

getShorthand({ value: { key: '' } })
.should.have.property('key', '')
getShorthand({ value: { key: '' } }).should.have.property('key', '')
})
})
})

describe('childKey', () => {
it('is consumed', () => {
getShorthand({ value: { childKey: 123 } })
.props.should.not.have.property('childKey')
getShorthand({ value: { childKey: 123 } }).props.should.not.have.property('childKey')
})

it('is called with the final `props` if it is a function', () => {
Expand All @@ -239,47 +228,37 @@ describe('factories', () => {

describe('on an element', () => {
it('works with a string', () => {
getShorthand({ value: <div childKey='foo' /> })
.should.have.property('key', 'foo')
getShorthand({ value: <div childKey='foo' /> }).should.have.property('key', 'foo')
})

it('works with a number', () => {
getShorthand({ value: <div childKey={123} /> })
.should.have.property('key', '123')
getShorthand({ value: <div childKey={123} /> }).should.have.property('key', '123')
})

it('works with falsy values', () => {
getShorthand({ value: <div childKey={null} /> })
.should.have.property('key', null)
getShorthand({ value: <div childKey={null} /> }).should.have.property('key', null)

getShorthand({ value: <div childKey={0} /> })
.should.have.property('key', '0')
getShorthand({ value: <div childKey={0} /> }).should.have.property('key', '0')

getShorthand({ value: <div childKey='' /> })
.should.have.property('key', '')
getShorthand({ value: <div childKey='' /> }).should.have.property('key', '')
})
})

describe('on an object', () => {
it('works with a string', () => {
getShorthand({ value: { childKey: 'foo' } })
.should.have.property('key', 'foo')
getShorthand({ value: { childKey: 'foo' } }).should.have.property('key', 'foo')
})

it('works with a number', () => {
getShorthand({ value: { childKey: 123 } })
.should.have.property('key', '123')
getShorthand({ value: { childKey: 123 } }).should.have.property('key', '123')
})

it('works with falsy values', () => {
getShorthand({ value: { childKey: null } })
.should.have.property('key', null)
getShorthand({ value: { childKey: null } }).should.have.property('key', null)

getShorthand({ value: { childKey: 0 } })
.should.have.property('key', '0')
getShorthand({ value: { childKey: 0 } }).should.have.property('key', '0')

getShorthand({ value: { childKey: '' } })
.should.have.property('key', '')
getShorthand({ value: { childKey: '' } }).should.have.property('key', '')
})
})
})
Expand Down Expand Up @@ -376,10 +355,14 @@ describe('factories', () => {
mapValueToProps: () => ({ className: 'mapped' }),
})

itAppliesProps('mapValueToProps', { 'data-prop': 'present' }, {
value: 'foo',
mapValueToProps: () => ({ 'data-prop': 'present' }),
})
itAppliesProps(
'mapValueToProps',
{ 'data-prop': 'present' },
{
value: 'foo',
mapValueToProps: () => ({ 'data-prop': 'present' }),
},
)

itOverridesDefaultProps(
'mapValueToProps',
Expand Down Expand Up @@ -419,6 +402,45 @@ describe('factories', () => {
})
})

describe('from a function', () => {
itReturnsAValidElement(() => <div />)
itDoesNotIncludePropsFromMapValueToProps(() => <div />)

it('is called once', () => {
const spy = sandbox.spy()

getShorthand({ value: spy })

spy.should.have.been.calledOnce()
})

it('is called with Component, props, children', () => {
const spy = sandbox.spy(() => <div />)

getShorthand({ Component: 'p', value: spy })

spy.should.have.been.calledWithExactly('p', {}, undefined)
})

it('receives defaultProps in its props argument', () => {
const spy = sandbox.spy(() => <div />)
const defaultProps = { defaults: true }

getShorthand({ Component: 'p', defaultProps, value: spy })

spy.should.have.been.calledWithExactly('p', defaultProps, undefined)
})

it('receives overrideProps in its props argument', () => {
const spy = sandbox.spy(() => <div />)
const overrideProps = { overrides: true }

getShorthand({ Component: 'p', overrideProps, value: spy })

spy.should.have.been.calledWithExactly('p', overrideProps, undefined)
})
})

describe('from an array', () => {
itReturnsAValidElement(['foo'])
itAppliesDefaultProps(['foo'])
Expand All @@ -427,10 +449,14 @@ describe('factories', () => {
mapValueToProps: () => ({ className: 'mapped' }),
})

itAppliesProps('mapValueToProps', { 'data-prop': 'present' }, {
value: ['foo'],
mapValueToProps: () => ({ 'data-prop': 'present' }),
})
itAppliesProps(
'mapValueToProps',
{ 'data-prop': 'present' },
{
value: ['foo'],
mapValueToProps: () => ({ 'data-prop': 'present' }),
},
)

itOverridesDefaultProps(
'mapValueToProps',
Expand All @@ -455,31 +481,35 @@ describe('factories', () => {
const overrideProps = { style: { right: 5 } }

shallow(getShorthand({ defaultProps, overrideProps, value: userProps }))
.should.have.prop('style').deep.equal({ left: 5, bottom: 5, right: 5 })
.should.have.prop('style')
.deep.equal({ left: 5, bottom: 5, right: 5 })
})

it('merges style prop and handles override by userProps', () => {
const defaultProps = { style: { left: 10, bottom: 5 } }
const userProps = { style: { bottom: 10 } }

shallow(getShorthand({ defaultProps, value: userProps }))
.should.have.prop('style').deep.equal({ left: 10, bottom: 10 })
.should.have.prop('style')
.deep.equal({ left: 10, bottom: 10 })
})

it('merges style prop and handles override by overrideProps', () => {
const userProps = { style: { bottom: 10, right: 5 } }
const overrideProps = { style: { right: 10 } }

shallow(getShorthand({ overrideProps, value: userProps }))
.should.have.prop('style').deep.equal({ bottom: 10, right: 10 })
.should.have.prop('style')
.deep.equal({ bottom: 10, right: 10 })
})

it('merges style prop from defaultProps and overrideProps', () => {
const defaultProps = { style: { left: 10, bottom: 5 } }
const overrideProps = { style: { bottom: 10 } }

shallow(getShorthand({ defaultProps, overrideProps, value: 'foo' }))
.should.have.prop('style').deep.equal({ left: 10, bottom: 10 })
.should.have.prop('style')
.deep.equal({ left: 10, bottom: 10 })
})
})
})
Expand Down