diff --git a/src/linkClass.js b/src/linkClass.js index a9883dd..cef60e8 100644 --- a/src/linkClass.js +++ b/src/linkClass.js @@ -21,6 +21,19 @@ const mapChildrenWithoutKeyPrefix = (children: ReactElement, mapper: Function, c return result; }; +const linkArray = (array: Array, styles: Object, configuration: Object) => { + return _.map(array, (value) => { + if (React.isValidElement(value)) { + // eslint-disable-next-line no-use-before-define + return linkElement(React.Children.only(value), styles, configuration); + } else if (_.isArray(value)) { + return linkArray(value, styles, configuration); + } + + return value; + }); +}; + const linkElement = (element: ReactElement, styles: Object, configuration: Object): ReactElement => { let appendClassName; let elementIsFrozen; @@ -37,19 +50,37 @@ const linkElement = (element: ReactElement, styles: Object, configuration: Objec } const styleNames = parseStyleName(elementShallowCopy.props.styleName || '', configuration.allowMultiple); + const {children, ...restProps} = elementShallowCopy.props; - if (React.isValidElement(elementShallowCopy.props.children)) { - elementShallowCopy.props.children = linkElement(React.Children.only(elementShallowCopy.props.children), styles, configuration); - } else if (_.isArray(elementShallowCopy.props.children) || isIterable(elementShallowCopy.props.children)) { - elementShallowCopy.props.children = mapChildrenWithoutKeyPrefix(elementShallowCopy.props.children, (node) => { + if (React.isValidElement(children)) { + elementShallowCopy.props.children = linkElement(React.Children.only(children), styles, configuration); + } else if (_.isArray(children) || isIterable(children)) { + elementShallowCopy.props.children = mapChildrenWithoutKeyPrefix(children, (node) => { if (React.isValidElement(node)) { - return linkElement(node, styles, configuration); + // eslint-disable-next-line no-use-before-define + return linkElement(React.Children.only(node), styles, configuration); } else { return node; } }); } + _.forEach(restProps, (propValue, propName) => { + if (React.isValidElement(propValue)) { + elementShallowCopy.props[propName] = linkElement(React.Children.only(propValue), styles, configuration); + } else if (_.isArray(propValue)) { + elementShallowCopy.props[propName] = _.map(propValue, (node) => { + if (React.isValidElement(node)) { + return linkElement(React.Children.only(node), styles, configuration); + } else if (_.isArray(node)) { + return linkArray(node, styles, configuration); + } + + return node; + }); + } + }); + if (styleNames.length) { appendClassName = generateAppendClassName(styles, styleNames, configuration.errorWhenNotFound); diff --git a/tests/linkClass.js b/tests/linkClass.js index 62fbc83..0e3cd06 100644 --- a/tests/linkClass.js +++ b/tests/linkClass.js @@ -18,6 +18,10 @@ describe('linkClass', () => { expect(linkClass(

)).to.deep.equal(

); }); + it('does not affect element properties with a single element child in non-`children` prop', () => { + expect(linkClass(
} />)).to.deep.equal(
} />); + }); + it('does not affect element properties with a single text child', () => { expect(linkClass(
test
)).to.deep.equal(
test
); }); @@ -81,6 +85,26 @@ describe('linkClass', () => { expect(subject.props.children.props.className).to.equal('foo-1'); }); }); + context('when a descendant element in non-`children` prop has styleName', () => { + it('assigns a generated className', () => { + let subject; + + subject =
} + els={[

, [

]]} + />; + + subject = linkClass(subject, { + bar: 'bar-1', + baz: 'baz-1', + foo: 'foo-1' + }); + + expect(subject.props.el.props.className).to.equal('foo-1'); + expect(subject.props.els[0].props.className).to.equal('bar-1'); + expect(subject.props.els[1][0].props.className).to.equal('baz-1'); + }); + }); context('when multiple descendant elements have styleName', () => { it('assigns a generated className', () => { let subject; @@ -139,6 +163,32 @@ describe('linkClass', () => { expect(subject.props.children[1].props.className).to.equal('bar-1'); }); }); + context('when non-`children` prop is an iterable', () => { + it('it is left untouched', () => { + let subject; + + const iterable = { + 0:

, + 1:

, + length: 2, + + // eslint-disable-next-line no-use-extend-native/no-use-extend-native + [Symbol.iterator]: Array.prototype[Symbol.iterator] + }; + + subject =

; + + subject = linkClass(subject, { + bar: 'bar-1', + foo: 'foo-1' + }); + + expect(subject.props.els[0].props.styleName).to.equal('foo'); + expect(subject.props.els[1].props.styleName).to.equal('bar'); + expect(subject.props.els[0].props).not.to.have.property('className'); + expect(subject.props.els[1].props).not.to.have.property('className'); + }); + }); context('when ReactElement does not have an existing className', () => { it('uses the generated class name to set the className property', () => { let subject; @@ -277,24 +327,35 @@ describe('linkClass', () => { it('deletes styleName property from the target element (deep)', () => { let subject; - subject =
+ subject =
} + els={[, []]} + styleName='foo' + >
; subject = linkClass(subject, { bar: 'bar-1', + baz: 'baz-1', foo: 'foo-1' }); expect(subject.props.children[0].props.className).to.deep.equal('bar-1'); expect(subject.props.children[0].props).not.to.have.property('styleName'); + expect(subject.props.el.props.className).to.deep.equal('baz-1'); + expect(subject.props.el.props).not.to.have.property('styleName'); + expect(subject.props.els[0].props.className).to.deep.equal('foo-1'); + expect(subject.props.els[0].props).not.to.have.property('styleName'); + expect(subject.props.els[1][0].props.className).to.deep.equal('bar-1'); + expect(subject.props.els[1][0].props).not.to.have.property('styleName'); }); it('does not change defined keys of children if there are multiple children', () => { let subject; - subject =
+ subject =
, ]}>
; @@ -303,5 +364,7 @@ describe('linkClass', () => { expect(subject.props.children[0].key).to.equal('foo'); expect(subject.props.children[1].key).to.equal('bar'); + expect(subject.props.els[0].key).to.equal('foo'); + expect(subject.props.els[1].key).to.equal('bar'); }); });