Skip to content

Add support for React elements and arrays of React elements in non-children props #243

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

Merged
merged 1 commit into from
Jul 3, 2017
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
41 changes: 36 additions & 5 deletions src/linkClass.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);

Expand Down
67 changes: 65 additions & 2 deletions tests/linkClass.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ describe('linkClass', () => {
expect(linkClass(<div><p /></div>)).to.deep.equal(<div><p /></div>);
});

it('does not affect element properties with a single element child in non-`children` prop', () => {
expect(linkClass(<div el={<p />} />)).to.deep.equal(<div el={<p />} />);
});

it('does not affect element properties with a single text child', () => {
expect(linkClass(<div>test</div>)).to.deep.equal(<div>test</div>);
});
Expand Down Expand Up @@ -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 = <div
el={<p styleName='foo' />}
els={[<p key='bar' styleName='bar' />, [<p key='baz' styleName='baz' />]]}
/>;

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;
Expand Down Expand Up @@ -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: <p key='1' styleName='foo' />,
1: <p key='2' styleName='bar' />,
length: 2,

// eslint-disable-next-line no-use-extend-native/no-use-extend-native
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};

subject = <div els={iterable} />;

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;
Expand Down Expand Up @@ -277,24 +327,35 @@ describe('linkClass', () => {
it('deletes styleName property from the target element (deep)', () => {
let subject;

subject = <div styleName='foo'>
subject = <div
el={<span styleName='baz' />}
els={[<span key='foo' styleName='foo' />, [<span key='bar' styleName='bar' />]]}
styleName='foo'
>
<div styleName='bar' />
<div styleName='bar' />
</div>;

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 = <div>
subject = <div els={[<span key='foo' />, <span key='bar' />]}>
<span key='foo' />
<span key='bar' />
</div>;
Expand All @@ -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');
});
});