diff --git a/.README/react-css-modules.png b/.README/react-css-modules.png deleted file mode 100644 index 2614951..0000000 Binary files a/.README/react-css-modules.png and /dev/null differ diff --git a/.README/react-css-modules.sketch b/.README/react-css-modules.sketch deleted file mode 100644 index a014d75..0000000 Binary files a/.README/react-css-modules.sketch and /dev/null differ diff --git a/.babelrc b/.babelrc index 3751ba8..f651443 100644 --- a/.babelrc +++ b/.babelrc @@ -1,19 +1,14 @@ { + "presets": [ + "es2015", + "stage-0", + "react" + ], "plugins": [ "add-module-exports", "lodash", "transform-class-properties", - [ - "transform-es2015-classes", - { - "loose": true - } - ], + ["transform-es2015-classes", { "loose": true }], "transform-proto-to-assign" - ], - "presets": [ - "es2015", - "stage-0", - "react" ] -} +} \ No newline at end of file diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 0f17867..0000000 --- a/.editorconfig +++ /dev/null @@ -1,9 +0,0 @@ -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 index bcaad1b..84b918b 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,6 +1,3 @@ { - "extends": [ - "canonical", - "canonical/mocha" - ] -} + "extends": "canonical" +} \ No newline at end of file diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 2f093a7..0000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,2 +0,0 @@ -github: gajus -patreon: gajus diff --git a/.gitignore b/.gitignore old mode 100755 new mode 100644 index df978de..bc32025 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,39 @@ -node_modules -coverage -dist +# Created by .ignore support plugin (hsz.mobi) +.idea/workspace.xml +.idea/tasks.xml +.idea/dictionaries +.idea/vcs.xml +.idea/jsLibraryMappings.xml +.idea/dataSources.ids +.idea/dataSources.xml +.idea/dataSources.local.xml +.idea/sqlDataSources.xml +.idea/dynamic.xml +.idea/uiDesigner.xml +.idea/gradle.xml +.idea/libraries +.idea/mongoSettings.xml +*.iws +/out/ +.idea_modules/ +atlassian-ide-plugin.xml +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties +logs *.log -.* -!.babelrc -!.editorconfig -!.eslintrc -!.gitignore -!.npmignore -!.README -!.travis.yml +npm-debug.log* +pids +*.pid +*.seed +lib-cov +coverage +.nyc_output +.grunt +.lock-wscript +build/Release +node_modules +jspm_packages +.npm +.node_repl_history diff --git a/.npmignore b/.npmignore deleted file mode 100755 index e8add85..0000000 --- a/.npmignore +++ /dev/null @@ -1,5 +0,0 @@ -src -tests -coverage -.* -*.log diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index a786167..0000000 --- a/.travis.yml +++ /dev/null @@ -1,14 +0,0 @@ -language: node_js -node_js: - - node - - 8 -before_install: - - npm config set depth 0 -notifications: - 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 a6b7399..5c8c0ef 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ # React CSS Modules -[![GitSpo Mentions](https://gitspo.com/badges/mentions/gajus/react-css-modules?style=flat-square)](https://gitspo.com/mentions/gajus/react-css-modules) [![Travis build status](http://img.shields.io/travis/gajus/react-css-modules/master.svg?style=flat-square)](https://travis-ci.org/gajus/react-css-modules) [![NPM version](http://img.shields.io/npm/v/react-css-modules.svg?style=flat-square)](https://www.npmjs.org/package/react-css-modules) [![js-canonical-style](https://img.shields.io/badge/code%20style-canonical-blue.svg?style=flat-square)](https://github.com/gajus/canonical) @@ -9,16 +8,6 @@ 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) @@ -35,18 +24,19 @@ React CSS Modules implement automatic mapping of CSS modules. Every CSS class is - [Decorator](#decorator) - [Options](#options) - [`allowMultiple`](#allowmultiple) - - [`handleNotFoundStyleName`](#handlenotfoundstylename) + - [`errorWhenNotFound`](#errorwhennotfound) - [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 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 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/). In the context of React, CSS Modules look like this: @@ -71,7 +61,7 @@ Rendering the component will produce a markup similar to: ```js
-
A0
+
A0
B0
@@ -83,7 +73,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 the 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 as existing CSS Modules implementation webpack [css-loader](https://github.com/webpack/css-loader#css-modules). ## What's the Problem? @@ -125,7 +115,7 @@ Using `react-css-modules`:
``` -* You are warned when `styleName` refers to an undefined CSS Module ([`handleNotFoundStyleName`](#handlenotfoundstylename) option). +* You are warned when `styleName` refers to an undefined CSS Module ([`errorWhenNotFound`](#errorwhennotfound) option). * You can enforce use of a single CSS module per `ReactElement` ([`allowMultiple`](#allowmultiple) option). ## The Implementation @@ -159,8 +149,8 @@ Setup: { test: /\.css$/, loaders: [ - 'style-loader?sourceMap', - 'css-loader?modules&importLoaders=1&localIdentName=[path]___[name]__[local]___[hash:base64:5]' + 'style?sourceMap', + 'css?modules&importLoaders=1&localIdentName=[path]___[name]__[local]___[hash:base64:5]' ] } ``` @@ -192,48 +182,22 @@ 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]"' - }), - } - ``` +```js +{ + test: /\.css$/, + loader: ExtractTextPlugin.extract('style', 'css?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]') +} +``` * Setup `extract-text-webpack-plugin` plugin: - * ExtractTextPlugin v1x: - - ```js - new ExtractTextPlugin('app.css', { - allChunks: true - }) - ``` - - * ExtractTextPlugin v2x: - - ```js - new ExtractTextPlugin({ - filename: 'app.css', - allChunks: true - }) - ``` +```js +new ExtractTextPlugin('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. @@ -406,7 +370,7 @@ export default CSSModules(CustomList, styles); * @typedef CSSModules~Options * @see {@link https://github.com/gajus/react-css-modules#options} * @property {Boolean} allowMultiple - * @property {String} handleNotFoundStyleName + * @property {Boolean} errorWhenNotFound */ /** @@ -490,17 +454,11 @@ When `false`, the following will cause an error:
``` -#### `handleNotFoundStyleName` - -Default: `throw`. - -Defines the desired action when `styleName` cannot be mapped to an existing CSS Module. +#### `errorWhenNotFound` -Available options: +Default: `true`. -* `throw` throws an error -* `log` logs a warning using `console.warn` -* `ignore` silently ignores the missing style name +Throws an error when `styleName` cannot be mapped to an existing CSS Module. ## SASS, SCSS, LESS and other CSS Preprocessors @@ -590,7 +548,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 composing 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 composting 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 new file mode 100644 index 0000000..a0a2688 --- /dev/null +++ b/dist/extendReactClass.js @@ -0,0 +1,91 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _assign2 = require('lodash/assign'); + +var _assign3 = _interopRequireDefault(_assign2); + +var _isObject2 = require('lodash/isObject'); + +var _isObject3 = _interopRequireDefault(_isObject2); + +var _react = require('react'); + +var _react2 = _interopRequireDefault(_react); + +var _hoistNonReactStatics = require('hoist-non-react-statics'); + +var _hoistNonReactStatics2 = _interopRequireDefault(_hoistNonReactStatics); + +var _linkClass = require('./linkClass'); + +var _linkClass2 = _interopRequireDefault(_linkClass); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _defaults(obj, defaults) { var keys = Object.getOwnPropertyNames(defaults); for (var i = 0; i < keys.length; i++) { var key = keys[i]; var value = Object.getOwnPropertyDescriptor(defaults, key); if (value && value.configurable && obj[key] === undefined) { Object.defineProperty(obj, key, value); } } return obj; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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) : _defaults(subClass, superClass); } /* eslint-disable react/prop-types */ + +/** + * @param {ReactClass} Component + * @param {Object} defaultStyles + * @param {Object} options + * @returns {ReactClass} + */ +exports.default = function (Component, defaultStyles, options) { + var WrappedComponent = function (_Component) { + _inherits(WrappedComponent, _Component); + + function WrappedComponent() { + _classCallCheck(this, WrappedComponent); + + return _possibleConstructorReturn(this, _Component.apply(this, arguments)); + } + + WrappedComponent.prototype.render = function render() { + var propsChanged = void 0, + styles = void 0; + + propsChanged = false; + + if (this.props.styles) { + styles = this.props.styles; + } else if ((0, _isObject3.default)(defaultStyles)) { + this.props = (0, _assign3.default)({}, this.props, { + styles: defaultStyles + }); + + propsChanged = true; + styles = defaultStyles; + } else { + styles = {}; + } + + var renderResult = _Component.prototype.render.call(this); + + if (propsChanged) { + delete this.props.styles; + } + + if (renderResult) { + return (0, _linkClass2.default)(renderResult, styles, options); + } + + return _react2.default.createElement('noscript'); + }; + + return WrappedComponent; + }(Component); + + return (0, _hoistNonReactStatics2.default)(WrappedComponent, Component); +}; + +module.exports = exports['default']; \ No newline at end of file diff --git a/dist/generateAppendClassName.js b/dist/generateAppendClassName.js new file mode 100644 index 0000000..295c566 --- /dev/null +++ b/dist/generateAppendClassName.js @@ -0,0 +1,53 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _simpleMap = require('./simple-map'); + +var _simpleMap2 = _interopRequireDefault(_simpleMap); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +var stylesIndex = new _simpleMap2.default(); + +exports.default = function (styles, styleNames, errorWhenNotFound) { + var appendClassName = void 0, + stylesIndexMap = void 0; + + stylesIndexMap = stylesIndex.get(styles); + + if (stylesIndexMap) { + var styleNameIndex = stylesIndexMap.get(styleNames); + + if (styleNameIndex) { + return styleNameIndex; + } + } else { + stylesIndexMap = new _simpleMap2.default(); + stylesIndex.set(styles, new _simpleMap2.default()); + } + + appendClassName = ''; + + for (var styleName in styleNames) { + if (styleNames.hasOwnProperty(styleName)) { + var className = styles[styleNames[styleName]]; + + if (className) { + appendClassName += ' ' + className; + } else if (errorWhenNotFound === true) { + throw new Error('"' + styleNames[styleName] + '" CSS module is undefined.'); + } + } + } + + appendClassName = appendClassName.trim(); + + stylesIndexMap.set(styleNames, appendClassName); + + return appendClassName; +}; + +module.exports = exports['default']; \ No newline at end of file diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..552029b --- /dev/null +++ b/dist/index.js @@ -0,0 +1,77 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _isFunction2 = require('lodash/isFunction'); + +var _isFunction3 = _interopRequireDefault(_isFunction2); + +var _extendReactClass = require('./extendReactClass'); + +var _extendReactClass2 = _interopRequireDefault(_extendReactClass); + +var _wrapStatelessFunction = require('./wrapStatelessFunction'); + +var _wrapStatelessFunction2 = _interopRequireDefault(_wrapStatelessFunction); + +var _makeConfiguration = require('./makeConfiguration'); + +var _makeConfiguration2 = _interopRequireDefault(_makeConfiguration); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +/** + * Determines if the given object has the signature of a class that inherits React.Component. + */ + + +/** + * @see https://github.com/gajus/react-css-modules#options + */ +var isReactComponent = function isReactComponent(maybeReactComponent) { + return 'prototype' in maybeReactComponent && (0, _isFunction3.default)(maybeReactComponent.prototype.render); +}; + +/** + * When used as a function. + */ +var functionConstructor = function functionConstructor(Component, defaultStyles, options) { + var decoratedClass = void 0; + + var configuration = (0, _makeConfiguration2.default)(options); + + if (isReactComponent(Component)) { + decoratedClass = (0, _extendReactClass2.default)(Component, defaultStyles, configuration); + } else { + decoratedClass = (0, _wrapStatelessFunction2.default)(Component, defaultStyles, configuration); + } + + if (Component.displayName) { + decoratedClass.displayName = Component.displayName; + } else { + decoratedClass.displayName = Component.name; + } + + return decoratedClass; +}; + +/** + * When used as a ES7 decorator. + */ +var decoratorConstructor = function decoratorConstructor(defaultStyles, options) { + return function (Component) { + return functionConstructor(Component, defaultStyles, options); + }; +}; + +exports.default = function () { + if ((0, _isFunction3.default)(arguments.length <= 0 ? undefined : arguments[0])) { + return functionConstructor(arguments.length <= 0 ? undefined : arguments[0], arguments.length <= 1 ? undefined : arguments[1], arguments.length <= 2 ? undefined : arguments[2]); + } else { + return decoratorConstructor(arguments.length <= 0 ? undefined : arguments[0], arguments.length <= 1 ? undefined : arguments[1]); + } +}; + +module.exports = exports['default']; \ No newline at end of file diff --git a/dist/isIterable.js b/dist/isIterable.js new file mode 100644 index 0000000..d3d33d5 --- /dev/null +++ b/dist/isIterable.js @@ -0,0 +1,41 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _isObject2 = require('lodash/isObject'); + +var _isObject3 = _interopRequireDefault(_isObject2); + +var _isFunction2 = require('lodash/isFunction'); + +var _isFunction3 = _interopRequireDefault(_isFunction2); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +var ITERATOR_SYMBOL = typeof Symbol !== 'undefined' && (0, _isFunction3.default)(Symbol) && Symbol.iterator; +var OLD_ITERATOR_SYMBOL = '@@iterator'; + +/** + * @see https://github.com/lodash/lodash/issues/1668 + * @see https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Iteration_protocols + */ + +exports.default = function (maybeIterable) { + var iterator = void 0; + + if (!(0, _isObject3.default)(maybeIterable)) { + return false; + } + + if (ITERATOR_SYMBOL) { + iterator = maybeIterable[ITERATOR_SYMBOL]; + } else { + iterator = maybeIterable[OLD_ITERATOR_SYMBOL]; + } + + return (0, _isFunction3.default)(iterator); +}; + +module.exports = exports['default']; \ No newline at end of file diff --git a/dist/linkClass.js b/dist/linkClass.js new file mode 100644 index 0000000..c73b88c --- /dev/null +++ b/dist/linkClass.js @@ -0,0 +1,106 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _isObject2 = require('lodash/isObject'); + +var _isObject3 = _interopRequireDefault(_isObject2); + +var _isArray2 = require('lodash/isArray'); + +var _isArray3 = _interopRequireDefault(_isArray2); + +var _react = require('react'); + +var _react2 = _interopRequireDefault(_react); + +var _objectUnfreeze = require('object-unfreeze'); + +var _objectUnfreeze2 = _interopRequireDefault(_objectUnfreeze); + +var _isIterable = require('./isIterable'); + +var _isIterable2 = _interopRequireDefault(_isIterable); + +var _parseStyleName = require('./parseStyleName'); + +var _parseStyleName2 = _interopRequireDefault(_parseStyleName); + +var _generateAppendClassName = require('./generateAppendClassName'); + +var _generateAppendClassName2 = _interopRequireDefault(_generateAppendClassName); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +var linkElement = function linkElement(element, styles, configuration) { + var appendClassName = void 0, + elementIsFrozen = void 0, + elementShallowCopy = void 0; + + elementShallowCopy = element; + + if (Object.isFrozen && Object.isFrozen(elementShallowCopy)) { + elementIsFrozen = true; + + // https://github.com/facebook/react/blob/v0.13.3/src/classic/element/ReactElement.js#L131 + elementShallowCopy = (0, _objectUnfreeze2.default)(elementShallowCopy); + elementShallowCopy.props = (0, _objectUnfreeze2.default)(elementShallowCopy.props); + } + + var styleNames = (0, _parseStyleName2.default)(elementShallowCopy.props.styleName || '', configuration.allowMultiple); + + if (_react2.default.isValidElement(elementShallowCopy.props.children)) { + elementShallowCopy.props.children = linkElement(_react2.default.Children.only(elementShallowCopy.props.children), styles, configuration); + } else if ((0, _isArray3.default)(elementShallowCopy.props.children) || (0, _isIterable2.default)(elementShallowCopy.props.children)) { + elementShallowCopy.props.children = _react2.default.Children.map(elementShallowCopy.props.children, function (node) { + if (_react2.default.isValidElement(node)) { + return linkElement(node, styles, configuration); + } else { + return node; + } + }); + } + + if (styleNames.length) { + appendClassName = (0, _generateAppendClassName2.default)(styles, styleNames, configuration.errorWhenNotFound); + + if (appendClassName) { + if (elementShallowCopy.props.className) { + appendClassName = elementShallowCopy.props.className + ' ' + appendClassName; + } + + elementShallowCopy.props.className = appendClassName; + } + } + + delete elementShallowCopy.props.styleName; + + if (elementIsFrozen) { + Object.freeze(elementShallowCopy.props); + Object.freeze(elementShallowCopy); + } + + return elementShallowCopy; +}; + +/** + * @param {ReactElement} element + * @param {Object} styles CSS modules class map. + * @param {CSSModules~Options} configuration + */ + +exports.default = function (element) { + var styles = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + var configuration = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; + + // @see https://github.com/gajus/react-css-modules/pull/30 + if (!(0, _isObject3.default)(element)) { + return element; + } + + return linkElement(element, styles, configuration); +}; + +module.exports = exports['default']; \ No newline at end of file diff --git a/dist/makeConfiguration.js b/dist/makeConfiguration.js new file mode 100644 index 0000000..a8bbc81 --- /dev/null +++ b/dist/makeConfiguration.js @@ -0,0 +1,55 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _isBoolean2 = require('lodash/isBoolean'); + +var _isBoolean3 = _interopRequireDefault(_isBoolean2); + +var _isUndefined2 = require('lodash/isUndefined'); + +var _isUndefined3 = _interopRequireDefault(_isUndefined2); + +var _forEach2 = require('lodash/forEach'); + +var _forEach3 = _interopRequireDefault(_forEach2); + +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 + * @returns {CSSModules~Options} + */ +exports.default = function () { + var userConfiguration = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + var configuration = { + allowMultiple: false, + errorWhenNotFound: true + }; + + (0, _forEach3.default)(userConfiguration, function (value, name) { + if ((0, _isUndefined3.default)(configuration[name])) { + throw new Error('Unknown configuration property "' + name + '".'); + } + + if (!(0, _isBoolean3.default)(value)) { + throw new Error('"' + name + '" property value must be a boolean.'); + } + + configuration[name] = value; + }); + + return configuration; +}; + +module.exports = exports['default']; \ No newline at end of file diff --git a/dist/parseStyleName.js b/dist/parseStyleName.js new file mode 100644 index 0000000..8a8be42 --- /dev/null +++ b/dist/parseStyleName.js @@ -0,0 +1,38 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _filter2 = require('lodash/filter'); + +var _filter3 = _interopRequireDefault(_filter2); + +var _trim2 = require('lodash/trim'); + +var _trim3 = _interopRequireDefault(_trim2); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +var styleNameIndex = {}; + +exports.default = function (styleNamePropertyValue, allowMultiple) { + var styleNames = void 0; + + if (styleNameIndex[styleNamePropertyValue]) { + styleNames = styleNameIndex[styleNamePropertyValue]; + } else { + styleNames = (0, _trim3.default)(styleNamePropertyValue).split(' '); + styleNames = (0, _filter3.default)(styleNames); + + styleNameIndex[styleNamePropertyValue] = styleNames; + } + + if (allowMultiple === false && styleNames.length > 1) { + throw new Error('ReactElement styleName property defines multiple module names ("' + styleNamePropertyValue + '").'); + } + + return styleNames; +}; + +module.exports = exports['default']; \ No newline at end of file diff --git a/dist/simple-map.js b/dist/simple-map.js new file mode 100644 index 0000000..33db5e6 --- /dev/null +++ b/dist/simple-map.js @@ -0,0 +1,44 @@ +'use strict'; + +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; }; }(); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var SimpleMap = exports.SimpleMap = function () { + function SimpleMap() { + _classCallCheck(this, SimpleMap); + + this.keys = []; + this.values = []; + } + + SimpleMap.prototype.get = function get(key) { + var index = this.keys.indexOf(key); + + return this.values[index]; + }; + + SimpleMap.prototype.set = function set(key, value) { + this.keys.push(key); + this.values.push(value); + + return value; + }; + + _createClass(SimpleMap, [{ + key: 'size', + get: function get() { + return this.keys.length; + } + }]); + + return SimpleMap; +}(); + +var exportedMap = typeof Map === 'undefined' ? SimpleMap : Map; + +exports.default = exportedMap; \ No newline at end of file diff --git a/dist/wrapStatelessFunction.js b/dist/wrapStatelessFunction.js new file mode 100644 index 0000000..9948dc2 --- /dev/null +++ b/dist/wrapStatelessFunction.js @@ -0,0 +1,69 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _assign2 = require('lodash/assign'); + +var _assign3 = _interopRequireDefault(_assign2); + +var _isObject2 = require('lodash/isObject'); + +var _isObject3 = _interopRequireDefault(_isObject2); + +var _react = require('react'); + +var _react2 = _interopRequireDefault(_react); + +var _linkClass = require('./linkClass'); + +var _linkClass2 = _interopRequireDefault(_linkClass); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +/** + * @see https://facebook.github.io/react/blog/2015/09/10/react-v0.14-rc1.html#stateless-function-components + */ +/* eslint-disable react/prop-types */ + +exports.default = function (Component, defaultStyles, options) { + var WrappedComponent = function WrappedComponent() { + 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 styles = void 0, + useProps = void 0; + + if (props.styles) { + useProps = props; + styles = props.styles; + } else if ((0, _isObject3.default)(defaultStyles)) { + useProps = (0, _assign3.default)({}, props, { + styles: defaultStyles + }); + + styles = defaultStyles; + } else { + useProps = props; + styles = {}; + } + + var renderResult = Component.apply(undefined, [useProps].concat(args)); + + if (renderResult) { + return (0, _linkClass2.default)(renderResult, styles, options); + } + + return _react2.default.createElement('noscript'); + }; + + (0, _assign3.default)(WrappedComponent, Component); + + return WrappedComponent; +}; + +module.exports = exports['default']; \ No newline at end of file diff --git a/package.json b/package.json index 00e1ffa..383dd4b 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/index.js", + "main": "./dist/", "repository": { "type": "git", "url": "https://github.com/gajus/react-css-modules" @@ -12,7 +12,7 @@ "css", "modules" ], - "version": "4.3.0", + "version": "3.7.9", "author": { "name": "Gajus Kuizinas", "email": "gajus@gajus.com", @@ -20,36 +20,32 @@ }, "license": "BSD-3-Clause", "dependencies": { - "hoist-non-react-statics": "^2.5.5", - "lodash": "^4.16.6", - "object-unfreeze": "^1.1.0" + "hoist-non-react-statics": "^1.0.5", + "lodash": "^4.6.1", + "object-unfreeze": "^1.0.2" }, "devDependencies": { - "babel-cli": "^6.18.0", + "babel-cli": "^6.10.1", "babel-plugin-add-module-exports": "^0.2.1", - "babel-plugin-lodash": "^3.2.9", + "babel-plugin-lodash": "^3.2.5", "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" + "babel-preset-es2015": "^6.9.0", + "babel-preset-react": "^6.11.1", + "babel-preset-stage-0": "^6.5.0", + "babel-register": "^6.9.0", + "chai": "^3.5.0", + "eslint": "^3.0.0", + "eslint-config-canonical": "^1.7.12", + "jsdom": "^8.1.0", + "mocha": "^2.5.3", + "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" }, "scripts": { "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" + "test": "mocha --compilers js:babel-register ./tests/**/*.js", + "build": "babel ./src --out-dir ./dist" } -} +} \ No newline at end of file diff --git a/src/SimpleMap.js b/src/SimpleMap.js deleted file mode 100644 index 7cf618b..0000000 --- a/src/SimpleMap.js +++ /dev/null @@ -1,21 +0,0 @@ -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 4741bea..b5cfb2d 100644 --- a/src/extendReactClass.js +++ b/src/extendReactClass.js @@ -1,10 +1,9 @@ /* eslint-disable react/prop-types */ -import _ from 'lodash'; import React from 'react'; +import _ from 'lodash'; import hoistNonReactStatics from 'hoist-non-react-statics'; import linkClass from './linkClass'; -import renderNothing from './renderNothing'; /** * @param {ReactClass} Component @@ -13,62 +12,39 @@ import renderNothing from './renderNothing'; * @returns {ReactClass} */ export default (Component: Object, defaultStyles: Object, options: Object) => { - const WrappedComponent = class extends Component { - render () { - let styles; - - const hasDefaultstyles = _.isObject(defaultStyles); + const WrappedComponent = class extends Component { + render () { + let propsChanged, + styles; - 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 = false; - Object.defineProperty(props, 'styles', { - configurable: true, - enumerable: false, - value: styles, - writable: false - }); + if (this.props.styles) { + styles = this.props.styles; + } else if (_.isObject(defaultStyles)) { + this.props = _.assign({}, this.props, { + styles: defaultStyles + }); - const originalProps = this.props; + propsChanged = true; + styles = defaultStyles; + } else { + styles = {}; + } - let renderIsSuccessful = false; + const renderResult = super.render(); - try { - this.props = props; + if (propsChanged) { + delete this.props.styles; + } - renderResult = super.render(); + if (renderResult) { + return linkClass(renderResult, styles, options); + } - renderIsSuccessful = true; - } finally { - this.props = originalProps; + return React.createElement('noscript'); } + }; - // @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 127aa2a..3bbb23f 100644 --- a/src/generateAppendClassName.js +++ b/src/generateAppendClassName.js @@ -1,49 +1,41 @@ -import SimpleMap from './SimpleMap'; +import Map from './simple-map'; -const CustomMap = typeof Map === 'undefined' ? SimpleMap : Map; +const stylesIndex = new Map(); -const stylesIndex = new CustomMap(); +export default (styles, styleNames: Array, errorWhenNotFound: boolean): string => { + let appendClassName, + stylesIndexMap; -export default (styles, styleNames: Array, handleNotFoundStyleName: "throw" | "log" | "ignore"): string => { - let appendClassName; - let stylesIndexMap; + stylesIndexMap = stylesIndex.get(styles); - stylesIndexMap = stylesIndex.get(styles); + if (stylesIndexMap) { + const styleNameIndex = stylesIndexMap.get(styleNames); - 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 (styleNameIndex) { + return styleNameIndex; } - if (handleNotFoundStyleName === 'log') { - // eslint-disable-next-line no-console - console.warn('"' + styleNames[styleName] + '" CSS module is undefined.'); + } else { + stylesIndexMap = new Map(); + stylesIndex.set(styles, new Map()); + } + + appendClassName = ''; + + 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.'); + } } - } } - } - 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 b913b29..7ee7e4e 100644 --- a/src/index.js +++ b/src/index.js @@ -12,45 +12,45 @@ 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: TypeOptions): Function => { - let decoratedClass; + let decoratedClass; - const configuration = makeConfiguration(options); + const configuration = makeConfiguration(options); - if (isReactComponent(Component)) { - decoratedClass = extendReactClass(Component, defaultStyles, configuration); - } else { - decoratedClass = wrapStatelessFunction(Component, defaultStyles, configuration); - } + 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; - } + if (Component.displayName) { + decoratedClass.displayName = Component.displayName; + } else { + decoratedClass.displayName = Component.name; + } - return decoratedClass; + return decoratedClass; }; /** * When used as a ES7 decorator. */ const decoratorConstructor = (defaultStyles: Object, options: TypeOptions): Function => { - return (Component: Function) => { - return functionConstructor(Component, defaultStyles, options); - }; + 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 67d0a94..2c800b1 100644 --- a/src/isIterable.js +++ b/src/isIterable.js @@ -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 4ed3975..ea199e9 100644 --- a/src/linkClass.js +++ b/src/linkClass.js @@ -7,88 +7,55 @@ 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); - } - }); - - return array; -}; - const linkElement = (element: ReactElement, styles: Object, configuration: Object): ReactElement => { - let appendClassName; - let elementShallowCopy; - - elementShallowCopy = element; + let appendClassName, + elementIsFrozen, + elementShallowCopy; - if (Array.isArray(elementShallowCopy)) { - return elementShallowCopy.map((arrayElement) => { - return linkElement(arrayElement, styles, configuration); - }); - } + elementShallowCopy = element; - const elementIsFrozen = Object.isFrozen && Object.isFrozen(elementShallowCopy); - const propsFrozen = Object.isFrozen && Object.isFrozen(elementShallowCopy.props); - const propsNotExtensible = Object.isExtensible && !Object.isExtensible(elementShallowCopy.props); + if (Object.isFrozen && Object.isFrozen(elementShallowCopy)) { + elementIsFrozen = true; - 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); - } + // https://github.com/facebook/react/blob/v0.13.3/src/classic/element/ReactElement.js#L131 + elementShallowCopy = objectUnfreeze(elementShallowCopy); + elementShallowCopy.props = objectUnfreeze(elementShallowCopy.props); + } - _.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); + 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 (styleNames.length) { - appendClassName = generateAppendClassName(styles, styleNames, configuration.handleNotFoundStyleName); + if (styleNames.length) { + appendClassName = generateAppendClassName(styles, styleNames, configuration.errorWhenNotFound); - if (appendClassName) { - if (elementShallowCopy.props.className) { - appendClassName = elementShallowCopy.props.className + ' ' + appendClassName; - } + if (appendClassName) { + if (elementShallowCopy.props.className) { + appendClassName = elementShallowCopy.props.className + ' ' + appendClassName; + } - elementShallowCopy.props.className = appendClassName; + elementShallowCopy.props.className = appendClassName; + } } - } - delete elementShallowCopy.props.styleName; + 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); - } + if (elementIsFrozen) { + Object.freeze(elementShallowCopy.props); + Object.freeze(elementShallowCopy); + } - return elementShallowCopy; + return elementShallowCopy; }; /** @@ -98,9 +65,9 @@ const linkElement = (element: ReactElement, styles: Object, configuration: Objec */ export default (element: ReactElement, styles = {}, configuration = {}): ReactElement => { // @see https://github.com/gajus/react-css-modules/pull/30 - if (!_.isObject(element)) { - return element; - } + 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 cc3169c..83b2d41 100644 --- a/src/makeConfiguration.js +++ b/src/makeConfiguration.js @@ -4,7 +4,7 @@ import _ from 'lodash'; * @typedef CSSModules~Options * @see {@link https://github.com/gajus/react-css-modules#options} * @property {boolean} allowMultiple - * @property {string} handleNotFoundStyleName + * @property {boolean} errorWhenNotFound */ /** @@ -12,26 +12,22 @@ import _ from 'lodash'; * @returns {CSSModules~Options} */ export default (userConfiguration = {}) => { - const configuration = { - allowMultiple: false, - handleNotFoundStyleName: 'throw' - }; + const 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 (_.isUndefined(configuration[name])) { + throw new Error('Unknown configuration property "' + name + '".'); + } - if (name === 'allowMultiple' && !_.isBoolean(value)) { - throw new Error('"allowMultiple" property value must be a boolean.'); - } + if (!_.isBoolean(value)) { + 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 index 937b2d6..39c328a 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(/\s+/); - styleNames = _.filter(styleNames); + if (styleNameIndex[styleNamePropertyValue]) { + styleNames = styleNameIndex[styleNamePropertyValue]; + } else { + styleNames = _.trim(styleNamePropertyValue).split(' '); + 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 deleted file mode 100644 index 93675fe..0000000 --- a/src/renderNothing.js +++ /dev/null @@ -1,7 +0,0 @@ -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/simple-map.js b/src/simple-map.js new file mode 100644 index 0000000..4582e33 --- /dev/null +++ b/src/simple-map.js @@ -0,0 +1,27 @@ +export class SimpleMap { + constructor () { + this.keys = []; + this.values = []; + } + + get size () { + return this.keys.length; + } + + get (key) { + const index = this.keys.indexOf(key); + + return this.values[index]; + } + + set (key, value) { + this.keys.push(key); + this.values.push(value); + + return value; + } +} + +const exportedMap = typeof Map === 'undefined' ? SimpleMap : Map; + +export default exportedMap; diff --git a/src/wrapStatelessFunction.js b/src/wrapStatelessFunction.js index b6b9162..878119a 100644 --- a/src/wrapStatelessFunction.js +++ b/src/wrapStatelessFunction.js @@ -3,47 +3,39 @@ 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; - 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; + 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; }; diff --git a/tests/SimpleMap.js b/tests/SimpleMap.js deleted file mode 100644 index 08ca888..0000000 --- a/tests/SimpleMap.js +++ /dev/null @@ -1,38 +0,0 @@ -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 deleted file mode 100644 index 01733ee..0000000 --- a/tests/extendReactClass.js +++ /dev/null @@ -1,167 +0,0 @@ -/* 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; - }); - 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); - - 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; - } - }; - - Component = extendReactClass(Component); - - 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'; - - render () { - return null; - } - }; - - const WrappedComponent = extendReactClass(Component); - - expect(Component.foo).to.equal('FOO'); - expect(WrappedComponent.foo).to.equal(Component.foo); - }); - }); -}); diff --git a/tests/linkClass.js b/tests/linkClass.js deleted file mode 100644 index cf7c289..0000000 --- a/tests/linkClass.js +++ /dev/null @@ -1,483 +0,0 @@ -/* eslint-disable max-nested-callbacks, react/prefer-stateless-function, class-methods-use-this, no-console, no-unused-expressions */ - -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(
); - }); - - 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' - }); - - 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' - }); - - 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' - }); - - 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' - }); - - 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' - }); - - 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, - - // eslint-disable-next-line no-use-extend-native/no-use-extend-native - [Symbol.iterator]: Array.prototype[Symbol.iterator] - }; - - 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'); - }); - }); - context('when non-`children` prop is an iterable', () => { - it('it is left untouched', () => { - 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 =

; - - 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 =
; - - 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; - - subject =
; - - 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; - - subject =
-

-

; - - subject = linkClass(subject, { - bar: 'bar-1', - foo: 'foo-1' - }, { - allowMultiple: true - }); - - expect(subject.props.children.props.className).to.equal('foo-1 bar-1'); - }); - }); - - context('can\'t write to properties', () => { - context('when the element is frozen', () => { - it('adds className but is still frozen', () => { - let subject; - - 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; - - 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; - - 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
; - } - }; - - 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 deleted file mode 100644 index 4193311..0000000 --- a/tests/makeConfiguration.js +++ /dev/null @@ -1,42 +0,0 @@ -/* eslint-disable max-nested-callbacks */ - -import { - expect -} from 'chai'; -import makeConfiguration from './../src/makeConfiguration'; - -describe('makeConfiguration', () => { - describe('when using default configuration', () => { - let configuration; - - beforeEach(() => { - configuration = makeConfiguration(); - }); - 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 = {}; - - makeConfiguration(userConfiguration); - - expect(userConfiguration).to.deep.equal({}); - }); -}); diff --git a/tests/reactCssModules.js b/tests/reactCssModules.js deleted file mode 100644 index d484d95..0000000 --- a/tests/reactCssModules.js +++ /dev/null @@ -1,184 +0,0 @@ -/* 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; - - Foo = class extends React.Component {}; - - // @todo https://phabricator.babeljs.io/T2779 - Foo.displayName = 'Bar'; - - Foo = reactCssModules(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 = reactCssModules(Foo); - - 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' - }); - - shallowRenderer.render(); - - 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; - - beforeEach(() => { - const shallowRenderer = TestUtils.createRenderer(); - - Foo = () => { - return
Hello
; - }; - - Foo = reactCssModules(Foo, { - foo: 'foo-1' - }); - - shallowRenderer.render(); - - 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(); - - const component = shallowRenderer.getRenderOutput(); - - expect(typeof component).to.equal('object'); - }); - }); - context('the component is a stateless function component', () => { - it('that element should contain the equivalent className', () => { - let Foo; - - const shallowRenderer = TestUtils.createRenderer(); - - Foo = () => { - return null; - }; - - Foo = reactCssModules(Foo, { - foo: 'foo-1' - }); - - shallowRenderer.render(); - - const component = shallowRenderer.getRenderOutput(); - - expect(typeof component).to.equal('object'); - }); - }); - }); - 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; - let Foo; - let 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(); - - // 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 deleted file mode 100644 index 7a666e8..0000000 --- a/tests/renderNothing.js +++ /dev/null @@ -1,16 +0,0 @@ -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 deleted file mode 100644 index 9c71935..0000000 --- a/tests/wrapStatelessFunction.js +++ /dev/null @@ -1,92 +0,0 @@ -/* eslint-disable max-nested-callbacks */ - -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', () => { - const styles = { - foo: 'foo-1' - }; - - const InnerComponent = () => { - return null; - }; - - InnerComponent.propTypes = {}; - InnerComponent.defaultProps = {}; - - const WrappedComponent = wrapStatelessFunction(InnerComponent, 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' - }; - - 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.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' - }); - }); - }); - 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 - }); - }); - }); - context('rendering Component that returns null', () => { - it('generates null', () => { - const shallowRenderer = TestUtils.createRenderer(); - - const Component = wrapStatelessFunction(() => { - return null; - }); - - shallowRenderer.render(); - - const component = shallowRenderer.getRenderOutput(); - - expect(component).to.equal(null); - }); - }); -});