├── .gitignore ├── .npmignore ├── .editorconfig ├── CHANGELOG.md ├── LICENSE ├── .github └── workflows │ └── test.yml ├── package.json ├── index.js ├── README.md └── index.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | yarn-error.log 4 | 5 | coverage/ 6 | .idea/ 7 | .vscode/ 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | yarn-error.log 2 | npm-debug.log 3 | package-lock.json 4 | yarn.lock 5 | 6 | *.test.js 7 | .travis.yml 8 | .editorconfig 9 | coverage/ 10 | .idea/ 11 | .vscode/ 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | This project adheres to [Semantic Versioning](http://semver.org/). 4 | 5 | ## 1.0.6 6 | * added @container query support 7 | 8 | ## 1.0.5 9 | * fixed issue with incorrect parent link 10 | 11 | ## 1.0.4 12 | * fixed package.json dependency syntax 13 | 14 | ## 1.0.3 15 | * vendor-prefixes support 16 | * postcss version 7 and 8 support 17 | * include/exclude file lists options 18 | 19 | ## 1.0.2 20 | * support for * (all) rules and root selector itself 21 | 22 | ## 1.0.0 23 | * Initial release. 24 | 25 | ## 1.0.1 26 | * split inherited and self-applied declarations of global rules. 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright 2021 Ivan Agafonov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: 3 | push: 4 | pull_request: 5 | env: 6 | FORCE_COLOR: 2 7 | jobs: 8 | full: 9 | name: Node.js 15 Full 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout the repository 13 | uses: actions/checkout@v2 14 | - name: Install Node.js 15 | uses: actions/setup-node@v2-beta 16 | with: 17 | node-version: 15 18 | - name: Install dependencies 19 | uses: bahmutov/npm-install@v1 20 | - name: Run tests 21 | run: yarn test 22 | short: 23 | runs-on: ubuntu-latest 24 | strategy: 25 | matrix: 26 | node-version: 27 | - 14 28 | - 12 29 | - 10 30 | name: Node.js ${{ matrix.node-version }} Quick 31 | steps: 32 | - name: Checkout the repository 33 | uses: actions/checkout@v2 34 | - name: Install Node.js ${{ matrix.node-version }} 35 | uses: actions/setup-node@v2-beta 36 | with: 37 | node-version: ${{ matrix.node-version }} 38 | - name: Install dependencies 39 | uses: bahmutov/npm-install@v1 40 | - name: Run unit tests 41 | run: npx jest 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "postcss-add-root-selector", 3 | "version": "1.0.6", 4 | "description": "PostCSS plugin adds root selector to all other rules", 5 | "keywords": [ 6 | "postcss", 7 | "css", 8 | "postcss-plugin", 9 | "root" 10 | ], 11 | "scripts": { 12 | "test": "jest --coverage && eslint ." 13 | }, 14 | "author": "Ivan Agafonov ", 15 | "license": "MIT", 16 | "repository": "trailskr/postcss-add-root-selector", 17 | "engines": { 18 | "node": ">=10.0.0" 19 | }, 20 | "peerDependencies": { 21 | "postcss": "^7.0.35 || ^8.1.5" 22 | }, 23 | "devDependencies": { 24 | "clean-publish": "^1.1.8", 25 | "eslint": "^7.14.0", 26 | "eslint-plugin-jest": "^24.1.3", 27 | "husky": "^4.3.0", 28 | "jest": "^26.4.2", 29 | "lint-staged": "^10.4.0", 30 | "postcss": "^8.1.5", 31 | "postcss7": "npm:postcss@7.0.35" 32 | }, 33 | "husky": { 34 | "hooks": { 35 | "pre-commit": "lint-staged" 36 | } 37 | }, 38 | "lint-staged": { 39 | "*.js": "eslint --fix" 40 | }, 41 | "eslintConfig": { 42 | "parserOptions": { 43 | "ecmaVersion": 2017 44 | }, 45 | "env": { 46 | "node": true, 47 | "es6": true 48 | }, 49 | "extends": [ 50 | "eslint:recommended", 51 | "plugin:jest/recommended" 52 | ], 53 | "rules": { 54 | "jest/expect-expect": "off" 55 | } 56 | }, 57 | "jest": { 58 | "testEnvironment": "node", 59 | "coverageThreshold": { 60 | "global": { 61 | "statements": 100 62 | } 63 | } 64 | }, 65 | "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" 66 | } 67 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const postcss = require('postcss') 2 | 3 | const globalRulesRe = /^((?:body|html)(?:[^\w ][^ ]+)?)( .*)?/ 4 | const inheritedDeclarationNamesRe = /^(-webkit-|-moz-|-moz-osx-|-o-|-ms-)?(?:color|font-.*|text-.*|line-height|letter-spacing|line-break|overflow-wrap|hyphens|tab-size|white-space|word-break|word-spacing|direction)$/ 5 | 6 | /** 7 | * @param opts {{ 8 | * rootSelector: string, 9 | * include: Array, 10 | * exclude: Array, 11 | * }} 12 | */ 13 | const makeRuleProcessor = (opts = { }) => { 14 | if (!opts.rootSelector || typeof opts.rootSelector !== 'string') { 15 | throw new Error('rootSelector is not specified or it is not a string') 16 | } 17 | 18 | const prependRoot = (selector) => { 19 | return `${opts.rootSelector} ${selector}` 20 | } 21 | const insertRoot = (global, selector) => { 22 | return `${global} ${opts.rootSelector}${selector || ''}` 23 | } 24 | 25 | const prependLocalSelector = selector => { 26 | if (selector === opts.rootSelector) return selector 27 | if (selector === ':root') { 28 | return opts.rootSelector 29 | } 30 | return prependRoot(selector) 31 | } 32 | 33 | const insertRootSelectorIntoGlobal = selector => { 34 | const m = selector.match(globalRulesRe) 35 | return insertRoot(m[1], m[2]) 36 | } 37 | 38 | const updateNodeParent = parent => node => { 39 | node.parent = parent 40 | return node 41 | } 42 | 43 | return (rule) => { 44 | if (rule.parent.type === 'atrule') { 45 | if (!['media', 'supports', 'document', 'container'].includes(rule.parent.name)) return 46 | } 47 | if (rule.selectors.some(selector => selector.startsWith('*'))) { 48 | rule.selectors = [opts.rootSelector, ...rule.selectors] 49 | } 50 | 51 | const global = [] 52 | const local = [] 53 | rule.selectors.forEach(selector => { 54 | if (selector.match(globalRulesRe)) { 55 | global.push(selector) 56 | } else { 57 | local.push(selector) 58 | } 59 | }) 60 | 61 | if (global.length === 0) { 62 | rule.selectors = rule.selectors.map(prependLocalSelector) 63 | return 64 | } 65 | 66 | if (local.length > 0) { 67 | if (rule.every(decl => inheritedDeclarationNamesRe.test(decl.prop))) { 68 | rule.selectors = rule.selectors.map(selector => { 69 | return globalRulesRe.test(selector) 70 | ? insertRootSelectorIntoGlobal(selector) 71 | : prependLocalSelector(selector) 72 | }) 73 | return 74 | } 75 | const localRule = rule.cloneBefore() 76 | localRule.selectors = local.map(prependLocalSelector) 77 | rule.selectors = global 78 | } 79 | 80 | const inherited = [] 81 | const selfApplied = [] 82 | rule.each(decl => { 83 | if (inheritedDeclarationNamesRe.test(decl.prop)) { 84 | inherited.push(decl) 85 | } else { 86 | selfApplied.push(decl) 87 | } 88 | }) 89 | 90 | if (selfApplied.length === 0) { 91 | rule.selectors = global.map(insertRootSelectorIntoGlobal) 92 | return 93 | } else if (inherited.length === 0) { 94 | return 95 | } 96 | 97 | const inheritedRule = rule.cloneBefore() 98 | inheritedRule.selectors = global.map(insertRootSelectorIntoGlobal) 99 | inheritedRule.nodes = inherited.map(updateNodeParent(inheritedRule)) 100 | 101 | rule.nodes = selfApplied 102 | } 103 | } 104 | 105 | const includeFile = (root, opts) => { 106 | const fileName = root.source && root.source.input.file 107 | if (!fileName) return true 108 | 109 | if (opts.include && opts.include.length > 0) { 110 | return opts.include.some(pattern => fileName.match(pattern)) 111 | } 112 | 113 | if (opts.exclude && opts.exclude.length > 0) { 114 | return !opts.exclude.some(pattern => fileName.match(pattern)) 115 | } 116 | 117 | return true 118 | } 119 | 120 | const makeRootProcessor = (opts) => (root) => { 121 | if (includeFile(root, opts)) { 122 | root.walkRules(makeRuleProcessor(opts)) 123 | } 124 | } 125 | 126 | const pluginName = 'postcss-add-root-selector' 127 | 128 | const versionFormat = (postcss) => { 129 | const isPostCSSv8 = postcss.Root !== undefined 130 | 131 | if (isPostCSSv8) { 132 | const plugin = (opts) => { 133 | return { 134 | postcssPlugin: pluginName, 135 | Once: makeRootProcessor(opts) 136 | } 137 | } 138 | plugin.postcss = true 139 | return plugin 140 | } else { 141 | return postcss.plugin(pluginName, (opts) => { 142 | return makeRootProcessor(opts) 143 | }) 144 | } 145 | } 146 | 147 | module.exports = versionFormat(postcss) 148 | module.exports.versionFormat = versionFormat 149 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PostCSS Add Root Selector 2 | 3 | [PostCSS] Plugin adds root selector to all other rules. 4 | 5 | [PostCSS]: https://github.com/postcss/postcss 6 | 7 | ## Input: 8 | 9 | ```css 10 | .foo { 11 | color: red; 12 | } 13 | 14 | a.foo, 15 | section { 16 | color: red; 17 | } 18 | 19 | @media (max-width: 700px) { 20 | #some-id { 21 | color: red; 22 | } 23 | } 24 | 25 | /* html and body selectors will be popped up */ 26 | html, 27 | body.desktop { 28 | font-family: sans-serif; 29 | } 30 | 31 | body.desktop .bar { 32 | font-weight: bold; 33 | } 34 | 35 | /* self-applied declarations of html and body selectors will be not touched */ 36 | /* mixed selectors will be properly split */ 37 | html, 38 | body, 39 | .some-root-selector { 40 | margin: 0; 41 | color: red !important; 42 | } 43 | 44 | /* ads root itself to * (all) rules */ 45 | *, 46 | *:before, 47 | *:after { 48 | box-sizing: border-box; 49 | } 50 | ``` 51 | 52 | ## Output 53 | 54 | ```css 55 | .some-root-selector .foo { 56 | color: red; 57 | } 58 | 59 | .some-root-selector a.foo, 60 | .some-root-selector section { 61 | color: red; 62 | } 63 | 64 | @media (max-width: 700px) { 65 | .some-root-selector #some-id { 66 | color: red; 67 | } 68 | } 69 | 70 | /* html and body selectors is popped up */ 71 | html .some-root-selector, 72 | body.desktop .some-root-selector { 73 | font-family: sans-serif; 74 | } 75 | 76 | body.desktop .some-root-selector .bar { 77 | font-weight: bold; 78 | } 79 | 80 | /* self-applied declarations of html and body selectors will be not touched */ 81 | /* mixed selectors will be properly split */ 82 | .some-root .some-root-selector { 83 | margin: 0; 84 | color: red !important; 85 | } 86 | 87 | html .some-root-selector, 88 | body .some-root-selector { 89 | color: red !important; 90 | } 91 | 92 | html, 93 | body { 94 | margin: 0; 95 | } 96 | 97 | /* ads root itself to * (all) selectors list */ 98 | .some-root-selector, 99 | .some-root-selector *, 100 | .some-root-selector *:before, 101 | .some-root-selector *:after { 102 | box-sizing: border-box; 103 | } 104 | ``` 105 | 106 | ## Usage 107 | 108 | **Step 1:** Install plugin: 109 | 110 | ```sh 111 | npm install --save-dev postcss postcss-add-root-selector 112 | ``` 113 | 114 | **Step 2:** Check you project for existed PostCSS config: `postcss.config.js` 115 | in the project root, `"postcss"` section in `package.json` 116 | or `postcss` in bundle config. 117 | 118 | If you do not use PostCSS, add it according to [official docs] 119 | and set this plugin in settings. 120 | 121 | **Step 3:** Add the plugin to plugins list: 122 | 123 | ```diff 124 | module.exports = { 125 | plugins: [ 126 | + require('postcss-add-root-selector'), 127 | require('autoprefixer') 128 | ] 129 | } 130 | ``` 131 | 132 | ### Gulp 133 | 134 | In Gulp you can use [gulp-postcss] with `postcss-add-root-selector` npm package. 135 | 136 | ```js 137 | gulp.task('postcss-add-root-selector', () => { 138 | const addRootSelector = require('postcss-add-root-selector') 139 | const sourcemaps = require('gulp-sourcemaps') 140 | const postcss = require('gulp-postcss') 141 | 142 | return gulp.src('./src/*.css') 143 | .pipe(sourcemaps.init()) 144 | .pipe(postcss([ addRootSelector({ rootSelector: '.some-root' }) ])) 145 | .pipe(sourcemaps.write('.')) 146 | .pipe(gulp.dest('./dest')) 147 | }) 148 | ``` 149 | 150 | With `gulp-postcss` you also can combine Postcss Add Root Selector plugin 151 | with [other PostCSS plugins]. 152 | 153 | [gulp-postcss]: https://github.com/postcss/gulp-postcss 154 | [other PostCSS plugins]: https://github.com/postcss/postcss#plugins 155 | 156 | 157 | ### Webpack 158 | 159 | In [webpack] you can use [postcss-loader] with `postcss-add-root-selector` 160 | and [other PostCSS plugins]. 161 | 162 | ```js 163 | module.exports = { 164 | module: { 165 | rules: [ 166 | { 167 | test: /library\.css$/, 168 | use: [ 169 | 'style-loader', 170 | 'css-loader', 171 | { 172 | loader: 'postcss-loader', 173 | options: { 174 | postcssOptions: { 175 | plugins: [ 176 | [ 177 | 'postcss-preset-env', 178 | { 179 | include: ['some-style-lib.css'], 180 | rootSelector: '.some-root', 181 | } 182 | ] 183 | ] 184 | } 185 | } 186 | } 187 | ] 188 | } 189 | ] 190 | } 191 | } 192 | ``` 193 | 194 | Or you can add loader to specific import 195 | 196 | ```js 197 | import 'postcss-loader?postcssOptions.plugins=postcss-preset-env?postcssOptions.plugins.rootSelector=.some-root!./library.css' 198 | ``` 199 | 200 | And create a `postcss.config.js` with: 201 | 202 | ```js 203 | module.exports = { 204 | plugins: [ 205 | require('postcss-add-root-selector')({ 206 | include: ['some-style-lib.css'], 207 | rootSelector: '.my-root', 208 | }) 209 | ] 210 | } 211 | ``` 212 | 213 | ## Options 214 | Function `addRootSelector(options)` returns a new PostCSS plugin. 215 | See [PostCSS API] for plugin usage documentation. 216 | 217 | ```js 218 | addRootSelector({ rootSelector: '.some-root' }) 219 | ``` 220 | 221 | Available options are: 222 | 223 | * `rootSelector` (string): root selector (required). 224 | * `include` (Array): file masks to be included. 225 | * `exclude` (Array): file masks to be excluded. 226 | 227 | [other PostCSS plugins]: https://github.com/postcss/postcss#plugins 228 | [postcss-loader]: https://github.com/postcss/postcss-loader 229 | [webpack]: https://webpack.js.org/ 230 | [official docs]: https://github.com/postcss/postcss#usage 231 | -------------------------------------------------------------------------------- /index.test.js: -------------------------------------------------------------------------------- 1 | const postcss = require('postcss') 2 | const postcss7 = require('postcss7') 3 | 4 | const plugin = require('./') 5 | 6 | async function run (input, output, opts = { }, from) { 7 | const result = await postcss([plugin(opts)]).process(input, { from }) 8 | expect(result.css).toEqual(output) 9 | expect(result.warnings()).toHaveLength(0) 10 | } 11 | 12 | it('should throw if not rootSector option specified', async () => { 13 | let err 14 | try { 15 | await postcss([plugin({})]).process('', {from: undefined}) 16 | } catch (e) { 17 | err = e 18 | } 19 | expect(err).toBeInstanceOf(Error) 20 | }) 21 | 22 | it('adds root to tag', async () => { 23 | await run('a{color: red}', '.some-root a{color: red}', { rootSelector: '.some-root' }) 24 | }) 25 | 26 | it('adds root to tag with class', async () => { 27 | await run('a.some{color: red}', '.some-root a.some{color: red}', { rootSelector: '.some-root' }) 28 | }) 29 | 30 | it('adds root to class', async () => { 31 | await run('.some{color: red}', '.some-root .some{color: red}', { rootSelector: '.some-root' }) 32 | }) 33 | 34 | it('adds root to id selector', async () => { 35 | await run('#some{color: red}', '.some-root #some{color: red}', { rootSelector: '.some-root' }) 36 | }) 37 | 38 | it('adds root to sub-selector', async () => { 39 | await run('#some>.some{color: red}', '.some-root #some>.some{color: red}', { rootSelector: '.some-root' }) 40 | }) 41 | 42 | it('adds root in between for global selector and inherited declarations', async () => { 43 | await run('html{font-size: 10px;-webkit-font-smoothing: antialiased}', 'html .some-root{font-size: 10px;-webkit-font-smoothing: antialiased}', { rootSelector: '.some-root' }) 44 | }) 45 | 46 | it('preserves self-applied declarations to global elements', async () => { 47 | await run('html,body{margin: 0}', 'html,body{margin: 0}', { rootSelector: '.some-root' }) 48 | }) 49 | 50 | it('split self-applied declarations to global elements, and adds root in between for inherited declarations', async () => { 51 | await run(`\ 52 | html,body{ 53 | margin: 0; 54 | padding-top: 10px; 55 | margin-left: 10px; 56 | background: white; 57 | background-color: red; 58 | font-size: 14px; 59 | font-family: Arial; 60 | font-smoothing: antialiased; 61 | -webkit-font-smoothing: antialiased; 62 | position: relative; 63 | display: block; 64 | top: 10px; 65 | border-top-width: 1px; 66 | outline: none; 67 | color: red !important; 68 | }`, `\ 69 | html .some-root,body .some-root{ 70 | font-size: 14px; 71 | font-family: Arial; 72 | font-smoothing: antialiased; 73 | -webkit-font-smoothing: antialiased; 74 | color: red !important; 75 | } 76 | html,body{ 77 | margin: 0; 78 | padding-top: 10px; 79 | margin-left: 10px; 80 | background: white; 81 | background-color: red; 82 | position: relative; 83 | display: block; 84 | top: 10px; 85 | border-top-width: 1px; 86 | outline: none; 87 | }`, { rootSelector: '.some-root' }) 88 | }) 89 | 90 | it('extracts not-global declarations for mixed global/not global selectors', async () => { 91 | await run(`\ 92 | html,body,.some-class{ 93 | margin: 0; 94 | color: red !important; 95 | }`, `\ 96 | .some-root .some-class{ 97 | margin: 0; 98 | color: red !important; 99 | } 100 | html .some-root,body .some-root{ 101 | color: red !important; 102 | } 103 | html,body{ 104 | margin: 0; 105 | }`, { rootSelector: '.some-root' }) 106 | }) 107 | 108 | it('adds root in between for global selector body with nested selector and inherited declarations', async () => { 109 | await run('body:hover .some-class{color: red}', 'body:hover .some-root .some-class{color: red}', { rootSelector: '.some-root' }) 110 | }) 111 | 112 | it('add root to multiple selectors', async () => { 113 | await run('.some-class,.other-class,a{color: red}', '.some-root .some-class,.some-root .other-class,.some-root a{color: red}', { rootSelector: '.some-root' }) 114 | }) 115 | 116 | it('add root to mixed global and local selectors', async () => { 117 | await run('.some-class,body,a{color: red}', '.some-root .some-class,body .some-root,.some-root a{color: red}', { rootSelector: '.some-root' }) 118 | }) 119 | 120 | it('change every rule inside @media queries', async () => { 121 | await run('@media (max-width: 200px) {.some-class{color: red}}', '@media (max-width: 200px) {.some-root .some-class{color: red}}', { rootSelector: '.some-root' }) 122 | }) 123 | it('change every rule inside @container queries', async () => { 124 | await run('@container some-container (width > 60ch) {.card h2{font-size: 2em}}', '@container some-container (width > 60ch) {.some-root .card h2{font-size: 2em}}', { rootSelector: '.some-root' }) 125 | }) 126 | 127 | it('change every rule inside @document rules', async () => { 128 | await run('@document url("https://www.example.com/") {.some-class{color: red}}', '@document url("https://www.example.com/") {.some-root .some-class{color: red}}', { rootSelector: '.some-root' }) 129 | }) 130 | 131 | it('change every rule inside @supports rules', async () => { 132 | await run('@supports (animation-name: test) {.some-class{color: red}}', '@supports (animation-name: test) {.some-root .some-class{color: red}}', { rootSelector: '.some-root' }) 133 | }) 134 | 135 | it('do not change @keyframes rules', async () => { 136 | await run('@keyframes some-transition{0%{opacity: 0}100%{opacity: 1}}', '@keyframes some-transition{0%{opacity: 0}100%{opacity: 1}}', { rootSelector: '.some-root' }) 137 | }) 138 | 139 | it('replace :root with new root', async () => { 140 | await run(':root{--some-var: 12px}', '.some-root{--some-var: 12px}', { rootSelector: '.some-root' }) 141 | }) 142 | 143 | it('adds root to * (all) selector-lists', async () => { 144 | await run('*,*::before{box-sizing: border-box;}', '.some-root,.some-root *,.some-root *::before{box-sizing: border-box;}', { rootSelector: '.some-root' }) 145 | }) 146 | 147 | it('do not change root selector itself', async () => { 148 | await run('.some-root{margin:0}', '.some-root{margin:0}', { rootSelector: '.some-root' }) 149 | }) 150 | 151 | it('should include files', async () => { 152 | await run('a{color: red}', '.some-root a{color: red}', { rootSelector: '.some-root', include: ['some.css'] }, 'some.css') 153 | }) 154 | 155 | it('should exclude files', async () => { 156 | await run('a{color: red}', 'a{color: red}', { rootSelector: '.some-root', exclude: ['some.css'] }, 'some.css') 157 | }) 158 | 159 | it('should ignore empty exclude and include lists', async () => { 160 | await run('a{color: red}', '.some-root a{color: red}', { rootSelector: '.some-root', exclude: [], include: [] }, 'some.css') 161 | }) 162 | 163 | it('works with postcss v7 and v8', async () => { 164 | const input = '.some-root{margin:0}' 165 | const output = '.some-root{margin:0}' 166 | const opts = { rootSelector: '.some-root' } 167 | const result = await postcss([plugin.versionFormat(postcss)(opts)]).process(input, { from: undefined }) 168 | const result7 = await postcss7([plugin.versionFormat(postcss7)(opts)]).process(input, { from: undefined }) 169 | expect(result.css).toEqual(output) 170 | expect(result7.css).toEqual(output) 171 | expect(result.warnings()).toHaveLength(0) 172 | expect(result7.warnings()).toHaveLength(0) 173 | }) 174 | 175 | it('complex example should work', async () => { 176 | await run(`\ 177 | .foo { 178 | color: red; 179 | } 180 | 181 | a.foo, 182 | section { 183 | color: red; 184 | } 185 | 186 | @media (max-width: 700px) { 187 | #some-id { 188 | color: red; 189 | } 190 | } 191 | 192 | /* html and body selectors will be popped up */ 193 | html, 194 | body.desktop { 195 | font-family: sans-serif; 196 | } 197 | 198 | body.desktop .bar { 199 | font-weight: bold; 200 | } 201 | 202 | :root { 203 | --some-var: 10px; 204 | }`, `\ 205 | .some-root .foo { 206 | color: red; 207 | } 208 | 209 | .some-root a.foo, 210 | .some-root section { 211 | color: red; 212 | } 213 | 214 | @media (max-width: 700px) { 215 | .some-root #some-id { 216 | color: red; 217 | } 218 | } 219 | 220 | /* html and body selectors will be popped up */ 221 | html .some-root, 222 | body.desktop .some-root { 223 | font-family: sans-serif; 224 | } 225 | 226 | body.desktop .some-root .bar { 227 | font-weight: bold; 228 | } 229 | 230 | .some-root { 231 | --some-var: 10px; 232 | }`, { rootSelector: '.some-root' }) 233 | }) 234 | --------------------------------------------------------------------------------