diff --git a/README/react-css-modules.png b/.README/react-css-modules.png
similarity index 100%
rename from README/react-css-modules.png
rename to .README/react-css-modules.png
diff --git a/README/react-css-modules.sketch b/.README/react-css-modules.sketch
similarity index 100%
rename from README/react-css-modules.sketch
rename to .README/react-css-modules.sketch
diff --git a/.babelrc b/.babelrc
index 12606a3..3751ba8 100644
--- a/.babelrc
+++ b/.babelrc
@@ -1,3 +1,19 @@
{
- "stage": 0
+ "plugins": [
+ "add-module-exports",
+ "lodash",
+ "transform-class-properties",
+ [
+ "transform-es2015-classes",
+ {
+ "loose": true
+ }
+ ],
+ "transform-proto-to-assign"
+ ],
+ "presets": [
+ "es2015",
+ "stage-0",
+ "react"
+ ]
}
diff --git a/.coveralls.yml b/.coveralls.yml
deleted file mode 100644
index 65bf7c7..0000000
--- a/.coveralls.yml
+++ /dev/null
@@ -1 +0,0 @@
-repo_token: bqiXmaOA0xarSUociqxGKJ72bp8SSHlQC
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..0f17867
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,9 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+indent_size = 2
+indent_style = space
+insert_final_newline = true
+trim_trailing_whitespace = true
diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 0000000..bcaad1b
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,6 @@
+{
+ "extends": [
+ "canonical",
+ "canonical/mocha"
+ ]
+}
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..2f093a7
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,2 @@
+github: gajus
+patreon: gajus
diff --git a/.gitignore b/.gitignore
index fa88f70..df978de 100755
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,12 @@
node_modules
coverage
-.coveralls.yml
+dist
*.log
+.*
+!.babelrc
+!.editorconfig
+!.eslintrc
+!.gitignore
+!.npmignore
+!.README
+!.travis.yml
diff --git a/.npmignore b/.npmignore
index 4f341b3..e8add85 100755
--- a/.npmignore
+++ b/.npmignore
@@ -1,7 +1,5 @@
src
tests
coverage
-.coveralls.yml
-.travis.yml
-.eslintrc
+.*
*.log
diff --git a/.travis.yml b/.travis.yml
index e0fced4..a786167 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,14 @@
language: node_js
node_js:
- - '4.1'
- - '4.0'
+ - node
+ - 8
+before_install:
+ - npm config set depth 0
notifications:
- email: false
+ email: false
sudo: false
+script:
+ - npm run test
+ - npm run lint
+after_success:
+ - semantic-release pre && npm publish && semantic-release post
diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md
new file mode 100644
index 0000000..109eb2e
--- /dev/null
+++ b/ISSUE_TEMPLATE.md
@@ -0,0 +1,5 @@
+
diff --git a/README.md b/README.md
index 362e693..a6b7399 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,24 @@
# React CSS Modules
-[](https://travis-ci.org/gajus/react-css-modules)
-[](https://www.npmjs.org/package/react-css-modules)
-[](https://coveralls.io/github/gajus/react-css-modules?branch=master)
+[](https://gitspo.com/mentions/gajus/react-css-modules)
+[](https://travis-ci.org/gajus/react-css-modules)
+[](https://www.npmjs.org/package/react-css-modules)
+[](https://github.com/gajus/canonical)
-
+
React CSS Modules implement automatic mapping of CSS modules. Every CSS class is assigned a local-scoped identifier with a global unique name. CSS Modules enable a modular and reusable CSS!
+> ## ⚠️⚠️⚠️ DEPRECATION NOTICE ⚠️⚠️⚠️
+>
+> If you are considering to use `react-css-modules`, evaluate if [`babel-plugin-react-css-modules`](https://github.com/gajus/babel-plugin-react-css-modules) covers your use case.
+> `babel-plugin-react-css-modules` is a lightweight alternative of `react-css-modules`.
+>
+> `babel-plugin-react-css-modules` is not a drop-in replacement and does not cover all the use cases of `react-css-modules`.
+> However, it has a lot smaller performance overhead (0-10% vs +50%; see [Performance](https://github.com/gajus/babel-plugin-react-css-modules#performance)) and a lot smaller size footprint (less than 2kb vs +17kb).
+>
+> It is easy to get started! See the demo https://github.com/gajus/babel-plugin-react-css-modules/tree/master/demo
+
- [CSS Modules](#css-modules)
- [webpack `css-loader`](#webpack-css-loader)
- [What's the Problem?](#whats-the-problem)
@@ -15,23 +26,27 @@ React CSS Modules implement automatic mapping of CSS modules. Every CSS class is
- [Usage](#usage)
- [Module Bundler](#module-bundler)
- [webpack](#webpack)
+ - [Development](#development)
+ - [Production](#production)
- [Browserify](#browserify)
- [Extending Component Styles](#extending-component-styles)
- - [Styles Property](#styles-property)
+ - [`styles` Property](#styles-property)
+ - [Loops and Child Components](#loops-and-child-components)
- [Decorator](#decorator)
- [Options](#options)
- [`allowMultiple`](#allowmultiple)
- - [`errorWhenNotFound`](#errorwhennotfound)
+ - [`handleNotFoundStyleName`](#handlenotfoundstylename)
+- [SASS, SCSS, LESS and other CSS Preprocessors](#sass-scss-less-and-other-css-preprocessors)
+ - [Enable Sourcemaps](#enable-sourcemaps)
- [Class Composition](#class-composition)
- [What Problems does Class Composition Solve?](#what-problems-does-class-composition-solve)
- [Class Composition Using CSS Preprocessors](#class-composition-using-css-preprocessors)
-- [SASS, SCSS, LESS and other CSS Preprocessors](#sass-scss-less-and-other-css-preprocessors)
- [Global CSS](#global-css)
- [Multiple CSS Modules](#multiple-css-modules)
## CSS Modules
-[CSS Modules](https://github.com/css-modules/css-modules) are awesome. If you are not familiar with CSS Modules, it is a concept of using a module bundler such as [webpack](http://webpack.github.io/docs/) to load CSS scoped to a particular document. CSS module loader will generate a unique name for a each CSS class at the time of loading the CSS document ([Interoperable CSS](https://github.com/css-modules/icss) to be precise). To see CSS Modules in practice, [webpack-demo](https://css-modules.github.io/webpack-demo/).
+[CSS Modules](https://github.com/css-modules/css-modules) are awesome. If you are not familiar with CSS Modules, it is a concept of using a module bundler such as [webpack](http://webpack.github.io/docs/) to load CSS scoped to a particular document. CSS module loader will generate a unique name for each CSS class at the time of loading the CSS document ([Interoperable CSS](https://github.com/css-modules/icss) to be precise). To see CSS Modules in practice, [webpack-demo](https://css-modules.github.io/webpack-demo/).
In the context of React, CSS Modules look like this:
@@ -56,7 +71,7 @@ Rendering the component will produce a markup similar to:
```js
@@ -68,7 +83,7 @@ Awesome!
### webpack `css-loader`
-[CSS Modules](https://github.com/css-modules/css-modules) is a specification that can be implemented in multiple ways. `react-css-modules` leverages as existing CSS Modules implementation webpack [css-loader](https://github.com/webpack/css-loader#css-modules).
+[CSS Modules](https://github.com/css-modules/css-modules) is a specification that can be implemented in multiple ways. `react-css-modules` leverages the existing CSS Modules implementation webpack [css-loader](https://github.com/webpack/css-loader#css-modules).
## What's the Problem?
@@ -110,7 +125,7 @@ Using `react-css-modules`:
```
-* You are warned when `styleName` refers to an undefined CSS Module ([`errorWhenNotFound`](#errorwhennotfound) option).
+* You are warned when `styleName` refers to an undefined CSS Module ([`handleNotFoundStyleName`](#handlenotfoundstylename) option).
* You can enforce use of a single CSS module per `ReactElement` ([`allowMultiple`](#allowmultiple) option).
## The Implementation
@@ -123,35 +138,106 @@ Using `react-css-modules`:
Setup consists of:
-* Setting up a [module bundler](#modulebundler) to load the [Interoperable CSS](https://github.com/css-modules/icss).
+* Setting up a [module bundler](#module-bundler) to load the [Interoperable CSS](https://github.com/css-modules/icss).
* [Decorating](#decorator) your component using `react-css-modules`.
### Module Bundler
#### webpack
-* Install [`style-loader`](https://www.npmjs.com/package/style-loader) and [`css-loader`](https://www.npmjs.com/package/css-loader).
-* You need to use [`extract-text-webpack-plugin`](https://www.npmjs.com/package/extract-text-webpack-plugin) to aggregate the CSS into a single file.
+##### Development
+
+In development environment, you want to [Enable Sourcemaps](#enable-sourcemaps) and webpack [Hot Module Replacement](https://webpack.github.io/docs/hot-module-replacement.html) (HMR). [`style-loader`](https://github.com/webpack/style-loader) already supports HMR. Therefore, Hot Module Replacement will work out of the box.
+
+Setup:
+
+* Install [`style-loader`](https://www.npmjs.com/package/style-loader).
+* Install [`css-loader`](https://www.npmjs.com/package/css-loader).
* Setup `/\.css$/` loader:
```js
{
test: /\.css$/,
- loader: ExtractTextPlugin.extract('style', 'css?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]')
+ loaders: [
+ 'style-loader?sourceMap',
+ 'css-loader?modules&importLoaders=1&localIdentName=[path]___[name]__[local]___[hash:base64:5]'
+ ]
}
```
+##### Production
+
+In production environment, you want to extract chunks of CSS into a single stylesheet file.
+
+> Advantages:
+>
+> * Fewer style tags (older IE has a limit)
+> * CSS SourceMap (with `devtool: "source-map"` and `css-loader?sourceMap`)
+> * CSS requested in parallel
+> * CSS cached separate
+> * Faster runtime (less code and DOM operations)
+>
+> Caveats:
+>
+> * Additional HTTP request
+> * Longer compilation time
+> * More complex configuration
+> * No runtime public path modification
+> * No Hot Module Replacement
+
+– [extract-text-webpack-plugin](https://github.com/webpack/extract-text-webpack-plugin)
+
+Setup:
+
+* Install [`style-loader`](https://www.npmjs.com/package/style-loader).
+* Install [`css-loader`](https://www.npmjs.com/package/css-loader).
+* Use [`extract-text-webpack-plugin`](https://www.npmjs.com/package/extract-text-webpack-plugin) to extract chunks of CSS into a single stylesheet.
+
+* Setup `/\.css$/` loader:
+
+ * ExtractTextPlugin v1x:
+
+ ```js
+ {
+ test: /\.css$/,
+ loader: ExtractTextPlugin.extract('style', 'css?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]')
+ }
+ ```
+
+ * ExtractTextPlugin v2x:
+
+ ```js
+ {
+ test: /\.css$/,
+ use: ExtractTextPlugin.extract({
+ fallback: 'style-loader',
+ use: 'css-loader?modules,localIdentName="[name]-[local]-[hash:base64:6]"'
+ }),
+ }
+ ```
+
* Setup `extract-text-webpack-plugin` plugin:
-```js
-new ExtractTextPlugin('app.css', {
- allChunks: true
-})
-```
+ * ExtractTextPlugin v1x:
+
+ ```js
+ new ExtractTextPlugin('app.css', {
+ allChunks: true
+ })
+ ```
+
+ * ExtractTextPlugin v2x:
+
+ ```js
+ new ExtractTextPlugin({
+ filename: 'app.css',
+ allChunks: true
+ })
+ ```
Refer to [webpack-demo](https://github.com/css-modules/webpack-demo) or [react-css-modules-examples](https://github.com/gajus/react-css-modules-examples) for an example of a complete setup.
-#### Browserify
+##### Browserify
Refer to [`css-modulesify`](https://github.com/css-modules/css-modulesify).
@@ -219,45 +305,99 @@ In this example, `table-custom-styles.css` selectively extends `table.css` (the
Refer to the [`UsingStylesProperty` example](https://github.com/gajus/react-css-modules-examples/tree/master/src/UsingStylesProperty) for an example of a working implementation.
-### Styles Property
+### `styles` Property
Decorated components inherit `styles` property that describes the mapping between CSS modules and CSS classes.
-`styles` property within the component itself is designed to be used only when `styleNames` cannot be used.
+```js
+class extends React.Component {
+ render () {
+ ;
+ }
+}
+```
+
+In the above example, `styleName='foo'` and `className={this.props.styles.foo}` are equivalent.
+
+`styles` property is designed to enable component decoration of [Loops and Child Components](#loops-and-child-components).
-`styleNames` cannot be used within a component to define styles of a `ReactElement` that will be generated by another component (https://github.com/gajus/react-css-modules/issues/11), e.g.
+### Loops and Child Components
+
+`styleName` cannot be used to define styles of a `ReactElement` that will be generated by another component, e.g.
```js
-class extends React.Component {
+import React from 'react';
+import CSSModules from 'react-css-modules';
+import List from './List';
+import styles from './table.css';
+
+class CustomList extends React.Component {
render () {
let itemTemplate;
itemTemplate = (name) => {
- return {name};
+ return {name};
};
- return
;
+ return
;
}
}
+
+export default CSSModules(CustomList, styles);
```
-For that purpose, the decorated component inherits `styles` property that you can use just as a regular CSS Modules object. The earlier example can be therefore rewritten to:
+The above example will not work. `CSSModules` is used to decorate `CustomList` component. However, it is the `List` component that will render `itemTemplate`.
+
+For that purpose, the decorated component inherits [`styles` property](#styles-property) that you can use just as a regular CSS Modules object. The earlier example can be therefore rewritten to:
```js
-class extends React.Component {
+import React from 'react';
+import CSSModules from 'react-css-modules';
+import List from './List';
+import styles from './table.css';
+
+class CustomList extends React.Component {
render () {
let itemTemplate;
itemTemplate = (name) => {
- return {name};
+ return {name};
};
- return
;
+ return
;
}
}
+
+export default CSSModules(CustomList, styles);
```
-`styles` property works with ES6 classes and stateless function components.
+You can use `styleName` property within the child component if you decorate the child component using `CSSModules` before passing it to the rendering component, e.g.
+
+```js
+import React from 'react';
+import CSSModules from 'react-css-modules';
+import List from './List';
+import styles from './table.css';
+
+class CustomList extends React.Component {
+ render () {
+ let itemTemplate;
+
+ itemTemplate = (name) => {
+ return {name};
+ };
+
+ itemTemplate = CSSModules(itemTemplate, this.props.styles);
+
+ return
;
+ }
+}
+
+export default CSSModules(CustomList, styles);
+```
### Decorator
@@ -266,7 +406,7 @@ class extends React.Component {
* @typedef CSSModules~Options
* @see {@link https://github.com/gajus/react-css-modules#options}
* @property {Boolean} allowMultiple
- * @property {Boolean} errorWhenNotFound
+ * @property {String} handleNotFoundStyleName
*/
/**
@@ -350,11 +490,17 @@ When `false`, the following will cause an error:
```
-#### `errorWhenNotFound`
+#### `handleNotFoundStyleName`
+
+Default: `throw`.
-Default: `true`.
+Defines the desired action when `styleName` cannot be mapped to an existing CSS Module.
-Throws an error when `styleName` cannot be mapped to an existing CSS Module.
+Available options:
+
+* `throw` throws an error
+* `log` logs a warning using `console.warn`
+* `ignore` silently ignores the missing style name
## SASS, SCSS, LESS and other CSS Preprocessors
@@ -363,7 +509,28 @@ Throws an error when `styleName` cannot be mapped to an existing CSS Module.
```js
{
test: /\.scss$/,
- loader: ExtractTextPlugin.extract('style', 'css?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]!sass')
+ loaders: [
+ 'style',
+ 'css?modules&importLoaders=1&localIdentName=[path]___[name]__[local]___[hash:base64:5]',
+ 'resolve-url',
+ 'sass'
+ ]
+}
+```
+
+### Enable Sourcemaps
+
+To enable CSS Source maps, add `sourceMap` parameter to the css-loader and to the `sass-loader`:
+
+```js
+{
+ test: /\.scss$/,
+ loaders: [
+ 'style?sourceMap',
+ 'css?modules&importLoaders=1&localIdentName=[path]___[name]__[local]___[hash:base64:5]',
+ 'resolve-url',
+ 'sass?sourceMap'
+ ]
}
```
@@ -423,7 +590,7 @@ This pattern emerged with the advent of OOCSS. The biggest disadvantage of this
### Class Composition Using CSS Preprocessors
-This section of the document is included as a learning exercise to broaden the understanding about the origin of Class Composition. CSS Modules support a native method of composting CSS Modules using [`composes`](https://github.com/css-modules/css-modules#composition) keyword. CSS Preprocessor is not required.
+This section of the document is included as a learning exercise to broaden the understanding about the origin of Class Composition. CSS Modules support a native method of composing CSS Modules using [`composes`](https://github.com/css-modules/css-modules#composition) keyword. CSS Preprocessor is not required.
You can write compositions in SCSS using [`@extend`](http://sass-lang.com/documentation/file.SASS_REFERENCE.html#extend) keyword and using [Mixin Directives](http://sass-lang.com/documentation/file.SASS_REFERENCE.html#mixins), e.g.
diff --git a/dist/extendReactClass.js b/dist/extendReactClass.js
deleted file mode 100644
index bec936c..0000000
--- a/dist/extendReactClass.js
+++ /dev/null
@@ -1,85 +0,0 @@
-'use strict';
-
-var _lodashLangIsObject2 = require('lodash/lang/isObject');
-
-var _lodashLangIsObject3 = _interopRequireDefault(_lodashLangIsObject2);
-
-var _lodashObjectAssign2 = require('lodash/object/assign');
-
-var _lodashObjectAssign3 = _interopRequireDefault(_lodashObjectAssign2);
-
-Object.defineProperty(exports, '__esModule', {
- value: true
-});
-
-var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
-
-var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
-
-function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
-
-function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
-
-var _linkClass = require('./linkClass');
-
-var _linkClass2 = _interopRequireDefault(_linkClass);
-
-var _react = require('react');
-
-var _react2 = _interopRequireDefault(_react);
-
-function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
-
-var extendReactClass = undefined;
-
-/**
- * @param {ReactClass} Component
- * @param {Object} defaultStyles
- * @param {Object} options
- * @returns {ReactClass}
- */
-extendReactClass = function (Component, defaultStyles, options) {
- return (function (_Component) {
- _inherits(_class, _Component);
-
- function _class() {
- _classCallCheck(this, _class);
-
- _get(Object.getPrototypeOf(_class.prototype), 'constructor', this).apply(this, arguments);
- }
-
- _createClass(_class, [{
- key: 'render',
- value: function render() {
- var renderResult = undefined,
- styles = undefined;
-
- if (this.props.styles) {
- styles = this.props.styles;
- } else if ((0, _lodashLangIsObject3['default'])(defaultStyles)) {
- this.props = (0, _lodashObjectAssign3['default'])({}, this.props, {
- styles: defaultStyles
- });
-
- styles = defaultStyles;
- } else {
- styles = {};
- }
-
- renderResult = _get(Object.getPrototypeOf(_class.prototype), 'render', this).call(this);
-
- if (renderResult) {
- return (0, _linkClass2['default'])(renderResult, styles, options);
- }
-
- return _react2['default'].createElement('noscript');
- }
- }]);
-
- return _class;
- })(Component);
-};
-
-exports['default'] = extendReactClass;
-module.exports = exports['default'];
-//# sourceMappingURL=extendReactClass.js.map
diff --git a/dist/extendReactClass.js.map b/dist/extendReactClass.js.map
deleted file mode 100644
index 63c37a9..0000000
--- a/dist/extendReactClass.js.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"sources":["extendReactClass.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;yBAAsB,aAAa;;;;qBACjB,OAAO;;;;;;AAGzB,IAAI,gBAAgB,YAAA,CAAC;;;;;;;;AAQrB,gBAAgB,GAAG,UAAC,SAAS,EAAE,aAAa,EAAE,OAAO,EAAK;AACtD;;;;;;;;;;;mBACW,kBAAG;AACN,oBAAI,YAAY,YAAA;oBACZ,MAAM,YAAA,CAAC;;AAEX,oBAAI,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE;AACnB,0BAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;iBAC9B,MAAM,IAAI,qCAAW,aAAa,CAAC,EAAE;AAClC,wBAAI,CAAC,KAAK,GAAG,qCAAS,EAAE,EAAE,IAAI,CAAC,KAAK,EAAE;AAClC,8BAAM,EAAE,aAAa;qBACxB,CAAC,CAAC;;AAEH,0BAAM,GAAG,aAAa,CAAC;iBAC1B,MAAM;AACH,0BAAM,GAAG,EAAE,CAAC;iBACf;;AAED,4BAAY,2EAAiB,CAAC;;AAE9B,oBAAI,YAAY,EAAE;AACd,2BAAO,4BAAU,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;iBACnD;;AAED,uBAAO,mBAAM,aAAa,CAAC,UAAU,CAAC,CAAC;aAC1C;;;;OAxBgB,SAAS,EAyB5B;CACL,CAAC;;qBAEa,gBAAgB","file":"extendReactClass.js","sourcesContent":["import linkClass from './linkClass';\nimport React from 'react';\nimport _ from 'lodash';\n\nlet extendReactClass;\n\n/**\n * @param {ReactClass} Component\n * @param {Object} defaultStyles\n * @param {Object} options\n * @returns {ReactClass}\n */\nextendReactClass = (Component, defaultStyles, options) => {\n return class extends Component {\n render () {\n let renderResult,\n styles;\n\n if (this.props.styles) {\n styles = this.props.styles;\n } else if (_.isObject(defaultStyles)) {\n this.props = _.assign({}, this.props, {\n styles: defaultStyles\n });\n\n styles = defaultStyles;\n } else {\n styles = {};\n }\n\n renderResult = super.render();\n\n if (renderResult) {\n return linkClass(renderResult, styles, options);\n }\n\n return React.createElement('noscript');\n }\n };\n};\n\nexport default extendReactClass;\n"],"sourceRoot":"/source/"}
\ No newline at end of file
diff --git a/dist/index.js b/dist/index.js
deleted file mode 100644
index ef6b8b4..0000000
--- a/dist/index.js
+++ /dev/null
@@ -1,79 +0,0 @@
-'use strict';
-
-Object.defineProperty(exports, '__esModule', {
- value: true
-});
-
-function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
-
-var _extendReactClass = require('./extendReactClass');
-
-var _extendReactClass2 = _interopRequireDefault(_extendReactClass);
-
-var _wrapStatelessFunction = require('./wrapStatelessFunction');
-
-var _wrapStatelessFunction2 = _interopRequireDefault(_wrapStatelessFunction);
-
-var decoratorConstructor = undefined,
- functionConstructor = undefined,
- isReactComponent = undefined;
-
-/**
- * Determines if the given object has the signature of a class that inherits React.Component.
- *
- * @param {*} Component
- * @return {boolean}
- */
-isReactComponent = function (Component) {
- return 'prototype' in Component && typeof Component.prototype.render === 'function';
-};
-
-/**
- * When used as a function.
- *
- * @param {Function} Component
- * @param {Object} defaultStyles CSS Modules class map.
- * @param {Object} options {@link https://github.com/gajus/react-css-modules#options}
- * @return {Function}
- */
-functionConstructor = function (Component, defaultStyles, options) {
- var decoratedClass = undefined;
-
- if (isReactComponent(Component)) {
- decoratedClass = (0, _extendReactClass2['default'])(Component, defaultStyles, options);
- } else {
- decoratedClass = (0, _wrapStatelessFunction2['default'])(Component, defaultStyles, options);
- }
-
- if (Component.displayName) {
- decoratedClass.displayName = Component.displayName;
- } else {
- decoratedClass.displayName = Component.name;
- }
-
- return decoratedClass;
-};
-
-/**
- * When used as a ES7 decorator.
- *
- * @param {Object} defaultStyles CSS Modules class map.
- * @param {Object} options {@link https://github.com/gajus/react-css-modules#options}
- * @return {Function}
- */
-decoratorConstructor = function (defaultStyles, options) {
- return function (Component) {
- return functionConstructor(Component, defaultStyles, options);
- };
-};
-
-exports['default'] = function () {
- if (typeof arguments[0] === 'function') {
- return functionConstructor(arguments[0], arguments[1], arguments[2]);
- } else {
- return decoratorConstructor(arguments[0], arguments[1]);
- }
-};
-
-module.exports = exports['default'];
-//# sourceMappingURL=index.js.map
diff --git a/dist/index.js.map b/dist/index.js.map
deleted file mode 100644
index 161c75a..0000000
--- a/dist/index.js.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"sources":["index.js"],"names":[],"mappings":";;;;;;;;gCAA6B,oBAAoB;;;;qCACf,yBAAyB;;;;AAE3D,IAAI,oBAAoB,YAAA;IACpB,mBAAmB,YAAA;IACnB,gBAAgB,YAAA,CAAC;;;;;;;;AAQrB,gBAAgB,GAAG,UAAC,SAAS,EAAK;AAC9B,WAAO,WAAW,IAAI,SAAS,IAAI,OAAO,SAAS,CAAC,SAAS,CAAC,MAAM,KAAK,UAAU,CAAC;CACvF,CAAC;;;;;;;;;;AAUF,mBAAmB,GAAG,UAAC,SAAS,EAAE,aAAa,EAAE,OAAO,EAAK;AACzD,QAAI,cAAc,YAAA,CAAC;;AAEnB,QAAI,gBAAgB,CAAC,SAAS,CAAC,EAAE;AAC7B,sBAAc,GAAG,mCAAiB,SAAS,EAAE,aAAa,EAAE,OAAO,CAAC,CAAC;KACxE,MAAM;AACH,sBAAc,GAAG,wCAAsB,SAAS,EAAE,aAAa,EAAE,OAAO,CAAC,CAAC;KAC7E;;AAED,QAAI,SAAS,CAAC,WAAW,EAAE;AACvB,sBAAc,CAAC,WAAW,GAAG,SAAS,CAAC,WAAW,CAAC;KACtD,MAAM;AACH,sBAAc,CAAC,WAAW,GAAG,SAAS,CAAC,IAAI,CAAC;KAC/C;;AAED,WAAO,cAAc,CAAC;CACzB,CAAC;;;;;;;;;AASF,oBAAoB,GAAG,UAAC,aAAa,EAAE,OAAO,EAAK;AAC/C,WAAO,UAAC,SAAS,EAAK;AAClB,eAAO,mBAAmB,CAAC,SAAS,EAAE,aAAa,EAAE,OAAO,CAAC,CAAC;KACjE,CAAC;CACL,CAAC;;qBAEa,YAAa;AACxB,QAAI,OAAO,UAAK,CAAC,CAAC,KAAK,UAAU,EAAE;AAC/B,eAAO,mBAAmB,CAAC,UAAK,CAAC,CAAC,EAAE,UAAK,CAAC,CAAC,EAAE,UAAK,CAAC,CAAC,CAAC,CAAC;KACzD,MAAM;AACH,eAAO,oBAAoB,CAAC,UAAK,CAAC,CAAC,EAAE,UAAK,CAAC,CAAC,CAAC,CAAC;KACjD;CACJ","file":"index.js","sourcesContent":["import extendReactClass from './extendReactClass';\nimport wrapStatelessFunction from './wrapStatelessFunction';\n\nlet decoratorConstructor,\n functionConstructor,\n isReactComponent;\n\n/**\n * Determines if the given object has the signature of a class that inherits React.Component.\n *\n * @param {*} Component\n * @return {boolean}\n */\nisReactComponent = (Component) => {\n return 'prototype' in Component && typeof Component.prototype.render === 'function';\n};\n\n/**\n * When used as a function.\n *\n * @param {Function} Component\n * @param {Object} defaultStyles CSS Modules class map.\n * @param {Object} options {@link https://github.com/gajus/react-css-modules#options}\n * @return {Function}\n */\nfunctionConstructor = (Component, defaultStyles, options) => {\n let decoratedClass;\n\n if (isReactComponent(Component)) {\n decoratedClass = extendReactClass(Component, defaultStyles, options);\n } else {\n decoratedClass = wrapStatelessFunction(Component, defaultStyles, options);\n }\n\n if (Component.displayName) {\n decoratedClass.displayName = Component.displayName;\n } else {\n decoratedClass.displayName = Component.name;\n }\n\n return decoratedClass;\n};\n\n/**\n * When used as a ES7 decorator.\n *\n * @param {Object} defaultStyles CSS Modules class map.\n * @param {Object} options {@link https://github.com/gajus/react-css-modules#options}\n * @return {Function}\n */\ndecoratorConstructor = (defaultStyles, options) => {\n return (Component) => {\n return functionConstructor(Component, defaultStyles, options);\n };\n};\n\nexport default (...args) => {\n if (typeof args[0] === 'function') {\n return functionConstructor(args[0], args[1], args[2]);\n } else {\n return decoratorConstructor(args[0], args[1]);\n }\n};\n"],"sourceRoot":"/source/"}
\ No newline at end of file
diff --git a/dist/linkClass.js b/dist/linkClass.js
deleted file mode 100644
index 7b48e71..0000000
--- a/dist/linkClass.js
+++ /dev/null
@@ -1,125 +0,0 @@
-'use strict';
-
-var _lodashCollectionFilter2 = require('lodash/collection/filter');
-
-var _lodashCollectionFilter3 = _interopRequireDefault(_lodashCollectionFilter2);
-
-var _lodashLangIsArray2 = require('lodash/lang/isArray');
-
-var _lodashLangIsArray3 = _interopRequireDefault(_lodashLangIsArray2);
-
-Object.defineProperty(exports, '__esModule', {
- value: true
-});
-
-var _react = require('react');
-
-var _react2 = _interopRequireDefault(_react);
-
-var _makeConfiguration = require('./makeConfiguration');
-
-var _makeConfiguration2 = _interopRequireDefault(_makeConfiguration);
-
-function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
-
-var linkClass = undefined;
-
-/**
- * @param {ReactElement} element
- * @param {Object} styles CSS modules class map.
- * @param {CSSModules~Options} userConfiguration
- * @return {ReactElement}
- */
-linkClass = function (element, styles, userConfiguration) {
- if (styles === undefined) styles = {};
-
- var appendClassName = undefined,
- clonedElement = undefined,
- configuration = undefined,
- newChildren = undefined,
- newProps = undefined,
- styleNames = undefined;
-
- // @see https://github.com/gajus/react-css-modules/pull/30
- if (!element) {
- return element;
- }
-
- configuration = (0, _makeConfiguration2['default'])(userConfiguration);
-
- styleNames = element.props.styleName;
-
- if (styleNames) {
- styleNames = styleNames.split(' ');
- styleNames = (0, _lodashCollectionFilter3['default'])(styleNames);
-
- if (configuration.allowMultiple === false && styleNames.length > 1) {
- throw new Error('ReactElement styleName property defines multiple module names ("' + element.props.styleName + '").');
- }
-
- appendClassName = styleNames.map(function (styleName) {
- if (styles[styleName]) {
- return styles[styleName];
- } else {
- if (configuration.errorWhenNotFound === true) {
- throw new Error('"' + styleName + '" CSS module is undefined.');
- }
-
- return '';
- }
- });
-
- appendClassName = appendClassName.filter(function (className) {
- return className.length;
- });
-
- appendClassName = appendClassName.join(' ');
- }
-
- // element.props.children can be one of the following:
- // 'text'
- // ['text']
- // [ReactElement, 'text']
- // ReactElement
-
- // console.log(`element.props.children`, element.props.children, `React.Children.count(element.props.children)`, React.Children.count(element.props.children));
-
- if (_react2['default'].isValidElement(element.props.children)) {
- newChildren = linkClass(_react2['default'].Children.only(element.props.children), styles, configuration);
- } else if ((0, _lodashLangIsArray3['default'])(element.props.children)) {
- newChildren = _react2['default'].Children.map(element.props.children, function (node) {
- if (_react2['default'].isValidElement(node)) {
- return linkClass(node, styles, configuration);
- } else {
- return node;
- }
- });
-
- // https://github.com/facebook/react/issues/4723#issuecomment-135555277
- // Forcing children into an array produces the following error:
- // Warning: A ReactFragment is an opaque type. Accessing any of its properties is deprecated. Pass it to one of the React.Children helpers.
- // newChildren = _.values(newChildren);
- }
-
- if (appendClassName) {
- if (element.props.className) {
- appendClassName = element.props.className + ' ' + appendClassName;
- }
-
- newProps = {
- className: appendClassName
- };
- }
-
- if (newChildren) {
- clonedElement = _react2['default'].cloneElement(element, newProps, newChildren);
- } else {
- clonedElement = _react2['default'].cloneElement(element, newProps);
- }
-
- return clonedElement;
-};
-
-exports['default'] = linkClass;
-module.exports = exports['default'];
-//# sourceMappingURL=linkClass.js.map
diff --git a/dist/linkClass.js.map b/dist/linkClass.js.map
deleted file mode 100644
index 3367091..0000000
--- a/dist/linkClass.js.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"sources":["linkClass.js"],"names":[],"mappings":";;;;;;;;;;;;;;qBAAkB,OAAO;;;;iCACK,qBAAqB;;;;;;AAGnD,IAAI,SAAS,YAAA,CAAC;;;;;;;;AAQd,SAAS,GAAG,UAAC,OAAO,EAAE,MAAM,EAAO,iBAAiB,EAAK;QAAnC,MAAM,gBAAN,MAAM,GAAG,EAAE;;AAC7B,QAAI,eAAe,YAAA;QACf,aAAa,YAAA;QACb,aAAa,YAAA;QACb,WAAW,YAAA;QACX,QAAQ,YAAA;QACR,UAAU,YAAA,CAAC;;;AAGf,QAAI,CAAC,OAAO,EAAE;AACV,eAAO,OAAO,CAAC;KAClB;;AAED,iBAAa,GAAG,oCAAkB,iBAAiB,CAAC,CAAC;;AAErD,cAAU,GAAG,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC;;AAErC,QAAI,UAAU,EAAE;AACZ,kBAAU,GAAG,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;AACnC,kBAAU,GAAG,yCAAS,UAAU,CAAC,CAAC;;AAElC,YAAI,aAAa,CAAC,aAAa,KAAK,KAAK,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE;AAChE,kBAAM,IAAI,KAAK,sEAAoE,OAAO,CAAC,KAAK,CAAC,SAAS,SAAM,CAAC;SACpH;;AAED,uBAAe,GAAG,UAAU,CAAC,GAAG,CAAC,UAAC,SAAS,EAAK;AAC5C,gBAAI,MAAM,CAAC,SAAS,CAAC,EAAE;AACnB,uBAAO,MAAM,CAAC,SAAS,CAAC,CAAC;aAC5B,MAAM;AACH,oBAAI,aAAa,CAAC,iBAAiB,KAAK,IAAI,EAAE;AAC1C,0BAAM,IAAI,KAAK,OAAK,SAAS,gCAA6B,CAAC;iBAC9D;;AAED,uBAAO,EAAE,CAAC;aACb;SACJ,CAAC,CAAC;;AAEH,uBAAe,GAAG,eAAe,CAAC,MAAM,CAAC,UAAC,SAAS,EAAK;AACpD,mBAAO,SAAS,CAAC,MAAM,CAAC;SAC3B,CAAC,CAAC;;AAEH,uBAAe,GAAG,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;KAC/C;;;;;;;;;;AAUD,QAAI,mBAAM,cAAc,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE;AAC9C,mBAAW,GAAG,SAAS,CAAC,mBAAM,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC;KAC/F,MAAM,IAAI,oCAAU,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE;AAC1C,mBAAW,GAAG,mBAAM,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,UAAC,IAAI,EAAK;AAC/D,gBAAI,mBAAM,cAAc,CAAC,IAAI,CAAC,EAAE;AAC5B,uBAAO,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC;aACjD,MAAM;AACH,uBAAO,IAAI,CAAC;aACf;SACJ,CAAC,CAAC;;;;;;KAMN;;AAED,QAAI,eAAe,EAAE;AACjB,YAAI,OAAO,CAAC,KAAK,CAAC,SAAS,EAAE;AACzB,2BAAe,GAAM,OAAO,CAAC,KAAK,CAAC,SAAS,SAAI,eAAe,AAAE,CAAC;SACrE;;AAED,gBAAQ,GAAG;AACP,qBAAS,EAAE,eAAe;SAC7B,CAAC;KACL;;AAED,QAAI,WAAW,EAAE;AACb,qBAAa,GAAG,mBAAM,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC;KACtE,MAAM;AACH,qBAAa,GAAG,mBAAM,YAAY,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;KACzD;;AAED,WAAO,aAAa,CAAC;CACxB,CAAC;;qBAEa,SAAS","file":"linkClass.js","sourcesContent":["import React from 'react';\nimport makeConfiguration from './makeConfiguration';\nimport _ from 'lodash';\n\nlet linkClass;\n\n/**\n * @param {ReactElement} element\n * @param {Object} styles CSS modules class map.\n * @param {CSSModules~Options} userConfiguration\n * @return {ReactElement}\n */\nlinkClass = (element, styles = {}, userConfiguration) => {\n let appendClassName,\n clonedElement,\n configuration,\n newChildren,\n newProps,\n styleNames;\n\n // @see https://github.com/gajus/react-css-modules/pull/30\n if (!element) {\n return element;\n }\n\n configuration = makeConfiguration(userConfiguration);\n\n styleNames = element.props.styleName;\n\n if (styleNames) {\n styleNames = styleNames.split(' ');\n styleNames = _.filter(styleNames);\n\n if (configuration.allowMultiple === false && styleNames.length > 1) {\n throw new Error(`ReactElement styleName property defines multiple module names (\"${element.props.styleName}\").`);\n }\n\n appendClassName = styleNames.map((styleName) => {\n if (styles[styleName]) {\n return styles[styleName];\n } else {\n if (configuration.errorWhenNotFound === true) {\n throw new Error(`\"${styleName}\" CSS module is undefined.`);\n }\n\n return '';\n }\n });\n\n appendClassName = appendClassName.filter((className) => {\n return className.length;\n });\n\n appendClassName = appendClassName.join(' ');\n }\n\n // element.props.children can be one of the following:\n // 'text'\n // ['text']\n // [ReactElement, 'text']\n // ReactElement\n\n // console.log(`element.props.children`, element.props.children, `React.Children.count(element.props.children)`, React.Children.count(element.props.children));\n\n if (React.isValidElement(element.props.children)) {\n newChildren = linkClass(React.Children.only(element.props.children), styles, configuration);\n } else if (_.isArray(element.props.children)) {\n newChildren = React.Children.map(element.props.children, (node) => {\n if (React.isValidElement(node)) {\n return linkClass(node, styles, configuration);\n } else {\n return node;\n }\n });\n\n // https://github.com/facebook/react/issues/4723#issuecomment-135555277\n // Forcing children into an array produces the following error:\n // Warning: A ReactFragment is an opaque type. Accessing any of its properties is deprecated. Pass it to one of the React.Children helpers.\n // newChildren = _.values(newChildren);\n }\n\n if (appendClassName) {\n if (element.props.className) {\n appendClassName = `${element.props.className} ${appendClassName}`;\n }\n\n newProps = {\n className: appendClassName\n };\n }\n\n if (newChildren) {\n clonedElement = React.cloneElement(element, newProps, newChildren);\n } else {\n clonedElement = React.cloneElement(element, newProps);\n }\n\n return clonedElement;\n};\n\nexport default linkClass;\n"],"sourceRoot":"/source/"}
\ No newline at end of file
diff --git a/dist/makeConfiguration.js b/dist/makeConfiguration.js
deleted file mode 100644
index 178a574..0000000
--- a/dist/makeConfiguration.js
+++ /dev/null
@@ -1,51 +0,0 @@
-'use strict';
-
-var _lodashCollectionForEach2 = require('lodash/collection/forEach');
-
-var _lodashCollectionForEach3 = _interopRequireDefault(_lodashCollectionForEach2);
-
-Object.defineProperty(exports, '__esModule', {
- value: true
-});
-
-function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
-
-/**
- * @typedef CSSModules~Options
- * @see {@link https://github.com/gajus/react-css-modules#options}
- * @property {boolean} allowMultiple
- * @property {boolean} errorWhenNotFound
- */
-
-/**
- * @param {CSSModules~Options} userConfiguration
- * @return {CSSModules~Options}
- */
-
-exports['default'] = function () {
- var userConfiguration = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
-
- var configuration = undefined;
-
- configuration = {
- allowMultiple: false,
- errorWhenNotFound: true
- };
-
- (0, _lodashCollectionForEach3['default'])(userConfiguration, function (value, name) {
- if (typeof configuration[name] === 'undefined') {
- throw new Error('Unknown configuration property "' + name + '".');
- }
-
- if (typeof value !== 'boolean') {
- throw new Error('"' + name + '" property value must be a boolean.');
- }
-
- configuration[name] = value;
- });
-
- return configuration;
-};
-
-module.exports = exports['default'];
-//# sourceMappingURL=makeConfiguration.js.map
diff --git a/dist/makeConfiguration.js.map b/dist/makeConfiguration.js.map
deleted file mode 100644
index dd0546d..0000000
--- a/dist/makeConfiguration.js.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"sources":["makeConfiguration.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;qBAae,YAA4B;QAA3B,iBAAiB,yDAAG,EAAE;;AAClC,QAAI,aAAa,YAAA,CAAC;;AAElB,iBAAa,GAAG;AACZ,qBAAa,EAAE,KAAK;AACpB,yBAAiB,EAAE,IAAI;KAC1B,CAAC;;AAEF,8CAAU,iBAAiB,EAAE,UAAC,KAAK,EAAE,IAAI,EAAK;AAC1C,YAAI,OAAO,aAAa,CAAC,IAAI,CAAC,KAAK,WAAW,EAAE;AAC5C,kBAAM,IAAI,KAAK,sCAAoC,IAAI,QAAK,CAAC;SAChE;;AAED,YAAI,OAAO,KAAK,KAAK,SAAS,EAAE;AAC5B,kBAAM,IAAI,KAAK,OAAK,IAAI,yCAAsC,CAAC;SAClE;;AAED,qBAAa,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;KAC/B,CAAC,CAAC;;AAEH,WAAO,aAAa,CAAC;CACxB","file":"makeConfiguration.js","sourcesContent":["import _ from 'lodash';\n\n/**\n * @typedef CSSModules~Options\n * @see {@link https://github.com/gajus/react-css-modules#options}\n * @property {boolean} allowMultiple\n * @property {boolean} errorWhenNotFound\n */\n\n/**\n * @param {CSSModules~Options} userConfiguration\n * @return {CSSModules~Options}\n */\nexport default (userConfiguration = {}) => {\n let configuration;\n\n configuration = {\n allowMultiple: false,\n errorWhenNotFound: true\n };\n\n _.forEach(userConfiguration, (value, name) => {\n if (typeof configuration[name] === 'undefined') {\n throw new Error(`Unknown configuration property \"${name}\".`);\n }\n\n if (typeof value !== 'boolean') {\n throw new Error(`\"${name}\" property value must be a boolean.`);\n }\n\n configuration[name] = value;\n });\n\n return configuration;\n};\n"],"sourceRoot":"/source/"}
\ No newline at end of file
diff --git a/dist/wrapStatelessFunction.js b/dist/wrapStatelessFunction.js
deleted file mode 100644
index 7723dfa..0000000
--- a/dist/wrapStatelessFunction.js
+++ /dev/null
@@ -1,78 +0,0 @@
-'use strict';
-
-var _lodashLangIsObject2 = require('lodash/lang/isObject');
-
-var _lodashLangIsObject3 = _interopRequireDefault(_lodashLangIsObject2);
-
-var _lodashObjectAssign2 = require('lodash/object/assign');
-
-var _lodashObjectAssign3 = _interopRequireDefault(_lodashObjectAssign2);
-
-Object.defineProperty(exports, '__esModule', {
- value: true
-});
-
-var _linkClass = require('./linkClass');
-
-var _linkClass2 = _interopRequireDefault(_linkClass);
-
-var _react = require('react');
-
-var _react2 = _interopRequireDefault(_react);
-
-function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
-
-var wrapStatelessFunction = undefined;
-
-/**
- * @see https://facebook.github.io/react/blog/2015/09/10/react-v0.14-rc1.html#stateless-function-components
- * @param {Function} Component
- * @param {Object} defaultStyles
- * @param {Object} options
- * @returns {Function}
- */
-wrapStatelessFunction = function (Component, defaultStyles, options) {
- var WrappedComponent = undefined;
-
- WrappedComponent = function () {
- for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
- args[_key - 1] = arguments[_key];
- }
-
- var props = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
-
- var renderResult = undefined,
- styles = undefined,
- useProps = undefined;
-
- if (props.styles) {
- useProps = props;
- styles = props.styles;
- } else if ((0, _lodashLangIsObject3['default'])(defaultStyles)) {
- useProps = (0, _lodashObjectAssign3['default'])({}, props, {
- styles: defaultStyles
- });
-
- styles = defaultStyles;
- } else {
- useProps = props;
- styles = {};
- }
-
- renderResult = Component.apply(undefined, [useProps].concat(args));
-
- if (renderResult) {
- return (0, _linkClass2['default'])(renderResult, styles, options);
- }
-
- return _react2['default'].createElement('noscript');
- };
-
- (0, _lodashObjectAssign3['default'])(WrappedComponent, Component);
-
- return WrappedComponent;
-};
-
-exports['default'] = wrapStatelessFunction;
-module.exports = exports['default'];
-//# sourceMappingURL=wrapStatelessFunction.js.map
diff --git a/dist/wrapStatelessFunction.js.map b/dist/wrapStatelessFunction.js.map
deleted file mode 100644
index 78ef1e4..0000000
--- a/dist/wrapStatelessFunction.js.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"sources":["wrapStatelessFunction.js"],"names":[],"mappings":";;;;;;;;;;;;;;yBAAsB,aAAa;;;;qBACjB,OAAO;;;;;;AAGzB,IAAI,qBAAqB,YAAA,CAAC;;;;;;;;;AAS1B,qBAAqB,GAAG,UAAC,SAAS,EAAE,aAAa,EAAE,OAAO,EAAK;AAC3D,QAAI,gBAAgB,YAAA,CAAC;;AAErB,oBAAgB,GAAG,YAAyB;0CAAT,IAAI;AAAJ,gBAAI;;;YAAnB,KAAK,yDAAG,EAAE;;AAC1B,YAAI,YAAY,YAAA;YACZ,MAAM,YAAA;YACN,QAAQ,YAAA,CAAC;;AAEb,YAAI,KAAK,CAAC,MAAM,EAAE;AACd,oBAAQ,GAAG,KAAK,CAAC;AACjB,kBAAM,GAAG,KAAK,CAAC,MAAM,CAAC;SACzB,MAAM,IAAI,qCAAW,aAAa,CAAC,EAAE;AAClC,oBAAQ,GAAG,qCAAS,EAAE,EAAE,KAAK,EAAE;AAC3B,sBAAM,EAAE,aAAa;aACxB,CAAC,CAAC;;AAEH,kBAAM,GAAG,aAAa,CAAC;SAC1B,MAAM;AACH,oBAAQ,GAAG,KAAK,CAAC;AACjB,kBAAM,GAAG,EAAE,CAAC;SACf;;AAED,oBAAY,GAAG,SAAS,mBAAC,QAAQ,SAAK,IAAI,EAAC,CAAC;;AAE5C,YAAI,YAAY,EAAE;AACd,mBAAO,4BAAU,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;SACnD;;AAED,eAAO,mBAAM,aAAa,CAAC,UAAU,CAAC,CAAC;KAC1C,CAAC;;AAEF,yCAAS,gBAAgB,EAAE,SAAS,CAAC,CAAC;;AAEtC,WAAO,gBAAgB,CAAC;CAC3B,CAAC;;qBAEa,qBAAqB","file":"wrapStatelessFunction.js","sourcesContent":["import linkClass from './linkClass';\nimport React from 'react';\nimport _ from 'lodash';\n\nlet wrapStatelessFunction;\n\n/**\n * @see https://facebook.github.io/react/blog/2015/09/10/react-v0.14-rc1.html#stateless-function-components\n * @param {Function} Component\n * @param {Object} defaultStyles\n * @param {Object} options\n * @returns {Function}\n */\nwrapStatelessFunction = (Component, defaultStyles, options) => {\n let WrappedComponent;\n\n WrappedComponent = (props = {}, ...args) => {\n let renderResult,\n styles,\n useProps;\n\n if (props.styles) {\n useProps = props;\n styles = props.styles;\n } else if (_.isObject(defaultStyles)) {\n useProps = _.assign({}, props, {\n styles: defaultStyles\n });\n\n styles = defaultStyles;\n } else {\n useProps = props;\n styles = {};\n }\n\n renderResult = Component(useProps, ...args);\n\n if (renderResult) {\n return linkClass(renderResult, styles, options);\n }\n\n return React.createElement('noscript');\n };\n\n _.assign(WrappedComponent, Component);\n\n return WrappedComponent;\n};\n\nexport default wrapStatelessFunction;\n"],"sourceRoot":"/source/"}
\ No newline at end of file
diff --git a/package.json b/package.json
index ca140d3..00e1ffa 100644
--- a/package.json
+++ b/package.json
@@ -12,28 +12,44 @@
"css",
"modules"
],
- "version": "3.6.1",
+ "version": "4.3.0",
"author": {
"name": "Gajus Kuizinas",
- "email": "gk@anuary.com",
+ "email": "gajus@gajus.com",
"url": "http://gajus.com"
},
"license": "BSD-3-Clause",
"dependencies": {
- "lodash": "^3.10.1"
+ "hoist-non-react-statics": "^2.5.5",
+ "lodash": "^4.16.6",
+ "object-unfreeze": "^1.1.0"
},
"devDependencies": {
- "chai": "^3.4.0",
- "coveralls": "^2.11.4",
- "jsdom": "^7.0.2",
- "pragmatist": "^1.1.5",
- "react": "^0.14.0",
- "react-addons-test-utils": "^0.14.0"
+ "babel-cli": "^6.18.0",
+ "babel-plugin-add-module-exports": "^0.2.1",
+ "babel-plugin-lodash": "^3.2.9",
+ "babel-plugin-transform-proto-to-assign": "^6.9.0",
+ "babel-preset-es2015": "^6.18.0",
+ "babel-preset-react": "^6.16.0",
+ "babel-preset-stage-0": "^6.16.0",
+ "babel-register": "^6.18.0",
+ "chai": "^4.0.0-canary.1",
+ "chai-spies": "^0.7.1",
+ "eslint": "^3.10.0",
+ "eslint-config-canonical": "^5.5.0",
+ "husky": "^0.11.9",
+ "jsdom": "^9.8.3",
+ "mocha": "^3.1.2",
+ "react": "^15.4.0-rc.4",
+ "react-addons-shallow-compare": "^15.4.0-rc.4",
+ "react-addons-test-utils": "^15.4.0-rc.4",
+ "react-dom": "^15.4.0-rc.4",
+ "semantic-release": "^6.3.2"
},
"scripts": {
- "pragmatist": "node ./node_modules/.bin/pragmatist",
- "lint": "npm run pragmatist lint",
- "build": "npm run pragmatist build",
- "test": "npm run pragmatist test && cat ./coverage/lcov.info | ./node_modules/.bin/coveralls"
+ "lint": "eslint ./src ./tests",
+ "test": "NODE_ENV=development mocha --compilers js:babel-register ./tests/**/*.js && npm run lint && npm run build",
+ "build": "NODE_ENV=production babel ./src --out-dir ./dist",
+ "precommit": "npm run test"
}
}
diff --git a/src/SimpleMap.js b/src/SimpleMap.js
new file mode 100644
index 0000000..7cf618b
--- /dev/null
+++ b/src/SimpleMap.js
@@ -0,0 +1,21 @@
+export default class {
+ constructor () {
+ this.size = 0;
+ this.keys = [];
+ this.values = [];
+ }
+
+ get (key) {
+ const index = this.keys.indexOf(key);
+
+ return this.values[index];
+ }
+
+ set (key, value) {
+ this.keys.push(key);
+ this.values.push(value);
+ this.size = this.keys.length;
+
+ return value;
+ }
+}
diff --git a/src/extendReactClass.js b/src/extendReactClass.js
index 4ff5114..4741bea 100644
--- a/src/extendReactClass.js
+++ b/src/extendReactClass.js
@@ -1,8 +1,10 @@
-import linkClass from './linkClass';
-import React from 'react';
-import _ from 'lodash';
+/* eslint-disable react/prop-types */
-let extendReactClass;
+import _ from 'lodash';
+import React from 'react';
+import hoistNonReactStatics from 'hoist-non-react-statics';
+import linkClass from './linkClass';
+import renderNothing from './renderNothing';
/**
* @param {ReactClass} Component
@@ -10,33 +12,63 @@ let extendReactClass;
* @param {Object} options
* @returns {ReactClass}
*/
-extendReactClass = (Component, defaultStyles, options) => {
- return class extends Component {
- render () {
- let renderResult,
- styles;
-
- if (this.props.styles) {
- styles = this.props.styles;
- } else if (_.isObject(defaultStyles)) {
- this.props = _.assign({}, this.props, {
- styles: defaultStyles
- });
-
- styles = defaultStyles;
- } else {
- styles = {};
- }
-
- renderResult = super.render();
-
- if (renderResult) {
- return linkClass(renderResult, styles, options);
- }
-
- return React.createElement('noscript');
+export default (Component: Object, defaultStyles: Object, options: Object) => {
+ const WrappedComponent = class extends Component {
+ render () {
+ let styles;
+
+ const hasDefaultstyles = _.isObject(defaultStyles);
+
+ let renderResult;
+
+ if (this.props.styles || hasDefaultstyles) {
+ const props = Object.assign({}, this.props);
+
+ if (props.styles) {
+ styles = props.styles;
+ } else if (hasDefaultstyles) {
+ styles = defaultStyles;
+ delete props.styles;
}
- };
-};
-export default extendReactClass;
+ Object.defineProperty(props, 'styles', {
+ configurable: true,
+ enumerable: false,
+ value: styles,
+ writable: false
+ });
+
+ const originalProps = this.props;
+
+ let renderIsSuccessful = false;
+
+ try {
+ this.props = props;
+
+ renderResult = super.render();
+
+ renderIsSuccessful = true;
+ } finally {
+ this.props = originalProps;
+ }
+
+ // @see https://github.com/facebook/react/issues/14224
+ if (!renderIsSuccessful) {
+ renderResult = super.render();
+ }
+ } else {
+ styles = {};
+
+ renderResult = super.render();
+ }
+
+ if (renderResult) {
+ return linkClass(renderResult, styles, options);
+ }
+
+ return renderNothing(React.version);
+ }
+ };
+
+ return hoistNonReactStatics(WrappedComponent, Component);
+};
diff --git a/src/generateAppendClassName.js b/src/generateAppendClassName.js
new file mode 100644
index 0000000..127aa2a
--- /dev/null
+++ b/src/generateAppendClassName.js
@@ -0,0 +1,49 @@
+import SimpleMap from './SimpleMap';
+
+const CustomMap = typeof Map === 'undefined' ? SimpleMap : Map;
+
+const stylesIndex = new CustomMap();
+
+export default (styles, styleNames: Array, handleNotFoundStyleName: "throw" | "log" | "ignore"): string => {
+ let appendClassName;
+ let stylesIndexMap;
+
+ stylesIndexMap = stylesIndex.get(styles);
+
+ if (stylesIndexMap) {
+ const styleNameIndex = stylesIndexMap.get(styleNames);
+
+ if (styleNameIndex) {
+ return styleNameIndex;
+ }
+ } else {
+ stylesIndexMap = new CustomMap();
+ stylesIndex.set(styles, new CustomMap());
+ }
+
+ appendClassName = '';
+
+ for (const styleName in styleNames) {
+ if (styleNames.hasOwnProperty(styleName)) {
+ const className = styles[styleNames[styleName]];
+
+ if (className) {
+ appendClassName += ' ' + className;
+ } else {
+ if (handleNotFoundStyleName === 'throw') {
+ throw new Error('"' + styleNames[styleName] + '" CSS module is undefined.');
+ }
+ if (handleNotFoundStyleName === 'log') {
+ // eslint-disable-next-line no-console
+ console.warn('"' + styleNames[styleName] + '" CSS module is undefined.');
+ }
+ }
+ }
+ }
+
+ appendClassName = appendClassName.trim();
+
+ stylesIndexMap.set(styleNames, appendClassName);
+
+ return appendClassName;
+};
diff --git a/src/index.js b/src/index.js
index 9f1ff1e..b913b29 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,63 +1,56 @@
+import _ from 'lodash';
import extendReactClass from './extendReactClass';
import wrapStatelessFunction from './wrapStatelessFunction';
+import makeConfiguration from './makeConfiguration';
-let decoratorConstructor,
- functionConstructor,
- isReactComponent;
+/**
+ * @see https://github.com/gajus/react-css-modules#options
+ */
+type TypeOptions = {};
/**
* Determines if the given object has the signature of a class that inherits React.Component.
- *
- * @param {*} Component
- * @return {boolean}
*/
-isReactComponent = (Component) => {
- return 'prototype' in Component && typeof Component.prototype.render === 'function';
+const isReactComponent = (maybeReactComponent: any): boolean => {
+ return 'prototype' in maybeReactComponent && _.isFunction(maybeReactComponent.prototype.render);
};
/**
* When used as a function.
- *
- * @param {Function} Component
- * @param {Object} defaultStyles CSS Modules class map.
- * @param {Object} options {@link https://github.com/gajus/react-css-modules#options}
- * @return {Function}
*/
-functionConstructor = (Component, defaultStyles, options) => {
- let decoratedClass;
-
- if (isReactComponent(Component)) {
- decoratedClass = extendReactClass(Component, defaultStyles, options);
- } else {
- decoratedClass = wrapStatelessFunction(Component, defaultStyles, options);
- }
-
- if (Component.displayName) {
- decoratedClass.displayName = Component.displayName;
- } else {
- decoratedClass.displayName = Component.name;
- }
-
- return decoratedClass;
+const functionConstructor = (Component: Function, defaultStyles: Object, options: TypeOptions): Function => {
+ let decoratedClass;
+
+ const configuration = makeConfiguration(options);
+
+ if (isReactComponent(Component)) {
+ decoratedClass = extendReactClass(Component, defaultStyles, configuration);
+ } else {
+ decoratedClass = wrapStatelessFunction(Component, defaultStyles, configuration);
+ }
+
+ if (Component.displayName) {
+ decoratedClass.displayName = Component.displayName;
+ } else {
+ decoratedClass.displayName = Component.name;
+ }
+
+ return decoratedClass;
};
/**
* When used as a ES7 decorator.
- *
- * @param {Object} defaultStyles CSS Modules class map.
- * @param {Object} options {@link https://github.com/gajus/react-css-modules#options}
- * @return {Function}
*/
-decoratorConstructor = (defaultStyles, options) => {
- return (Component) => {
- return functionConstructor(Component, defaultStyles, options);
- };
+const decoratorConstructor = (defaultStyles: Object, options: TypeOptions): Function => {
+ return (Component: Function) => {
+ return functionConstructor(Component, defaultStyles, options);
+ };
};
export default (...args) => {
- if (typeof args[0] === 'function') {
- return functionConstructor(args[0], args[1], args[2]);
- } else {
- return decoratorConstructor(args[0], args[1]);
- }
+ if (_.isFunction(args[0])) {
+ return functionConstructor(args[0], args[1], args[2]);
+ } else {
+ return decoratorConstructor(args[0], args[1]);
+ }
};
diff --git a/src/isIterable.js b/src/isIterable.js
index 2a5b74c..67d0a94 100644
--- a/src/isIterable.js
+++ b/src/isIterable.js
@@ -1,10 +1,24 @@
-import { isObject } from 'lodash';
+import _ from 'lodash';
-const ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator;
+const ITERATOR_SYMBOL = typeof Symbol !== 'undefined' && _.isFunction(Symbol) && Symbol.iterator;
const OLD_ITERATOR_SYMBOL = '@@iterator';
-export default function isIterable(obj) {
- return isObject(obj) &&
- typeof ((ITERATOR_SYMBOL && obj[ITERATOR_SYMBOL])
- || obj[OLD_ITERATOR_SYMBOL]) === 'function';
-}
+/**
+ * @see https://github.com/lodash/lodash/issues/1668
+ * @see https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Iteration_protocols
+ */
+export default (maybeIterable: any): boolean => {
+ let iterator;
+
+ if (!_.isObject(maybeIterable)) {
+ return false;
+ }
+
+ if (ITERATOR_SYMBOL) {
+ iterator = maybeIterable[ITERATOR_SYMBOL];
+ } else {
+ iterator = maybeIterable[OLD_ITERATOR_SYMBOL];
+ }
+
+ return _.isFunction(iterator);
+};
diff --git a/src/linkClass.js b/src/linkClass.js
index 58dd47a..4ed3975 100644
--- a/src/linkClass.js
+++ b/src/linkClass.js
@@ -1,105 +1,106 @@
-import React from 'react';
-import makeConfiguration from './makeConfiguration';
-import isIterable from './isIterable';
import _ from 'lodash';
-
-let linkClass;
-
-/**
- * @param {ReactElement} element
- * @param {Object} styles CSS modules class map.
- * @param {CSSModules~Options} userConfiguration
- * @return {ReactElement}
- */
-linkClass = (element, styles = {}, userConfiguration) => {
- let appendClassName,
- children,
- clonedElement,
- configuration,
- newChildren,
- newProps,
- styleNames;
-
- // @see https://github.com/gajus/react-css-modules/pull/30
- if (!element) {
- return element;
+import React, {
+ ReactElement
+} from 'react';
+import objectUnfreeze from 'object-unfreeze';
+import isIterable from './isIterable';
+import parseStyleName from './parseStyleName';
+import generateAppendClassName from './generateAppendClassName';
+
+const linkArray = (array: Array, styles: Object, configuration: Object) => {
+ _.forEach(array, (value, index) => {
+ if (React.isValidElement(value)) {
+ // eslint-disable-next-line no-use-before-define
+ array[index] = linkElement(React.Children.only(value), styles, configuration);
+ } else if (_.isArray(value)) {
+ const unfreezedValue = Object.isFrozen(value) ? objectUnfreeze(value) : value;
+
+ array[index] = linkArray(unfreezedValue, styles, configuration);
}
+ });
- configuration = makeConfiguration(userConfiguration);
-
- styleNames = element.props.styleName;
-
- if (styleNames) {
- styleNames = styleNames.split(' ');
- styleNames = _.filter(styleNames);
-
- if (configuration.allowMultiple === false && styleNames.length > 1) {
- throw new Error(`ReactElement styleName property defines multiple module names ("${element.props.styleName}").`);
- }
-
- appendClassName = styleNames.map((styleName) => {
- if (styles[styleName]) {
- return styles[styleName];
- } else {
- if (configuration.errorWhenNotFound === true) {
- throw new Error(`"${styleName}" CSS module is undefined.`);
- }
-
- return '';
- }
- });
-
- appendClassName = appendClassName.filter((className) => {
- return className.length;
- });
+ return array;
+};
- appendClassName = appendClassName.join(' ');
+const linkElement = (element: ReactElement, styles: Object, configuration: Object): ReactElement => {
+ let appendClassName;
+ let elementShallowCopy;
+
+ elementShallowCopy = element;
+
+ if (Array.isArray(elementShallowCopy)) {
+ return elementShallowCopy.map((arrayElement) => {
+ return linkElement(arrayElement, styles, configuration);
+ });
+ }
+
+ const elementIsFrozen = Object.isFrozen && Object.isFrozen(elementShallowCopy);
+ const propsFrozen = Object.isFrozen && Object.isFrozen(elementShallowCopy.props);
+ const propsNotExtensible = Object.isExtensible && !Object.isExtensible(elementShallowCopy.props);
+
+ if (elementIsFrozen) {
+ // https://github.com/facebook/react/blob/v0.13.3/src/classic/element/ReactElement.js#L131
+ elementShallowCopy = objectUnfreeze(elementShallowCopy);
+ elementShallowCopy.props = objectUnfreeze(elementShallowCopy.props);
+ } else if (propsFrozen || propsNotExtensible) {
+ elementShallowCopy.props = objectUnfreeze(elementShallowCopy.props);
+ }
+
+ const styleNames = parseStyleName(elementShallowCopy.props.styleName || '', configuration.allowMultiple);
+ const {children, ...restProps} = elementShallowCopy.props;
+
+ if (React.isValidElement(children)) {
+ elementShallowCopy.props.children = linkElement(React.Children.only(children), styles, configuration);
+ } else if (_.isArray(children) || isIterable(children)) {
+ elementShallowCopy.props.children = linkArray(objectUnfreeze(children), styles, configuration);
+ }
+
+ _.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] = linkArray(propValue, styles, configuration);
}
+ });
- // element.props.children can be one of the following:
- // 'text'
- // ['text']
- // [ReactElement, 'text']
- // ReactElement
-
- // console.log(`element.props.children`, element.props.children, `React.Children.count(element.props.children)`, React.Children.count(element.props.children));
-
- children = element.props.children;
-
- if (React.isValidElement(children)) {
- newChildren = linkClass(React.Children.only(children), styles, configuration);
- } else if (_.isArray(children) || isIterable(children)) {
- newChildren = React.Children.map(children, (node) => {
- if (React.isValidElement(node)) {
- return linkClass(node, styles, configuration);
- } else {
- return node;
- }
- });
-
- // https://github.com/facebook/react/issues/4723#issuecomment-135555277
- // Forcing children into an array produces the following error:
- // Warning: A ReactFragment is an opaque type. Accessing any of its properties is deprecated. Pass it to one of the React.Children helpers.
- // newChildren = _.values(newChildren);
- }
+ if (styleNames.length) {
+ appendClassName = generateAppendClassName(styles, styleNames, configuration.handleNotFoundStyleName);
if (appendClassName) {
- if (element.props.className) {
- appendClassName = `${element.props.className} ${appendClassName}`;
- }
+ if (elementShallowCopy.props.className) {
+ appendClassName = elementShallowCopy.props.className + ' ' + appendClassName;
+ }
- newProps = {
- className: appendClassName
- };
+ elementShallowCopy.props.className = appendClassName;
}
+ }
- if (newChildren) {
- clonedElement = React.cloneElement(element, newProps, newChildren);
- } else {
- clonedElement = React.cloneElement(element, newProps);
- }
+ delete elementShallowCopy.props.styleName;
+
+ if (elementIsFrozen) {
+ Object.freeze(elementShallowCopy.props);
+ Object.freeze(elementShallowCopy);
+ } else if (propsFrozen) {
+ Object.freeze(elementShallowCopy.props);
+ }
+
+ if (propsNotExtensible) {
+ Object.preventExtensions(elementShallowCopy.props);
+ }
- return clonedElement;
+ return elementShallowCopy;
};
-export default linkClass;
+/**
+ * @param {ReactElement} element
+ * @param {Object} styles CSS modules class map.
+ * @param {CSSModules~Options} configuration
+ */
+export default (element: ReactElement, styles = {}, configuration = {}): ReactElement => {
+ // @see https://github.com/gajus/react-css-modules/pull/30
+ if (!_.isObject(element)) {
+ return element;
+ }
+
+ return linkElement(element, styles, configuration);
+};
diff --git a/src/makeConfiguration.js b/src/makeConfiguration.js
index dae8ca5..cc3169c 100644
--- a/src/makeConfiguration.js
+++ b/src/makeConfiguration.js
@@ -4,32 +4,34 @@ import _ from 'lodash';
* @typedef CSSModules~Options
* @see {@link https://github.com/gajus/react-css-modules#options}
* @property {boolean} allowMultiple
- * @property {boolean} errorWhenNotFound
+ * @property {string} handleNotFoundStyleName
*/
/**
* @param {CSSModules~Options} userConfiguration
- * @return {CSSModules~Options}
+ * @returns {CSSModules~Options}
*/
export default (userConfiguration = {}) => {
- let configuration;
+ const configuration = {
+ allowMultiple: false,
+ handleNotFoundStyleName: 'throw'
+ };
- configuration = {
- allowMultiple: false,
- errorWhenNotFound: true
- };
+ _.forEach(userConfiguration, (value, name) => {
+ if (_.isUndefined(configuration[name])) {
+ throw new Error('Unknown configuration property "' + name + '".');
+ }
- _.forEach(userConfiguration, (value, name) => {
- if (typeof configuration[name] === 'undefined') {
- throw new Error(`Unknown configuration property "${name}".`);
- }
+ if (name === 'allowMultiple' && !_.isBoolean(value)) {
+ throw new Error('"allowMultiple" property value must be a boolean.');
+ }
- if (typeof value !== 'boolean') {
- throw new Error(`"${name}" property value must be a boolean.`);
- }
+ if (name === 'handleNotFoundStyleName' && !_.includes(['throw', 'log', 'ignore'], value)) {
+ throw new Error('"handleNotFoundStyleName" property value must be "throw", "log" or "ignore".');
+ }
- configuration[name] = value;
- });
+ configuration[name] = value;
+ });
- return configuration;
+ return configuration;
};
diff --git a/src/parseStyleName.js b/src/parseStyleName.js
new file mode 100644
index 0000000..937b2d6
--- /dev/null
+++ b/src/parseStyleName.js
@@ -0,0 +1,22 @@
+import _ from 'lodash';
+
+const styleNameIndex = {};
+
+export default (styleNamePropertyValue: string, allowMultiple: boolean): Array => {
+ let styleNames;
+
+ if (styleNameIndex[styleNamePropertyValue]) {
+ styleNames = styleNameIndex[styleNamePropertyValue];
+ } else {
+ styleNames = _.trim(styleNamePropertyValue).split(/\s+/);
+ styleNames = _.filter(styleNames);
+
+ styleNameIndex[styleNamePropertyValue] = styleNames;
+ }
+
+ if (allowMultiple === false && styleNames.length > 1) {
+ throw new Error('ReactElement styleName property defines multiple module names ("' + styleNamePropertyValue + '").');
+ }
+
+ return styleNames;
+};
diff --git a/src/renderNothing.js b/src/renderNothing.js
new file mode 100644
index 0000000..93675fe
--- /dev/null
+++ b/src/renderNothing.js
@@ -0,0 +1,7 @@
+import React from 'react';
+
+export default function (version) {
+ const major = version.split('.')[0];
+
+ return parseInt(major, 10) < 15 ? React.createElement('noscript') : null;
+}
diff --git a/src/wrapStatelessFunction.js b/src/wrapStatelessFunction.js
index cacf1e4..b6b9162 100644
--- a/src/wrapStatelessFunction.js
+++ b/src/wrapStatelessFunction.js
@@ -1,50 +1,49 @@
-import linkClass from './linkClass';
-import React from 'react';
-import _ from 'lodash';
+/* eslint-disable react/prop-types */
-let wrapStatelessFunction;
+import _ from 'lodash';
+import React from 'react';
+import linkClass from './linkClass';
+import renderNothing from './renderNothing';
/**
* @see https://facebook.github.io/react/blog/2015/09/10/react-v0.14-rc1.html#stateless-function-components
- * @param {Function} Component
- * @param {Object} defaultStyles
- * @param {Object} options
- * @returns {Function}
*/
-wrapStatelessFunction = (Component, defaultStyles, options) => {
- let WrappedComponent;
-
- WrappedComponent = (props = {}, ...args) => {
- let renderResult,
- styles,
- useProps;
-
- if (props.styles) {
- useProps = props;
- styles = props.styles;
- } else if (_.isObject(defaultStyles)) {
- useProps = _.assign({}, props, {
- styles: defaultStyles
- });
-
- styles = defaultStyles;
- } else {
- useProps = props;
- styles = {};
- }
-
- renderResult = Component(useProps, ...args);
-
- if (renderResult) {
- return linkClass(renderResult, styles, options);
- }
-
- return React.createElement('noscript');
- };
-
- _.assign(WrappedComponent, Component);
-
- return WrappedComponent;
+export default (Component: Function, defaultStyles: Object, options: Object): Function => {
+ const WrappedComponent = (props = {}, ...args) => {
+ let styles;
+ let useProps;
+ const hasDefaultstyles = _.isObject(defaultStyles);
+
+ if (props.styles || hasDefaultstyles) {
+ useProps = Object.assign({}, props);
+
+ if (props.styles) {
+ styles = props.styles;
+ } else {
+ styles = defaultStyles;
+ }
+
+ Object.defineProperty(useProps, 'styles', {
+ configurable: true,
+ enumerable: false,
+ value: styles,
+ writable: false
+ });
+ } else {
+ useProps = props;
+ styles = {};
+ }
+
+ const renderResult = Component(useProps, ...args);
+
+ if (renderResult) {
+ return linkClass(renderResult, styles, options);
+ }
+
+ return renderNothing(React.version);
+ };
+
+ _.assign(WrappedComponent, Component);
+
+ return WrappedComponent;
};
-
-export default wrapStatelessFunction;
diff --git a/tests/SimpleMap.js b/tests/SimpleMap.js
new file mode 100644
index 0000000..08ca888
--- /dev/null
+++ b/tests/SimpleMap.js
@@ -0,0 +1,38 @@
+import {
+ expect
+} from 'chai';
+import SimpleMap from './../src/SimpleMap';
+
+describe('SimpleMap', () => {
+ context('simple map with primitive or object as keys', () => {
+ const values = [
+ [1, 'something'],
+ ['1', 'somethingElse'],
+ [{}, []],
+ [null, null]
+ ];
+
+ let map;
+
+ beforeEach(() => {
+ map = new SimpleMap();
+ });
+
+ it('should set', () => {
+ values.forEach(([key, value]) => {
+ map.set(key, value);
+ });
+ expect(map.size).to.equal(values.length);
+ });
+
+ it('should get', () => {
+ values.forEach(([key, value]) => {
+ map.set(key, value);
+ });
+
+ values.forEach(([key, value]) => {
+ expect(map.get(key)).to.equal(value);
+ });
+ });
+ });
+});
diff --git a/tests/extendReactClass.js b/tests/extendReactClass.js
index 2b32174..01733ee 100644
--- a/tests/extendReactClass.js
+++ b/tests/extendReactClass.js
@@ -1,89 +1,167 @@
-/* eslint-disable max-nested-callbacks */
+/* eslint-disable max-nested-callbacks, react/prefer-stateless-function, react/prop-types, react/no-multi-comp, class-methods-use-this */
import {
expect
} from 'chai';
-
import React from 'react';
import TestUtils from 'react-addons-test-utils';
+import shallowCompare from 'react-addons-shallow-compare';
import jsdom from 'jsdom';
import extendReactClass from './../src/extendReactClass';
describe('extendReactClass', () => {
- beforeEach(() => {
- global.document = jsdom.jsdom(`
-
-
-
-
-
-
-
- `);
-
- global.window = document.defaultView;
+ beforeEach(() => {
+ global.document = jsdom.jsdom('');
+
+ global.window = document.defaultView;
+ });
+ context('using default styles', () => {
+ it('exposes styles through this.props.styles property', (done) => {
+ let Component;
+
+ const styles = {
+ foo: 'foo-1'
+ };
+
+ Component = class extends React.Component {
+ render () {
+ expect(this.props.styles).to.equal(styles);
+ done();
+ }
+ };
+
+ Component = extendReactClass(Component, styles);
+
+ TestUtils.renderIntoDocument();
+ });
+ it('exposes non-enumerable styles property', (done) => {
+ let Component;
+
+ const styles = {
+ foo: 'foo-1'
+ };
+
+ Component = class extends React.Component {
+ render () {
+ expect(this.props.propertyIsEnumerable('styles')).to.equal(false);
+ done();
+ }
+ };
+
+ Component = extendReactClass(Component, styles);
+
+ TestUtils.renderIntoDocument();
+ });
+ it('does not affect the other instance properties', (done) => {
+ let Component;
+
+ Component = class extends React.Component {
+ render () {
+ expect(this.props.bar).to.equal('baz');
+ done();
+ }
+ };
+
+ const styles = {
+ foo: 'foo-1'
+ };
+
+ Component = extendReactClass(Component, styles);
+
+ TestUtils.renderIntoDocument();
});
+ it('does not affect pure-render logic', (done) => {
+ let Component;
+ let rendered;
+
+ rendered = false;
+
+ const styles = {
+ foo: 'foo-1'
+ };
+
+ Component = class extends React.Component {
+ shouldComponentUpdate (newProps) {
+ if (rendered) {
+ expect(shallowCompare(this.props, newProps)).to.equal(true);
+
+ done();
+ }
+
+ return true;
+ }
+
+ render () {
+ rendered = true;
+ }
+ };
+
+ Component = extendReactClass(Component, styles);
- context('using default styles', () => {
- it('exposes styles through styles property', (done) => {
- let Component,
- styles;
-
- Component = class extends React.Component {
- render () {
- expect(this.props.styles).to.equal(styles);
- done();
- }
- };
-
- styles = {
- foo: 'foo-1'
- };
-
- Component = extendReactClass(Component, styles);
-
- TestUtils.renderIntoDocument();
- });
- it('does not affect the other instance properties', (done) => {
- let Component,
- styles;
-
- Component = class extends React.Component {
- render () {
- expect(this.props.bar).to.equal('baz');
- done();
- }
- };
-
- styles = {
- foo: 'foo-1'
- };
-
- Component = extendReactClass(Component, styles);
-
- TestUtils.renderIntoDocument();
- });
+ const instance = TestUtils.renderIntoDocument();
+
+ // trigger shouldComponentUpdate
+ instance.setState({});
+ });
+ });
+ context('overwriting default styles using "styles" property of the extended component', () => {
+ it('overwrites default styles', (done) => {
+ let Component;
+
+ const styles = {
+ foo: 'foo-1'
+ };
+
+ Component = class extends React.Component {
+ render () {
+ expect(this.props.styles).to.equal(styles);
+ done();
+ }
+ };
+
+ Component = extendReactClass(Component, {
+ bar: 'bar-0',
+ foo: 'foo-0'
+ });
+
+ TestUtils.renderIntoDocument();
});
+ });
+ context('rendering Component that returns null', () => {
+ it('generates null', () => {
+ let Component;
+
+ const shallowRenderer = TestUtils.createRenderer();
+
+ Component = class extends React.Component {
+ render () {
+ return null;
+ }
+ };
- context('using explicit styles', () => {
- it('exposes styles through styles property', (done) => {
- let Component,
- styles;
+ Component = extendReactClass(Component);
- Component = class extends React.Component {
- render () {
- expect(this.props.styles).to.equal(styles);
- done();
- }
- };
+ shallowRenderer.render();
+
+ const component = shallowRenderer.getRenderOutput();
+
+ expect(component).to.equal(null);
+ });
+ });
+ context('target component have static properties', () => {
+ it('hoists static properties', () => {
+ const Component = class extends React.Component {
+ static foo = 'FOO';
- styles = {
- foo: 'foo-1'
- };
+ render () {
+ return null;
+ }
+ };
- Component = extendReactClass(Component);
+ const WrappedComponent = extendReactClass(Component);
- TestUtils.renderIntoDocument();
- });
+ expect(Component.foo).to.equal('FOO');
+ expect(WrappedComponent.foo).to.equal(Component.foo);
});
+ });
});
diff --git a/tests/linkClass.js b/tests/linkClass.js
index e0778a8..cf7c289 100644
--- a/tests/linkClass.js
+++ b/tests/linkClass.js
@@ -1,261 +1,483 @@
-/* eslint-disable max-nested-callbacks */
+/* eslint-disable max-nested-callbacks, react/prefer-stateless-function, class-methods-use-this, no-console, no-unused-expressions */
-import {
+import chai, {
expect
} from 'chai';
-
+import spies from 'chai-spies';
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import jsdom from 'jsdom';
import linkClass from './../src/linkClass';
+chai.use(spies);
+
describe('linkClass', () => {
- context('ReactElement does not define styleName', () => {
- it('does not affect element properties', () => {
- expect(linkClass()).to.deep.equal();
+ context('ReactElement does not define styleName', () => {
+ it('does not affect element properties', () => {
+ expect(linkClass()).to.deep.equal();
+ });
+
+ it('does not affect element properties with a single element child', () => {
+ 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
);
+ });
+
+ it('does not affect the className', () => {
+ expect(linkClass()).to.deep.equal();
+ });
+
+ xit('does not affect element with a single children when that children is contained in an array', () => {
+ const subject = React.createElement('div', null, [
+ React.createElement('p')
+ ]);
+ const outcome = React.createElement('div', null, [
+ React.createElement('p')
+ ]);
+
+ expect(linkClass(subject)).to.deep.equal(outcome);
+ });
+
+ xit('does not affect element with multiple children', () => {
+ // Using array instead of object causes the following error:
+ // Warning: Each child in an array or iterator should have a unique "key" prop.
+ // Check the render method of _class. See https://fb.me/react-warning-keys for more information.
+ // @see https://github.com/facebook/react/issues/4723#issuecomment-135555277
+ // expect(linkClass()).to.deep.equal();
+
+ const subject = React.createElement('div', null, [
+ React.createElement('p'),
+ React.createElement('p')
+ ]);
+ const outcome = React.createElement('div', null, [
+ React.createElement('p'),
+ React.createElement('p')
+ ]);
+
+ expect(linkClass(subject)).to.deep.equal(outcome);
+ });
+ });
+
+ context('called with null instead of ReactElement', () => {
+ it('returns null', () => {
+ const subject = linkClass(null);
+
+ expect(subject).to.equal(null);
+ });
+ });
+
+ context('styleName matches an existing CSS module', () => {
+ context('when a descendant element has styleName', () => {
+ it('assigns a generated className', () => {
+ let subject;
+
+ subject = ;
+
+ subject = linkClass(subject, {
+ foo: 'foo-1'
});
- it('does not affect element properties with a single element child', () => {
- expect(linkClass()).to.deep.equal();
+ 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'
});
- it('does not affect element properties with a single text child', () => {
- expect(linkClass(test
)).to.deep.equal(test
);
+ 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;
+
+ subject = ;
+
+ subject = linkClass(subject, {
+ bar: 'bar-1',
+ foo: 'foo-1'
});
- it('does not affect the className', () => {
- expect(linkClass()).to.deep.equal();
+ expect(subject.props.children[0].props.className).to.equal('foo-1');
+ expect(subject.props.children[1].props.className).to.equal('bar-1');
+ });
+ it('assigns a generated className to elements inside nested arrays', () => {
+ let subject;
+
+ subject =
+ {[
+ [
+
,
+
+ ],
+ [
+
,
+
+ ]
+ ]}
+
;
+
+ subject = linkClass(subject, {
+ bar: 'bar-1',
+ foo: 'foo-1'
});
- xit('does not affect element with a single children when that children is contained in an array', () => {
- let outcome,
- subject;
+ expect(subject.props.children[0][0].props.className).to.equal('foo-1');
+ expect(subject.props.children[0][1].props.className).to.equal('bar-1');
+
+ expect(subject.props.children[1][0].props.className).to.equal('foo-1');
+ expect(subject.props.children[1][1].props.className).to.equal('bar-1');
+ });
+ it('styleName is deleted from props', () => {
+ let subject;
- subject = React.createElement('div', null, [
- React.createElement('p')
- ]);
- outcome = React.createElement('div', null, [
- React.createElement('p')
- ]);
+ subject = ;
- expect(linkClass(subject)).to.deep.equal(outcome);
+ subject = linkClass(subject, {
+ bar: 'bar-1',
+ foo: 'foo-1'
});
- xit('does not affect element with multiple children', () => {
- // Using array instead of object causes the following error:
- // Warning: Each child in an array or iterator should have a unique "key" prop.
- // Check the render method of _class. See https://fb.me/react-warning-keys for more information.
- // @see https://github.com/facebook/react/issues/4723#issuecomment-135555277
- // expect(linkClass()).to.deep.equal();
-
- let outcome,
- subject;
-
- subject = React.createElement('div', null, [
- React.createElement('p'),
- React.createElement('p')
- ]);
- outcome = React.createElement('div', null, [
- React.createElement('p'),
- React.createElement('p')
- ]);
-
- expect(linkClass(subject)).to.deep.equal(outcome);
+ expect(subject.props.children[0].props).not.to.have.property('styleName');
+ expect(subject.props.children[1].props).not.to.have.property('styleName');
+ });
+ it('preserves original keys', () => {
+ let subject;
+
+ subject = ;
+
+ subject = linkClass(subject, {
+ bar: 'bar-1',
+ foo: 'foo-1'
});
+
+ expect(subject.props.children[0].key).to.equal('1');
+ expect(subject.props.children[1].key).to.equal('2');
+ });
});
+ context('when multiple descendants have styleName and are iterable', () => {
+ it('assigns a generated className', () => {
+ let subject;
+
+ const iterable = {
+ 0: ,
+ 1: ,
+ length: 2,
- context('called with null instead of ReactElement', () => {
- it('returns null', () => {
- let subject;
+ // eslint-disable-next-line no-use-extend-native/no-use-extend-native
+ [Symbol.iterator]: Array.prototype[Symbol.iterator]
+ };
- subject = linkClass(null);
+ subject = {iterable}
;
- expect(subject).to.equal(null);
+ subject = linkClass(subject, {
+ bar: 'bar-1',
+ foo: 'foo-1'
});
+
+ expect(subject.props.children[0].props.className).to.equal('foo-1');
+ 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;
- context('styleName matches an existing CSS module', () => {
- context('when a descendant element has styleName', () => {
- it('assigns a generated className', () => {
- let subject;
+ const iterable = {
+ 0: ,
+ 1: ,
+ length: 2,
- subject = ;
+ // eslint-disable-next-line no-use-extend-native/no-use-extend-native
+ [Symbol.iterator]: Array.prototype[Symbol.iterator]
+ };
- subject = linkClass(subject, {
- foo: 'foo-1'
- });
+ subject = ;
- expect(subject.props.children.props.className).to.equal('foo-1');
- });
- });
- context('when multiple descendant elements have styleName', () => {
- it('assigns a generated className', () => {
- let subject;
-
- subject = ;
-
- subject = linkClass(subject, {
- foo: 'foo-1',
- bar: 'bar-1'
- });
-
- expect(subject.props.children[0].props.className).to.equal('foo-1');
- expect(subject.props.children[1].props.className).to.equal('bar-1');
- });
+ subject = linkClass(subject, {
+ bar: 'bar-1',
+ foo: 'foo-1'
});
- context('when multiple descendants have styleName and are iterable', () => {
- it('assigns a generated className', () => {
- let subject, iterable;
-
- iterable = {
- 0: ,
- 1: ,
- length: 2,
- [Symbol.iterator]: Array.prototype[Symbol.iterator]
- };
-
- subject = {iterable}
;
-
- subject = linkClass(subject, {
- foo: 'foo-1',
- bar: 'bar-1'
- });
-
- expect(subject.props.children[0].props.className).to.equal('foo-1');
- expect(subject.props.children[1].props.className).to.equal('bar-1');
- });
- });
- context('when ReactElement does not have an existing className', () => {
- it('uses the generated class name to set the className property', () => {
- let subject;
- subject = ;
+ 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;
- subject = linkClass(subject, {
- foo: 'foo-1'
- });
+ subject = ;
- expect(subject.props.className).to.deep.equal('foo-1');
- });
+ subject = linkClass(subject, {
+ foo: 'foo-1'
});
- context('when ReactElement has an existing className', () => {
- it('appends the generated class name to the className property', () => {
- let subject;
- subject = ;
+ expect(subject.props.className).to.deep.equal('foo-1');
+ });
+ });
+ context('when ReactElement has an existing className', () => {
+ it('appends the generated class name to the className property', () => {
+ let subject;
- subject = linkClass(subject, {
- bar: 'bar-1'
- });
+ subject = ;
- expect(subject.props.className).to.deep.equal('foo bar-1');
- });
+ subject = linkClass(subject, {
+ bar: 'bar-1'
});
+
+ expect(subject.props.className).to.deep.equal('foo bar-1');
+ });
});
+ });
+
+ context('styleName includes multiple whitespace characters', () => {
+ it('resolves CSS modules', () => {
+ let subject;
- context('styleName includes multiple whitespace characters', () => {
- it('resolves CSS modules', () => {
- let subject;
+ subject = ;
- subject = ;
+ subject = linkClass(subject, {
+ bar: 'bar-1',
+ foo: 'foo-1'
+ }, {
+ allowMultiple: true
+ });
+
+ expect(subject.props.children.props.className).to.equal('foo-1 bar-1');
+ });
+ });
- subject = linkClass(subject, {
- foo: 'foo-1',
- bar: 'bar-1'
- }, {
- allowMultiple: true
- });
+ context('can\'t write to properties', () => {
+ context('when the element is frozen', () => {
+ it('adds className but is still frozen', () => {
+ let subject;
- expect(subject.props.children.props.className).to.equal('foo-1 bar-1');
+ subject = ;
+
+ Object.freeze(subject);
+ subject = linkClass(subject, {
+ foo: 'foo-1'
});
+
+ expect(subject).to.be.frozen;
+ expect(subject.props.className).to.equal('foo-1');
+ });
});
+ context('when the element\'s props are frozen', () => {
+ it('adds className and only props are still frozen', () => {
+ let subject;
- describe('options.allowMultiple', () => {
- context('when multiple module names are used', () => {
- context('when false', () => {
- it('throws an error', () => {
- expect(() => {
- linkClass(, {}, {allowMultiple: false});
- }).to.throw(Error, 'ReactElement styleName property defines multiple module names ("foo bar").');
- });
- });
- context('when true', () => {
- it('appends a generated class name for every referenced CSS module', () => {
- let subject;
-
- subject = ;
-
- subject = linkClass(subject, {
- foo: 'foo-1',
- bar: 'bar-1'
- }, {allowMultiple: true});
-
- expect(subject.props.className).to.deep.equal('foo-1 bar-1');
- });
- });
+ subject = ;
+
+ Object.freeze(subject.props);
+ subject = linkClass(subject, {
+ foo: 'foo-1'
});
+
+ expect(subject.props).to.be.frozen;
+ expect(subject.props.className).to.equal('foo-1');
+ });
});
+ context('when the element\'s props are not extensible', () => {
+ it('adds className and props are still not extensible', () => {
+ let subject;
- describe('options.errorWhenNotFound', () => {
- context('when styleName does not match an existing CSS module', () => {
- context('when false', () => {
- it('ignores the missing CSS module', () => {
- let subject;
-
- subject = ;
-
- subject = linkClass(subject, {}, {errorWhenNotFound: false});
-
- expect(subject.props.className).to.be.an('undefined');
- });
- });
- context('when is true', () => {
- it('throws an error', () => {
- expect(() => {
- linkClass(, {}, {errorWhenNotFound: true});
- }).to.throw(Error, '"foo" CSS module is undefined.');
- });
- });
+ subject = ;
+
+ Object.preventExtensions(subject.props);
+ subject = linkClass(subject, {
+ foo: 'foo-1'
});
+
+ expect(subject.props).to.not.be.extensible;
+ expect(subject.props.className).to.equal('foo-1');
+ });
});
+ });
+
+ context('when element is an array', () => {
+ it('handles each element individually', () => {
+ let subject;
+
+ subject = [
+ ,
+
+ ];
+
+ subject = linkClass(subject, {
+ bar: 'bar-1',
+ foo: 'foo-1'
+ });
+
+ expect(subject).to.be.an('array');
+ expect(subject[0].props.className).to.equal('foo-1');
+ expect(subject[1].props.children.props.className).to.equal('bar-1');
+ });
+ });
+
+ describe('options.allowMultiple', () => {
+ context('when multiple module names are used', () => {
+ context('when false', () => {
+ it('throws an error', () => {
+ expect(() => {
+ linkClass(, {}, {allowMultiple: false});
+ }).to.throw(Error, 'ReactElement styleName property defines multiple module names ("foo bar").');
+ });
+ });
+ context('when true', () => {
+ it('appends a generated class name for every referenced CSS module', () => {
+ let subject;
- context('when ReactElement includes ReactComponent', () => {
- let Foo,
- nodeList;
-
- beforeEach(() => {
- global.document = jsdom.jsdom(`
-
-
-
-
-
-
-
- `);
-
- global.window = document.defaultView;
-
- Foo = class extends React.Component {
- render () {
- return Hello
;
- }
- };
-
- nodeList = TestUtils.renderIntoDocument(linkClass(
, {foo: 'foo-1'}));
+ subject = ;
+
+ subject = linkClass(subject, {
+ bar: 'bar-1',
+ foo: 'foo-1'
+ }, {
+ allowMultiple: true
+ });
+
+ expect(subject.props.className).to.deep.equal('foo-1 bar-1');
+ });
+ });
+ });
+ });
+
+ describe('options.handleNotFoundStyleName', () => {
+ context('when styleName does not match an existing CSS module', () => {
+ context('when throw', () => {
+ it('throws an error', () => {
+ expect(() => {
+ linkClass(, {}, {handleNotFoundStyleName: 'throw'});
+ }).to.throw(Error, '"foo" CSS module is undefined.');
});
- it('processes ReactElement nodes', () => {
- expect(nodeList.className).to.equal('foo-1');
+ });
+ context('when log', () => {
+ it('logs a warning to the console', () => {
+ const warnSpy = chai.spy(() => {});
+
+ console.warn = warnSpy;
+ linkClass(, {}, {handleNotFoundStyleName: 'log'});
+ expect(warnSpy).to.have.been.called();
});
- it('does not process ReactComponent nodes', () => {
- expect(nodeList.firstChild.className).to.equal('');
+ });
+ context('when ignore', () => {
+ it('does not log a warning', () => {
+ const warnSpy = chai.spy(() => {});
+
+ console.warn = warnSpy;
+ linkClass(, {}, {handleNotFoundStyleName: 'ignore'});
+ expect(warnSpy).to.not.have.been.called();
});
+
+ it('does not throw an error', () => {
+ expect(() => {
+ linkClass(, {}, {handleNotFoundStyleName: 'ignore'});
+ }).to.not.throw(Error, '"foo" CSS module is undefined.');
+ });
+ });
+ });
+ });
+
+ context('when ReactElement includes ReactComponent', () => {
+ let Foo;
+ let nodeList;
+
+ beforeEach(() => {
+ global.document = jsdom.jsdom('');
+ global.window = document.defaultView;
+
+ Foo = class extends React.Component {
+ render () {
+ return Hello
;
+ }
+ };
+
+ nodeList = TestUtils.renderIntoDocument(linkClass(
, {foo: 'foo-1'}));
+ });
+ it('processes ReactElement nodes', () => {
+ expect(nodeList.className).to.equal('foo-1');
});
+ it('does not process ReactComponent nodes', () => {
+ expect(nodeList.firstChild.className).to.equal('');
+ });
+ });
+
+ it('deletes styleName property from the target element', () => {
+ let subject;
+
+ subject = ;
+
+ subject = linkClass(subject, {
+ foo: 'foo-1'
+ });
+
+ expect(subject.props.className).to.deep.equal('foo-1');
+ expect(subject.props).not.to.have.property('styleName');
+ });
+
+ it('deletes styleName property from the target element (deep)', () => {
+ let 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');
+ });
});
diff --git a/tests/makeConfiguration.js b/tests/makeConfiguration.js
index 3a74ae7..4193311 100644
--- a/tests/makeConfiguration.js
+++ b/tests/makeConfiguration.js
@@ -3,43 +3,40 @@
import {
expect
} from 'chai';
-
import makeConfiguration from './../src/makeConfiguration';
describe('makeConfiguration', () => {
- describe('when using default configuration', () => {
- let configuration;
+ describe('when using default configuration', () => {
+ let configuration;
- beforeEach(() => {
- configuration = makeConfiguration();
- });
- describe('allowMultiple property', () => {
- it('defaults to false', () => {
- expect(configuration.allowMultiple).to.equal(false);
- });
- });
- describe('errorWhenNotFound property', () => {
- it('defaults to true', () => {
- expect(configuration.errorWhenNotFound).to.equal(true);
- });
- });
+ beforeEach(() => {
+ configuration = makeConfiguration();
});
- describe('when unknown property is provided', () => {
- it('throws an error', () => {
- expect(() => {
- makeConfiguration({
- unknownProperty: true
- });
- }).to.throw(Error, 'Unknown configuration property "unknownProperty".');
+ describe('allowMultiple property', () => {
+ it('defaults to false', () => {
+ expect(configuration.allowMultiple).to.equal(false);
+ });
+ });
+ describe('handleNotFoundStyleName property', () => {
+ it('defaults to "throw"', () => {
+ expect(configuration.handleNotFoundStyleName).to.equal('throw');
+ });
+ });
+ });
+ describe('when unknown property is provided', () => {
+ it('throws an error', () => {
+ expect(() => {
+ makeConfiguration({
+ unknownProperty: true
});
+ }).to.throw(Error, 'Unknown configuration property "unknownProperty".');
});
- it('does not mutate user configuration', () => {
- let userConfiguration;
+ });
+ it('does not mutate user configuration', () => {
+ const userConfiguration = {};
- userConfiguration = {};
+ makeConfiguration(userConfiguration);
- makeConfiguration(userConfiguration);
-
- expect(userConfiguration).to.deep.equal({});
- });
+ expect(userConfiguration).to.deep.equal({});
+ });
});
diff --git a/tests/mocha.opts b/tests/mocha.opts
deleted file mode 100644
index 13374d0..0000000
--- a/tests/mocha.opts
+++ /dev/null
@@ -1 +0,0 @@
---compilers js:babel/register
diff --git a/tests/reactCssModules.js b/tests/reactCssModules.js
index 685988f..d484d95 100644
--- a/tests/reactCssModules.js
+++ b/tests/reactCssModules.js
@@ -1,133 +1,184 @@
-/* eslint-disable max-nested-callbacks */
+/* eslint-disable max-nested-callbacks, react/no-multi-comp, react/prop-types, react/prefer-stateless-function, class-methods-use-this */
import {
expect
} from 'chai';
-
import React from 'react';
+import ReactDOM from 'react-dom';
import TestUtils from 'react-addons-test-utils';
+import jsdom from 'jsdom';
import reactCssModules from './../src/index';
describe('reactCssModules', () => {
- context('a ReactComponent is decorated using react-css-modules', () => {
- it('inherits displayName', () => {
- let Foo;
+ context('a ReactComponent is decorated using react-css-modules', () => {
+ it('inherits displayName', () => {
+ let Foo;
- Foo = class extends React.Component {
- static displayName = 'Bar';
- };
+ Foo = class extends React.Component {};
- Foo = reactCssModules(Foo);
+ // @todo https://phabricator.babeljs.io/T2779
+ Foo.displayName = 'Bar';
- expect(Foo.displayName).to.equal('Bar');
- });
- context('target component does not name displayName', () => {
- it('uses name for displayName', () => {
- let Foo;
+ Foo = reactCssModules(Foo);
- Foo = class Bar extends React.Component {};
+ expect(Foo.displayName).to.equal('Bar');
+ });
+ context('target component does not name displayName', () => {
+ it('uses name for displayName', () => {
+ let Foo;
- Foo = reactCssModules(Foo);
+ Foo = class Bar extends React.Component {};
- expect(Foo.displayName).to.equal('Bar');
- });
- });
- });
- context('a ReactComponent renders an element with the styleName prop', () => {
- context('the component is a class that extends React.Component', () => {
- it('that element should contain the equivalent className', () => {
- let Foo,
- component,
- shallowRenderer;
+ Foo = reactCssModules(Foo);
- shallowRenderer = TestUtils.createRenderer();
+ expect(Foo.displayName).to.equal('Bar');
+ });
+ });
+ });
+ context('a ReactComponent renders an element with the styleName prop', () => {
+ context('the component is a class that extends React.Component', () => {
+ let Foo;
+ let component;
+
+ beforeEach(() => {
+ const shallowRenderer = TestUtils.createRenderer();
+
+ Foo = class extends React.Component {
+ render () {
+ return Hello
;
+ }
+ };
+
+ Foo = reactCssModules(Foo, {
+ foo: 'foo-1'
+ });
- Foo = class extends React.Component {
- render () {
- return Hello
;
- }
- };
+ shallowRenderer.render();
- Foo = reactCssModules(Foo, {
- foo: 'foo-1'
- });
+ component = shallowRenderer.getRenderOutput();
+ });
+ it('that element should contain the equivalent className', () => {
+ expect(component.props.className).to.equal('foo-1');
+ });
+ it('the styleName prop should be "consumed" in the process', () => {
+ expect(component.props).not.to.have.property('styleName');
+ });
+ });
+ context('the component is a stateless function component', () => {
+ let Foo;
+ let component;
- shallowRenderer.render();
+ beforeEach(() => {
+ const shallowRenderer = TestUtils.createRenderer();
- component = shallowRenderer.getRenderOutput();
+ Foo = () => {
+ return Hello
;
+ };
- expect(component.props.className).to.equal('foo-1');
- });
+ Foo = reactCssModules(Foo, {
+ foo: 'foo-1'
});
- context('the component is a stateless function component', () => {
- it('that element should contain the equivalent className', () => {
- let Foo,
- component,
- shallowRenderer;
-
- shallowRenderer = TestUtils.createRenderer();
- Foo = () => Hello
;
+ shallowRenderer.render();
- Foo = reactCssModules(Foo, {
- foo: 'foo-1'
- });
+ component = shallowRenderer.getRenderOutput();
+ });
+ it('that element should contain the equivalent className', () => {
+ expect(component.props.className).to.equal('foo-1');
+ });
+ it('the styleName prop should be "consumed" in the process', () => {
+ expect(component.props).not.to.have.property('styleName');
+ });
+ });
+ });
+ context('a ReactComponent renders nothing', () => {
+ context('the component is a class that extends React.Component', () => {
+ it('linkClass must not intervene', () => {
+ let Foo;
+
+ const shallowRenderer = TestUtils.createRenderer();
+
+ Foo = class extends React.Component {
+ render () {
+ return null;
+ }
+ };
+
+ Foo = reactCssModules(Foo, {
+ foo: 'foo-1'
+ });
- shallowRenderer.render();
+ shallowRenderer.render();
- component = shallowRenderer.getRenderOutput();
+ const component = shallowRenderer.getRenderOutput();
- expect(component.props.className).to.equal('foo-1');
- });
- });
+ expect(typeof component).to.equal('object');
+ });
});
+ context('the component is a stateless function component', () => {
+ it('that element should contain the equivalent className', () => {
+ let Foo;
- context('a ReactComponent renders nothing', () => {
- context('the component is a class that extends React.Component', () => {
- it('linkClass must not intervene', () => {
- let Foo,
- component,
- shallowRenderer;
+ const shallowRenderer = TestUtils.createRenderer();
- shallowRenderer = TestUtils.createRenderer();
+ Foo = () => {
+ return null;
+ };
- Foo = class extends React.Component {
- render () {
- return null;
- }
- };
+ Foo = reactCssModules(Foo, {
+ foo: 'foo-1'
+ });
- Foo = reactCssModules(Foo, {
- foo: 'foo-1'
- });
+ shallowRenderer.render();
- shallowRenderer.render();
+ const component = shallowRenderer.getRenderOutput();
- component = shallowRenderer.getRenderOutput();
+ expect(typeof component).to.equal('object');
+ });
+ });
+ });
+ context('rendering element', () => {
+ beforeEach(() => {
+ global.document = jsdom.jsdom('');
- expect(typeof component).to.equal('object');
- });
+ global.window = document.defaultView;
+ });
+ context('parent component is using react-css-modules and interpolates props.children', () => {
+ // @see https://github.com/gajus/react-css-modules/issues/76
+ it('unsets the styleName property', () => {
+ let Bar;
+ let Foo;
+ let subject;
+
+ Foo = class extends React.Component {
+ render () {
+ return
+ foo
+ ;
+ }
+ };
+
+ Foo = reactCssModules(Foo, {
+ test: 'foo-0'
});
- context('the component is a stateless function component', () => {
- it('that element should contain the equivalent className', () => {
- let Foo,
- component,
- shallowRenderer;
-
- shallowRenderer = TestUtils.createRenderer();
- Foo = () => null;
+ Bar = class extends React.Component {
+ render () {
+ return {this.props.children}
;
+ }
+ };
- Foo = reactCssModules(Foo, {
- foo: 'foo-1'
- });
+ Bar = reactCssModules(Bar, {
+ test: 'bar-0'
+ });
- shallowRenderer.render();
+ subject = TestUtils.renderIntoDocument();
- component = shallowRenderer.getRenderOutput();
+ // eslint-disable-next-line react/no-find-dom-node
+ subject = ReactDOM.findDOMNode(subject);
- expect(typeof component).to.equal('object');
- });
- });
+ expect(subject.firstChild.className).to.equal('foo-0');
+ });
});
+ });
});
diff --git a/tests/renderNothing.js b/tests/renderNothing.js
new file mode 100644
index 0000000..7a666e8
--- /dev/null
+++ b/tests/renderNothing.js
@@ -0,0 +1,16 @@
+import {
+ expect
+} from 'chai';
+import renderNothing from '../src/renderNothing';
+
+describe('renderNothing', () => {
+ context('renderNothing should return different node types for various React versions', () => {
+ it('should return noscript tag for React v14 or lower', () => {
+ expect(renderNothing('14.0.0').type).to.equal('noscript');
+ });
+
+ it('should return null for React v15 or higher', () => {
+ expect(renderNothing('15.0.0')).to.equal(null);
+ });
+ });
+});
diff --git a/tests/wrapStatelessFunction.js b/tests/wrapStatelessFunction.js
index 44d027f..9c71935 100644
--- a/tests/wrapStatelessFunction.js
+++ b/tests/wrapStatelessFunction.js
@@ -3,69 +3,90 @@
import {
expect
} from 'chai';
-
+import React from 'react';
+import TestUtils from 'react-addons-test-utils';
import wrapStatelessFunction from './../src/wrapStatelessFunction';
describe('wrapStatelessFunction', () => {
- it('hoists static own properties from the input component to the wrapped component', () => {
- let Component, WrappedComponent, styles;
+ it('hoists static own properties from the input component to the wrapped component', () => {
+ const styles = {
+ foo: 'foo-1'
+ };
- styles = {
- foo: 'foo-1'
- };
+ const InnerComponent = () => {
+ return null;
+ };
- Component = function InnerComponent () { return null; };
- Component.propTypes = {};
- Component.defaultProps = {};
+ InnerComponent.propTypes = {};
+ InnerComponent.defaultProps = {};
- WrappedComponent = wrapStatelessFunction(Component, styles);
+ const WrappedComponent = wrapStatelessFunction(InnerComponent, styles);
- expect(WrappedComponent.propTypes).to.equal(Component.propTypes);
- expect(WrappedComponent.defaultProps).to.equal(Component.defaultProps);
- expect(WrappedComponent.name).not.to.equal(Component.name);
- });
- context('using default styles', () => {
- it('exposes styles through styles property', (done) => {
- let styles;
+ expect(WrappedComponent.propTypes).to.equal(InnerComponent.propTypes);
+ expect(WrappedComponent.defaultProps).to.equal(InnerComponent.defaultProps);
+ expect(WrappedComponent.name).not.to.equal(InnerComponent.name);
+ });
+ context('using default styles', () => {
+ it('exposes styles through styles property', (done) => {
+ const styles = {
+ foo: 'foo-1'
+ };
- styles = {
- foo: 'foo-1'
- };
+ wrapStatelessFunction((props) => {
+ expect(props.styles).to.equal(styles);
+ done();
+ }, styles)();
+ });
+ it('exposes non-enumerable styles property', (done) => {
+ const styles = {
+ foo: 'foo-1'
+ };
- wrapStatelessFunction((props) => {
- expect(props.styles).to.equal(styles);
- done();
- }, styles)();
- });
- it('does not affect the other instance properties', (done) => {
- let styles;
+ wrapStatelessFunction((props) => {
+ expect(props.propertyIsEnumerable('styles')).to.equal(false);
+ done();
+ }, styles)();
+ });
+ it('does not affect the other instance properties', (done) => {
+ const styles = {
+ foo: 'foo-1'
+ };
- styles = {
- foo: 'foo-1'
- };
+ wrapStatelessFunction((props) => {
+ expect(props.bar).to.equal('baz');
+ done();
+ }, styles)({
+ bar: 'baz'
+ });
+ });
+ });
+ context('using explicit styles', () => {
+ it('exposes styles through styles property', (done) => {
+ const styles = {
+ foo: 'foo-1'
+ };
- wrapStatelessFunction((props) => {
- expect(props.bar).to.equal('baz');
- done();
- }, styles)({
- bar: 'baz'
- });
- });
+ wrapStatelessFunction((props) => {
+ expect(props.styles).to.equal(styles);
+ done();
+ })({
+ styles
+ });
});
- context('using explicit styles', () => {
- it('exposes styles through styles property', (done) => {
- let styles;
+ });
+ context('rendering Component that returns null', () => {
+ it('generates null', () => {
+ const shallowRenderer = TestUtils.createRenderer();
+
+ const Component = wrapStatelessFunction(() => {
+ return null;
+ });
+
+ shallowRenderer.render();
- styles = {
- foo: 'foo-1'
- };
+ const component = shallowRenderer.getRenderOutput();
- wrapStatelessFunction((props) => {
- expect(props.styles).to.equal(styles);
- done();
- })({
- styles: styles
- });
- });
+ expect(component).to.equal(null);
});
+ });
});