diff --git a/.babelrc b/.babelrc
new file mode 100644
index 0000000..3751ba8
--- /dev/null
+++ b/.babelrc
@@ -0,0 +1,19 @@
+{
+ "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/.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 cf9c7f9..df978de 100755
--- a/.gitignore
+++ b/.gitignore
@@ -3,8 +3,10 @@ coverage
dist
*.log
.*
-!.README
+!.babelrc
+!.editorconfig
+!.eslintrc
!.gitignore
!.npmignore
-!.babelrc
+!.README
!.travis.yml
diff --git a/.travis.yml b/.travis.yml
index e07d95b..a786167 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,14 @@
language: node_js
node_js:
- - 5
- - 4
+ - 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/README.md b/README.md
index 5c8c0ef..a6b7399 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,6 @@
# React CSS Modules
+[](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)
@@ -8,6 +9,16 @@
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)
@@ -24,19 +35,18 @@ React CSS Modules implement automatic mapping of CSS modules. Every CSS class is
- [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:
@@ -61,7 +71,7 @@ Rendering the component will produce a markup similar to:
```js
@@ -73,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?
@@ -115,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
@@ -149,8 +159,8 @@ Setup:
{
test: /\.css$/,
loaders: [
- 'style?sourceMap',
- 'css?modules&importLoaders=1&localIdentName=[path]___[name]__[local]___[hash:base64:5]'
+ 'style-loader?sourceMap',
+ 'css-loader?modules&importLoaders=1&localIdentName=[path]___[name]__[local]___[hash:base64:5]'
]
}
```
@@ -182,22 +192,48 @@ 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:
-```js
-{
- test: /\.css$/,
- loader: ExtractTextPlugin.extract('style', 'css?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]')
-}
-```
+ * 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.
@@ -370,7 +406,7 @@ export default CSSModules(CustomList, styles);
* @typedef CSSModules~Options
* @see {@link https://github.com/gajus/react-css-modules#options}
* @property {Boolean} allowMultiple
- * @property {Boolean} errorWhenNotFound
+ * @property {String} handleNotFoundStyleName
*/
/**
@@ -454,11 +490,17 @@ When `false`, the following will cause an error:
```
-#### `errorWhenNotFound`
+#### `handleNotFoundStyleName`
+
+Default: `throw`.
+
+Defines the desired action when `styleName` cannot be mapped to an existing CSS Module.
-Default: `true`.
+Available options:
-Throws an error when `styleName` cannot be mapped to an existing CSS Module.
+* `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
@@ -548,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/package.json b/package.json
index 6a40041..00e1ffa 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "react-css-modules",
"description": "Seamless mapping of class names to CSS modules inside of React components.",
- "main": "./dist/",
+ "main": "./dist/index.js",
"repository": {
"type": "git",
"url": "https://github.com/gajus/react-css-modules"
@@ -12,7 +12,7 @@
"css",
"modules"
],
- "version": "3.7.6",
+ "version": "4.3.0",
"author": {
"name": "Gajus Kuizinas",
"email": "gajus@gajus.com",
@@ -20,28 +20,36 @@
},
"license": "BSD-3-Clause",
"dependencies": {
- "es6-map": "^0.1.3",
- "hoist-non-react-statics": "^1.0.5",
- "lodash": "^4.6.1",
- "object-unfreeze": "^1.0.2"
+ "hoist-non-react-statics": "^2.5.5",
+ "lodash": "^4.16.6",
+ "object-unfreeze": "^1.1.0"
},
"devDependencies": {
- "chai": "^3.5.0",
- "jsdom": "^8.1.0",
- "pragmatist": "^3.0.21",
- "react": "^15.0.0-rc.1",
- "react-addons-shallow-compare": "^15.0.0-rc.1",
- "react-addons-test-utils": "^15.0.0-rc.1",
- "react-dom": "^15.0.0-rc.1"
+ "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 --es5",
- "lint": "npm run pragmatist lint",
- "test": "npm run pragmatist test --type-annotations",
- "build": "npm run pragmatist build",
- "watch": "npm run pragmatist watch",
- "watch-lint": "npm run pragmatist watch-lint",
- "watch-test": "npm run pragmatist watch-test --type-annotations",
- "watch-build": "npm run pragmatist watch-build"
+ "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 fd00abd..4741bea 100644
--- a/src/extendReactClass.js
+++ b/src/extendReactClass.js
@@ -1,9 +1,10 @@
/* eslint-disable react/prop-types */
-import linkClass from './linkClass';
-import React from 'react';
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
@@ -12,39 +13,62 @@ import hoistNonReactStatics from 'hoist-non-react-statics';
* @returns {ReactClass}
*/
export default (Component: Object, defaultStyles: Object, options: Object) => {
- const WrappedComponent = class extends Component {
- render () {
- let propsChanged,
- styles;
+ const WrappedComponent = class extends Component {
+ render () {
+ let styles;
- propsChanged = false;
+ const hasDefaultstyles = _.isObject(defaultStyles);
- if (this.props.styles) {
- styles = this.props.styles;
- } else if (_.isObject(defaultStyles)) {
- this.props = _.assign({}, this.props, {
- styles: 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;
+ }
- propsChanged = true;
- styles = defaultStyles;
- } else {
- styles = {};
- }
+ Object.defineProperty(props, 'styles', {
+ configurable: true,
+ enumerable: false,
+ value: styles,
+ writable: false
+ });
- const renderResult = super.render();
+ const originalProps = this.props;
- if (propsChanged) {
- delete this.props.styles;
- }
+ let renderIsSuccessful = false;
- if (renderResult) {
- return linkClass(renderResult, styles, options);
- }
+ try {
+ this.props = props;
- return React.createElement('noscript');
+ 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);
+ return hoistNonReactStatics(WrappedComponent, Component);
};
diff --git a/src/generateAppendClassName.js b/src/generateAppendClassName.js
index b4e6bd8..127aa2a 100644
--- a/src/generateAppendClassName.js
+++ b/src/generateAppendClassName.js
@@ -1,40 +1,49 @@
-import Map from 'es6-map';
+import SimpleMap from './SimpleMap';
-const stylesIndex = new Map();
+const CustomMap = typeof Map === 'undefined' ? SimpleMap : Map;
-export default (styles, styleNames: Array, errorWhenNotFound: boolean): string => {
- let appendClassName,
- stylesIndexMap;
+const stylesIndex = new CustomMap();
- stylesIndexMap = stylesIndex.get(styles);
+export default (styles, styleNames: Array, handleNotFoundStyleName: "throw" | "log" | "ignore"): string => {
+ let appendClassName;
+ let stylesIndexMap;
- if (stylesIndexMap) {
- const styleNameIndex = stylesIndexMap.get(styleNames);
+ stylesIndexMap = stylesIndex.get(styles);
- if (styleNameIndex) {
- return styleNameIndex;
- }
- } else {
- stylesIndexMap = stylesIndex.set(styles, new Map());
- }
-
- appendClassName = '';
+ if (stylesIndexMap) {
+ const styleNameIndex = stylesIndexMap.get(styleNames);
- for (const styleName in styleNames) {
- if (styleNames.hasOwnProperty(styleName)) {
- const className = styles[styleNames[styleName]];
-
- if (className) {
- appendClassName += ' ' + className;
- } else if (errorWhenNotFound === true) {
- throw new Error('"' + styleNames[styleName] + '" CSS module is undefined.');
- }
+ 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();
+ appendClassName = appendClassName.trim();
- stylesIndexMap.set(styleNames, appendClassName);
+ stylesIndexMap.set(styleNames, appendClassName);
- return appendClassName;
+ return appendClassName;
};
diff --git a/src/index.js b/src/index.js
index 6c1a78c..b913b29 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,53 +1,56 @@
import _ from 'lodash';
import extendReactClass from './extendReactClass';
import wrapStatelessFunction from './wrapStatelessFunction';
+import makeConfiguration from './makeConfiguration';
/**
* @see https://github.com/gajus/react-css-modules#options
*/
-type OptionsType = {};
+type TypeOptions = {};
/**
* Determines if the given object has the signature of a class that inherits React.Component.
*/
const isReactComponent = (maybeReactComponent: any): boolean => {
- return 'prototype' in maybeReactComponent && _.isFunction(maybeReactComponent.prototype.render);
+ return 'prototype' in maybeReactComponent && _.isFunction(maybeReactComponent.prototype.render);
};
/**
* When used as a function.
*/
-const functionConstructor = (Component: Function, defaultStyles: Object, options: OptionsType): Function => {
- 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.
*/
-const decoratorConstructor = (defaultStyles: Object, options: OptionsType): Function => {
- return (Component: Function) => {
- return functionConstructor(Component, defaultStyles, options);
- };
+const decoratorConstructor = (defaultStyles: Object, options: TypeOptions): Function => {
+ return (Component: Function) => {
+ return functionConstructor(Component, defaultStyles, options);
+ };
};
export default (...args) => {
- if (_.isFunction(args[0])) {
- 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 ac6dc72..67d0a94 100644
--- a/src/isIterable.js
+++ b/src/isIterable.js
@@ -1,6 +1,6 @@
import _ from 'lodash';
-const ITERATOR_SYMBOL = _.isFunction(Symbol) && Symbol.iterator;
+const ITERATOR_SYMBOL = typeof Symbol !== 'undefined' && _.isFunction(Symbol) && Symbol.iterator;
const OLD_ITERATOR_SYMBOL = '@@iterator';
/**
@@ -8,17 +8,17 @@ const OLD_ITERATOR_SYMBOL = '@@iterator';
* @see https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Iteration_protocols
*/
export default (maybeIterable: any): boolean => {
- let iterator;
+ let iterator;
- if (!_.isObject(maybeIterable)) {
- return false;
- }
+ if (!_.isObject(maybeIterable)) {
+ return false;
+ }
- if (ITERATOR_SYMBOL) {
- iterator = maybeIterable[ITERATOR_SYMBOL];
- } else {
- iterator = maybeIterable[OLD_ITERATOR_SYMBOL];
- }
+ if (ITERATOR_SYMBOL) {
+ iterator = maybeIterable[ITERATOR_SYMBOL];
+ } else {
+ iterator = maybeIterable[OLD_ITERATOR_SYMBOL];
+ }
- return _.isFunction(iterator);
+ return _.isFunction(iterator);
};
diff --git a/src/linkClass.js b/src/linkClass.js
index 2c3c930..4ed3975 100644
--- a/src/linkClass.js
+++ b/src/linkClass.js
@@ -2,74 +2,105 @@ import _ from 'lodash';
import React, {
ReactElement
} from 'react';
-import makeConfiguration from './makeConfiguration';
+import objectUnfreeze from 'object-unfreeze';
import isIterable from './isIterable';
import parseStyleName from './parseStyleName';
import generateAppendClassName from './generateAppendClassName';
-import objectUnfreeze from 'object-unfreeze';
+
+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);
+ }
+ });
+
+ return array;
+};
const linkElement = (element: ReactElement, styles: Object, configuration: Object): ReactElement => {
- let appendClassName,
- elementIsFrozen,
- elementShallowCopy;
+ let appendClassName;
+ let elementShallowCopy;
- elementShallowCopy = element;
+ elementShallowCopy = element;
- if (Object.isFrozen && Object.isFrozen(elementShallowCopy)) {
- elementIsFrozen = true;
+ if (Array.isArray(elementShallowCopy)) {
+ return elementShallowCopy.map((arrayElement) => {
+ return linkElement(arrayElement, styles, configuration);
+ });
+ }
- // https://github.com/facebook/react/blob/v0.13.3/src/classic/element/ReactElement.js#L131
- elementShallowCopy = objectUnfreeze(elementShallowCopy);
- elementShallowCopy.props = objectUnfreeze(elementShallowCopy.props);
- }
+ const elementIsFrozen = Object.isFrozen && Object.isFrozen(elementShallowCopy);
+ const propsFrozen = Object.isFrozen && Object.isFrozen(elementShallowCopy.props);
+ const propsNotExtensible = Object.isExtensible && !Object.isExtensible(elementShallowCopy.props);
- const styleNames = parseStyleName(elementShallowCopy.props.styleName || '', configuration.allowMultiple);
-
- if (React.isValidElement(elementShallowCopy.props.children)) {
- elementShallowCopy.props.children = linkElement(React.Children.only(elementShallowCopy.props.children), styles, configuration);
- } else if (_.isArray(elementShallowCopy.props.children) || isIterable(elementShallowCopy.props.children)) {
- elementShallowCopy.props.children = React.Children.map(elementShallowCopy.props.children, (node) => {
- if (React.isValidElement(node)) {
- return linkElement(node, styles, configuration);
- } else {
- return node;
- }
- });
- }
+ 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);
+ }
- if (styleNames.length) {
- appendClassName = generateAppendClassName(styles, styleNames, configuration.errorWhenNotFound);
+ const styleNames = parseStyleName(elementShallowCopy.props.styleName || '', configuration.allowMultiple);
+ const {children, ...restProps} = elementShallowCopy.props;
- if (appendClassName) {
- if (elementShallowCopy.props.className) {
- appendClassName = elementShallowCopy.props.className + ' ' + appendClassName;
- }
+ 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);
+ }
- elementShallowCopy.props.className = appendClassName;
- elementShallowCopy.props.styleName = null;
- }
+ _.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);
}
+ });
+
+ if (styleNames.length) {
+ appendClassName = generateAppendClassName(styles, styleNames, configuration.handleNotFoundStyleName);
- if (elementIsFrozen) {
- Object.freeze(elementShallowCopy.props);
- Object.freeze(elementShallowCopy);
+ if (appendClassName) {
+ if (elementShallowCopy.props.className) {
+ appendClassName = elementShallowCopy.props.className + ' ' + appendClassName;
+ }
+
+ elementShallowCopy.props.className = appendClassName;
}
+ }
+
+ delete elementShallowCopy.props.styleName;
- return elementShallowCopy;
+ if (elementIsFrozen) {
+ Object.freeze(elementShallowCopy.props);
+ Object.freeze(elementShallowCopy);
+ } else if (propsFrozen) {
+ Object.freeze(elementShallowCopy.props);
+ }
+
+ if (propsNotExtensible) {
+ Object.preventExtensions(elementShallowCopy.props);
+ }
+
+ return elementShallowCopy;
};
/**
* @param {ReactElement} element
* @param {Object} styles CSS modules class map.
- * @param {CSSModules~Options} userConfiguration
+ * @param {CSSModules~Options} configuration
*/
-export default (element: ReactElement, styles = {}, userConfiguration): ReactElement => {
+export default (element: ReactElement, styles = {}, configuration = {}): ReactElement => {
// @see https://github.com/gajus/react-css-modules/pull/30
- if (!_.isObject(element)) {
- return element;
- }
-
- const configuration = makeConfiguration(userConfiguration);
+ if (!_.isObject(element)) {
+ return element;
+ }
- return linkElement(element, styles, configuration);
+ return linkElement(element, styles, configuration);
};
diff --git a/src/makeConfiguration.js b/src/makeConfiguration.js
index 22d0298..cc3169c 100644
--- a/src/makeConfiguration.js
+++ b/src/makeConfiguration.js
@@ -1,13 +1,10 @@
import _ from 'lodash';
-import Map from 'es6-map';
-
-const userConfigurationIndex = new Map();
/**
* @typedef CSSModules~Options
* @see {@link https://github.com/gajus/react-css-modules#options}
* @property {boolean} allowMultiple
- * @property {boolean} errorWhenNotFound
+ * @property {string} handleNotFoundStyleName
*/
/**
@@ -15,32 +12,26 @@ const userConfigurationIndex = new Map();
* @returns {CSSModules~Options}
*/
export default (userConfiguration = {}) => {
- let configuration;
-
- configuration = userConfigurationIndex.get(userConfiguration);
-
- if (configuration) {
- return configuration;
+ const configuration = {
+ allowMultiple: false,
+ handleNotFoundStyleName: 'throw'
+ };
+
+ _.forEach(userConfiguration, (value, name) => {
+ if (_.isUndefined(configuration[name])) {
+ throw new Error('Unknown configuration property "' + name + '".');
}
- configuration = {
- allowMultiple: false,
- errorWhenNotFound: true
- };
-
- _.forEach(userConfiguration, (value, name) => {
- if (_.isUndefined(configuration[name])) {
- throw new Error('Unknown configuration property "' + name + '".');
- }
-
- if (!_.isBoolean(value)) {
- throw new Error('"' + name + '" property value must be a boolean.');
- }
+ if (name === 'allowMultiple' && !_.isBoolean(value)) {
+ throw new Error('"allowMultiple" property value must be a boolean.');
+ }
- configuration[name] = value;
- });
+ if (name === 'handleNotFoundStyleName' && !_.includes(['throw', 'log', 'ignore'], value)) {
+ throw new Error('"handleNotFoundStyleName" property value must be "throw", "log" or "ignore".');
+ }
- userConfigurationIndex.set(userConfiguration, configuration);
+ configuration[name] = value;
+ });
- return configuration;
+ return configuration;
};
diff --git a/src/parseStyleName.js b/src/parseStyleName.js
index 39c328a..937b2d6 100644
--- a/src/parseStyleName.js
+++ b/src/parseStyleName.js
@@ -3,20 +3,20 @@ import _ from 'lodash';
const styleNameIndex = {};
export default (styleNamePropertyValue: string, allowMultiple: boolean): Array => {
- let styleNames;
+ let styleNames;
- if (styleNameIndex[styleNamePropertyValue]) {
- styleNames = styleNameIndex[styleNamePropertyValue];
- } else {
- styleNames = _.trim(styleNamePropertyValue).split(' ');
- styleNames = _.filter(styleNames);
+ if (styleNameIndex[styleNamePropertyValue]) {
+ styleNames = styleNameIndex[styleNamePropertyValue];
+ } else {
+ styleNames = _.trim(styleNamePropertyValue).split(/\s+/);
+ styleNames = _.filter(styleNames);
- styleNameIndex[styleNamePropertyValue] = styleNames;
- }
+ styleNameIndex[styleNamePropertyValue] = styleNames;
+ }
- if (allowMultiple === false && styleNames.length > 1) {
- throw new Error('ReactElement styleName property defines multiple module names ("' + styleNamePropertyValue + '").');
- }
+ if (allowMultiple === false && styleNames.length > 1) {
+ throw new Error('ReactElement styleName property defines multiple module names ("' + styleNamePropertyValue + '").');
+ }
- return styleNames;
+ 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 878119a..b6b9162 100644
--- a/src/wrapStatelessFunction.js
+++ b/src/wrapStatelessFunction.js
@@ -3,39 +3,47 @@
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
*/
export default (Component: Function, defaultStyles: Object, options: Object): Function => {
- const WrappedComponent = (props = {}, ...args) => {
- let 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 = {};
- }
-
- const renderResult = Component(useProps, ...args);
-
- if (renderResult) {
- return linkClass(renderResult, styles, options);
- }
-
- return React.createElement('noscript');
- };
-
- _.assign(WrappedComponent, Component);
-
- return WrappedComponent;
+ 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;
};
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 16aed7f..01733ee 100644
--- a/tests/extendReactClass.js
+++ b/tests/extendReactClass.js
@@ -1,9 +1,8 @@
-/* eslint-disable max-nested-callbacks, react/prefer-stateless-function, react/prop-types, react/no-multi-comp */
+/* 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';
@@ -11,140 +10,158 @@ import jsdom from 'jsdom';
import extendReactClass from './../src/extendReactClass';
describe('extendReactClass', () => {
- beforeEach(() => {
- global.document = jsdom.jsdom('');
+ 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();
+ }
+ };
- global.window = document.defaultView;
+ Component = extendReactClass(Component, styles);
+
+ TestUtils.renderIntoDocument( );
});
- context('using default styles', () => {
- it('exposes styles through this.props.styles property', (done) => {
- let Component;
+ it('exposes non-enumerable styles property', (done) => {
+ let Component;
- const styles = {
- foo: 'foo-1'
- };
+ const styles = {
+ foo: 'foo-1'
+ };
- Component = class extends React.Component {
- render () {
- expect(this.props.styles).to.equal(styles);
- done();
- }
- };
+ Component = class extends React.Component {
+ render () {
+ expect(this.props.propertyIsEnumerable('styles')).to.equal(false);
+ done();
+ }
+ };
- Component = extendReactClass(Component, styles);
+ Component = extendReactClass(Component, styles);
- TestUtils.renderIntoDocument( );
- });
- it('does not affect the other instance properties', (done) => {
- let Component;
+ 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();
- }
- };
+ Component = class extends React.Component {
+ render () {
+ expect(this.props.bar).to.equal('baz');
+ done();
+ }
+ };
- const styles = {
- foo: 'foo-1'
- };
+ const styles = {
+ foo: 'foo-1'
+ };
- Component = extendReactClass(Component, styles);
+ Component = extendReactClass(Component, styles);
- TestUtils.renderIntoDocument( );
- });
- it('does not affect pure-render logic', (done) => {
- let Component,
- rendered;
+ TestUtils.renderIntoDocument( );
+ });
+ it('does not affect pure-render logic', (done) => {
+ let Component;
+ let rendered;
- rendered = false;
+ rendered = false;
- const styles = {
- foo: 'foo-1'
- };
+ const styles = {
+ foo: 'foo-1'
+ };
- Component = class extends React.Component {
- shouldComponentUpdate (newProps) {
- if (rendered) {
- expect(shallowCompare(this.props, newProps)).to.equal(true);
+ Component = class extends React.Component {
+ shouldComponentUpdate (newProps) {
+ if (rendered) {
+ expect(shallowCompare(this.props, newProps)).to.equal(true);
- done();
- }
+ done();
+ }
- return true;
- }
+ return true;
+ }
- render () {
- rendered = true;
- }
- };
+ render () {
+ rendered = true;
+ }
+ };
- Component = extendReactClass(Component, styles);
+ Component = extendReactClass(Component, styles);
- const instance = TestUtils.renderIntoDocument( );
+ const instance = TestUtils.renderIntoDocument( );
- // trigger shouldComponentUpdate
- instance.setState({});
- });
+ // 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('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 element', () => {
- let Component;
+ });
+ context('rendering Component that returns null', () => {
+ it('generates null', () => {
+ let Component;
- const shallowRenderer = TestUtils.createRenderer();
+ const shallowRenderer = TestUtils.createRenderer();
- Component = class extends React.Component {
- render () {
- return null;
- }
- };
+ Component = class extends React.Component {
+ render () {
+ return null;
+ }
+ };
- Component = extendReactClass(Component);
+ Component = extendReactClass(Component);
- shallowRenderer.render( );
+ shallowRenderer.render( );
- const component = shallowRenderer.getRenderOutput();
+ const component = shallowRenderer.getRenderOutput();
- expect(component.type).to.equal('noscript');
- });
+ expect(component).to.equal(null);
});
- context('target component have static properties', () => {
- it('hoists static properties', () => {
- const Component = class extends React.Component {
- static foo = 'FOO';
+ });
+ context('target component have static properties', () => {
+ it('hoists static properties', () => {
+ const Component = class extends React.Component {
+ static foo = 'FOO';
- render () {
- return null;
- }
- };
+ render () {
+ return null;
+ }
+ };
- const WrappedComponent = extendReactClass(Component);
+ const WrappedComponent = extendReactClass(Component);
- expect(Component.foo).to.equal('FOO');
- expect(WrappedComponent.foo).to.equal(Component.foo);
- });
+ expect(Component.foo).to.equal('FOO');
+ expect(WrappedComponent.foo).to.equal(Component.foo);
});
+ });
});
diff --git a/tests/linkClass.js b/tests/linkClass.js
index 5825ef3..cf7c289 100644
--- a/tests/linkClass.js
+++ b/tests/linkClass.js
@@ -1,294 +1,483 @@
-/* eslint-disable max-nested-callbacks, react/prefer-stateless-function */
+/* 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', () => {
+ 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 element properties with a single element child in non-`children` prop', () => {
+ expect(linkClass(
} />)).to.deep.equal(
} />);
+ });
- it('does not affect the className', () => {
- expect(linkClass(
)).to.deep.equal(
);
- });
+ it('does not affect element properties with a single text child', () => {
+ expect(linkClass(test
)).to.deep.equal(test
);
+ });
- 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')
- ]);
+ it('does not affect the className', () => {
+ expect(linkClass(
)).to.deep.equal(
);
+ });
- expect(linkClass(subject)).to.deep.equal(outcome);
- });
+ 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')
+ ]);
- 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);
- });
+ expect(linkClass(subject)).to.deep.equal(outcome);
});
- context('called with null instead of ReactElement', () => {
- it('returns null', () => {
- const subject = linkClass(null);
+ 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);
+ });
+ });
- 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'
});
+
+ 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={[
, [
]]}
+ />;
- context('styleName matches an existing CSS module', () => {
- context('when a descendant element has styleName', () => {
- it('assigns a generated className', () => {
- let subject;
+ subject = linkClass(subject, {
+ bar: 'bar-1',
+ baz: 'baz-1',
+ foo: 'foo-1'
+ });
- subject = ;
+ 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 = linkClass(subject, {
- foo: 'foo-1'
- });
+ subject = ;
- expect(subject.props.children.props.className).to.equal('foo-1');
- });
+ subject = linkClass(subject, {
+ bar: 'bar-1',
+ foo: 'foo-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'
- });
-
- expect(subject.props.children[0].props.className).to.equal('foo-1');
- expect(subject.props.children[1].props.className).to.equal('bar-1');
- });
- it('styleName is reset to null', () => {
- let subject;
-
- subject = ;
-
- subject = linkClass(subject, {
- bar: 'bar-1',
- foo: 'foo-1'
- });
-
- expect(subject.props.children[0].props.styleName).to.equal(null);
- expect(subject.props.children[1].props.styleName).to.equal(null);
- });
+
+ 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'
});
- context('when multiple descendants have styleName and are iterable', () => {
- it('assigns a generated className', () => {
- let subject;
-
- const iterable = {
- 0:
,
- 1:
,
- length: 2,
- /* eslint-disable no-use-extend-native/no-use-extend-native */
- [Symbol.iterator]: Array.prototype[Symbol.iterator]
- /* eslint-enable no-use-extend-native/no-use-extend-native */
- };
-
- subject = {iterable}
;
-
- 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');
- });
+
+ 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 = ;
+
+ subject = linkClass(subject, {
+ bar: 'bar-1',
+ foo: 'foo-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.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 = linkClass(subject, {
- foo: 'foo-1'
- });
+ subject = ;
- expect(subject.props.className).to.deep.equal('foo-1');
- });
+ subject = linkClass(subject, {
+ bar: 'bar-1',
+ 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.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,
+
+ // eslint-disable-next-line no-use-extend-native/no-use-extend-native
+ [Symbol.iterator]: Array.prototype[Symbol.iterator]
+ };
- subject = linkClass(subject, {
- bar: 'bar-1'
- });
+ subject = {iterable}
;
- expect(subject.props.className).to.deep.equal('foo bar-1');
- });
+ 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 includes multiple whitespace characters', () => {
- it('resolves CSS modules', () => {
- 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, {
- bar: 'bar-1',
- foo: 'foo-1'
- }, {
- allowMultiple: true
- });
+ subject =
;
- expect(subject.props.children.props.className).to.equal('foo-1 bar-1');
+ subject = linkClass(subject, {
+ bar: 'bar-1',
+ foo: 'foo-1'
});
+
+ expect(subject.props.els[0].props.styleName).to.equal('foo');
+ expect(subject.props.els[1].props.styleName).to.equal('bar');
+ expect(subject.props.els[0].props).not.to.have.property('className');
+ expect(subject.props.els[1].props).not.to.have.property('className');
+ });
});
+ context('when ReactElement does not have an existing className', () => {
+ it('uses the generated class name to set the className property', () => {
+ let subject;
+
+ 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, {
- bar: 'bar-1',
- foo: 'foo-1'
- }, {
- allowMultiple: true
- });
-
- expect(subject.props.className).to.deep.equal('foo-1 bar-1');
- });
- });
+ subject = linkClass(subject, {
+ foo: 'foo-1'
});
+
+ 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;
- 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 =
;
+
+ subject = linkClass(subject, {
+ bar: 'bar-1'
});
+
+ expect(subject.props.className).to.deep.equal('foo bar-1');
+ });
});
+ });
- context('when ReactElement includes ReactComponent', () => {
- let Foo,
- nodeList;
+ context('styleName includes multiple whitespace characters', () => {
+ it('resolves CSS modules', () => {
+ let subject;
- beforeEach(() => {
- global.document = jsdom.jsdom('');
- global.window = document.defaultView;
+ subject = ;
- Foo = class extends React.Component {
- render () {
- return Hello
;
- }
- };
+ subject = linkClass(subject, {
+ bar: 'bar-1',
+ foo: 'foo-1'
+ }, {
+ allowMultiple: true
+ });
- 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('');
- });
+ expect(subject.props.children.props.className).to.equal('foo-1 bar-1');
});
+ });
- it('unsets styleName property of the target element', () => {
+ context('can\'t write to properties', () => {
+ context('when the element is frozen', () => {
+ it('adds className but is still frozen', () => {
let subject;
- subject =
;
+ subject =
;
+ Object.freeze(subject);
subject = linkClass(subject, {
- foo: 'foo-1'
+ foo: 'foo-1'
});
- expect(subject.props.className).to.deep.equal('foo-1');
- expect(subject.props.styleName).to.deep.equal(null);
+ 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;
+
+ subject =
;
- it('unsets styleName property of the target element (deep)', () => {
+ 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;
- subject = ;
+ 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;
+
+ 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.');
+ });
+ });
+ 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();
+ });
+ });
+ 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
;
+ }
+ };
- expect(subject.props.children[0].props.className).to.deep.equal('bar-1');
- expect(subject.props.children[0].props.styleName).to.deep.equal(null);
+ 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 815fe52..4193311 100644
--- a/tests/makeConfiguration.js
+++ b/tests/makeConfiguration.js
@@ -3,41 +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', () => {
- const userConfiguration = {};
+ });
+ it('does not mutate user configuration', () => {
+ const userConfiguration = {};
- makeConfiguration(userConfiguration);
+ makeConfiguration(userConfiguration);
- expect(userConfiguration).to.deep.equal({});
- });
+ expect(userConfiguration).to.deep.equal({});
+ });
});
diff --git a/tests/reactCssModules.js b/tests/reactCssModules.js
index 16b1f47..d484d95 100644
--- a/tests/reactCssModules.js
+++ b/tests/reactCssModules.js
@@ -1,9 +1,8 @@
-/* eslint-disable max-nested-callbacks, react/no-multi-comp, react/prop-types, react/prefer-stateless-function */
+/* 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';
@@ -11,164 +10,175 @@ 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 {};
+ Foo = class extends React.Component {};
- // @todo https://phabricator.babeljs.io/T2779
- Foo.displayName = 'Bar';
+ // @todo https://phabricator.babeljs.io/T2779
+ Foo.displayName = 'Bar';
- Foo = reactCssModules(Foo);
+ Foo = reactCssModules(Foo);
- expect(Foo.displayName).to.equal('Bar');
- });
- context('target component does not name displayName', () => {
- it('uses name for displayName', () => {
- let Foo;
+ expect(Foo.displayName).to.equal('Bar');
+ });
+ context('target component does not name displayName', () => {
+ it('uses name for displayName', () => {
+ let Foo;
- Foo = class Bar extends React.Component {};
+ Foo = class Bar extends React.Component {};
- Foo = reactCssModules(Foo);
+ Foo = reactCssModules(Foo);
- expect(Foo.displayName).to.equal('Bar');
- });
- });
+ 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;
-
- const shallowRenderer = TestUtils.createRenderer();
+ });
+ 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();
- const 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;
-
- const shallowRenderer = TestUtils.createRenderer();
-
- Foo = () => {
- return Hello
;
- };
-
- Foo = reactCssModules(Foo, {
- foo: 'foo-1'
- });
-
- shallowRenderer.render( );
- const component = shallowRenderer.getRenderOutput();
+ shallowRenderer.render( );
- expect(component.props.className).to.equal('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;
+ });
+ 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'
+ });
- const shallowRenderer = TestUtils.createRenderer();
+ shallowRenderer.render( );
- Foo = class extends React.Component {
- render () {
- return null;
- }
- };
+ const component = shallowRenderer.getRenderOutput();
- Foo = reactCssModules(Foo, {
- foo: '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;
- shallowRenderer.render( );
+ const shallowRenderer = TestUtils.createRenderer();
- const component = shallowRenderer.getRenderOutput();
+ Foo = () => {
+ return null;
+ };
- expect(typeof component).to.equal('object');
- });
+ Foo = reactCssModules(Foo, {
+ foo: 'foo-1'
});
- context('the component is a stateless function component', () => {
- it('that element should contain the equivalent className', () => {
- let Foo;
- const shallowRenderer = TestUtils.createRenderer();
+ shallowRenderer.render( );
- Foo = () => {
- return null;
- };
+ const component = shallowRenderer.getRenderOutput();
- Foo = reactCssModules(Foo, {
- foo: 'foo-1'
- });
+ expect(typeof component).to.equal('object');
+ });
+ });
+ });
+ context('rendering element', () => {
+ beforeEach(() => {
+ global.document = jsdom.jsdom('');
- shallowRenderer.render( );
+ 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'
+ });
- const component = shallowRenderer.getRenderOutput();
+ Bar = class extends React.Component {
+ render () {
+ return {this.props.children}
;
+ }
+ };
- expect(typeof component).to.equal('object');
- });
+ Bar = reactCssModules(Bar, {
+ test: 'bar-0'
});
- });
- context('rendering element', () => {
- beforeEach(() => {
- global.document = jsdom.jsdom('');
- 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,
- Foo,
- subject;
-
- Foo = class extends React.Component {
- render () {
- return
- foo
- ;
- }
- };
-
- Foo = reactCssModules(Foo, {
- test: 'foo-0'
- });
-
- Bar = class extends React.Component {
- render () {
- return {this.props.children}
;
- }
- };
-
- Bar = reactCssModules(Bar, {
- test: 'bar-0'
- });
-
- subject = TestUtils.renderIntoDocument( );
-
- subject = ReactDOM.findDOMNode(subject);
-
- expect(subject.firstChild.className).to.equal('foo-0');
- });
- });
+ subject = TestUtils.renderIntoDocument( );
+
+ // eslint-disable-next-line react/no-find-dom-node
+ subject = ReactDOM.findDOMNode(subject);
+
+ 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 6472cca..9c71935 100644
--- a/tests/wrapStatelessFunction.js
+++ b/tests/wrapStatelessFunction.js
@@ -8,75 +8,85 @@ 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', () => {
- const styles = {
- foo: 'foo-1'
- };
+ it('hoists static own properties from the input component to the wrapped component', () => {
+ const styles = {
+ foo: 'foo-1'
+ };
- const Component = function InnerComponent () {
- return null;
- };
+ const InnerComponent = () => {
+ return null;
+ };
- Component.propTypes = {};
- Component.defaultProps = {};
+ InnerComponent.propTypes = {};
+ InnerComponent.defaultProps = {};
- const 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);
+ 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'
+ };
+
+ wrapStatelessFunction((props) => {
+ expect(props.styles).to.equal(styles);
+ done();
+ }, styles)();
});
- context('using default styles', () => {
- it('exposes styles through styles property', (done) => {
- const styles = {
- foo: 'foo-1'
- };
+ 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) => {
- const styles = {
- foo: 'foo-1'
- };
+ 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'
+ };
- wrapStatelessFunction((props) => {
- expect(props.bar).to.equal('baz');
- done();
- }, styles)({
- bar: 'baz'
- });
- });
+ 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'
- };
+ });
+ context('using explicit styles', () => {
+ it('exposes styles through styles property', (done) => {
+ const styles = {
+ foo: 'foo-1'
+ };
- wrapStatelessFunction((props) => {
- expect(props.styles).to.equal(styles);
- done();
- })({
- styles
- });
- });
+ wrapStatelessFunction((props) => {
+ expect(props.styles).to.equal(styles);
+ done();
+ })({
+ styles
+ });
});
- context('rendering Component that returns null', () => {
- it('generates element', () => {
- const shallowRenderer = TestUtils.createRenderer();
+ });
+ context('rendering Component that returns null', () => {
+ it('generates null', () => {
+ const shallowRenderer = TestUtils.createRenderer();
- const Component = wrapStatelessFunction(() => {
- return null;
- });
+ const Component = wrapStatelessFunction(() => {
+ return null;
+ });
- shallowRenderer.render( );
+ shallowRenderer.render( );
- const component = shallowRenderer.getRenderOutput();
+ const component = shallowRenderer.getRenderOutput();
- expect(component.type).to.equal('noscript');
- });
+ expect(component).to.equal(null);
});
+ });
});