Skip to content

Commit 0296a96

Browse files
committed
Test cases.
1 parent 4047ac5 commit 0296a96

File tree

6 files changed

+210
-87
lines changed

6 files changed

+210
-87
lines changed

README.md

Lines changed: 3 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,4 @@
1-
# react-css-modules
1+
# React CSS Modules
22

3-
`index.css`:
4-
5-
```css
6-
.foo {
7-
color: #f00;
8-
}
9-
```
10-
11-
`Rainbow.js`:
12-
13-
```js
14-
import React from 'react';
15-
import styles from './index.css';
16-
import CSSModules from 'react-css-modules';
17-
18-
class Rainbow extends React.Component {
19-
render () {
20-
return <div>
21-
<span className='foo'>I am rainbow!</span>
22-
<span className='bar'>I am rainbow!</span>
23-
</div>;
24-
}
25-
}
26-
27-
export default CSSModules(Rainbow, styles);
28-
```
29-
30-
When used, this component will generate:
31-
32-
```html
33-
<div data-reactid=".0">
34-
<div data-reactid=".0.0">
35-
<span class="foo index__foo___1fSgh" data-reactid=".0.0.0">I am rainbow!</span>
36-
<span class="bar" data-reactid=".0.0.1">I am rainbow!</span>
37-
</div>
38-
</div>
39-
```
3+
[![Travis build status](http://img.shields.io/travis/gajus/react-css-modules/master.svg?style=flat)](https://travis-ci.org/gajus/react-css-modules)
4+
[![NPM version](http://img.shields.io/npm/v/react-css-modules.svg?style=flat)](https://www.npmjs.org/package/react-css-modules)

dist/linkClass.js

Lines changed: 34 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -14,46 +14,27 @@ var _lodashLangIsArray = require('lodash/lang/isArray');
1414

1515
var _lodashLangIsArray2 = _interopRequireDefault(_lodashLangIsArray);
1616

17-
var linkClass = undefined,
18-
unfreeze = undefined;
17+
var _lodashLangToarray = require('lodash/lang/toarray');
1918

20-
/**
21-
* Make a shallow copy of the object.
22-
*
23-
* @param {Object} source Frozen object.
24-
* @return {Object}
25-
*/
26-
unfreeze = function (source) {
27-
var property = undefined,
28-
target = undefined;
29-
30-
target = {};
19+
var _lodashLangToarray2 = _interopRequireDefault(_lodashLangToarray);
3120

32-
for (property in source) {
33-
target[property] = source[property];
34-
}
35-
36-
return target;
37-
};
21+
var linkClass = undefined;
3822

3923
/**
4024
* @param {ReactElement} element
4125
* @param {Object} styles
4226
* @return {ReactElement}
4327
*/
44-
linkClass = function (element, styles) {
45-
var isFrozen = undefined;
28+
linkClass = function (element) {
29+
var styles = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
4630

47-
if (Object.isFrozen && Object.isFrozen(element)) {
48-
isFrozen = true;
49-
50-
// https://github.com/facebook/react/blob/v0.13.3/src/classic/element/ReactElement.js#L131
51-
element = unfreeze(element);
52-
element.props = unfreeze(element.props);
53-
}
31+
var newProps = undefined,
32+
newClassName = undefined,
33+
newChildren = undefined,
34+
childrenCount = undefined;
5435

5536
if (element.props.className) {
56-
element.props.className = element.props.className.split(' ').map(function (className) {
37+
newClassName = element.props.className.split(' ').map(function (className) {
5738
if (styles[className]) {
5839
return className + ' ' + styles[className];
5940
} else {
@@ -62,19 +43,36 @@ linkClass = function (element, styles) {
6243
}).join(' ');
6344
}
6445

65-
if ((0, _lodashLangIsArray2['default'])(element.props.children)) {
66-
element.props.children = element.props.children.map(function (node) {
46+
childrenCount = _react2['default'].Children.count(element.props.children);
47+
48+
if (childrenCount > 1) {
49+
newChildren = [];
50+
51+
_react2['default'].Children.forEach(element.props.children, function (node) {
6752
if (_react2['default'].isValidElement(node)) {
68-
return linkClass(node, styles);
53+
newChildren.push(linkClass(node, styles));
6954
} else {
70-
return node;
55+
newChildren.push(node);
7156
}
7257
});
58+
59+
// Do not use React.Children.map.
60+
// For whatever reason React render multiple children as an array, while
61+
// React.Children.map generates an object.
62+
} else if (childrenCount === 1) {
63+
newChildren = linkClass(_react2['default'].Children.only(element.props.children), styles);
64+
} else {}
65+
66+
if (newClassName) {
67+
newProps = {
68+
className: newClassName
69+
};
7370
}
7471

75-
if (isFrozen) {
76-
Object.freeze(element);
77-
Object.freeze(element.props);
72+
if (newChildren) {
73+
element = _react2['default'].cloneElement(element, newProps, newChildren);
74+
} else {
75+
element = _react2['default'].cloneElement(element, newProps);
7876
}
7977

8078
return element;

package.json

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-css-modules",
3-
"description": "",
3+
"description": "Seamless CSS modules for React.",
44
"main": "dist/index.js",
55
"repository": {
66
"type": "git",
@@ -23,5 +23,17 @@
2323
},
2424
"dependencies": {
2525
"lodash": "^3.10.1"
26+
},
27+
"devDependencies": {
28+
"chai": "^3.2.0",
29+
"jsdom": "^6.2.0",
30+
"mocha": "^2.2.5",
31+
"react": "^0.14.0-beta3",
32+
"react-addons-test-utils": "^0.14.0-beta3"
33+
},
34+
"scripts": {
35+
"test": "mocha",
36+
"build": "babel ./src/ --out-dir ./dist/",
37+
"watch": "babel --watch ./src/ --out-dir ./dist/"
2638
}
2739
}

src/linkClass.js

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react';
22
import isArray from 'lodash/lang/isArray';
3-
import _ from 'lodash';
3+
import toArray from 'lodash/lang/toarray';
44

55
let linkClass;
66

@@ -9,9 +9,11 @@ let linkClass;
99
* @param {Object} styles
1010
* @return {ReactElement}
1111
*/
12-
linkClass = (element, styles) => {
13-
let newClassName,
14-
newChildren;
12+
linkClass = (element, styles = {}) => {
13+
let newProps,
14+
newClassName,
15+
newChildren,
16+
childrenCount;
1517

1618
if (element.props.className) {
1719
newClassName = element.props.className.split(' ').map((className) => {
@@ -23,21 +25,42 @@ linkClass = (element, styles) => {
2325
}).join(' ');
2426
}
2527

26-
if (isArray(element.props.children)) {
27-
newChildren = React.Children.map(element.props.children, (node) => {
28+
childrenCount = React.Children.count(element.props.children);
29+
30+
if (childrenCount > 1) {
31+
newChildren = [];
32+
33+
React.Children.forEach(element.props.children, (node) => {
2834
if (React.isValidElement(node)) {
29-
return linkClass(node, styles);
35+
newChildren.push(linkClass(node, styles));
3036
} else {
31-
return node;
37+
newChildren.push(node);
3238
}
3339
});
40+
41+
// Do not use React.Children.map.
42+
// For whatever reason React render multiple children as an array, while
43+
// React.Children.map generates an object.
44+
45+
} else if (childrenCount === 1) {
46+
newChildren = linkClass(React.Children.only(element.props.children), styles);
47+
} else {
48+
49+
}
50+
51+
if (newClassName) {
52+
newProps = {
53+
className: newClassName
54+
};
55+
}
56+
57+
if (newChildren) {
58+
element = React.cloneElement(element, newProps, newChildren);
3459
} else {
35-
newChildren = element.props.children;
60+
element = React.cloneElement(element, newProps);
3661
}
3762

38-
return React.cloneElement(element, {
39-
className: newClassName
40-
}, newChildren);
63+
return element;
4164
};
4265

4366
export default linkClass;

test/linkClass.js

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import {
2+
expect
3+
} from 'chai';
4+
5+
import React from 'react';
6+
import TestUtils from 'react-addons-test-utils';
7+
import jsdom from 'jsdom';
8+
import linkClass from './../dist/linkClass';
9+
10+
describe('linkClass', () => {
11+
describe('when elements do not define className', () => {
12+
it('does not affect the element declaration', () => {
13+
expect(linkClass(<div></div>)).to.deep.equal(<div></div>);
14+
});
15+
16+
it('does not affect element with a single child', () => {
17+
expect(linkClass(<div><p></p></div>)).to.deep.equal(<div><p></p></div>);
18+
});
19+
20+
it('does not affect element with multiple children', () => {
21+
expect(linkClass(<div><p></p><p></p></div>)).to.deep.equal(<div><p></p><p></p></div>);
22+
});
23+
});
24+
25+
describe('when element className does not match an existing CSS class', () => {
26+
it('does not affect element className', () => {
27+
let subject;
28+
29+
subject = <div className='foo'></div>;
30+
31+
subject = linkClass(subject, {});
32+
33+
expect(subject.props.className).to.deep.equal('foo');
34+
});
35+
});
36+
37+
describe('when element className matches an existing CSS class', () => {
38+
it('appends the generated class name to the className property', () => {
39+
let subject;
40+
41+
subject = <div className='foo'></div>;
42+
43+
subject = linkClass(subject, {
44+
foo: 'foo-1'
45+
});
46+
47+
expect(subject.props.className).to.deep.equal('foo foo-1');
48+
});
49+
});
50+
51+
describe('when element classNames refers to multiple CSS classes', () => {
52+
describe('when all referenced CSS classes exist', () => {
53+
it('appends a generated class name for every referenced CSS class', () => {
54+
let subject;
55+
56+
subject = <div className='foo bar'></div>;
57+
58+
subject = linkClass(subject, {
59+
foo: 'foo-1',
60+
bar: 'bar-1'
61+
});
62+
63+
expect(subject.props.className).to.deep.equal('foo foo-1 bar bar-1');
64+
});
65+
});
66+
describe('when some referenced CSS classes exist', () => {
67+
it('appends a generated class name for the matched CSS classes', () => {
68+
let subject;
69+
70+
subject = <div className='foo bar'></div>;
71+
72+
subject = linkClass(subject, {
73+
foo: 'foo-1'
74+
});
75+
76+
expect(subject.props.className).to.deep.equal('foo foo-1 bar');
77+
});
78+
});
79+
describe('when none of the referenced CSS classes exist', () => {
80+
it('does not append anything', () => {
81+
let subject;
82+
83+
subject = <div className='foo bar'></div>;
84+
85+
subject = linkClass(subject, {});
86+
87+
expect(subject.props.className).to.deep.equal('foo bar');
88+
});
89+
});
90+
});
91+
92+
describe('when ReactElement includes ReactComponent', () => {
93+
let Foo,
94+
nodeList;
95+
96+
beforeEach(() => {
97+
global.document = jsdom.jsdom(`
98+
<!DOCTYPE html>
99+
<html>
100+
<head>
101+
</head>
102+
<body>
103+
</body>
104+
</html>
105+
`);
106+
107+
global.window = document.defaultView;
108+
109+
Foo = class extends React.Component {
110+
render () {
111+
return <div className='foo'>Hello</div>;
112+
}
113+
};
114+
115+
nodeList = TestUtils.renderIntoDocument(linkClass(<div className='foo'><Foo /></div>, {foo: 'foo-1'}));
116+
});
117+
it('processes ReactElement nodes', () => {
118+
expect(nodeList.className).to.equal('foo foo-1');
119+
});
120+
it('does not process ReactComponent nodes', () => {
121+
expect(nodeList.firstChild.className).to.equal('foo');
122+
});
123+
});
124+
});

test/mocha.opts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
--compilers js:babel/register

0 commit comments

Comments
 (0)