diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..3c078e9 --- /dev/null +++ b/.babelrc @@ -0,0 +1,5 @@ +{ + "presets": [ + "es2015" + ] +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c270cfa --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +# cheat sheet: http://EditorConfig.org diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 925e6af..0000000 --- a/.eslintignore +++ /dev/null @@ -1,8 +0,0 @@ -dist -coverage -node_modules - -generate-tests.js -test/cases/webpack/* -test/common-test-cases.js -webpack.config.js diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 515956c..0000000 --- a/.eslintrc +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "eslint-config-airbnb-lite", - "env": { - "es6": true, - "mocha": true - }, - "rules": { - "key-spacing": [2, {"align": "value"}], - "no-use-before-define": [2, "nofunc"] - } -} diff --git a/.gitignore b/.gitignore index 7b2bf1d..905f8e6 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,3 @@ node_modules/* # added automatically by precommit-hook as defaults .jshint* - -dist/ -!node_modules/awesome-theme diff --git a/.npmignore b/.npmignore index 8e053ed..8daaae1 100644 --- a/.npmignore +++ b/.npmignore @@ -1,10 +1,7 @@ -# Generated by dmn (https://github.com/inikulin/dmn) - -.git* +.babelrc +.editorconfig +.gitignore .npmignore .travis.yml -generate-tests.js -src/ -test/ -utils/ -webpack.config.js +coverage +demo diff --git a/.travis.yml b/.travis.yml index b71507b..ca8e5dd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,3 @@ language: node_js node_js: - - "4.1" - - "0.12" + - "4.3" diff --git a/README.md b/README.md index d2cf103..d3047dd 100644 --- a/README.md +++ b/README.md @@ -14,13 +14,7 @@ Compiling in runtime, [universal](https://medium.com/@mjackson/universal-javascr ## Requirements -To use this tool we require [Node.js v0.12.x](https://github.com/nodejs/node) (or higher) and several modules to be installed. - -- [postcss](https://github.com/postcss/postcss) version 5 or higher -- [postcss-modules-values](https://github.com/css-modules/postcss-modules-values) -- [postcss-modules-extract-imports](https://github.com/css-modules/postcss-modules-extract-imports) -- [postcss-modules-local-by-default](https://github.com/css-modules/postcss-modules-local-by-default) -- [postcss-modules-scope](https://github.com/css-modules/postcss-modules-scope) +To use this tool we require [Node.js v0.12.x](https://github.com/nodejs/node) (or higher). ## Installation @@ -30,28 +24,45 @@ $ npm i css-modules-require-hook ## Usage -In this section I've tried to cover the common cases of usage. +Now, there are two ways to attach hook: manually or using preset file. -### Development mode +The first one allows you to pass options manually after module was required. Example: -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: +```javascript +const hook = require('css-modules-require-hook'); -```bash -$ NODE_ENV=development node server.js -``` +hook({ + generateScopedName: '[name]__[local]___[hash:base64:5]', +}); -Still you can use `devMode` option (see below) to override behavior which is imposed by environment variable. +// const styles = require('./icon.css'); +``` -### Basic example +The second one allows you to move options to the separate file `cmrh.conf.js`. Config file should be located in the same directory where executor is or in its ancestor directories. In that case hook will be attached right after the `css-modules-require-hook/preset` module will be required. Example: -Basically to attach the require hook you need to require this module. If you need to adjust it see the tuning section below. +```javascript +// cmrh.conf.js +module.exports = { + generateScopedName: '[name]__[local]___[hash:base64:5]', +}; +``` ```javascript -require('css-modules-require-hook'); +require('css-modules-require-hook/preset'); -// var styles = require('./icon.css'); +// const styles = require('./icon.css'); ``` +### 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: + +```bash +$ NODE_ENV=development node server.js +``` + +Still you can use `devMode` option (see below) to override behavior which is imposed by environment variable. + ### Adding custom PostCSS plugins ```javascript @@ -166,6 +177,10 @@ hook({ Attach the require hook to additional file extensions (for example `['.scss']`). +### `ignore` function|regex|string + +Provides possibility to exclude particular files from processing. Supports glob and regular expressions syntax. Also you may provide custom function. + ### `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. @@ -213,6 +228,7 @@ Short alias for the [postcss-modules-local-by-default](https://github.com/css-mo [debug](https://www.npmjs.com/package/debug) package is used for debugging. So to turn it on simply specify the **DEBUG** environment variable: - `DEBUG=css-modules:fetch` — to see resolved paths to the files. +- `DEBUG=css-modules:preset` — to see whether config was found or not. - `DEBUG=css-modules:setup` — to see the new options list. - `DEBUG=css-modules:*` — to see everything. diff --git a/demo/.babelrc b/demo/.babelrc new file mode 100644 index 0000000..3c45d73 --- /dev/null +++ b/demo/.babelrc @@ -0,0 +1,7 @@ +{ + "presets": [ + "es2015", + "react", + "stage-0" + ] +} diff --git a/demo/.csscomb.json b/demo/.csscomb.json new file mode 100644 index 0000000..a28b676 --- /dev/null +++ b/demo/.csscomb.json @@ -0,0 +1,401 @@ +{ + "exclude": [ + ".git/**", + ".reports/**", + "configs-package/**", + "debian/**", + "hooks/**", + "node_modules/**", + "server/**", + "static/desktop.blocks.test/**", + "static/desktop.bundles/**", + "static/.enb/**", + "static/.reports/**", + "static/.tanker/**", + "static/models/**", + "static/modules/**", + "static/modules.tests/**", + "static/node_modules/**", + "static/vendors/**", + "tasks/**", + "www/**" + ], + "remove-empty-rulesets": true, + "always-semicolon": true, + "color-case": "lower", + "block-indent": " ", + "color-shorthand": true, + "element-case": "lower", + "eof-newline": true, + "leading-zero": false, + "quotes": "single", + "sort-order-fallback": "abc", + "space-before-colon": "", + "space-after-colon": " ", + "space-before-combinator": " ", + "space-after-combinator": " ", + "space-between-declarations": "\n", + "space-before-opening-brace": "\n", + "space-after-opening-brace": "\n", + "space-after-selector-delimiter": "\n", + "space-before-selector-delimiter": "", + "space-before-closing-brace": "\n", + "strip-spaces": true, + "tab-size": true, + "unitless-zero": true, + "vendor-prefix-align": true, + "sort-order": [ + [ + "position", + "z-index", + "top", + "right", + "bottom", + "left" + ], + [ + "display", + "visibility", + "float", + "clear", + "overflow", + "overflow-x", + "overflow-y", + "-ms-overflow-x", + "-ms-overflow-y", + "-webkit-overflow-scrolling", + "clip", + "zoom", + "-webkit-flex-direction", + "-ms-flex-direction", + "flex-direction", + "-webkit-flex-wrap", + "-ms-flex-wrap", + "flex-wrap", + "-webkit-flex-flow", + "-ms-flex-flow", + "flex-flow", + "-webkit-order", + "-ms-flex-order", + "order", + "-webkit-justify-content", + "-ms-flex-pack", + "justify-content", + "-webkit-align-content", + "-ms-flex-line-pack", + "align-content", + "-webkit-align-items", + "-ms-flex-align", + "align-items", + "-webkit-align-self", + "-ms-flex-item-align", + "align-self" + ], + [ + "-webkit-flex-grow", + "-ms-flex-positive", + "flex-grow", + "-webkit-flex-shrink", + "-ms-flex-negative", + "flex-shrink", + "-webkit-flex-basis", + "-ms-flex-preferred-size", + "flex-basis", + "-webkit-flex", + "-ms-flex", + "flex" + ], + [ + "-webkit-box-sizing", + "-moz-box-sizing", + "box-sizing", + "width", + "min-width", + "max-width", + "height", + "min-height", + "max-height", + "margin", + "margin-top", + "margin-right", + "margin-bottom", + "margin-left", + "padding", + "padding-top", + "padding-right", + "padding-bottom", + "padding-left" + ], + [ + "table-layout", + "empty-cells", + "caption-side", + "border-spacing", + "border-collapse", + "list-style", + "list-style-position", + "list-style-type", + "list-style-image" + ], + [ + "content", + "quotes", + "counter-reset", + "counter-increment", + "resize", + "cursor", + "-webkit-user-select", + "-moz-user-select", + "-ms-user-select", + "user-select", + "nav-index", + "nav-up", + "nav-right", + "nav-down", + "nav-left", + "-webkit-transition", + "-moz-transition", + "-ms-transition", + "-o-transition", + "transition", + "-webkit-transition-delay", + "-moz-transition-delay", + "-ms-transition-delay", + "-o-transition-delay", + "transition-delay", + "-webkit-transition-timing-function", + "-moz-transition-timing-function", + "-ms-transition-timing-function", + "-o-transition-timing-function", + "transition-timing-function", + "-webkit-transition-duration", + "-moz-transition-duration", + "-ms-transition-duration", + "-o-transition-duration", + "transition-duration", + "-webkit-transition-property", + "-moz-transition-property", + "-ms-transition-property", + "-o-transition-property", + "transition-property", + "-webkit-transform", + "-moz-transform", + "-ms-transform", + "-o-transform", + "transform", + "-webkit-transform-origin", + "-moz-transform-origin", + "-ms-transform-origin", + "-o-transform-origin", + "transform-origin", + "-webkit-animation", + "-moz-animation", + "-ms-animation", + "-o-animation", + "animation", + "-webkit-animation-name", + "-moz-animation-name", + "-ms-animation-name", + "-o-animation-name", + "animation-name", + "-webkit-animation-duration", + "-moz-animation-duration", + "-ms-animation-duration", + "-o-animation-duration", + "animation-duration", + "-webkit-animation-play-state", + "-moz-animation-play-state", + "-ms-animation-play-state", + "-o-animation-play-state", + "animation-play-state", + "-webkit-animation-timing-function", + "-moz-animation-timing-function", + "-ms-animation-timing-function", + "-o-animation-timing-function", + "animation-timing-function", + "-webkit-animation-delay", + "-moz-animation-delay", + "-ms-animation-delay", + "-o-animation-delay", + "animation-delay", + "-webkit-animation-iteration-count", + "-moz-animation-iteration-count", + "-ms-animation-iteration-count", + "-o-animation-iteration-count", + "animation-iteration-count", + "-webkit-animation-iteration-count", + "-moz-animation-iteration-count", + "-ms-animation-iteration-count", + "-o-animation-iteration-count", + "animation-iteration-count", + "-webkit-animation-direction", + "-moz-animation-direction", + "-ms-animation-direction", + "-o-animation-direction", + "animation-direction", + "text-align", + "-webkit-text-align-last", + "-moz-text-align-last", + "-ms-text-align-last", + "text-align-last", + "vertical-align", + "white-space", + "text-decoration", + "text-emphasis", + "text-emphasis-color", + "text-emphasis-style", + "text-emphasis-position", + "text-indent", + "-ms-text-justify", + "text-justify", + "text-transform", + "letter-spacing", + "word-spacing", + "-ms-writing-mode", + "text-outline", + "text-transform", + "text-wrap", + "text-overflow", + "-ms-text-overflow", + "text-overflow-ellipsis", + "text-overflow-mode", + "-ms-word-wrap", + "word-wrap", + "word-break", + "-ms-word-break", + "-moz-tab-size", + "-o-tab-size", + "tab-size", + "-webkit-hyphens", + "-moz-hyphens", + "hyphens", + "pointer-events" + ], + [ + "opacity", + "filter:progid:DXImageTransform.Microsoft.Alpha(Opacity", + "-ms-filter:\\'progid:DXImageTransform.Microsoft.Alpha", + "-ms-interpolation-mode", + "color", + "border", + "border-collapse", + "border-width", + "border-style", + "border-color", + "border-top", + "border-top-width", + "border-top-style", + "border-top-color", + "border-right", + "border-right-width", + "border-right-style", + "border-right-color", + "border-bottom", + "border-bottom-width", + "border-bottom-style", + "border-bottom-color", + "border-left", + "border-left-width", + "border-left-style", + "border-left-color", + "-webkit-border-radius", + "-moz-border-radius", + "border-radius", + "-webkit-border-top-left-radius", + "-moz-border-radius-topleft", + "border-top-left-radius", + "-webkit-border-top-right-radius", + "-moz-border-radius-topright", + "border-top-right-radius", + "-webkit-border-bottom-right-radius", + "-moz-border-radius-bottomright", + "border-bottom-right-radius", + "-webkit-border-bottom-left-radius", + "-moz-border-radius-bottomleft", + "border-bottom-left-radius", + "-webkit-border-image", + "-moz-border-image", + "-o-border-image", + "border-image", + "-webkit-border-image-source", + "-moz-border-image-source", + "-o-border-image-source", + "border-image-source", + "-webkit-border-image-slice", + "-moz-border-image-slice", + "-o-border-image-slice", + "border-image-slice", + "-webkit-border-image-width", + "-moz-border-image-width", + "-o-border-image-width", + "border-image-width", + "-webkit-border-image-outset", + "-moz-border-image-outset", + "-o-border-image-outset", + "border-image-outset", + "-webkit-border-image-repeat", + "-moz-border-image-repeat", + "-o-border-image-repeat", + "border-image-repeat", + "outline", + "outline-width", + "outline-style", + "outline-color", + "outline-offset", + "background", + "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader", + "background-color", + "background-image", + "background-repeat", + "background-attachment", + "background-position", + "background-position-x", + "-ms-background-position-x", + "background-position-y", + "-ms-background-position-y", + "-webkit-background-clip", + "-moz-background-clip", + "background-clip", + "background-origin", + "-webkit-background-size", + "-moz-background-size", + "-o-background-size", + "background-size", + "box-decoration-break", + "-webkit-box-shadow", + "-moz-box-shadow", + "box-shadow", + "-webkit-box-shadow", + "-moz-box-shadow", + "box-shadow", + "-webkit-box-shadow", + "-moz-box-shadow", + "box-shadow", + "-webkit-box-shadow", + "-moz-box-shadow", + "box-shadow", + "filter:progid:DXImageTransform.Microsoft.gradient", + "-ms-filter:\\'progid:DXImageTransform.Microsoft.gradient", + "text-shadow" + ], + [ + "font", + "font-family", + "font-size", + "font-weight", + "font-style", + "font-variant", + "font-size-adjust", + "font-stretch", + "font-effect", + "font-emphasize", + "font-emphasize-position", + "font-emphasize-style", + "font-smooth", + "line-height" + ], + [ + "composes" + ] + ] +} diff --git a/demo/.editorconfig b/demo/.editorconfig new file mode 100644 index 0000000..c270cfa --- /dev/null +++ b/demo/.editorconfig @@ -0,0 +1,11 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +# cheat sheet: http://EditorConfig.org diff --git a/demo/.gitignore b/demo/.gitignore new file mode 100644 index 0000000..fe86a60 --- /dev/null +++ b/demo/.gitignore @@ -0,0 +1,35 @@ +static/ + +# 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* + +dist/ +!node_modules/awesome-theme diff --git a/demo/app/browser.js b/demo/app/browser.js new file mode 100644 index 0000000..90196cc --- /dev/null +++ b/demo/app/browser.js @@ -0,0 +1 @@ +import Button from '../components/Button'; diff --git a/demo/app/view-engine.js b/demo/app/view-engine.js new file mode 100644 index 0000000..23b1e82 --- /dev/null +++ b/demo/app/view-engine.js @@ -0,0 +1,38 @@ +'use strict'; + +const babel = require('babel-core/register'); +const lodash = require('lodash'); +const React = require('react'); +const ReactDOM = require('react-dom/server'); + +const doctype = ''; + +function dropCache(view) { + const detectView = new RegExp(`^${view}`); + + lodash.forEach(require.cache, (view, identity) => { + detectView.test(view.filename) + && delete require.cache[identity]; + }); +} + +babel({ + only: /components/, +}); + +module.exports = function viewEngine(file, opts, cb) { + let markup = doctype; + + try { + let component = require(file).default; + markup += ReactDOM.renderToStaticMarkup( + React.createElement(component, opts) + ); + } catch (e) { + dropCache(opts.settings.views); + return void cb(e); + } + + dropCache(opts.settings.views); + cb(null, markup); +}; diff --git a/demo/app/worker.js b/demo/app/worker.js new file mode 100644 index 0000000..ccf27dd --- /dev/null +++ b/demo/app/worker.js @@ -0,0 +1,24 @@ +'use strict'; + +const express = require('express'); +const hook = require('css-modules-require-hook'); +const path = require('path'); +const viewEngine = require('./view-engine'); + +const config = require('../package').config; +const app = express(); + +hook({ + generateScopedName: config.css, +}); + +// setting rendering engine +app.engine('js', viewEngine); +app.set('views', path.join(__dirname, '../components')); +app.set('view engine', 'js'); + +app.use(express.static(path.join(__dirname, '../static'))); + +app.get('/', (req, res) => res.render('Page')); + +app.listen(config.port, _ => console.log(`listening ${config.port}`)); diff --git a/demo/components/Button/Button.css b/demo/components/Button/Button.css new file mode 100644 index 0000000..1f768c8 --- /dev/null +++ b/demo/components/Button/Button.css @@ -0,0 +1,18 @@ +.common +{ + padding: 0 13px; + + cursor: pointer; + user-select: none; + text-align: center; + white-space: nowrap; + + border: none; + border-radius: 3px; + outline: none; + background: #ffdb4d; + + font-family: arial, sans-serif; + font-size: 13px; + line-height: 28px; +} diff --git a/demo/components/Button/index.js b/demo/components/Button/index.js new file mode 100644 index 0000000..b302af6 --- /dev/null +++ b/demo/components/Button/index.js @@ -0,0 +1,6 @@ +import React from 'react'; +import base from './Button.css'; + +export default ({ styles = base, ..._ }) => ( + + + +); diff --git a/demo/package.json b/demo/package.json new file mode 100644 index 0000000..62171af --- /dev/null +++ b/demo/package.json @@ -0,0 +1,43 @@ +{ + "name": "demo", + "version": "1.0.0", + "description": "Universal usage example", + "main": "worker.js", + "scripts": { + "compile": "webpack", + "start": "node app/worker", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "config": { + "css": "[name]_[local]__[hash:base64:5]", + "port": 3000 + }, + "keywords": [ + "css-modules" + ], + "author": "Alexey Litvinov", + "license": "MIT", + "devDependencies": { + "babel-loader": "^6.2.1", + "css-loader": "^0.23.1", + "extract-text-webpack-plugin": "^1.0.1", + "style-loader": "^0.13.0", + "webpack": "^1.12.10" + }, + "dependencies": { + "babel-core": "^6.4.0", + "babel-preset-es2015": "^6.3.13", + "babel-preset-react": "^6.3.13", + "babel-preset-stage-0": "^6.3.13", + "css-modules-require-hook": "^2.1.0", + "express": "^4.13.3", + "lodash": "^3.10.1", + "postcss": "^5.0.14", + "postcss-modules-extract-imports": "^1.0.0", + "postcss-modules-local-by-default": "^1.0.1", + "postcss-modules-scope": "^1.0.0", + "postcss-modules-values": "^1.1.1", + "react": "^0.14.6", + "react-dom": "^0.14.6" + } +} diff --git a/demo/readme.md b/demo/readme.md new file mode 100644 index 0000000..69cda3c --- /dev/null +++ b/demo/readme.md @@ -0,0 +1,10 @@ +Universal usage example +======================= + +## Usage + +```bash +npm i +npm run compile +npm run start +``` diff --git a/demo/webpack.config.js b/demo/webpack.config.js new file mode 100644 index 0000000..8acf37c --- /dev/null +++ b/demo/webpack.config.js @@ -0,0 +1,36 @@ +'use strict'; + +const ExtractTextPlugin = require('extract-text-webpack-plugin'); +const path = require('path'); + +const config = require('./package').config; + +module.exports = { + entry: path.resolve('app/browser.js'), + + output: { + filename: 'browser.js', + path: path.resolve('static'), + }, + + module: { + loaders: [ + { + test: /\.js$/i, + exclude: /node_modules/, + loader: 'babel?presets[]=es2015,presets[]=react,presets[]=stage-0', + }, + { + test: /\.css$/i, + loader: ExtractTextPlugin.extract('style', + `css?modules&localIdentName=${config.css}`), + }, + ], + }, + + plugins: [ + new ExtractTextPlugin('common.css', { + allChunks: true + }), + ], +}; diff --git a/generate-tests.js b/generate-tests.js deleted file mode 100644 index 734593d..0000000 --- a/generate-tests.js +++ /dev/null @@ -1,119 +0,0 @@ -import { existsSync, readdirSync, writeFile } from 'fs'; -import { join, resolve, sep } from 'path'; -import { toArray } from 'lodash'; - -const destination = resolve('test/common-test-cases.js'); -const cases = ['test-cases', 'cssi']; - -function resolveTo() { - const args = toArray(arguments); - return resolve(join.apply(null, ['test'].concat(args))); -} - -let content = -`import { dropCache } from '../utils/sugar';\n`+ -`import { equal } from 'assert';\n`+ -`import { readFileSync } from 'fs';\n`+ -`import { resolve } from 'path';\n`+ -`import { extend } from 'lodash';\n`+ -`import FileSystemLoader from 'css-modules-loader-core/lib/file-system-loader';\n`+ -`import hook from '../src';\n`+ -`\n`+ -`const normalize = str => str.replace(/\\r\\n?/g, '\\n');\n`+ -`const pipelines = {\n`+ -` 'test-cases': undefined,\n`+ -` 'cssi': [],\n`+ -`};\n`+ -`\n`+ -`let expectedCSS;\n`+ -`let expectedTokens;\n`+ -`\n`+ -`describe('common-test-cases', () => {\n`; - -cases.forEach(dirname => { - content += - `\n`+ - ` describe('${dirname}', () => {\n`; - - readdirSync(resolveTo(dirname)).forEach(testCase => { - if (existsSync(resolveTo(dirname, testCase, 'source.css'))) { - - content += - `\n`+ - ` describe('${testCase.replace(/-/g, ' ')}', () => {\n`+ - ` before(() => {\n`+ - ` dropCache(resolve('test${sep + dirname + sep + testCase + sep}source.css'));\n`+ - ` expectedCSS = normalize(readFileSync(resolve('test${sep + dirname + sep + testCase + sep}expected.css'), 'utf8'));\n`+ - ` expectedTokens = JSON.parse(readFileSync(resolve('test${sep + dirname + sep + testCase + sep}expected.json'), 'utf8'));\n`+ - ` hook({rootDir: resolve('test${sep + dirname}'), use: pipelines['${dirname}']});\n`+ - ` });\n`+ - `\n`+ - ` it.skip('loader-core', done => {\n`+ - ` const loader = new FileSystemLoader(resolve('test${sep + dirname}'), pipelines['${dirname}']);\n`+ - `\n`+ - ` loader.fetch('${testCase + sep}source.css', '/')\n`+ - ` .then(tokens => {\n`+ - ` equal(loader.finalSource, expectedCSS);\n`+ - ` equal(JSON.stringify(tokens), JSON.stringify(expectedTokens));\n`+ - ` })\n`+ - ` .then(done, done);\n`+ - ` });\n`+ - `\n`+ - ` it('require-hook', () => {\n`+ - ` const tokens = require(resolve('test${sep + dirname + sep + testCase + sep}source.css'));\n`+ - ` equal(JSON.stringify(tokens), JSON.stringify(expectedTokens));\n`+ - ` });\n`+ - ` });\n`; - - } else { - - content += - `\n`+ - ` describe('${testCase.replace(/-/g, ' ')}', () => {\n`+ - ` before(() => {\n`+ - ` dropCache(resolve('test${sep + dirname + sep + testCase + sep}source1.css'));\n`+ - ` dropCache(resolve('test${sep + dirname + sep + testCase + sep}source2.css'));\n`+ - ` expectedCSS = normalize(readFileSync(resolve('test${sep + dirname + sep + testCase + sep}expected.css'), 'utf8'));\n`+ - ` expectedTokens = JSON.parse(readFileSync(resolve('test${sep + dirname + sep + testCase + sep}expected.json'), 'utf8'));\n`+ - ` hook({rootDir: resolve('test${sep + dirname}'), use: pipelines['${dirname}']});\n`+ - ` });\n`+ - `\n`+ - ` it.skip('loader-core', done => {\n`+ - ` const loader = new FileSystemLoader(resolve('test${sep + dirname}'), pipelines['${dirname}']);\n`+ - `\n`+ - ` loader.fetch('${testCase + sep}source1.css', '/').then(tokens1 => {\n`+ - ` loader.fetch('${testCase + sep}source2.css', '/').then(tokens2 => {\n`+ - ` equal(loader.finalSource, expectedCSS);\n`+ - ` const tokens = extend({}, tokens1, tokens2);\n`+ - ` equal(JSON.stringify(tokens), JSON.stringify(expectedTokens));\n`+ - ` }).then(done, done);\n`+ - ` }).catch(done);\n`+ - ` });\n`+ - `\n`+ - ` it('require-hook', () => {\n`+ - ` const tokens = extend({},\n`+ - ` require(resolve('test${sep + dirname + sep + testCase + sep}source1.css')),\n`+ - ` require(resolve('test${sep + dirname + sep + testCase + sep}source2.css'))\n`+ - ` );\n`+ - `\n`+ - ` equal(JSON.stringify(tokens), JSON.stringify(expectedTokens));\n`+ - ` });\n`+ - ` });\n`; - - } - }); - - content += - `\n`+ - ` });\n`; -}); - -content += -`\n`+ -`});\n`; - -writeFile(destination, content, 'utf8', err => { - if (err) { - throw err; - } -}); diff --git a/index.js b/index.js deleted file mode 100644 index cce6933..0000000 --- a/index.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./dist'); diff --git a/lib/attachHook.js b/lib/attachHook.js new file mode 100644 index 0000000..37e4821 --- /dev/null +++ b/lib/attachHook.js @@ -0,0 +1,17 @@ +/** + * @param {function} compile + * @param {string} extension + * @param {function} isException + */ +module.exports = function attachHook(compile, extension, isException) { + const existingHook = require.extensions[extension]; + + require.extensions[extension] = function cssModulesHook(m, filename) { + if (isException(filename)) { + existingHook(m, filename); + } else { + const tokens = compile(filename); + return m._compile(`module.exports = ${JSON.stringify(tokens)}`, filename); + } + }; +}; diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..70fccc3 --- /dev/null +++ b/lib/index.js @@ -0,0 +1,155 @@ +const assign = require('lodash').assign; +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 readFileSync = require('fs').readFileSync; +const relative = require('path').relative; +const resolve = require('path').resolve; +const validate = require('./validate'); + +const postcss = require('postcss'); +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 debugFetch = require('debug')('css-modules:fetch'); +const debugSetup = require('debug')('css-modules:setup'); + +module.exports = function setupHook({ + devMode, + extensions = '.css', + ignore, + preprocessCss = identity, + processCss, + to, + append = [], + prepend = [], + createImportedName, + generateScopedName, + mode, + use, + rootDir: context = process.cwd(), +}) { + debugSetup(arguments[0]); + validate(arguments[0]); + + const tokensByFile = {}; + + // debug option is preferred NODE_ENV === 'development' + const debugMode = typeof devMode !== 'undefined' + ? devMode + : process.env.NODE_ENV === 'development'; + + let scopedName; + if (generateScopedName) { + scopedName = typeof generateScopedName !== 'function' + ? genericNames(generateScopedName, {context}) // for example '[name]__[local]___[hash:base64:5]' + : generateScopedName; + } else { + // small fallback + scopedName = (local, filename) => { + return Scope.generateScopedName(local, relative(context, filename)); + }; + } + + const plugins = (use || [ + ...prepend, + Values, + mode + ? new LocalByDefault({mode}) + : LocalByDefault, + createImportedName + ? new ExtractImports({createImportedName}) + : ExtractImports, + new Scope({generateScopedName: scopedName}), + ...append, + ]).concat(new Parser({fetch})); // no pushing in order to avoid the possible mutations; + + // https://github.com/postcss/postcss#options + const runner = postcss(plugins); + + /** + * @todo think about replacing sequential fetch function calls with requires calls + * @param {string} _to + * @param {string} from + * @return {object} + */ + function fetch(_to, from) { + // getting absolute path to the processing file + const filename = /\w/i.test(_to[0]) + ? require.resolve(_to) + : resolve(dirname(from), _to); + + + // checking cache + let tokens = tokensByFile[filename]; + if (tokens) { + debugFetch(`${filename} → cache`); + debugFetch(tokens); + return tokens; + } + + const source = preprocessCss(readFileSync(filename, 'utf8'), filename); + // https://github.com/postcss/postcss/blob/master/docs/api.md#processorprocesscss-opts + const lazyResult = runner.process(source, assign({}, {from: filename})); + + // https://github.com/postcss/postcss/blob/master/docs/api.md#lazywarnings + lazyResult.warnings().forEach(message => console.warn(message.text)); + + tokens = lazyResult.root.tokens; + + if (!debugMode) { + // updating cache + tokensByFile[filename] = tokens; + } else { + // clearing cache in development mode + delete require.cache[filename]; + } + + if (processCss) { + processCss(lazyResult.css, filename); + } + + debugFetch(`${filename} → fs`); + debugFetch(tokens); + + return tokens; + }; + + const exts = toArray(extensions); + const isException = buildExceptionChecker(ignore); + + // @todo add possibility to specify particular config for each extension + exts.forEach(extension => attachHook(filename => fetch(filename, filename), extension, isException)); +}; + +/** + * @param {*} option + * @return {array} + */ +function toArray(option) { + return Array.isArray(option) + ? option + : [option]; +} + +/** + * @param {function|regex|string} ignore glob, regex or function + * @return {function} + */ +function buildExceptionChecker(ignore) { + if (ignore instanceof RegExp) { + return filepath => ignore.test(filepath); + } + + if (typeof ignore === 'string') { + return filepath => globToRegex(ignore).test(filepath); + } + + return ignore || negate(identity); +} diff --git a/lib/validate.js b/lib/validate.js new file mode 100644 index 0000000..b12cd6f --- /dev/null +++ b/lib/validate.js @@ -0,0 +1,46 @@ +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', + to: 'string', + // plugins + append: 'array', + prepend: 'array', + use: 'array', + createImportedName: 'function', + generateScopedName: 'function|string', + mode: 'string', + rootDir: 'string', +}; + +const tests = { + array: require('lodash').isArray, + boolean: require('lodash').isBoolean, + function: require('lodash').isFunction, + 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 546f7c8..a0df845 100644 --- a/package.json +++ b/package.json @@ -1,63 +1,19 @@ { "name": "css-modules-require-hook", - "version": "2.1.1", + "version": "3.0.0", "description": "A require hook to compile CSS Modules on the fly", - "main": "index.js", + "main": "lib/index.js", "engines": { "node": ">=0.12" }, - "dependencies": { - "debug": "^2.2.0", - "generic-names": "^1.0.1", - "icss-replace-symbols": "^1.0.2", - "lodash.assign": "^3.2.0", - "lodash.foreach": "^3.0.3", - "lodash.identity": "^3.0.0", - "lodash.isarray": "^3.0.4", - "lodash.isfunction": "^3.0.6", - "lodash.isstring": "^3.0.1", - "postcss-modules-parser": "^1.1.0" - }, - "devDependencies": { - "babel": "^5.8.20", - "babel-eslint": "^4.0.5", - "css-loader": "^0.21.0", - "css-modules-loader-core": "^1.0.0", - "eslint": "^1.0.0", - "eslint-config-airbnb": "0.0.7", - "eslint-config-airbnb-lite": "^1.0.3", - "eslint-watch": "^1.2.4", - "extract-text-webpack-plugin": "^0.8.2", - "in-publish": "^2.0.0", - "isparta": "^3.0.3", - "lodash": "^3.10.1", - "mocha": "^2.2.5", - "postcss": "^5.0.10", - "postcss-loader": "^0.7.0", - "postcss-modules-extract-imports": "^1.0.0", - "postcss-modules-local-by-default": "^1.0.0", - "postcss-modules-scope": "^1.0.0", - "postcss-modules-values": "^1.1.0", - "precommit-hook": "^3.0.0", - "style-loader": "^0.13.0", - "webpack": "^1.12.2" - }, - "peerDependencies": { - "postcss": "^5.x", - "postcss-modules-extract-imports": "^1.0.0", - "postcss-modules-local-by-default": "^1.0.0", - "postcss-modules-scope": "^1.0.0", - "postcss-modules-values": "^1.1.0" - }, "scripts": { - "fixture": "webpack", - "start": "esw -w .", - "lint": "eslint .", - "test": "mocha --compilers js:babel/register --timeout 5000", - "test:cov": "`npm bin`/babel-node `npm bin`/isparta cover --report text --report html `npm bin`/_mocha", - "test:gen": "babel-node generate-tests", - "build": "babel src --out-dir dist", - "prepublish": "in-publish && npm run -s build || in-install" + "build": "babel lib --out-dir lib && babel preset.js --out-file preset.js", + "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 --harmony_destructuring --harmony_spread_arrays --use_strict", + "test:watch": "NODE_PATH=$(pwd)/test/tokens/node_modules $npm_package_scripts_test_unit --watch --harmony_destructuring --harmony_spread_arrays --use_strict", + "test:unit": "mocha --require test/setup.js --ui tdd test/*/*.js" }, "repository": { "type": "git", @@ -77,8 +33,27 @@ "url": "https://github.com/css-modules/css-modules-require-hook/issues" }, "homepage": "https://github.com/css-modules/css-modules-require-hook", - "pre-commit": [ - "lint", - "test" - ] + "pre-commit": [], + "dependencies": { + "debug": "^2.2.0", + "generic-names": "^1.0.1", + "glob-to-regexp": "^0.1.0", + "icss-replace-symbols": "^1.0.2", + "lodash": "^4.3.0", + "postcss": "^5.0.15", + "postcss-modules-extract-imports": "^1.0.0", + "postcss-modules-local-by-default": "^1.0.1", + "postcss-modules-parser": "^1.1.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", + "sinon": "^1.17.3" + } } diff --git a/preset.js b/preset.js new file mode 100644 index 0000000..b20216c --- /dev/null +++ b/preset.js @@ -0,0 +1,15 @@ +const basename = require('path').basename; +const debug = require('debug')('css-modules:preset'); +const dirname = require('path').dirname; +const hook = require('./lib/index'); +const seekout = require('seekout'); + +const preset = seekout('cmrh.conf.js', dirname(module.parent.filename)); + +if (preset) { + debug(`→ ${basename(preset)}`); + hook(require(preset)); +} else { + debug(`→ defaults`); + hook({}); +} diff --git a/src/extractor.js b/src/extractor.js deleted file mode 100644 index c7bcb07..0000000 --- a/src/extractor.js +++ /dev/null @@ -1,55 +0,0 @@ -import postcss from 'postcss'; -import genericNames from 'generic-names'; -import { relative } from 'path'; - -import Values from 'postcss-modules-values'; -import LocalByDefault from 'postcss-modules-local-by-default'; -import ExtractImports from 'postcss-modules-extract-imports'; -import Scope from 'postcss-modules-scope'; -import Parser from 'postcss-modules-parser'; - -/** - * @param {array} options.append - * @param {array} options.prepend - * @param {array} options.use - * @param {function} options.createImportedName - * @param {function|string} options.generateScopedName - * @param {string} options.mode - * @param {string} options.rootDir - * @param {function} fetch - * @return {object} - */ -export default function extractor({ - append = [], - prepend = [], - createImportedName, - generateScopedName, - mode, - use, - rootDir: context = process.cwd(), -} = {}, fetch) { - let scopedName; - if (generateScopedName) { - scopedName = typeof generateScopedName !== 'function' - ? genericNames(generateScopedName || '[name]__[local]___[hash:base64:5]', {context}) - : generateScopedName; - } else { - // small fallback - scopedName = (local, filename) => Scope.generateScopedName(local, relative(context, filename)); - } - - const plugins = (use || [ - ...prepend, - Values, - mode - ? new LocalByDefault({mode}) - : LocalByDefault, - createImportedName - ? new ExtractImports({createImportedName}) - : ExtractImports, - new Scope({generateScopedName: scopedName}), - ...append, - ]).concat(new Parser({fetch})); // no pushing in order to avoid the possible mutations - - return postcss(plugins); -} diff --git a/src/guard.js b/src/guard.js deleted file mode 100644 index 95aae22..0000000 --- a/src/guard.js +++ /dev/null @@ -1,5 +0,0 @@ -if (global._cssModulesPolyfill) { - throw new Error('only one instance of css-modules-require-hook is allowed'); -} - -global._cssModulesPolyfill = true; diff --git a/src/hook.js b/src/hook.js deleted file mode 100644 index 755b0c7..0000000 --- a/src/hook.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * @param {function} compile - * @param {string} extension - */ -export default function attachHook(compile, extension) { - require.extensions[extension] = function hook(m, filename) { - const tokens = compile(filename); - return m._compile('module.exports = ' + JSON.stringify(tokens), filename); - }; -} diff --git a/src/index.js b/src/index.js deleted file mode 100644 index 3cb5a18..0000000 --- a/src/index.js +++ /dev/null @@ -1,93 +0,0 @@ -import assign from 'lodash.assign'; -import debug from 'debug'; -import hook from './hook'; -import identity from 'lodash.identity'; -import extractor from './extractor'; -import { readFileSync } from 'fs'; -import { dirname, resolve } from 'path'; -import validate from './validate'; -import './guard'; - -// cache -let tokensByFile = {}; -// globals; -let debugMode = process.env.NODE_ENV !== 'development'; -let instance = extractor({}, fetch); -let processorOptions = {}; -let preProcess = identity; -let postProcess; - -const debugFetch = debug('css-modules:fetch'); -const debugSetup = debug('css-modules:setup'); - -/** - * @param {array} options.extensions - * @param {function} options.preprocessCss - * @param {function} options.processCss - * @param {string} options.to - * @param {object} options.rest - */ -export default function setup({ extensions: extraExtensions, preprocessCss, processCss, to, devMode, ...rest } = {}) { - debugSetup(arguments[0]); - validate(arguments[0]); - instance = extractor(rest, fetch); - processorOptions = {to}; - preProcess = preprocessCss || identity; - postProcess = processCss || null; - // clearing cache - tokensByFile = {}; - - // debug option is preferred NODE_ENV === 'development' - if (typeof devMode !== 'undefined') { - debugMode = devMode; - } - - if (extraExtensions) { - extraExtensions.forEach((extension) => hook(filename => fetch(filename, filename), extension)); - } -} - -/** - * @param {string} to Absolute or relative path. Also can be path to the Node.JS module. - * @param {string} from Absolute path. - * @return {object} - */ -function fetch(to, from) { - // getting absolute path to the processing file - const filename = /\w/i.test(to[0]) - ? require.resolve(to) - : resolve(dirname(from), to); - - // checking cache - let tokens = tokensByFile[filename]; - if (tokens) { - debugFetch({cache: true, filename}); - return tokens; - } - - debugFetch({cache: false, filename}); - const CSSSource = preProcess(readFileSync(filename, 'utf8'), filename); - // https://github.com/postcss/postcss/blob/master/docs/api.md#processorprocesscss-opts - const lazyResult = instance.process(CSSSource, assign(processorOptions, {from: filename})); - - // https://github.com/postcss/postcss/blob/master/docs/api.md#lazywarnings - lazyResult.warnings().forEach(message => console.warn(message.text)); - - tokens = lazyResult.root.tokens; - - if (!debugMode) { - // updating cache - tokensByFile[filename] = tokens; - } else { - // clearing cache in development mode - delete require.cache[filename]; - } - - if (postProcess) { - postProcess(lazyResult.css, filename); - } - - return tokens; -} - -hook(filename => fetch(filename, filename), '.css'); diff --git a/src/utility.js b/src/utility.js deleted file mode 100644 index bf38088..0000000 --- a/src/utility.js +++ /dev/null @@ -1,18 +0,0 @@ -import isArray from 'lodash.isarray'; -import isFunction from 'lodash.isfunction'; -import isString from 'lodash.isstring'; - -const check = { - 'array': isArray, - 'function': isFunction, - 'string': isString, -}; - -/** - * @param {string} type - * @param {*} value - * @return {boolean} - */ -export function is(type, value) { - return check[type](value); -} diff --git a/src/validate.js b/src/validate.js deleted file mode 100644 index a985046..0000000 --- a/src/validate.js +++ /dev/null @@ -1,31 +0,0 @@ -import forEach from 'lodash.foreach'; -import { is } from './utility'; -import { format } from 'util'; - -const rules = { - // hook - extensions: 'array', - preprocessCss: 'function', - processCss: 'function', - to: 'string', - // plugins - append: 'array', - prepend: 'array', - use: 'array', - createImportedName: 'function', - generateScopedName: 'function|string', - mode: 'string', - rootDir: 'string', -}; - -export default function validate(options = {}) { - forEach(rules, (types, rule) => { - if (!options[rule]) { - return; - } - - if (!types.split('|').some(type => is(type, options[rule]))) { - throw new Error(format('should specify %s for the %s', types, rule)); - } - }); -} diff --git a/test/api/append.js b/test/api/append.js new file mode 100644 index 0000000..0a4ddd3 --- /dev/null +++ b/test/api/append.js @@ -0,0 +1,22 @@ +const detachHook = require('../sugar').detachHook; +const dropCache = require('../sugar').dropCache; +const identity = require('lodash').lodash; +const Through = require('../sugar').Through; + +suite('api/append', () => { + suite('should add plugins to the pipeline', () => { + const processor = spy(identity); + + test('plugin should be called', () => assert(processor.called)); + + setup(() => { + hook({append: [new Through(processor)]}); + require('./fixture/oceanic.css'); + }); + + teardown(() => { + detachHook('.css'); + dropCache('./api/fixture/oceanic.css'); + }); + }); +}); diff --git a/test/api/devMode.js b/test/api/devMode.js new file mode 100644 index 0000000..3ba88e3 --- /dev/null +++ b/test/api/devMode.js @@ -0,0 +1,105 @@ +const detachHook = require('../sugar').detachHook; +const dropCache = require('../sugar').dropCache; +const resolve = require('path').resolve; +const writeFileSync = require('fs').writeFileSync; + +const destination = resolve(__dirname, './fixture/oceanic.css'); +const source1 = `.color\n{\n background: #1e2a35;\n}\n`; +const source2 = `.awesome-color\n{\n background: #1e2a35;\n}\n`; + +suite('api/devMode', () => { + suite('shouldn`t calls cache in development mode', () => { + suite('devMode:false options should override NODE_ENV="development"', () => { + test('should retrive data from cache', () => { + const tokens = require('./fixture/oceanic.css'); + + assert.deepEqual(tokens, { + color: '_test_api_fixture_oceanic__color', + }); + }); + + setup(() => { + process.env.NODE_ENV = 'development'; + hook({devMode: false}); + writeFile(source1); + require('./fixture/oceanic.css'); + writeFile(source2); + }); + + teardown(() => { + process.env.NODE_ENV = ''; + }); + }); + + suite('should cache calls without any options', () => { + test('should retrive data from cache', () => { + const tokens = require('./fixture/oceanic.css'); + + assert.deepEqual(tokens, { + color: '_test_api_fixture_oceanic__color', + }); + }); + + setup(() => { + hook({}); + writeFile(source1); + require('./fixture/oceanic.css'); + writeFile(source2); + }); + }); + }); + + suite('should clear cache in development mode', () => { + suite('devMode:true option should works without NODE_ENV="development"', () => { + test('should retrive data from fs', () => { + const tokens = require('./fixture/oceanic.css'); + + assert.deepEqual(tokens, { + 'awesome-color': '_test_api_fixture_oceanic__awesome-color', + }); + }); + + setup(() => { + hook({devMode: true}); + writeFile(source1); + require('./fixture/oceanic.css'); + writeFile(source2); + }); + }); + + suite('NODE_ENV="development" should works without debug:true option', () => { + test('should retrive data from fs', () => { + const tokens = require('./fixture/oceanic.css'); + + assert.deepEqual(tokens, { + 'awesome-color': '_test_api_fixture_oceanic__awesome-color', + }); + }); + + setup(() => { + process.env.NODE_ENV = 'development'; + hook({}); + writeFile(source1); + require('./fixture/oceanic.css'); + writeFile(source2); + }); + + teardown(() => { + process.env.NODE_ENV = ''; + }); + }); + }); + + teardown(() => { + writeFile(source1); + detachHook('.css'); + dropCache('./api/fixture/oceanic.css'); + }); +}); + +/** + * @param {string} data + */ +function writeFile(data) { + writeFileSync(destination, data, 'utf8'); +} diff --git a/test/api/extensions.js b/test/api/extensions.js new file mode 100644 index 0000000..775aba5 --- /dev/null +++ b/test/api/extensions.js @@ -0,0 +1,53 @@ +const detachHook = require('../sugar').detachHook; +const dropCache = require('../sugar').dropCache; +const identity = require('lodash').identity; + +suite('api/extensions', () => { + suite('uses .css by default', () => { + test('should provide tokens', () => { + const tokens = require('./fixture/oceanic.css'); + assert(tokens); + }); + + setup(() => hook({})); + + teardown(() => { + detachHook('.css'); + dropCache('./api/fixture/oceanic.css'); + }); + }); + + suite('uses provided extension', () => { + test('should provide tokens', () => { + const tokens = require('./fixture/oceanic.css'); + assert(tokens); + }); + + setup(() => hook({extensions: '.css'})); + + teardown(() => { + detachHook('.css'); + dropCache('./api/fixture/oceanic.css'); + }); + }); + + suite('uses multiple extensions', () => { + test('should provide tokens', () => { + const tokens = require('./fixture/oceanic.css'); + assert(tokens); + }); + + test('should provide tokens', () => { + const tokens = require('./fixture/oceanic.scss'); + assert(tokens); + }); + + setup(() => hook({extensions: ['.css', '.scss']})); + + teardown(() => { + detachHook('.css'); + dropCache('./api/fixture/oceanic.css'); + dropCache('./api/fixture/oceanic.scss'); + }); + }); +}); diff --git a/node_modules/awesome-theme/oceanic.css b/test/api/fixture/oceanic.css similarity index 100% rename from node_modules/awesome-theme/oceanic.css rename to test/api/fixture/oceanic.css diff --git a/test/api/fixture/oceanic.scss b/test/api/fixture/oceanic.scss new file mode 100644 index 0000000..7e4881c --- /dev/null +++ b/test/api/fixture/oceanic.scss @@ -0,0 +1,4 @@ +.color +{ + background: #1e2a35; +} diff --git a/test/api/fixture/typography.css b/test/api/fixture/typography.css new file mode 100644 index 0000000..f41b71d --- /dev/null +++ b/test/api/fixture/typography.css @@ -0,0 +1,6 @@ +@import url(http://fonts.googleapis.com/css?family=Vollkorn:400,400italic,700,700italic&subset=latin); + +.common +{ + font: 1.3em 'Vollkorn', Palatino, Times; +} diff --git a/test/api/generateScopedName.js b/test/api/generateScopedName.js new file mode 100644 index 0000000..a6d092e --- /dev/null +++ b/test/api/generateScopedName.js @@ -0,0 +1,57 @@ +const detachHook = require('../sugar').detachHook; +const dropCache = require('../sugar').dropCache; +const identity = require('lodash').lodash; +const resolve = require('path').resolve; + +suite('api/generateScopedName', () => { + suite('using function', () => { + let args; + let tokens; + + const processor = spy(function (selector, filepath, source) { + args = [selector, filepath, source]; + return selector; + }); + + test('processor should be called', () => { + assert(processor.called); + }); + + test('should provide selector, filepath and source to the function', () => { + assert.deepEqual(args, [ + 'color', + resolve('test/api/fixture/oceanic.css'), + '.color\n{\n background: #1e2a35;\n}\n', + ]); + }); + + test('should return tokens with same keys', () => { + assert.deepEqual(tokens, { + color: 'color', + }); + }); + + setup(() => { + hook({generateScopedName: processor}); + tokens = require('./fixture/oceanic.css'); + }); + }); + + suite('using string pattern', () => { + let tokens; + + test('should return tokens with id', () => assert.deepEqual(tokens, { + color: 'oceanic__color___1GAeQ', + })); + + setup(() => { + hook({generateScopedName: '[name]__[local]___[hash:base64:5]'}); + tokens = require('./fixture/oceanic.css'); + }); + }); + + teardown(() => { + detachHook('.css'); + dropCache('./api/fixture/oceanic.css'); + }); +}); diff --git a/test/api/ignore.js b/test/api/ignore.js new file mode 100644 index 0000000..e3645fa --- /dev/null +++ b/test/api/ignore.js @@ -0,0 +1,53 @@ +const detachHook = require('../sugar').detachHook; +const dropCache = require('../sugar').dropCache; + +suite('api/ignore', () => { + suite('glob', () => { + setup(() => hook({ + ignore: '*oceanic*', + })); + + test('should return tokens', () => { + assert.deepEqual(require('./fixture/typography.css'), { + common: '_test_api_fixture_typography__common', + }); + }); + + test('should throw an exception', () => { + assert.throws(() => require('./fixture/oceanic.css')); + }); + }); + + suite('function', () => { + setup(() => hook({ + ignore: filename => /typography/.test(filename), + })); + + test('should return tokens', () => { + assert.throws(() => require('./fixture/typography.css')); + }); + + test('should throw an exception', () => { + assert.deepEqual(require('./fixture/oceanic.css'), { + color: '_test_api_fixture_oceanic__color', + }); + }); + }); + + suite('regex', () => { + setup(() => hook({ + ignore: /\.css$/, + })); + + test('should throw an exception', () => { + assert.throws(() => require('./fixture/typography.css')); + assert.throws(() => require('./fixture/oceanic.css')); + }); + }); + + teardown(() => { + detachHook('.css'); + dropCache('./api/fixture/oceanic.css'); + dropCache('./api/fixture/typography.css'); + }); +}); diff --git a/test/api/prepend.js b/test/api/prepend.js new file mode 100644 index 0000000..c9b6e7b --- /dev/null +++ b/test/api/prepend.js @@ -0,0 +1,22 @@ +const detachHook = require('../sugar').detachHook; +const dropCache = require('../sugar').dropCache; +const identity = require('lodash').lodash; +const Through = require('../sugar').Through; + +suite('api/prepend', () => { + suite('should add plugins to the pipeline', () => { + const processor = spy(identity); + + test('plugin should be called', () => assert(processor.called)); + + setup(() => { + hook({prepend: [new Through(processor)]}); + require('./fixture/oceanic.css'); + }); + + teardown(() => { + detachHook('.css'); + dropCache('./api/fixture/oceanic.css'); + }); + }); +}); diff --git a/test/api/preprocessCss.js b/test/api/preprocessCss.js new file mode 100644 index 0000000..f5364d2 --- /dev/null +++ b/test/api/preprocessCss.js @@ -0,0 +1,19 @@ +const detachHook = require('../sugar').detachHook; +const dropCache = require('../sugar').dropCache; +const identity = require('lodash').identity; + +suite('api/preprocessCss', () => { + const preprocessCss = spy(identity); + + test('should be called', () => assert(preprocessCss.called)); + + setup(() => { + hook({preprocessCss}); + require('./fixture/oceanic.css'); + }); + + teardown(() => { + detachHook('.css'); + dropCache('./api/fixture/oceanic.css'); + }); +}); diff --git a/test/api/processCss.js b/test/api/processCss.js new file mode 100644 index 0000000..11a4aa9 --- /dev/null +++ b/test/api/processCss.js @@ -0,0 +1,19 @@ +const detachHook = require('../sugar').detachHook; +const dropCache = require('../sugar').dropCache; +const identity = require('lodash').identity; + +suite('api/processCss()', () => { + const processCss = spy(identity); + + test('should be called', () => assert(processCss.called)); + + setup(() => { + hook({processCss}); + require('./fixture/oceanic.css'); + }); + + teardown(() => { + detachHook('.css'); + dropCache('./api/fixture/oceanic.css'); + }); +}); diff --git a/test/api/rootDir.js b/test/api/rootDir.js new file mode 100644 index 0000000..2028fd8 --- /dev/null +++ b/test/api/rootDir.js @@ -0,0 +1,26 @@ +const detachHook = require('../sugar').detachHook; +const dropCache = require('../sugar').dropCache; +const join = require('path').join; + +suite('api/rootDir', () => { + suite('should change the way in which tokens are generated', () => { + let tokens1; + let tokens2; + + test('should return different tokens', () => assert.notDeepEqual(tokens1, tokens2)); + + setup(() => { + hook({rootDir: join(__dirname, '../../')}); + tokens1 = require('./fixture/oceanic.css'); + dropCache('./api/fixture/oceanic.css'); + + hook({rootDir: __dirname}); + tokens2 = require('./fixture/oceanic.css'); + }); + + teardown(() => { + detachHook('.css'); + dropCache('./api/fixture/oceanic.css'); + }); + }); +}); diff --git a/test/api/use.js b/test/api/use.js new file mode 100644 index 0000000..a78c487 --- /dev/null +++ b/test/api/use.js @@ -0,0 +1,26 @@ +const detachHook = require('../sugar').detachHook; +const dropCache = require('../sugar').dropCache; +const identity = require('lodash').lodash; +const Through = require('../sugar').Through; + +suite('api/use', () => { + suite('should replace plugins in the pipeline', () => { + const processor = spy(identity); + let tokens; + + test('plugin should be called', () => { + assert(processor.called); + assert.deepEqual(tokens, {}); + }); + + setup(() => { + hook({use: [new Through(processor)]}); + tokens = require('./fixture/oceanic.css'); + }); + + teardown(() => { + detachHook('.css'); + dropCache('./api/fixture/oceanic.css'); + }); + }); +}); diff --git a/test/cache.js b/test/cache.js deleted file mode 100644 index 5329e8e..0000000 --- a/test/cache.js +++ /dev/null @@ -1,101 +0,0 @@ -import { equal } from 'assert'; -import { writeFileSync } from 'fs'; -import { join } from 'path'; -import { dropCache } from '../utils/sugar'; -import hook from '../src'; - -const fixture = join(__dirname, 'fixture/dynamic.css'); - -function updateFile(content) { - writeFileSync(fixture, content, 'utf8'); -} - -describe('development mode', () => { - describe('shouldn`t calls cache in development mode', () => { - describe('devMode:false options should override NODE_ENV="development"', () => { - before(() => { - hook({ devMode: false }); - updateFile('.block\n{\n display: block;\n}'); - process.env.NODE_ENV = 'development'; - require(fixture); - updateFile('.inline-block\n{\n display: inline-block;\n}'); - }); - - after(() => { - process.env.NODE_ENV = ''; - dropCache(fixture); - }); - - it('should retrive data from cache', () => { - const tokens = require(fixture); - const keys = Object.keys(tokens); - equal(keys.length, 1); - equal(keys.join(''), 'block'); - }); - }); - - describe('should cache calls without any options', () => { - before(() => { - hook(); - updateFile('.block\n{\n display: block;\n}'); - require(fixture); - updateFile('.inline-block\n{\n display: inline-block;\n}'); - }); - - after(() => { - dropCache(fixture); - }); - - it('should retrive data from cache', () => { - const tokens = require(fixture); - const keys = Object.keys(tokens); - equal(keys.length, 1); - equal(keys.join(''), 'block'); - }); - }); - }); - - describe('should clear cache in development mode', () => { - describe('devMode:true option should works without NODE_ENV="development"', () => { - before(() => { - hook({ devMode: true }); - updateFile('.block\n{\n display: block;\n}'); - require(fixture); - updateFile('.inline-block\n{\n display: inline-block;\n}'); - }); - - after(() => { - dropCache(fixture); - }); - - it('should retrive data from fs', () => { - const tokens = require(fixture); - const keys = Object.keys(tokens); - equal(keys.length, 1); - equal(keys.join(''), 'inline-block'); - }); - }); - - describe('NODE_ENV="development" should works without debug:true option', () => { - before(() => { - hook(); - updateFile('.block\n{\n display: block;\n}'); - process.env.NODE_ENV = 'development'; - require(fixture); - updateFile('.inline-block\n{\n display: inline-block;\n}'); - }); - - after(() => { - process.env.NODE_ENV = ''; - dropCache(fixture); - }); - - it('should retrive data from fs', () => { - const tokens = require(fixture); - const keys = Object.keys(tokens); - equal(keys.length, 1); - equal(keys.join(''), 'inline-block'); - }); - }); - }); -}); diff --git a/test/cases/webpack/.gitignore b/test/cases/webpack/.gitignore deleted file mode 100644 index fcfc4a1..0000000 --- a/test/cases/webpack/.gitignore +++ /dev/null @@ -1 +0,0 @@ -result* diff --git a/test/cases/webpack/source.css b/test/cases/webpack/source.css deleted file mode 100644 index c8481a9..0000000 --- a/test/cases/webpack/source.css +++ /dev/null @@ -1,4 +0,0 @@ -.local -{ - color: white; -} diff --git a/test/cases/webpack/source.js b/test/cases/webpack/source.js deleted file mode 100644 index ae1bf86..0000000 --- a/test/cases/webpack/source.js +++ /dev/null @@ -1 +0,0 @@ -var css = require('./source.css'); diff --git a/test/common-test-cases.js b/test/common-test-cases.js deleted file mode 100644 index c0774c6..0000000 --- a/test/common-test-cases.js +++ /dev/null @@ -1,272 +0,0 @@ -import { dropCache } from '../utils/sugar'; -import { equal } from 'assert'; -import { readFileSync } from 'fs'; -import { resolve } from 'path'; -import { extend } from 'lodash'; -import FileSystemLoader from 'css-modules-loader-core/lib/file-system-loader'; -import hook from '../src'; - -const normalize = str => str.replace(/\r\n?/g, '\n'); -const pipelines = { - 'test-cases': undefined, - 'cssi': [], -}; - -let expectedCSS; -let expectedTokens; - -describe('common-test-cases', () => { - - describe('test-cases', () => { - - describe('compose node module', () => { - before(() => { - dropCache(resolve('test/test-cases/compose-node-module/source.css')); - expectedCSS = normalize(readFileSync(resolve('test/test-cases/compose-node-module/expected.css'), 'utf8')); - expectedTokens = JSON.parse(readFileSync(resolve('test/test-cases/compose-node-module/expected.json'), 'utf8')); - hook({rootDir: resolve('test/test-cases'), use: pipelines['test-cases']}); - }); - - it.skip('loader-core', done => { - const loader = new FileSystemLoader(resolve('test/test-cases'), pipelines['test-cases']); - - loader.fetch('compose-node-module/source.css', '/') - .then(tokens => { - equal(loader.finalSource, expectedCSS); - equal(JSON.stringify(tokens), JSON.stringify(expectedTokens)); - }) - .then(done, done); - }); - - it('require-hook', () => { - const tokens = require(resolve('test/test-cases/compose-node-module/source.css')); - equal(JSON.stringify(tokens), JSON.stringify(expectedTokens)); - }); - }); - - describe('extra extension', () => { - before(() => { - expectedTokens = JSON.parse(readFileSync(resolve('test/test-cases/extra-extension/expected.json'), 'utf8')); - hook({extensions: ['.scss']}); - }); - - it('require-hook', () => { - const tokens = require(resolve('test/test-cases/extra-extension/source.scss')); - equal(JSON.stringify(tokens), JSON.stringify(expectedTokens)); - }); - - }); - - describe('localise export', () => { - before(() => { - dropCache(resolve('test/test-cases/localise-export/source.css')); - expectedCSS = normalize(readFileSync(resolve('test/test-cases/localise-export/expected.css'), 'utf8')); - expectedTokens = JSON.parse(readFileSync(resolve('test/test-cases/localise-export/expected.json'), 'utf8')); - hook({rootDir: resolve('test/test-cases'), use: pipelines['test-cases']}); - }); - - it('loader-core', done => { - const loader = new FileSystemLoader(resolve('test/test-cases'), pipelines['test-cases']); - - loader.fetch('localise-export/source.css', '/') - .then(tokens => { - equal(loader.finalSource, expectedCSS); - equal(JSON.stringify(tokens), JSON.stringify(expectedTokens)); - }) - .then(done, done); - }); - - it('require-hook', () => { - const tokens = require(resolve('test/test-cases/localise-export/source.css')); - equal(JSON.stringify(tokens), JSON.stringify(expectedTokens)); - }); - }); - - describe('multiple dependencies', () => { - before(() => { - dropCache(resolve('test/test-cases/multiple-dependencies/source.css')); - expectedCSS = normalize(readFileSync(resolve('test/test-cases/multiple-dependencies/expected.css'), 'utf8')); - expectedTokens = JSON.parse(readFileSync(resolve('test/test-cases/multiple-dependencies/expected.json'), 'utf8')); - hook({rootDir: resolve('test/test-cases'), use: pipelines['test-cases']}); - }); - - it('loader-core', done => { - const loader = new FileSystemLoader(resolve('test/test-cases'), pipelines['test-cases']); - - loader.fetch('multiple-dependencies/source.css', '/') - .then(tokens => { - equal(loader.finalSource, expectedCSS); - equal(JSON.stringify(tokens), JSON.stringify(expectedTokens)); - }) - .then(done, done); - }); - - it('require-hook', () => { - const tokens = require(resolve('test/test-cases/multiple-dependencies/source.css')); - equal(JSON.stringify(tokens), JSON.stringify(expectedTokens)); - }); - }); - - describe('multiple sources', () => { - before(() => { - dropCache(resolve('test/test-cases/multiple-sources/source1.css')); - dropCache(resolve('test/test-cases/multiple-sources/source2.css')); - expectedCSS = normalize(readFileSync(resolve('test/test-cases/multiple-sources/expected.css'), 'utf8')); - expectedTokens = JSON.parse(readFileSync(resolve('test/test-cases/multiple-sources/expected.json'), 'utf8')); - hook({rootDir: resolve('test/test-cases'), use: pipelines['test-cases']}); - }); - - it('loader-core', done => { - const loader = new FileSystemLoader(resolve('test/test-cases'), pipelines['test-cases']); - - loader.fetch('multiple-sources/source1.css', '/').then(tokens1 => { - loader.fetch('multiple-sources/source2.css', '/').then(tokens2 => { - equal(loader.finalSource, expectedCSS); - const tokens = extend({}, tokens1, tokens2); - equal(JSON.stringify(tokens), JSON.stringify(expectedTokens)); - }).then(done, done); - }).catch(done); - }); - - it('require-hook', () => { - const tokens = extend({}, - require(resolve('test/test-cases/multiple-sources/source1.css')), - require(resolve('test/test-cases/multiple-sources/source2.css')) - ); - - equal(JSON.stringify(tokens), JSON.stringify(expectedTokens)); - }); - }); - - describe('simple export', () => { - before(() => { - dropCache(resolve('test/test-cases/simple-export/source.css')); - expectedCSS = normalize(readFileSync(resolve('test/test-cases/simple-export/expected.css'), 'utf8')); - expectedTokens = JSON.parse(readFileSync(resolve('test/test-cases/simple-export/expected.json'), 'utf8')); - hook({rootDir: resolve('test/test-cases'), use: pipelines['test-cases']}); - }); - - it('loader-core', done => { - const loader = new FileSystemLoader(resolve('test/test-cases'), pipelines['test-cases']); - - loader.fetch('simple-export/source.css', '/') - .then(tokens => { - equal(loader.finalSource, expectedCSS); - equal(JSON.stringify(tokens), JSON.stringify(expectedTokens)); - }) - .then(done, done); - }); - - it('require-hook', () => { - const tokens = require(resolve('test/test-cases/simple-export/source.css')); - equal(JSON.stringify(tokens), JSON.stringify(expectedTokens)); - }); - }); - - describe('single import export', () => { - before(() => { - dropCache(resolve('test/test-cases/single-import-export/source.css')); - expectedCSS = normalize(readFileSync(resolve('test/test-cases/single-import-export/expected.css'), 'utf8')); - expectedTokens = JSON.parse(readFileSync(resolve('test/test-cases/single-import-export/expected.json'), 'utf8')); - hook({rootDir: resolve('test/test-cases'), use: pipelines['test-cases']}); - }); - - it('loader-core', done => { - const loader = new FileSystemLoader(resolve('test/test-cases'), pipelines['test-cases']); - - loader.fetch('single-import-export/source.css', '/') - .then(tokens => { - equal(loader.finalSource, expectedCSS); - equal(JSON.stringify(tokens), JSON.stringify(expectedTokens)); - }) - .then(done, done); - }); - - it('require-hook', () => { - const tokens = require(resolve('test/test-cases/single-import-export/source.css')); - equal(JSON.stringify(tokens), JSON.stringify(expectedTokens)); - }); - }); - - describe('values', () => { - before(() => { - dropCache(resolve('test/test-cases/values/source.css')); - expectedCSS = normalize(readFileSync(resolve('test/test-cases/values/expected.css'), 'utf8')); - expectedTokens = JSON.parse(readFileSync(resolve('test/test-cases/values/expected.json'), 'utf8')); - hook({rootDir: resolve('test/test-cases'), use: pipelines['test-cases']}); - }); - - it('loader-core', done => { - const loader = new FileSystemLoader(resolve('test/test-cases'), pipelines['test-cases']); - - loader.fetch('values/source.css', '/') - .then(tokens => { - equal(loader.finalSource, expectedCSS); - equal(JSON.stringify(tokens), JSON.stringify(expectedTokens)); - }) - .then(done, done); - }); - - it('require-hook', () => { - const tokens = require(resolve('test/test-cases/values/source.css')); - equal(JSON.stringify(tokens), JSON.stringify(expectedTokens)); - }); - }); - - }); - - describe('cssi', () => { - - describe('interchange format', () => { - before(() => { - dropCache(resolve('test/cssi/interchange-format/source.css')); - expectedCSS = normalize(readFileSync(resolve('test/cssi/interchange-format/expected.css'), 'utf8')); - expectedTokens = JSON.parse(readFileSync(resolve('test/cssi/interchange-format/expected.json'), 'utf8')); - hook({rootDir: resolve('test/cssi'), use: pipelines['cssi']}); - }); - - it('loader-core', done => { - const loader = new FileSystemLoader(resolve('test/cssi'), pipelines['cssi']); - - loader.fetch('interchange-format/source.css', '/') - .then(tokens => { - equal(loader.finalSource, expectedCSS); - equal(JSON.stringify(tokens), JSON.stringify(expectedTokens)); - }) - .then(done, done); - }); - - it('require-hook', () => { - const tokens = require(resolve('test/cssi/interchange-format/source.css')); - equal(JSON.stringify(tokens), JSON.stringify(expectedTokens)); - }); - }); - - describe('pseudo variables', () => { - before(() => { - dropCache(resolve('test/cssi/pseudo-variables/source.css')); - expectedCSS = normalize(readFileSync(resolve('test/cssi/pseudo-variables/expected.css'), 'utf8')); - expectedTokens = JSON.parse(readFileSync(resolve('test/cssi/pseudo-variables/expected.json'), 'utf8')); - hook({rootDir: resolve('test/cssi'), use: pipelines['cssi']}); - }); - - it('loader-core', done => { - const loader = new FileSystemLoader(resolve('test/cssi'), pipelines['cssi']); - - loader.fetch('pseudo-variables/source.css', '/') - .then(tokens => { - equal(loader.finalSource, expectedCSS); - equal(JSON.stringify(tokens), JSON.stringify(expectedTokens)); - }) - .then(done, done); - }); - - it('require-hook', () => { - const tokens = require(resolve('test/cssi/pseudo-variables/source.css')); - equal(JSON.stringify(tokens), JSON.stringify(expectedTokens)); - }); - }); - - }); - -}); diff --git a/test/cssi/interchange-format/colors.css b/test/cssi/interchange-format/colors.css deleted file mode 100644 index 4057aae..0000000 --- a/test/cssi/interchange-format/colors.css +++ /dev/null @@ -1,7 +0,0 @@ -:export { - blackShadow: x__single_import_export_colors__blackShadow; -} - -.x__single_import_export_colors__blackShadow { - box-shadow: 0 0 10px -2px black; -} diff --git a/test/cssi/interchange-format/expected.css b/test/cssi/interchange-format/expected.css deleted file mode 100644 index 36f1092..0000000 --- a/test/cssi/interchange-format/expected.css +++ /dev/null @@ -1,6 +0,0 @@ -.x__single_import_export_colors__blackShadow { - box-shadow: 0 0 10px -2px black; -} -.x__single_import_export_source__localName { - color: red; -} diff --git a/test/cssi/interchange-format/expected.json b/test/cssi/interchange-format/expected.json deleted file mode 100644 index 43311c7..0000000 --- a/test/cssi/interchange-format/expected.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "localName": "x__single_import_export_source__localName x__single_import_export_colors__blackShadow" -} diff --git a/test/cssi/interchange-format/source.css b/test/cssi/interchange-format/source.css deleted file mode 100644 index d7125a0..0000000 --- a/test/cssi/interchange-format/source.css +++ /dev/null @@ -1,11 +0,0 @@ -:import("./colors.css") { - i__tmp_import_djhgdsag: blackShadow; -} - -:export { - localName: x__single_import_export_source__localName i__tmp_import_djhgdsag; -} - -.x__single_import_export_source__localName { - color: red; -} diff --git a/test/cssi/pseudo-variables/colors.css b/test/cssi/pseudo-variables/colors.css deleted file mode 100644 index 3ac72d9..0000000 --- a/test/cssi/pseudo-variables/colors.css +++ /dev/null @@ -1,4 +0,0 @@ -:export { - black: #222; - white: #ddd; -} diff --git a/test/cssi/pseudo-variables/expected.css b/test/cssi/pseudo-variables/expected.css deleted file mode 100644 index 371497c..0000000 --- a/test/cssi/pseudo-variables/expected.css +++ /dev/null @@ -1,5 +0,0 @@ - -.x__lol { - color: #222; - background: #ddd; -} diff --git a/test/cssi/pseudo-variables/expected.json b/test/cssi/pseudo-variables/expected.json deleted file mode 100644 index b8c3c23..0000000 --- a/test/cssi/pseudo-variables/expected.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "lol": "x__lol" -} diff --git a/test/cssi/pseudo-variables/source.css b/test/cssi/pseudo-variables/source.css deleted file mode 100644 index cc4b95a..0000000 --- a/test/cssi/pseudo-variables/source.css +++ /dev/null @@ -1,13 +0,0 @@ -:import("./colors.css") { - i__black: black; - i__white: white; -} - -:export { - lol: x__lol; -} - -.x__lol { - color: i__black; - background: i__white; -} diff --git a/test/env/fixture/oceanic.css b/test/env/fixture/oceanic.css new file mode 100644 index 0000000..7e4881c --- /dev/null +++ b/test/env/fixture/oceanic.css @@ -0,0 +1,4 @@ +.color +{ + background: #1e2a35; +} diff --git a/test/env/node_env.js b/test/env/node_env.js new file mode 100644 index 0000000..5a6c4f5 --- /dev/null +++ b/test/env/node_env.js @@ -0,0 +1,62 @@ +const detachHook = require('../sugar').detachHook; +const dropCache = require('../sugar').dropCache; +const resolve = require('path').resolve; +const writeFileSync = require('fs').writeFileSync; + +const destination = resolve(__dirname, './fixture/oceanic.css'); +const source1 = `.color\n{\n background: #1e2a35;\n}\n`; +const source2 = `.awesome-color\n{\n background: #1e2a35;\n}\n`; + +suite('env.NODE_ENV', () => { + suite('in the development mode', () => { + test('should get tokens from fs, not from cache', () => { + const tokens = require('./fixture/oceanic.css'); + + assert.deepEqual(tokens, { + 'awesome-color': '_test_env_fixture_oceanic__awesome-color', + }); + }); + + setup(() => { + process.env.NODE_ENV = 'development'; + hook({}); + writeFile(source1); + require('./fixture/oceanic.css'); + writeFile(source2); + }); + + teardown(() => { + process.env.NODE_ENV = ''; + writeFile(source1); + detachHook('.css'); + dropCache('./env/fixture/oceanic.css'); + }); + }); + + suite('not in the development mode', () => { + test('should get tokens from cache', () => { + const tokens = require('./fixture/oceanic.css'); + + assert.deepEqual(tokens, { + 'color': '_test_env_fixture_oceanic__color', + }); + }); + + setup(() => { + hook({}); + require('./fixture/oceanic.css'); + }); + + teardown(() => { + detachHook('.css'); + dropCache('./env/fixture/oceanic.css'); + }); + }); +}); + +/** + * @param {string} data + */ +function writeFile(data) { + writeFileSync(destination, data, 'utf8'); +} diff --git a/test/extractor.js b/test/extractor.js deleted file mode 100644 index 8dcb64a..0000000 --- a/test/extractor.js +++ /dev/null @@ -1,21 +0,0 @@ -import { equal } from 'assert'; -import { execSync } from 'child_process'; -import { readFileSync } from 'fs'; -import { resolve } from 'path'; -import hook from '../src'; - -describe('extractor', () => { - let selector; - - before(() => { - hook({generateScopedName: '[name]__[local]___[hash:base64:5]'}); - execSync('npm run fixture', {cwd: process.cwd()}); - selector = readFileSync(resolve('test/cases/webpack/result.css'), 'utf8') - .split('\n').shift().replace(/^\./, ''); - }); - - it('should generate the same selectors as the webpack does', () => { - const tokens = require('./cases/webpack/source.css'); - equal(selector, tokens.local); - }); -}); diff --git a/test/fixture/dynamic.css b/test/fixture/dynamic.css deleted file mode 100644 index d34188e..0000000 --- a/test/fixture/dynamic.css +++ /dev/null @@ -1,4 +0,0 @@ -.inline-block -{ - display: inline-block; -} \ No newline at end of file diff --git a/test/lib/validate.js b/test/lib/validate.js new file mode 100644 index 0000000..b0e7ef7 --- /dev/null +++ b/test/lib/validate.js @@ -0,0 +1,19 @@ +const validate = require('../../lib/validate'); + +suite('lib/validate', () => { + test('should thrown an error for the option with multiple types if wrong type specified', () => { + assert.throws(() => validate({extensions: null}, TypeError)); + }); + + test('should thrown an error for the option with single type if wrong type specified', () => { + assert.throws(() => validate({preprocessCss: ''}, TypeError)); + }); + + test('should NOT throw an error for the valid type', () => { + assert.doesNotThrow(() => validate({preprocessCss: function () {}})); + }); + + test('should throw an error if unknown options are specified', () => { + assert.throws(() => validate({a: '', b: ''}), Error); + }); +}); diff --git a/test/plugins.js b/test/plugins.js deleted file mode 100644 index 5e804cb..0000000 --- a/test/plugins.js +++ /dev/null @@ -1,36 +0,0 @@ -import { equal } from 'assert'; -import { identity } from 'lodash'; -import { dropCache } from '../utils/sugar'; -import hook from '../src'; - -import ExtractImports from 'postcss-modules-extract-imports'; -import LocalByDefault from 'postcss-modules-local-by-default'; -import Scope from 'postcss-modules-scope'; - -describe('plugins', () => { - afterEach(() => dropCache('awesome-theme/oceanic.css')); - - describe('custom generateScopedName() function', () => { - before(() => hook({generateScopedName: identity})); - - it('tokens should have the same generated names', () => { - const tokens = require('awesome-theme/oceanic.css'); - equal(tokens.color, 'color'); - }); - }); - - describe('explicit way to set generateScopedName() function', () => { - before(() => hook({ - use: [ - ExtractImports, - LocalByDefault, - new Scope({generateScopedName: identity}), - ], - })); - - it('tokens should have the same generated names', () => { - const tokens = require('awesome-theme/oceanic.css'); - equal(tokens.color, 'color'); - }); - }); -}); diff --git a/test/preset/fixture/oceanic.css b/test/preset/fixture/oceanic.css new file mode 100644 index 0000000..7e4881c --- /dev/null +++ b/test/preset/fixture/oceanic.css @@ -0,0 +1,4 @@ +.color +{ + background: #1e2a35; +} diff --git a/test/preset/index.js b/test/preset/index.js new file mode 100644 index 0000000..90f7b64 --- /dev/null +++ b/test/preset/index.js @@ -0,0 +1,34 @@ +const detachHook = require('../sugar').detachHook; +const dropCache = require('../sugar').dropCache; + +suite('css-modules-require-hook/preset', () => { + suite('using cmrh.conf.js file', () => { + test('should return tokens', () => { + const tokens = require('./fixture/oceanic.css'); + + assert.deepEqual(tokens, { + color: 'oceanic__color___1sqWL', + }); + }); + + setup(() => require('./js/preset')); + }); + + suite('using defaults', () => { + test('should return tokens', () => { + const tokens = require('./fixture/oceanic.css'); + + assert.deepEqual(tokens, { + color: '_test_preset_fixture_oceanic__color', + }); + }); + + setup(() => require('../../preset')); + }); + + teardown(() => { + detachHook('.css'); + dropCache('../preset'); + dropCache('./preset/fixture/oceanic.css'); + }); +}); diff --git a/test/preset/js/cmrh.conf.js b/test/preset/js/cmrh.conf.js new file mode 100644 index 0000000..0fc52b8 --- /dev/null +++ b/test/preset/js/cmrh.conf.js @@ -0,0 +1,3 @@ +module.exports = { + generateScopedName: '[name]__[local]___[hash:base64:5]', +}; diff --git a/test/preset/js/preset.js b/test/preset/js/preset.js new file mode 100644 index 0000000..6e12428 --- /dev/null +++ b/test/preset/js/preset.js @@ -0,0 +1,2 @@ +// using cmrh.conf.js in the local directory +require('../../../preset'); diff --git a/test/public-api.js b/test/public-api.js deleted file mode 100644 index d29beee..0000000 --- a/test/public-api.js +++ /dev/null @@ -1,137 +0,0 @@ -import { noop } from 'lodash'; -import { equal, ok } from 'assert'; -import { dropCache, spy, Through } from '../utils/sugar'; -import hook from '../src'; - -describe('public api', () => { - afterEach(() => dropCache('awesome-theme/oceanic.css')); - - describe('extending list of plugins', () => { - describe('"prepend" should add plugins in the beginning of the pipeline', () => { - let processor; - - beforeEach(() => { - processor = spy(noop); - - hook({ - prepend: [ - new Through({processor: processor}), - ], - }); - - require('awesome-theme/oceanic.css'); - }); - - it('processor should be called', () => - ok(processor.called)); - }); - - describe('"append" should add plugins to the pipeline', () => { - let processor; - - beforeEach(() => { - processor = spy(noop); - - hook({ - append: [ - new Through({processor: processor}), - ], - }); - - require('awesome-theme/oceanic.css'); - }); - - it('processor should be called', () => - ok(processor.called)); - }); - - describe('"use" should set the new list of plugins', () => { - let appendProcessor; - let prependProcessor; - let useProcessor; - - beforeEach(() => { - appendProcessor = spy(noop); - prependProcessor = spy(noop); - useProcessor = spy(noop); - - hook({ - append: [ - new Through({processor: appendProcessor}), - ], - prepend: [ - new Through({processor: prependProcessor}), - ], - use: [ - new Through({processor: useProcessor}), - ], - }); - - require('awesome-theme/oceanic.css'); - }); - - it('plugin added by append option should not be called', () => - ok(!appendProcessor.called)); - - it('plugin added by prepend option should not be called', () => - ok(!prependProcessor.called)); - - it('plugin added by use option should be called', () => - ok(useProcessor.called)); - }); - }); - - describe('setting custom processors for the CSS', () => { - describe('"preprocessCss()" should transform CSS before the PostCSS', () => { - let processor; - let providedFilename; - let providedContent; - - beforeEach(() => { - processor = spy((content, filename) => { - providedFilename = filename; - providedContent = content; - return ''; - }); - - hook({preprocessCss: processor}); - require('awesome-theme/oceanic.css'); - }); - - it('processor should be called', () => - ok(processor.called)); - - it('filename should be provided', () => - equal(providedFilename, require.resolve('awesome-theme/oceanic.css'))); - - it('content of the file should be provided', () => - ok(providedContent.length)); - }); - - describe('"processCss()" should transform CSS after the PostCSS', () => { - let processor; - let providedFilename; - let providedContent; - - beforeEach(() => { - processor = spy((content, filename) => { - providedFilename = filename; - providedContent = content; - return ''; - }); - - hook({processCss: processor}); - require('awesome-theme/oceanic.css'); - }); - - it('processor should be called', () => - ok(processor.called)); - - it('filename should be provided', () => - equal(providedFilename, require.resolve('awesome-theme/oceanic.css'))); - - it('content of the file should be provided', () => - ok(providedContent.length)); - }); - }); -}); diff --git a/test/setup.js b/test/setup.js new file mode 100644 index 0000000..762f142 --- /dev/null +++ b/test/setup.js @@ -0,0 +1,3 @@ +global.assert = require('assert'); +global.hook = require('../'); +global.spy = require('sinon').spy; diff --git a/test/sugar.js b/test/sugar.js new file mode 100644 index 0000000..ae8f9ff --- /dev/null +++ b/test/sugar.js @@ -0,0 +1,25 @@ +const plugin = require('postcss').plugin; + +/** + * Drops require cache for the certain module + * + * @param {string} modulePath + */ +function dropCache(modulePath) { + delete require.cache[require.resolve(modulePath)]; +}; + +/** + * @param {string} extension + */ +function detachHook(extension) { + delete require.extensions[extension]; +} + +const Through = plugin('through', function postcssThrough(processor) { + return css => processor(css); +}); + +exports.dropCache = dropCache; +exports.detachHook = detachHook; +exports.Through = Through; diff --git a/test/test-cases/compose-node-module/expected.css b/test/test-cases/compose-node-module/expected.css deleted file mode 100644 index 0667b94..0000000 --- a/test/test-cases/compose-node-module/expected.css +++ /dev/null @@ -1,5 +0,0 @@ -._compose_node_module_cool_styles_foo__example { - color: #F00; -} -._compose_node_module_source__foo { -} diff --git a/test/test-cases/compose-node-module/expected.json b/test/test-cases/compose-node-module/expected.json deleted file mode 100644 index c4555ba..0000000 --- a/test/test-cases/compose-node-module/expected.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "foo": "_compose_node_module_source__foo _node_modules_awesome_theme_common__paragraph _node_modules_awesome_theme_oceanic__color" -} diff --git a/test/test-cases/compose-node-module/source.css b/test/test-cases/compose-node-module/source.css deleted file mode 100644 index 4d73aec..0000000 --- a/test/test-cases/compose-node-module/source.css +++ /dev/null @@ -1,3 +0,0 @@ -.foo { - composes: paragraph from "awesome-theme/common.css"; -} diff --git a/test/test-cases/extra-extension/expected.json b/test/test-cases/extra-extension/expected.json deleted file mode 100644 index 9c3b35c..0000000 --- a/test/test-cases/extra-extension/expected.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "localName": "_test_test_cases_extra_extension_source__localName" -} diff --git a/test/test-cases/extra-extension/source.scss b/test/test-cases/extra-extension/source.scss deleted file mode 100644 index cc61ab6..0000000 --- a/test/test-cases/extra-extension/source.scss +++ /dev/null @@ -1,5 +0,0 @@ -$color: #c0ffee; - -.localName { - color: $color; -} diff --git a/test/test-cases/localise-export/expected.css b/test/test-cases/localise-export/expected.css deleted file mode 100644 index c4be0d9..0000000 --- a/test/test-cases/localise-export/expected.css +++ /dev/null @@ -1,9 +0,0 @@ -._localise_export_source__one { - color: red; -} -._localise_export_source__two { - color: green; -} -._localise_export_source__three { - color: blue; -} diff --git a/test/test-cases/localise-export/expected.json b/test/test-cases/localise-export/expected.json deleted file mode 100644 index 85eb15c..0000000 --- a/test/test-cases/localise-export/expected.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "one": "_localise_export_source__one", - "two": "_localise_export_source__two", - "three": "_localise_export_source__three" -} diff --git a/test/test-cases/localise-export/source.css b/test/test-cases/localise-export/source.css deleted file mode 100644 index 8fdc200..0000000 --- a/test/test-cases/localise-export/source.css +++ /dev/null @@ -1,9 +0,0 @@ -:local(.one) { - color: red; -} -:local(.two) { - color: green; -} -:local(.three) { - color: blue; -} diff --git a/test/test-cases/multiple-dependencies/b.css b/test/test-cases/multiple-dependencies/b.css deleted file mode 100644 index e29560a..0000000 --- a/test/test-cases/multiple-dependencies/b.css +++ /dev/null @@ -1,8 +0,0 @@ -.b1 { - composes: d1 d2 from "./d.css"; - color: #b1b1b1; -} - -.b2 { - color: #b2b2b2; -} diff --git a/test/test-cases/multiple-dependencies/c.css b/test/test-cases/multiple-dependencies/c.css deleted file mode 100644 index e5a7b52..0000000 --- a/test/test-cases/multiple-dependencies/c.css +++ /dev/null @@ -1,3 +0,0 @@ -.c { - color: #ccc; -} diff --git a/test/test-cases/multiple-dependencies/d.css b/test/test-cases/multiple-dependencies/d.css deleted file mode 100644 index 4e7ec24..0000000 --- a/test/test-cases/multiple-dependencies/d.css +++ /dev/null @@ -1,6 +0,0 @@ -.d1 { - color: #d1d1d1; -} -.d2 { - color: #d2d2d2; -} diff --git a/test/test-cases/multiple-dependencies/expected.css b/test/test-cases/multiple-dependencies/expected.css deleted file mode 100644 index 01b0d9f..0000000 --- a/test/test-cases/multiple-dependencies/expected.css +++ /dev/null @@ -1,20 +0,0 @@ -._multiple_dependencies_d__d1 { - color: #d1d1d1; -} -._multiple_dependencies_d__d2 { - color: #d2d2d2; -} -._multiple_dependencies_b__b1 { - color: #b1b1b1; -} - -._multiple_dependencies_b__b2 { - color: #b2b2b2; -} -._multiple_dependencies_c__c { - color: #ccc; -} -._multiple_dependencies_source__a { - color: #aaa; -} - diff --git a/test/test-cases/multiple-dependencies/expected.json b/test/test-cases/multiple-dependencies/expected.json deleted file mode 100644 index 7327ebb..0000000 --- a/test/test-cases/multiple-dependencies/expected.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "a": "_multiple_dependencies_source__a _multiple_dependencies_b__b1 _multiple_dependencies_d__d1 _multiple_dependencies_d__d2 _multiple_dependencies_b__b2 _multiple_dependencies_c__c something-global" -} diff --git a/test/test-cases/multiple-dependencies/source.css b/test/test-cases/multiple-dependencies/source.css deleted file mode 100644 index 0824a26..0000000 --- a/test/test-cases/multiple-dependencies/source.css +++ /dev/null @@ -1,7 +0,0 @@ -.a { - composes: b1 b2 from "./b.css"; - composes: c from "./c.css"; - composes: something-global from global; - color: #aaa; -} - diff --git a/test/test-cases/multiple-sources/b.css b/test/test-cases/multiple-sources/b.css deleted file mode 100644 index c4dcd92..0000000 --- a/test/test-cases/multiple-sources/b.css +++ /dev/null @@ -1,4 +0,0 @@ -.b { - composes: d from "./d.css"; - color: #bbb; -} diff --git a/test/test-cases/multiple-sources/c.css b/test/test-cases/multiple-sources/c.css deleted file mode 100644 index e5a7b52..0000000 --- a/test/test-cases/multiple-sources/c.css +++ /dev/null @@ -1,3 +0,0 @@ -.c { - color: #ccc; -} diff --git a/test/test-cases/multiple-sources/d.css b/test/test-cases/multiple-sources/d.css deleted file mode 100644 index 4638a27..0000000 --- a/test/test-cases/multiple-sources/d.css +++ /dev/null @@ -1,3 +0,0 @@ -.d { - color: #ddd; -} diff --git a/test/test-cases/multiple-sources/expected.css b/test/test-cases/multiple-sources/expected.css deleted file mode 100644 index da64401..0000000 --- a/test/test-cases/multiple-sources/expected.css +++ /dev/null @@ -1,15 +0,0 @@ -._multiple_sources_d__d { - color: #ddd; -} -._multiple_sources_b__b { - color: #bbb; -} -._multiple_sources_c__c { - color: #ccc; -} -._multiple_sources_source1__a { - color: #aaa; -} -._multiple_sources_source2__foo { - color: #f00; -} diff --git a/test/test-cases/multiple-sources/expected.json b/test/test-cases/multiple-sources/expected.json deleted file mode 100644 index 074abf9..0000000 --- a/test/test-cases/multiple-sources/expected.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "a": "_multiple_sources_source1__a _multiple_sources_b__b _multiple_sources_d__d _multiple_sources_c__c", - "foo": "_multiple_sources_source2__foo _multiple_sources_b__b _multiple_sources_d__d" -} diff --git a/test/test-cases/multiple-sources/source1.css b/test/test-cases/multiple-sources/source1.css deleted file mode 100644 index 983c7fd..0000000 --- a/test/test-cases/multiple-sources/source1.css +++ /dev/null @@ -1,5 +0,0 @@ -.a { - composes: b from "./b.css"; - composes: c from "./c.css"; - color: #aaa; -} diff --git a/test/test-cases/multiple-sources/source2.css b/test/test-cases/multiple-sources/source2.css deleted file mode 100644 index 151a720..0000000 --- a/test/test-cases/multiple-sources/source2.css +++ /dev/null @@ -1,4 +0,0 @@ -.foo { - composes: b from "./b.css"; - color: #f00; -} diff --git a/test/test-cases/simple-export/expected.css b/test/test-cases/simple-export/expected.css deleted file mode 100644 index 2f8e4da..0000000 --- a/test/test-cases/simple-export/expected.css +++ /dev/null @@ -1,7 +0,0 @@ -._simple_export_source__localName { - color: red; -} - -._simple_export_source__localName:hover { - color: blue; -} diff --git a/test/test-cases/simple-export/expected.json b/test/test-cases/simple-export/expected.json deleted file mode 100644 index c8ee742..0000000 --- a/test/test-cases/simple-export/expected.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "localName": "_simple_export_source__localName" -} diff --git a/test/test-cases/simple-export/source.css b/test/test-cases/simple-export/source.css deleted file mode 100644 index fbb8713..0000000 --- a/test/test-cases/simple-export/source.css +++ /dev/null @@ -1,7 +0,0 @@ -.localName { - color: red; -} - -.localName:hover { - color: blue; -} diff --git a/test/test-cases/single-import-export/colors.css b/test/test-cases/single-import-export/colors.css deleted file mode 100644 index a7c1ce1..0000000 --- a/test/test-cases/single-import-export/colors.css +++ /dev/null @@ -1,7 +0,0 @@ -.blackShadow { - box-shadow: 0 0 10px -2px black; -} - -.redBorder { - border: 1px solid red; -} diff --git a/test/test-cases/single-import-export/expected.css b/test/test-cases/single-import-export/expected.css deleted file mode 100644 index 74398b2..0000000 --- a/test/test-cases/single-import-export/expected.css +++ /dev/null @@ -1,10 +0,0 @@ -._single_import_export_colors__blackShadow { - box-shadow: 0 0 10px -2px black; -} - -._single_import_export_colors__redBorder { - border: 1px solid red; -} -._single_import_export_source__localName { - color: red; -} diff --git a/test/test-cases/single-import-export/expected.json b/test/test-cases/single-import-export/expected.json deleted file mode 100644 index 802cd4e..0000000 --- a/test/test-cases/single-import-export/expected.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "localName": "_single_import_export_source__localName _single_import_export_colors__blackShadow _single_import_export_colors__redBorder" -} diff --git a/test/test-cases/single-import-export/source.css b/test/test-cases/single-import-export/source.css deleted file mode 100644 index 57dbc17..0000000 --- a/test/test-cases/single-import-export/source.css +++ /dev/null @@ -1,4 +0,0 @@ -.localName { - composes: blackShadow redBorder from "./colors.css"; - color: red; -} diff --git a/test/test-cases/values/borders.css b/test/test-cases/values/borders.css deleted file mode 100644 index f3670f7..0000000 --- a/test/test-cases/values/borders.css +++ /dev/null @@ -1,3 +0,0 @@ -.dashed { - border: 4px dashed; -} diff --git a/test/test-cases/values/breakpoints.css b/test/test-cases/values/breakpoints.css deleted file mode 100644 index 0b80b8b..0000000 --- a/test/test-cases/values/breakpoints.css +++ /dev/null @@ -1,3 +0,0 @@ -@value small (max-width: 599px); -@value medium (min-width: 600px) and (max-width: 959px); -@value large (min-width: 960px); diff --git a/test/test-cases/values/colors.css b/test/test-cases/values/colors.css deleted file mode 100644 index ff83776..0000000 --- a/test/test-cases/values/colors.css +++ /dev/null @@ -1,16 +0,0 @@ -@value primary: #f01; -@value secondary: #2f2; - -.text-primary { - color: primary; -} -.bg-primary { - background-color: primary; -} - -.text-secondary { - color: secondary; -} -.bg-secondary { - background-color: secondary; -} diff --git a/test/test-cases/values/expected.css b/test/test-cases/values/expected.css deleted file mode 100644 index 0562fb8..0000000 --- a/test/test-cases/values/expected.css +++ /dev/null @@ -1,35 +0,0 @@ -._values_borders__dashed { - border: 4px dashed; -} - -._values_colors__text-primary { - color: #f01; -} -._values_colors__bg-primary { - background-color: #f01; -} - -._values_colors__text-secondary { - color: #2f2; -} -._values_colors__bg-secondary { - background-color: #2f2; -} -/* Imports without a "from" are just passed through */ -@import url('./old-skool.css'); - -._values_source__foo { - box-shadow: 0 0 10px #f01; - border-color: #2f2; -} - -@media (max-width: 599px) { - ._values_source__foo { - background: white; - } -} -@media (min-width: 600px) and (max-width: 959px) { - ._values_source__foo { - background: peru; - } -} diff --git a/test/test-cases/values/expected.json b/test/test-cases/values/expected.json deleted file mode 100644 index ee98683..0000000 --- a/test/test-cases/values/expected.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "borders": "\"./borders.css\"", - "breakpoints": "\"./breakpoints.css\"", - "small": "(max-width: 599px)", - "medium": "(min-width: 600px) and (max-width: 959px)", - "secondary": "#2f2", - "blue": "#f01", - "foo": "_values_source__foo _values_borders__dashed _values_colors__text-secondary" -} diff --git a/test/test-cases/values/source.css b/test/test-cases/values/source.css deleted file mode 100644 index 2c63c32..0000000 --- a/test/test-cases/values/source.css +++ /dev/null @@ -1,24 +0,0 @@ -@value borders: "./borders.css", breakpoints: "./breakpoints.css"; -@value small, medium from breakpoints; -@value secondary, primary as blue from "./colors.css"; - -/* Imports without a "from" are just passed through */ -@import url('./old-skool.css'); - -.foo { - composes: dashed from borders; - composes: text-secondary from "./colors.css"; - box-shadow: 0 0 10px blue; - border-color: secondary; -} - -@media small { - .foo { - background: white; - } -} -@media medium { - .foo { - background: peru; - } -} diff --git a/test/tokens/cases/compose-node-module/expected.json b/test/tokens/cases/compose-node-module/expected.json new file mode 100644 index 0000000..9b0d086 --- /dev/null +++ b/test/tokens/cases/compose-node-module/expected.json @@ -0,0 +1,3 @@ +{ + "foo": "_test_tokens_cases_compose_node_module_source__foo _test_tokens_node_modules_awesome_theme_common__paragraph _test_tokens_node_modules_awesome_theme_oceanic__color" +} diff --git a/test/tokens/cases/compose-node-module/source.css b/test/tokens/cases/compose-node-module/source.css new file mode 100644 index 0000000..996bf71 --- /dev/null +++ b/test/tokens/cases/compose-node-module/source.css @@ -0,0 +1,4 @@ +.foo +{ + composes: paragraph from 'awesome-theme/common.css'; +} diff --git a/test/tokens/cases/multiple-dependencies/colors.css b/test/tokens/cases/multiple-dependencies/colors.css new file mode 100644 index 0000000..b0469a7 --- /dev/null +++ b/test/tokens/cases/multiple-dependencies/colors.css @@ -0,0 +1,4 @@ +.text +{ + color: #333; +} diff --git a/test/tokens/cases/multiple-dependencies/expected.json b/test/tokens/cases/multiple-dependencies/expected.json new file mode 100644 index 0000000..9b02481 --- /dev/null +++ b/test/tokens/cases/multiple-dependencies/expected.json @@ -0,0 +1,3 @@ +{ + "article": "_test_tokens_cases_multiple_dependencies_source__article _test_tokens_cases_multiple_dependencies_colors__text _test_tokens_cases_multiple_dependencies_fonts__medium _test_tokens_cases_multiple_dependencies_margins__mediumBottom _test_tokens_cases_multiple_dependencies_margins__mediumTop" +} diff --git a/test/tokens/cases/multiple-dependencies/fonts.css b/test/tokens/cases/multiple-dependencies/fonts.css new file mode 100644 index 0000000..7420611 --- /dev/null +++ b/test/tokens/cases/multiple-dependencies/fonts.css @@ -0,0 +1,5 @@ +.medium +{ + font-family: helvetica; + font-size: 13px; +} diff --git a/test/tokens/cases/multiple-dependencies/margins.css b/test/tokens/cases/multiple-dependencies/margins.css new file mode 100644 index 0000000..8f8c3e6 --- /dev/null +++ b/test/tokens/cases/multiple-dependencies/margins.css @@ -0,0 +1,9 @@ +.mediumBottom +{ + margin-bottom: 20px; +} + +.mediumTop +{ + margin-top: 20px; +} diff --git a/test/tokens/cases/multiple-dependencies/source.css b/test/tokens/cases/multiple-dependencies/source.css new file mode 100644 index 0000000..38bcf2f --- /dev/null +++ b/test/tokens/cases/multiple-dependencies/source.css @@ -0,0 +1,6 @@ +.article +{ + composes: text from './colors.css'; + composes: medium from './fonts.css'; + composes: mediumBottom mediumTop from './margins.css'; +} diff --git a/test/tokens/cases/multiple-sources/colors.css b/test/tokens/cases/multiple-sources/colors.css new file mode 100644 index 0000000..179b73a --- /dev/null +++ b/test/tokens/cases/multiple-sources/colors.css @@ -0,0 +1,4 @@ +.oceanic +{ + background: #1e2a35; +} diff --git a/test/tokens/cases/multiple-sources/expected.json b/test/tokens/cases/multiple-sources/expected.json new file mode 100644 index 0000000..bf9d7e3 --- /dev/null +++ b/test/tokens/cases/multiple-sources/expected.json @@ -0,0 +1,4 @@ +{ + "article": "_test_tokens_cases_multiple_sources_source__article _test_tokens_cases_multiple_sources_colors__oceanic", + "text": "_test_tokens_cases_multiple_sources_source__text _test_tokens_cases_multiple_sources_typography__text _test_tokens_cases_multiple_sources_colors__oceanic" +} diff --git a/test/tokens/cases/multiple-sources/source.css b/test/tokens/cases/multiple-sources/source.css new file mode 100644 index 0000000..e9cb318 --- /dev/null +++ b/test/tokens/cases/multiple-sources/source.css @@ -0,0 +1,9 @@ +.article +{ + composes: oceanic from './colors.css'; +} + +.text +{ + composes: text from './typography.css'; +} diff --git a/test/tokens/cases/multiple-sources/typography.css b/test/tokens/cases/multiple-sources/typography.css new file mode 100644 index 0000000..560bcfc --- /dev/null +++ b/test/tokens/cases/multiple-sources/typography.css @@ -0,0 +1,5 @@ +.text +{ + composes: oceanic from './colors.css'; + font-family: helvetica; +} diff --git a/test/tokens/index.js b/test/tokens/index.js new file mode 100644 index 0000000..e63f9f7 --- /dev/null +++ b/test/tokens/index.js @@ -0,0 +1,48 @@ +const basename = require('path').basename; +const detachHook = require('../sugar').detachHook; +const readdirSync = require('fs').readdirSync; +const readFileSync = require('fs').readFileSync; +const resolve = require('path').resolve; + +/** + * @param {string} testCase + */ +function describeTest(testCase) { + const source = readfile(testCase, 'source.css'); + if (source === null) { + return; + } + + // @todo add a small shortcut to choose certain tests + test(basename(testCase), () => { + const expected = JSON.parse(readfile(testCase, 'expected.json')); + assert.deepEqual(require(resolve(testCase, 'source.css')), expected); + }); +} + +/** + * @param {string} dir + * @return {string[]} + */ +function readdir(dir) { + return readdirSync(resolve(__dirname, dir)) + .map(nesteddir => resolve(__dirname, dir, nesteddir)); +} + +/** + * @param {...string} file + * @return {string|null} + */ +function readfile(file) { + try { + return readFileSync(resolve.apply(null, arguments), 'utf8'); + } catch(e) { + return null; + } +} + +suite('tokens', () => { + setup(() => hook({})); + teardown(() => detachHook('.css')); + readdir('./cases').forEach(describeTest); +}); diff --git a/node_modules/awesome-theme/common.css b/test/tokens/node_modules/awesome-theme/common.css similarity index 100% rename from node_modules/awesome-theme/common.css rename to test/tokens/node_modules/awesome-theme/common.css diff --git a/test/tokens/node_modules/awesome-theme/oceanic.css b/test/tokens/node_modules/awesome-theme/oceanic.css new file mode 100644 index 0000000..7e4881c --- /dev/null +++ b/test/tokens/node_modules/awesome-theme/oceanic.css @@ -0,0 +1,4 @@ +.color +{ + background: #1e2a35; +} diff --git a/test/utility.js b/test/utility.js deleted file mode 100644 index 8e4c7cf..0000000 --- a/test/utility.js +++ /dev/null @@ -1,18 +0,0 @@ -import { ok } from 'assert'; -import { is } from '../src/utility'; - -describe('utility', () => { - describe('is()', () => { - it('should return true for an array', () => ok(is('array', []))); - - it('should return false for not an array', () => ok(!is('array', null))); - - it('should return true for a function', () => ok(is('function', () => {}))); - - it('should return false for not a function', () => ok(!is('function', null))); - - it('should return true for a string', () => ok(is('string', ''))); - - it('should return false for not a string', () => ok(!is('string', null))); - }); -}); diff --git a/utils/sugar.js b/utils/sugar.js deleted file mode 100644 index bec8e00..0000000 --- a/utils/sugar.js +++ /dev/null @@ -1,36 +0,0 @@ -import { identity } from 'lodash'; -import { plugin } from 'postcss'; - -/** - * @param {string} moduleName - */ -export function dropCache(moduleName) { - delete require.cache[require.resolve(moduleName)]; -} - -/** - * @param {function} fn - * @return {function} - */ -export function spy(fn) { - const wrapper = function wrapper() { - wrapper.called = true; - wrapper.times++; - return fn.apply(this, arguments); - }; - - wrapper.called = false; - wrapper.times = 0; - - return wrapper; -} - -/** - * Simple PostCSS plugin for the test purpose - * @param {object} opts - * @return {Function} - */ -export const Through = plugin('through', function through(opts = {}) { - const processor = opts.processor || identity; - return css => processor(css); -}); diff --git a/webpack.config.js b/webpack.config.js deleted file mode 100644 index f8b28da..0000000 --- a/webpack.config.js +++ /dev/null @@ -1,29 +0,0 @@ -'use strict'; - -var ExtractTextPlugin = require('extract-text-webpack-plugin'); -var path = require('path'); - -module.exports = { - entry: path.resolve('test/cases/webpack/source.js'), - - output: { - filename: 'result.js', - path: path.resolve('test/cases/webpack') - }, - - module: { - loaders: [ - { - test: /\.css$/, - loader: ExtractTextPlugin.extract('style-loader', - 'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]!postcss-loader') - } - ] - }, - - plugins: [ - new ExtractTextPlugin('result.css', { - allChunks: true - }) - ] -};