Skip to content

Commit

Permalink
feat: add support for non-children props (#243)
Browse files Browse the repository at this point in the history
  • Loading branch information
le0nik authored and gajus committed Jul 3, 2017
1 parent 407cdb4 commit b51b1f3
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 7 deletions.
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');
});
});

0 comments on commit b51b1f3

Please sign in to comment.