Skip to content

Commit

Permalink
feat: implement handleNotFoundStyleName (#249)
Browse files Browse the repository at this point in the history
* implement handleNotFoundStyleName

* disable linter only for console

* docs: reword
  • Loading branch information
p4bloch authored and gajus committed Jul 26, 2017
1 parent 76d8270 commit bb23402
Show file tree
Hide file tree
Showing 7 changed files with 65 additions and 32 deletions.
18 changes: 12 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ 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)
Expand Down Expand Up @@ -127,7 +127,7 @@ Using `react-css-modules`:
<div className='global-css' styleName='local-module'></div>
```

* 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
Expand Down Expand Up @@ -408,7 +408,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
*/
/**
Expand Down Expand Up @@ -492,11 +492,17 @@ When `false`, the following will cause an error:
<div styleName='foo bar' />
```

#### `errorWhenNotFound`
#### `handleNotFoundStyleName`

Default: `true`.
Default: `throw`.

Throws an error when `styleName` cannot be mapped to an existing CSS Module.
Defines the desired action when `styleName` cannot be mapped to an existing CSS Module.

Available options:

* `throw` throws an error
* `log` logs a warning using `console.warn`
* `ignore` silently ignores the missing style name

## SASS, SCSS, LESS and other CSS Preprocessors

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"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",
Expand Down
12 changes: 9 additions & 3 deletions src/generateAppendClassName.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const CustomMap = typeof Map === 'undefined' ? SimpleMap : Map;

const stylesIndex = new CustomMap();

export default (styles, styleNames: Array<string>, errorWhenNotFound: boolean): string => {
export default (styles, styleNames: Array<string>, handleNotFoundStyleName: "throw" | "log" | "ignore"): string => {
let appendClassName;
let stylesIndexMap;

Expand All @@ -29,8 +29,14 @@ export default (styles, styleNames: Array<string>, errorWhenNotFound: boolean):

if (className) {
appendClassName += ' ' + className;
} else if (errorWhenNotFound === true) {
throw new Error('"' + styleNames[styleName] + '" CSS module is undefined.');
} 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.');
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/linkClass.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ const linkElement = (element: ReactElement, styles: Object, configuration: Objec
});

if (styleNames.length) {
appendClassName = generateAppendClassName(styles, styleNames, configuration.errorWhenNotFound);
appendClassName = generateAppendClassName(styles, styleNames, configuration.handleNotFoundStyleName);

if (appendClassName) {
if (elementShallowCopy.props.className) {
Expand Down
12 changes: 8 additions & 4 deletions src/makeConfiguration.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import _ from 'lodash';
* @typedef CSSModules~Options
* @see {@link https://github.com/gajus/react-css-modules#options}
* @property {boolean} allowMultiple
* @property {boolean} errorWhenNotFound
* @property {string} handleNotFoundStyleName
*/

/**
Expand All @@ -14,16 +14,20 @@ import _ from 'lodash';
export default (userConfiguration = {}) => {
const configuration = {
allowMultiple: false,
errorWhenNotFound: true
handleNotFoundStyleName: 'throw'
};

_.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.');
}

if (name === 'handleNotFoundStyleName' && !['throw', 'log', 'ignore'].includes(value)) {
throw new Error('"handleNotFoundStyleName" property value must be "throw", "log" or "ignore".');
}

configuration[name] = value;
Expand Down
46 changes: 31 additions & 15 deletions tests/linkClass.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
/* eslint-disable max-nested-callbacks, react/prefer-stateless-function, class-methods-use-this */
/* eslint-disable max-nested-callbacks, react/prefer-stateless-function, class-methods-use-this, no-console */

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', () => {
Expand Down Expand Up @@ -264,24 +267,37 @@ describe('linkClass', () => {
});
});

describe('options.errorWhenNotFound', () => {
describe('options.handleNotFoundStyleName', () => {
context('when styleName does not match an existing CSS module', () => {
context('when false', () => {
it('ignores the missing CSS module', () => {
let subject;

subject = <div styleName='foo' />;

subject = linkClass(subject, {}, {errorWhenNotFound: false});
context('when throw', () => {
it('throws an error', () => {
expect(() => {
linkClass(<div styleName='foo' />, {}, {handleNotFoundStyleName: 'throw'});
}).to.throw(Error, '"foo" CSS module is undefined.');
});
});
context('when log', () => {
it('logs a warning to the console', () => {
const warnSpy = chai.spy(() => {});

expect(subject.props.className).to.be.an('undefined');
console.warn = warnSpy;
linkClass(<div styleName='foo' />, {}, {handleNotFoundStyleName: 'log'});
expect(warnSpy).to.have.been.called();
});
});
context('when is true', () => {
it('throws an error', () => {
context('when ignore', () => {
it('does not log a warning', () => {
const warnSpy = chai.spy(() => {});

console.warn = warnSpy;
linkClass(<div styleName='foo' />, {}, {handleNotFoundStyleName: 'ignore'});
expect(warnSpy).to.not.have.been.called();
});

it('does not throw an error', () => {
expect(() => {
linkClass(<div styleName='foo' />, {}, {errorWhenNotFound: true});
}).to.throw(Error, '"foo" CSS module is undefined.');
linkClass(<div styleName='foo' />, {}, {handleNotFoundStyleName: 'ignore'});
}).to.not.throw(Error, '"foo" CSS module is undefined.');
});
});
});
Expand Down
6 changes: 3 additions & 3 deletions tests/makeConfiguration.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ describe('makeConfiguration', () => {
expect(configuration.allowMultiple).to.equal(false);
});
});
describe('errorWhenNotFound property', () => {
it('defaults to true', () => {
expect(configuration.errorWhenNotFound).to.equal(true);
describe('handleNotFoundStyleName property', () => {
it('defaults to "throw"', () => {
expect(configuration.handleNotFoundStyleName).to.equal('throw');
});
});
});
Expand Down

0 comments on commit bb23402

Please sign in to comment.