diff --git a/.babelrc b/.babelrc index 3c078e9..9bbb1e7 100644 --- a/.babelrc +++ b/.babelrc @@ -1,5 +1,14 @@ { "presets": [ - "es2015" - ] + [ + "env", + { + "loose": true, + "modules": "commonjs", + "targets": { + "node": 4, + }, + }, + ], + ], } diff --git a/.editorconfig b/.editorconfig index c270cfa..def8631 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,4 +8,13 @@ charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true +[GNUmakefile] +indent_style = tab + +[Makefile] +indent_style = tab + +[makefile] +indent_style = tab + # cheat sheet: http://EditorConfig.org diff --git a/.gitignore b/.gitignore index 905f8e6..a2587e1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,30 +1,6 @@ -# Logs -logs -*.log - -# Runtime data -pids -*.pid -*.seed - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (http://nodejs.org/api/addons.html) -build/Release - -# Dependency directory -# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git -node_modules/* - -# added automatically by precommit-hook as defaults -.jshint* +.DS_Store +.npm +lib +node_modules +npm-debug.log* +yarn.lock diff --git a/.npmignore b/.npmignore index 8daaae1..f139cd7 100644 --- a/.npmignore +++ b/.npmignore @@ -5,3 +5,7 @@ .travis.yml coverage demo +release.sh +scripts +src +test diff --git a/.travis.yml b/.travis.yml index ca8e5dd..dd338dd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,5 @@ language: node_js node_js: - - "4.3" + - "4" + - "6" + - "8" diff --git a/README.md b/README.md index 892814a..851c195 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,29 @@ require('css-modules-require-hook/preset'); // const styles = require('./icon.css'); ``` + +### Using with babel-node / ES6 Imports +You will need to create a `cmrh.conf.js` file within the directory as you are importing `css-modules-require-hook`. + + +```javascript +// server.js +import csshook from 'css-modules-require-hook/preset' // import hook before routes +import routes from '/shared/views/routes' + +// create server, etc +``` + +```javascript +// cmrh.conf.js +module.exports = { + // Same scope name as in webpack build + generateScopedName: '[name]__[local]___[hash:base64:5]', +} +``` + + + ### Development mode Usually, Node.js caches all the `require` calls by default. In order to invalidate cache for the purpose of development you should set the environment variable `NODE_ENV` to `development`. For example: @@ -183,10 +206,12 @@ hook({ }); ``` -### `camelCase boolean` +### `camelCase boolean|string` Camelizes exported class names. Similar to [css-loader?camelCase](https://github.com/webpack/css-loader#camel-case). +Available options: `true`, `dashes`, `only`, `dashesOnly`. + ### `append` array Appends custom plugins to the end of the PostCSS pipeline. Since the `require` function is synchronous, you should provide synchronous plugins only. @@ -239,6 +264,20 @@ Provides additional hash uniqueness. Might be useful for projects with several s Short alias for the [postcss-modules-local-by-default](https://github.com/css-modules/postcss-modules-local-by-default) plugin's option. + +### `resolve` object + +Changes the way the paths of ICSS imports will be resolved (`@value a from './b.css'` and `composes a from './b.css'`). Supports: + +- `resolve.alias` `object` +- `resolve.extensions` `array` — default value is `['.css']`. +- `resolve.modules` `array` +- `resolve.mainFile` `string` — default value is `'index.css'`. +- `resolve.preserveSymlinks` `boolean` — default value is `false`. + +See the detailed description at: https://github.com/css-modules/postcss-modules-resolve-imports#options + + ### `rootDir` string Provides absolute path to the project directory. Providing this will result in better generated class names. It can be obligatory, if you run require hook and build tools (like [css-modulesify](https://github.com/css-modules/css-modulesify)) from different working directories. diff --git a/demo/components/Button/Button.css b/demo/components/Button/Button.css index 9f99289..edf20f9 100644 --- a/demo/components/Button/Button.css +++ b/demo/components/Button/Button.css @@ -1,24 +1,24 @@ +@value primary, primaryShadow from 'Colors.css'; + +.common +{ + composes: common from './Common.css'; +} + .common, .common:active { - display: inline-block; - margin: 0 0 20px; padding: 0 11px; - cursor: pointer; - user-select: none; transition: all .3s; - text-align: center; - white-space: nowrap; - text-decoration: none; color: #fff; border: none; border-radius: 5px; outline: none; - background: #43a047; - box-shadow: 0 5px 0 #2e7d32; + background: primary; + box-shadow: 0 5px 0 primaryShadow; font: normal 20px/38px Roboto; } diff --git a/demo/components/Button/Colors.css b/demo/components/Button/Colors.css new file mode 100644 index 0000000..f3a7bcc --- /dev/null +++ b/demo/components/Button/Colors.css @@ -0,0 +1,2 @@ +@value primary #43a047; +@value primaryShadow #2e7d32; diff --git a/demo/components/Button/Common.css b/demo/components/Button/Common.css new file mode 100644 index 0000000..d2370e3 --- /dev/null +++ b/demo/components/Button/Common.css @@ -0,0 +1,11 @@ +.common +{ + display: inline-block; + + cursor: pointer; + user-select: none; + + text-align: center; + white-space: nowrap; + text-decoration: none; +} diff --git a/demo/package.json b/demo/package.json index f71debf..4780d66 100644 --- a/demo/package.json +++ b/demo/package.json @@ -19,24 +19,24 @@ "author": "Alexey Litvinov", "license": "MIT", "dependencies": { - "css-modules-require-hook": "^4.0.0", - "express": "^4.13.4", - "react": "^0.14.7", - "react-dom": "^0.14.7" + "css-modules-require-hook": "next", + "express": "^4.14.0", + "react": "^15.3.2", + "react-dom": "^15.3.2" }, "devDependencies": { - "autoprefixer": "^6.3.3", - "babel-core": "^6.5.2", - "babel-loader": "^6.2.3", - "babel-preset-es2015": "^6.5.0", - "babel-preset-react": "^6.5.0", - "babel-preset-stage-0": "^6.5.0", - "css-loader": "0.23.1", - "extract-text-webpack-plugin": "^1.0.1", - "npm-install-webpack-plugin": "^2.0.2", - "postcss-font-magician": "^1.4.0", - "postcss-loader": "0.8.1", - "style-loader": "0.13.0", - "webpack": "^1.12.14" + "autoprefixer": "^7.2.4", + "babel-core": "^6.26.0", + "babel-loader": "^7.1.2", + "babel-preset-es2015": "^6.24.1", + "babel-preset-react": "^6.24.1", + "babel-preset-stage-0": "^6.24.1", + "css-loader": "0.28.7", + "extract-text-webpack-plugin": "^3.0.2", + "npm-install-webpack-plugin": "^4.0.4", + "postcss-font-magician": "^2.1.0", + "postcss-loader": "^2.0.10", + "style-loader": "^0.19.1", + "webpack": "^3.10.0" } } diff --git a/demo/postcss.config.js b/demo/postcss.config.js new file mode 100644 index 0000000..f6e339a --- /dev/null +++ b/demo/postcss.config.js @@ -0,0 +1,9 @@ +'use strict'; + +module.exports = { + plugins: [ + // small sugar for CSS + require('postcss-font-magician'), + require('autoprefixer'), + ], +}; diff --git a/demo/webpack.config.js b/demo/webpack.config.js index 952b716..a3197ab 100644 --- a/demo/webpack.config.js +++ b/demo/webpack.config.js @@ -15,29 +15,37 @@ module.exports = { }, module: { - loaders: [ + rules: [ { test: /\.js$/i, exclude: /node_modules/, - loader: 'babel?presets[]=es2015,presets[]=react,presets[]=stage-0', + loader: 'babel-loader', + options: { + presets: ['es2015', 'react', 'stage-0'], + }, }, { test: /\.css$/i, - loader: ExtractTextPlugin.extract('style', - `css?modules&localIdentName=${config.css}!postcss`), + use: ExtractTextPlugin.extract({ + fallback: 'style-loader', + use: [ + { + loader: 'css-loader', + options: { + modules: true, + localIdentName: config.css, + }, + }, + 'postcss-loader', + ], + }), }, ], }, - postcss: [ - // small sugar for CSS - require('postcss-font-magician'), - require('autoprefixer'), - ], - plugins: [ new ExtractTextPlugin('common.css', { - allChunks: true + allChunks: true, }), new NpmInstallPlugin({ cacheMin: 999999, @@ -50,5 +58,5 @@ module.exports = { { react: true, }, - ] + ], }; diff --git a/lib/validate.js b/lib/validate.js deleted file mode 100644 index 68d1efd..0000000 --- a/lib/validate.js +++ /dev/null @@ -1,49 +0,0 @@ -const difference = require('lodash').difference; -const forEach = require('lodash').forEach; -const keys = require('lodash').keys; - -const rules = { - // hook - devMode: 'boolean', - extensions: 'array|string', - ignore: 'function|regex|string', - preprocessCss: 'function', - processCss: 'function', - processorOpts: 'object', - camelCase: 'boolean', - // plugins - append: 'array', - prepend: 'array', - use: 'array', - createImportedName: 'function', - generateScopedName: 'function|string', - hashPrefix: 'string', - mode: 'string', - rootDir: 'string', -}; - -const tests = { - array: require('lodash').isArray, - boolean: require('lodash').isBoolean, - function: require('lodash').isFunction, - object: require('lodash').isPlainObject, - regex: require('lodash').isRegExp, - string: require('lodash').isString, -}; - -module.exports = function validate(options) { - const unknownOptions = difference(keys(options), keys(rules)); - if (unknownOptions.length) { - throw new Error(`unknown arguments: ${unknownOptions.join(', ')}.`); - } - - forEach(rules, (types, rule) => { - if (typeof options[rule] === 'undefined') { - return; - } - - if (!types.split('|').some(type => tests[type](options[rule]))) { - throw new TypeError(`should specify ${types} as ${rule}`); - } - }); -} diff --git a/package.json b/package.json index 878ca88..8dff914 100644 --- a/package.json +++ b/package.json @@ -1,22 +1,10 @@ { "name": "css-modules-require-hook", - "version": "4.0.3", + "version": "4.2.3", "description": "A require hook to compile CSS Modules on the fly", "main": "lib/index.js", "engines": { - "node": ">=0.12" - }, - "scripts": { - "build": "babel lib --out-dir lib && babel preset.js --out-file preset.js", - "cleanup": "git reset --hard", - "prepublish": "in-publish && npm run -s build || in-install", - "publish": "in-publish && npm run -s cleanup || in-install", - "test": "npm run test:babel", - "test:babel": "NODE_PATH=$(pwd)/test/tokens/node_modules $npm_package_scripts_test_unit --compilers js:babel-register", - "test:coverage": "NODE_PATH=$(pwd)/test/tokens/node_modules babel-node --presets es2015 `npm bin`/isparta cover --report text --report html `npm bin`/_mocha -- --require test/setup.js --ui tdd test/*/*.js", - "test:node": "NODE_PATH=$(pwd)/test/tokens/node_modules $npm_package_scripts_test_unit --use_strict", - "test:watch": "NODE_PATH=$(pwd)/test/tokens/node_modules $npm_package_scripts_test_unit --watch --use_strict", - "test:unit": "mocha --require test/setup.js --ui tdd test/*/*.js" + "node": ">= 4" }, "repository": { "type": "git", @@ -36,29 +24,51 @@ "url": "https://github.com/css-modules/css-modules-require-hook/issues" }, "homepage": "https://github.com/css-modules/css-modules-require-hook", - "pre-commit": [], + "eslintConfig": { + "extends": "@sullenor/eslint-config/node", + "rules": { + "max-len": [ + 2, + 120 + ] + } + }, + "pre-commit": [ + "lint" + ], + "devDependencies": { + "@sullenor/eslint-config": "next", + "babel-preset-env": "^1.6.0", + "babel-register": "^6.26.0", + "eslint": "^4.6.1", + "gulp": "^3.9.1", + "gulp-babel": "^7.0.0", + "gulp-debug": "^3.1.0", + "jest": "^21.0.2", + "mocha": "^3.5.1", + "postcss-less": "^1.1.0", + "pre-commit": "^1.2.2", + "sinon": "^3.2.1" + }, "dependencies": { "debug": "^2.2.0", "generic-names": "^1.0.1", - "glob-to-regexp": "^0.1.0", + "glob-to-regexp": "^0.3.0", "icss-replace-symbols": "^1.0.2", - "in-publish": "^2.0.0", "lodash": "^4.3.0", - "postcss": "^5.0.19", + "postcss": "^6.0.1", "postcss-modules-extract-imports": "^1.0.0", "postcss-modules-local-by-default": "^1.0.1", - "postcss-modules-parser": "^1.1.0", + "postcss-modules-resolve-imports": "^1.3.0", "postcss-modules-scope": "^1.0.0", "postcss-modules-values": "^1.1.1", "seekout": "^1.0.1" }, - "devDependencies": { - "babel-cli": "^6.5.1", - "babel-preset-es2015": "^6.5.0", - "babel-register": "^6.5.2", - "isparta": "^4.0.0", - "mocha": "^2.4.5", - "postcss-less": "^0.2.0", - "sinon": "^1.17.3" + "scripts": { + "lint": "eslint src/**/*.js", + "prepublish": "npm run transpile", + "pretest": "npm run transpile", + "test": "NODE_PATH=$(pwd)/test/tokens/node_modules mocha --require test/setup.js --ui tdd test/*/*.js --compilers js:babel-register", + "transpile": "gulp --cwd . --gulpfile scripts/gulpfile.js transpile" } } diff --git a/preset.js b/preset.js index b20216c..b5acbc5 100644 --- a/preset.js +++ b/preset.js @@ -1,3 +1,5 @@ +'use strict'; + const basename = require('path').basename; const debug = require('debug')('css-modules:preset'); const dirname = require('path').dirname; diff --git a/release.sh b/release.sh new file mode 100755 index 0000000..2720a39 --- /dev/null +++ b/release.sh @@ -0,0 +1,33 @@ +#!/bin/sh + +if [ `git status -s|wc -l` -gt '0' ]; then + echo 'commit changes first' + exit 1 +fi + +case "$1" in + major|x..) + versionType=major;; + + minor|.x.) + versionType=minor;; + + patch|..x) + versionType=patch;; + + *) + echo 'choose the version to deploy' + echo 'deploy major|minor|patch|x..|.x.|..x' + exit;; +esac + +# test the code +npm run test || exit 1 +# update package version +npm --no-git-tag-version version "$versionType" +git add package.json +version=`sed -n '/version/p' package.json|cut -d'"' -f4` +git commit -m "version $version" +git tag "$version" + +npm publish diff --git a/scripts/gulpfile.js b/scripts/gulpfile.js new file mode 100644 index 0000000..5a0b41f --- /dev/null +++ b/scripts/gulpfile.js @@ -0,0 +1,24 @@ +'use strict'; + +const babel = require('gulp-babel'); +const debug = require('gulp-debug'); +const gulp = require('gulp'); + +gulp.task('transpile', () => + gulp.src('src/*.js') + .pipe(babel({ + presets: [ + [ + 'env', + { + loose: true, + modules: 'commonjs', + targets: { + node: 4, + }, + }, + ], + ], + })) + .pipe(debug({title: 'transpiled'})) + .pipe(gulp.dest('lib'))); diff --git a/lib/attachHook.js b/src/attachHook.js similarity index 97% rename from lib/attachHook.js rename to src/attachHook.js index 37e4821..1ea95f5 100644 --- a/lib/attachHook.js +++ b/src/attachHook.js @@ -1,3 +1,5 @@ +'use strict'; + /** * @param {function} compile * @param {string} extension diff --git a/lib/index.js b/src/index.js similarity index 74% rename from lib/index.js rename to src/index.js index 42b0526..8d4afae 100644 --- a/lib/index.js +++ b/src/index.js @@ -1,15 +1,13 @@ -const assign = require('lodash').assign; +'use strict'; + +const {assign, identity, negate} = require('lodash'); +const {dirname, relative, resolve} = require('path'); +const {readFileSync} = require('fs'); +const {transformTokens} = require('./transformTokens'); + const attachHook = require('./attachHook'); -const dirname = require('path').dirname; const genericNames = require('generic-names'); const globToRegex = require('glob-to-regexp'); -const identity = require('lodash').identity; -const negate = require('lodash').negate; -const camelCaseFunc = require('lodash').camelCase; -const mapKeys = require('lodash').mapKeys; -const readFileSync = require('fs').readFileSync; -const relative = require('path').relative; -const resolve = require('path').resolve; const validate = require('./validate'); const postcss = require('postcss'); @@ -17,31 +15,33 @@ const Values = require('postcss-modules-values'); const LocalByDefault = require('postcss-modules-local-by-default'); const ExtractImports = require('postcss-modules-extract-imports'); const Scope = require('postcss-modules-scope'); -const Parser = require('postcss-modules-parser'); +const ResolveImports = require('postcss-modules-resolve-imports'); const debugFetch = require('debug')('css-modules:fetch'); const debugSetup = require('debug')('css-modules:setup'); module.exports = function setupHook({ + camelCase, devMode, extensions = '.css', ignore, preprocessCss = identity, processCss, processorOpts, - camelCase, append = [], prepend = [], createImportedName, generateScopedName, hashPrefix, mode, + resolve: resolveOpts, use, rootDir: context = process.cwd(), }) { debugSetup(arguments[0]); validate(arguments[0]); + const exts = toArray(extensions); const tokensByFile = {}; // debug option is preferred NODE_ENV === 'development' @@ -50,18 +50,15 @@ module.exports = function setupHook({ : process.env.NODE_ENV === 'development'; let scopedName; - if (generateScopedName) { + if (generateScopedName) scopedName = typeof generateScopedName !== 'function' ? genericNames(generateScopedName, {context, hashPrefix}) // for example '[name]__[local]___[hash:base64:5]' : generateScopedName; - } else { + else // small fallback - scopedName = (local, filename) => { - return Scope.generateScopedName(local, relative(context, filename)); - }; - } + scopedName = (local, filename) => Scope.generateScopedName(local, relative(context, filename)); - const plugins = (use || [ + const plugins = use || [ ...prepend, Values, mode @@ -71,8 +68,9 @@ module.exports = function setupHook({ ? new ExtractImports({createImportedName}) : ExtractImports, new Scope({generateScopedName: scopedName}), + new ResolveImports({resolve: Object.assign({}, {extensions: exts}, resolveOpts)}), ...append, - ]).concat(new Parser({fetch})); // no pushing in order to avoid the possible mutations; + ]; // https://github.com/postcss/postcss#options const runner = postcss(plugins); @@ -85,11 +83,10 @@ module.exports = function setupHook({ */ function fetch(_to, from) { // getting absolute path to the processing file - const filename = /[^\\/?%*:|"<>\.]/i.test(_to[0]) + const filename = /[^\\/?%*:|"<>.]/i.test(_to[0]) ? require.resolve(_to) : resolve(dirname(from), _to); - // checking cache let tokens = tokensByFile[filename]; if (tokens) { @@ -105,35 +102,33 @@ module.exports = function setupHook({ // https://github.com/postcss/postcss/blob/master/docs/api.md#lazywarnings lazyResult.warnings().forEach(message => console.warn(message.text)); - tokens = lazyResult.root.tokens; - - if (camelCase) { - tokens = assign(mapKeys(tokens, (value, key) => camelCaseFunc(key)), tokens); - } + tokens = lazyResult.root.exports || {}; - if (!debugMode) { + if (!debugMode) // updating cache tokensByFile[filename] = tokens; - } else { + else // clearing cache in development mode delete require.cache[filename]; - } - if (processCss) { + if (processCss) processCss(lazyResult.css, filename); - } debugFetch(`${filename} → fs`); debugFetch(tokens); return tokens; - }; + } - const exts = toArray(extensions); const isException = buildExceptionChecker(ignore); + const hook = filename => { + const tokens = fetch(filename, filename); + return camelCase ? transformTokens(tokens, camelCase) : tokens; + }; + // @todo add possibility to specify particular config for each extension - exts.forEach(extension => attachHook(filename => fetch(filename, filename), extension, isException)); + exts.forEach(extension => attachHook(hook, extension, isException)); }; /** @@ -151,13 +146,11 @@ function toArray(option) { * @return {function} */ function buildExceptionChecker(ignore) { - if (ignore instanceof RegExp) { + if (ignore instanceof RegExp) return filepath => ignore.test(filepath); - } - if (typeof ignore === 'string') { + if (typeof ignore === 'string') return filepath => globToRegex(ignore).test(filepath); - } return ignore || negate(identity); } diff --git a/src/transformTokens.js b/src/transformTokens.js new file mode 100644 index 0000000..aadbd1b --- /dev/null +++ b/src/transformTokens.js @@ -0,0 +1,63 @@ +'use strict'; + +const {assign, camelCase, reduce} = require('lodash'); + +const camelizeKeys = (acc, value, key) => { + const camelizedKey = camelCase(key); + if (camelizedKey !== key) acc[camelizedKey] = value; + return acc; +}; + +const camelizeDashedKeys = (acc, value, key) => { + const camelizedKey = camelizeDashes(key); + if (camelizedKey !== key) acc[camelizedKey] = value; + return acc; +}; + +const camelizeOnlyKeys = (acc, value, key) => { + const camelizedKey = camelCase(key); + if (camelizedKey !== key) acc[camelizedKey] = value; + else acc[key] = value; + return acc; +}; + +const camelizeOnlyDashedKeys = (acc, value, key) => { + const camelizedKey = camelizeDashes(key); + if (camelizedKey !== key) acc[camelizedKey] = value; + else acc[key] = value; + return acc; +}; + +exports.camelizeDashes = camelizeDashes; +exports.transformTokens = transformTokens; + +/** + * @param {string} str + * @return {string} + */ +function camelizeDashes(str) { + return str.replace(/-+(\w)/g, (m, letter) => letter.toUpperCase()); +} + +/** + * @param {object} tokens + * @param {boolean|string} camelCase 'dashes|dashesOnly|only' + * @return {object} + */ +function transformTokens(tokens, camelCase) { + switch (camelCase) { + case true: + return reduce(tokens, camelizeKeys, assign({}, tokens)); + + case 'dashes': + return reduce(tokens, camelizeDashedKeys, assign({}, tokens)); + + case 'dashesOnly': + return reduce(tokens, camelizeOnlyDashedKeys, {}); + + case 'only': + return reduce(tokens, camelizeOnlyKeys, {}); + } + + return tokens; +} diff --git a/src/validate.js b/src/validate.js new file mode 100644 index 0000000..ba34ca7 --- /dev/null +++ b/src/validate.js @@ -0,0 +1,57 @@ +'use strict'; + +const { + difference, + forEach, + isArray, + isBoolean, + isFunction, + isPlainObject, + isRegExp, + isString, + keys, +} = require('lodash'); + +const rules = { + // hook + camelCase: 'boolean|string', + devMode: 'boolean', + extensions: 'array|string', + ignore: 'function|regex|string', + preprocessCss: 'function', + processCss: 'function', + processorOpts: 'object', + // plugins + append: 'array', + prepend: 'array', + use: 'array', + createImportedName: 'function', + generateScopedName: 'function|string', + hashPrefix: 'string', + mode: 'string', + resolve: 'object', + rootDir: 'string', +}; + +const tests = { + array: isArray, + boolean: isBoolean, + function: isFunction, + object: isPlainObject, + regex: isRegExp, + string: isString, +}; + +module.exports = function validate(options) { + const unknownOptions = difference(keys(options), keys(rules)); + if (unknownOptions.length) + throw new Error(`unknown arguments: ${unknownOptions.join(', ')}.`); + + forEach(rules, (types, rule) => { + if (typeof options[rule] === 'undefined') + return; + + if (!types.split('|').some(type => tests[type](options[rule]))) + throw new TypeError(`should specify ${types} as ${rule}`); + }); +}; diff --git a/test/api/camelCase.js b/test/api/camelCase.js index 98e5e16..94194b9 100644 --- a/test/api/camelCase.js +++ b/test/api/camelCase.js @@ -2,18 +2,36 @@ const detachHook = require('../sugar').detachHook; const dropCache = require('../sugar').dropCache; suite('api/camelCase', () => { - test('should add camel case keys in token', () => { - const tokens = require('./fixture/bem.css'); - assert.deepEqual(tokens, { - blockElementModifier: '_test_api_fixture_bem__block__element--modifier', - 'block__element--modifier': '_test_api_fixture_bem__block__element--modifier', + suite('-> `true`', () => { + test('should add camel case keys in token', () => { + const tokens = require('./fixture/bem.css'); + assert.deepEqual(tokens, { + blockElementModifier: '_test_api_fixture_bem__block__element--modifier', + 'block__element--modifier': '_test_api_fixture_bem__block__element--modifier', + }); + }); + + setup(() => hook({ camelCase: true })); + + teardown(() => { + detachHook('.css'); + dropCache('./api/fixture/bem.css'); }); }); - setup(() => hook({ camelCase: true })); + suite('-> `dashesOnly`', () => { + test('should replace keys with dashes by its camel-cased equivalent', () => { + const tokens = require('./fixture/bem.css'); + assert.deepEqual(tokens, { + 'block__elementModifier': '_test_api_fixture_bem__block__element--modifier', + }); + }); + + setup(() => hook({camelCase: 'dashesOnly'})); - teardown(() => { - detachHook('.css'); - dropCache('./api/fixture/bem.css'); + teardown(() => { + detachHook('.css'); + dropCache('./api/fixture/bem.css'); + }); }); }); diff --git a/test/api/fixture/shortcuts.css b/test/api/fixture/shortcuts.css new file mode 100644 index 0000000..86df41e --- /dev/null +++ b/test/api/fixture/shortcuts.css @@ -0,0 +1,4 @@ +.color +{ + composes: color from 'oceanic'; +} diff --git a/test/api/generateScopedName.js b/test/api/generateScopedName.js index a6d092e..c39faab 100644 --- a/test/api/generateScopedName.js +++ b/test/api/generateScopedName.js @@ -8,7 +8,7 @@ suite('api/generateScopedName', () => { let args; let tokens; - const processor = spy(function (selector, filepath, source) { + const processor = spy((selector, filepath, source) => { args = [selector, filepath, source]; return selector; }); diff --git a/test/api/hashPrefix.js b/test/api/hashPrefix.js index 87fdb7a..a2a5ec4 100644 --- a/test/api/hashPrefix.js +++ b/test/api/hashPrefix.js @@ -2,7 +2,7 @@ const detachHook = require('../sugar').detachHook; const dropCache = require('../sugar').dropCache; suite('api/hashPrefix', () => { - let samples = []; + const samples = []; suite('using string pattern and hashPrefix', () => { let tokens; diff --git a/test/api/resolve.js b/test/api/resolve.js new file mode 100644 index 0000000..5ed85b9 --- /dev/null +++ b/test/api/resolve.js @@ -0,0 +1,23 @@ +const {detachHook, dropCache} = require('../sugar'); +const path = require('path'); + +suite('api/resolve', () => { + test('should be called', () => { + const tokens = require('./fixture/shortcuts.css'); + + assert.deepEqual(tokens, { + color: '_test_api_fixture_shortcuts__color _test_api_fixture_oceanic__color', + }); + }); + + setup(() => { + hook({resolve: { + modules: [path.join(__dirname, 'fixture')], + }}); + }); + + teardown(() => { + detachHook('.css'); + dropCache('./api/fixture/oceanic.css'); + }); +}); diff --git a/test/lib/transformTokens.js b/test/lib/transformTokens.js new file mode 100644 index 0000000..779e9d4 --- /dev/null +++ b/test/lib/transformTokens.js @@ -0,0 +1,65 @@ +'use strict'; + +const {camelizeDashes, transformTokens} = require('../../lib/transformTokens'); + +suite('lib/transformTokens', () => { + test('camelizeDashes', () => { + assert.equal(camelizeDashes(''), ''); + assert.equal(camelizeDashes('a-a'), 'aA'); + assert.equal(camelizeDashes('a-a-b'), 'aAB'); + assert.equal(camelizeDashes('a-'), 'a-'); + }); + + suite('transformTokens', () => { + test('`true -> should transform all the keys to CC, keeps original keys', () => { + assert.deepEqual(transformTokens({ + 'k-e-bab': 'kebab case', + 's_na_ke': 'snake case', + 'simple': 'aaa', + }, true), { + 'k-e-bab': 'kebab case', + 's_na_ke': 'snake case', + 'simple': 'aaa', + 'kEBab': 'kebab case', + 'sNaKe': 'snake case', + }); + }); + + test('`dashes` -> should transform the keys with dashed, keeps original keys', () => { + assert.deepEqual(transformTokens({ + 'k-e-bab': 'kebab case', + 's_na_ke': 'snake case', + 'simple': 'aaa', + }, 'dashes'), { + 'k-e-bab': 'kebab case', + 's_na_ke': 'snake case', + 'simple': 'aaa', + 'kEBab': 'kebab case', + }); + }); + + test('`dashesOnly` -> should transform only keys with dashes', () => { + assert.deepEqual(transformTokens({ + 'k-e-bab': 'kebab case', + 's_na_ke': 'snake case', + 'simple': 'aaa', + }, 'dashesOnly'), { + 's_na_ke': 'snake case', + 'simple': 'aaa', + 'kEBab': 'kebab case', + }); + }); + + test('`only` -> should camelize keys', () => { + assert.deepEqual(transformTokens({ + 'k-e-bab': 'kebab case', + 's_na_ke': 'snake case', + 'simple': 'aaa', + }, 'only'), { + 'simple': 'aaa', + 'kEBab': 'kebab case', + 'sNaKe': 'snake case', + }); + }); + }); +});