├── .editorconfig ├── .github └── workflows │ └── node.js.yml ├── .gitignore ├── .jscsrc ├── .npmignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── index.js ├── package-lock.json ├── package.json └── test ├── __snapshots__ └── test.js.snap ├── cases ├── multiline-expressions.css ├── multiple-assignment.css ├── multiple-properties.css ├── multiple-selectors.css ├── multiple-values.css ├── nested-iteration-parent-vars.css ├── nested-iteration.css ├── one-value.css ├── other-variables.css ├── short-names.css ├── value-with-index.css └── with-in-substring.css └── test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.css] 13 | indent_size = 4 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | env: 4 | FORCE_COLOR: "1" 5 | 6 | on: 7 | push: 8 | pull_request: 9 | 10 | jobs: 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | strategy: 16 | matrix: 17 | node-version: [10.x, 12.x, 14.x, 15.x] 18 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Use Node.js ${{ matrix.node-version }} 23 | uses: actions/setup-node@v1 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | - run: npm ci 27 | - run: npm run build --if-present 28 | - run: npm test 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | index.es5.js 4 | .log 5 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "airbnb", 3 | "disallowMultipleVarDecl": "exceptUndefined", 4 | "disallowQuotedKeysInObjects": "allButReserved", 5 | "disallowArrayDestructuringReturn": false 6 | } 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | 3 | node_modules/ 4 | 5 | test/ 6 | .travis.yml 7 | .jscsrc 8 | 9 | gulpfile.js 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.1.0 2 | - Updated postcss-simple-vars (by Charles Suh @charlessuh) 3 | 4 | ## 1.0.0 5 | - Updated to PostCSS 8 (by Charles Suh @charlessuh) 6 | - Got rid of Babel 7 | 8 | ## 0.10.0 9 | Updated to PostCSS 6 10 | 11 | ## 0.9.3 12 | Babel packages moved to devDependencies 13 | 14 | ## 0.9.2 15 | Updated dependencies 16 | 17 | ## 0.9.1 18 | Fixed nodes processing order https://github.com/outpunk/postcss-each/issues/17 (by Muhamad Gameel @hexpanic) 19 | 20 | ## 0.9.0 21 | Added `afterEach` and `beforeEach` options to run plugins before and after each iteration (by @GitScrum) 22 | 23 | ## 0.8.0 24 | Added support for using parent's variables in a nested loop (by @GitScrum) 25 | 26 | ## 0.7.2 27 | Updated dependencies 28 | 29 | ## 0.7.1 30 | Updated dependencies 31 | 32 | ## 0.7.0 33 | Updated to PostCSS 5 34 | 35 | ## 0.6.0 36 | Added multiple variable assignment (by Ryan Tsao @rtsao): 37 | 38 | ```css 39 | @each $animal, $color in (puma, black), (sea-slug, blue) { 40 | .$(animal)-icon { 41 | background-image: url('/images/$(animal).png'); 42 | border: 2px solid $color; 43 | } 44 | } 45 | 46 | ```css 47 | .puma-icon { 48 | background-image: url('/images/puma.png'); 49 | border: 2px solid black; 50 | } 51 | 52 | .sea-slug-icon { 53 | background-image: url('/images/sea-slug.png'); 54 | border: 2px solid blue; 55 | } 56 | ``` 57 | 58 | ## 0.5.0 59 | ### Internal changes 60 | * Translated to ES6 61 | * PostCSS updated 62 | 63 | ## 0.4.1 64 | Internal improvements 65 | 66 | ## 0.4.0 67 | Added nested iteration (by Ryan Tsao @rtsao) 68 | 69 | ## 0.3.1 70 | Fixed #4: Do not search the `in` keyword inside vars 71 | 72 | ## 0.3.0 73 | Added index iteration (by Anton Winogradov @verybigman): 74 | 75 | ```css 76 | @each $val, $i in foo, bar { 77 | .icon-$(val) { 78 | background: url("$(val)_$(i).png"); 79 | } 80 | } 81 | ``` 82 | 83 | ```css 84 | .icon-foo { 85 | background: url("foo_0.png"); 86 | } 87 | 88 | .icon-bar { 89 | background: url("bar_1.png"); 90 | } 91 | ``` 92 | 93 | ## 0.2.1 94 | Fixed short vars issue #2 95 | 96 | ## 0.2.0 97 | ### Internal changes 98 | * The code has been cleaned. 99 | * The variables processing code has been replaced with [`postcss-simple-vars`]. 100 | 101 | [`postcss-simple-vars`]: https://github.com/postcss/postcss-simple-vars 102 | 103 | ## 0.1.0 104 | Initial version 105 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright 2015 Alexander Madyankin 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # postcss-each 2 | 3 | A [PostCSS] plugin to iterate through values. 4 | 5 | [PostCSS]: https://github.com/postcss/postcss 6 | 7 | Iterate through values: 8 | 9 | ```css 10 | @each $icon in foo, bar, baz { 11 | .icon-$(icon) { 12 | background: url('icons/$(icon).png'); 13 | } 14 | } 15 | ``` 16 | 17 | ```css 18 | .icon-foo { 19 | background: url('icons/foo.png'); 20 | } 21 | 22 | .icon-bar { 23 | background: url('icons/bar.png'); 24 | } 25 | 26 | .icon-baz { 27 | background: url('icons/baz.png'); 28 | } 29 | ``` 30 | 31 | Iterate through values with index: 32 | 33 | ```css 34 | @each $val, $i in foo, bar { 35 | .icon-$(val) { 36 | background: url("$(val)_$(i).png"); 37 | } 38 | } 39 | ``` 40 | 41 | ```css 42 | .icon-foo { 43 | background: url("foo_0.png"); 44 | } 45 | 46 | .icon-bar { 47 | background: url("bar_1.png"); 48 | } 49 | ``` 50 | 51 | Iterate through multiple variables: 52 | 53 | ```css 54 | @each $animal, $color in (puma, sea-slug), (black, blue) { 55 | .$(animal)-icon { 56 | background-image: url('/images/$(animal).png'); 57 | border: 2px solid $color; 58 | } 59 | } 60 | ``` 61 | 62 | ```css 63 | .puma-icon { 64 | background-image: url('/images/puma.png'); 65 | border: 2px solid black; 66 | } 67 | .sea-slug-icon { 68 | background-image: url('/images/sea-slug.png'); 69 | border: 2px solid blue; 70 | } 71 | ``` 72 | 73 | # Installation 74 | 75 | ``` 76 | npm install --save-dev postcss postcss-each 77 | ``` 78 | 79 | ## Usage 80 | 81 | ```js 82 | postcss([ require('postcss-each') ]) 83 | ``` 84 | 85 | ### Options 86 | 87 | #### `plugins` 88 | 89 | Type: `object` 90 | Default: `{}` 91 | 92 | Accepts two properties: `afterEach` and `beforeEach` 93 | 94 | #### `afterEach` 95 | 96 | Type: `array` 97 | Default: `[]` 98 | 99 | Plugins to be called after each iteration 100 | 101 | #### `beforeEach` 102 | 103 | Type: `array` 104 | Default: `[]` 105 | 106 | Plugins to be called before each iteration 107 | 108 | ```javascript 109 | require('postcss-each')({ 110 | plugins: { 111 | afterEach: [ 112 | require('postcss-at-rules-variables') 113 | ], 114 | beforeEach: [ 115 | require('postcss-custom-properties') 116 | ] 117 | } 118 | }) 119 | ``` 120 | 121 | 122 | See [PostCSS] docs for examples for your environment. 123 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const postcss = require('postcss'); 2 | const vars = require('postcss-simple-vars'); 3 | 4 | const PLUGIN_NAME = 'postcss-each'; 5 | const SEPARATOR = /\s+in\s+/; 6 | 7 | function checkParams(params) { 8 | if (!SEPARATOR.test(params)) return 'Missed "in" keyword in @each'; 9 | 10 | const [name, values] = params.split(SEPARATOR).map(str => str.trim()); 11 | 12 | if (!name.match(/\$[_a-zA-Z]?\w+/)) return 'Missed variable name in @each'; 13 | if (!values.match(/(\w+\,?\s?)+/)) return 'Missed values list in @each'; 14 | 15 | return null; 16 | } 17 | 18 | function tokenize(str) { 19 | return postcss.list.comma(str).map(str => str.replace(/^\$/, '')); 20 | } 21 | 22 | function paramsList(params) { 23 | let [vars, values] = params.split(SEPARATOR).map(tokenize); 24 | let matched = false; 25 | 26 | values = values.map(value => { 27 | let match = value.match(/^\((.*)\)$/); 28 | if (match) matched = true; 29 | return match ? postcss.list.comma(match[1]) : value; 30 | }); 31 | 32 | values = matched ? values : [values]; 33 | 34 | return { 35 | names: values.map((_, i) => vars[i]), 36 | indexName: vars[values.length], 37 | values: values, 38 | }; 39 | } 40 | 41 | function processRules(rule, params) { 42 | params.values[0].forEach((_, i) => { 43 | let vals = {}; 44 | 45 | params.names.forEach((name, j) => { 46 | vals[name] = params.values[j][i]; 47 | }); 48 | 49 | if (params.indexName) vals[params.indexName] = i; 50 | 51 | rule.nodes.forEach(node => { 52 | const proxy = postcss.rule({ nodes: [node] }); 53 | const { root } = postcss([vars({ only: vals })]).process(proxy); 54 | rule.parent.insertBefore(rule, root.nodes[0].nodes[0]); 55 | }); 56 | }); 57 | } 58 | 59 | function processEach(rule) { 60 | const params = ` ${rule.params} `; 61 | const error = checkParams(params); 62 | if (error) throw rule.error(error); 63 | 64 | const parsedParams = paramsList(params); 65 | processRules(rule, parsedParams); 66 | rule.remove(); 67 | } 68 | 69 | function rulesExists(css) { 70 | let rulesLength = 0; 71 | css.walkAtRules('each', () => rulesLength++); 72 | return rulesLength; 73 | } 74 | 75 | function processLoop(css, afterEach, beforeEach) { 76 | if (afterEach) { 77 | css = postcss(afterEach).process(css).root; 78 | } 79 | 80 | css.walkAtRules('each', (rule) => { 81 | processEach(rule); 82 | processLoop(rule.root()); 83 | }); 84 | 85 | if (beforeEach) { 86 | css = postcss(beforeEach).process(css).root; 87 | } 88 | 89 | if (rulesExists(css)) processLoop(css, afterEach, beforeEach); 90 | }; 91 | 92 | const pluginCreator = (opts = {}) => { 93 | const hasPlugins = opts && opts.plugins; 94 | const hasAfterEach = hasPlugins && opts.plugins.afterEach && opts.plugins.afterEach.length; 95 | const hasBeforeEach = hasPlugins && opts.plugins.beforeEach && opts.plugins.beforeEach.length; 96 | 97 | if (hasAfterEach || hasBeforeEach) { 98 | return { 99 | postcssPlugin: PLUGIN_NAME, 100 | Once: (css) => processLoop( 101 | css, 102 | hasAfterEach && opts.plugins.afterEach, 103 | hasBeforeEach && opts.plugins.beforeEach 104 | ), 105 | }; 106 | } else { 107 | return { 108 | postcssPlugin: PLUGIN_NAME, 109 | AtRule: { 110 | each: processEach, 111 | }, 112 | }; 113 | } 114 | }; 115 | 116 | pluginCreator.postcss = true; 117 | 118 | module.exports = pluginCreator; 119 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "postcss-each", 3 | "version": "1.1.0", 4 | "description": "PostCSS plugin to iterate through values", 5 | "main": "index.js", 6 | "keywords": [ 7 | "postcss", 8 | "css", 9 | "postcss-plugin", 10 | "each", 11 | "iterator" 12 | ], 13 | "author": "Alexander Madyankin ", 14 | "license": "MIT", 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/outpunk/postcss-each.git" 18 | }, 19 | "dependencies": { 20 | "postcss-simple-vars": "^6.0.0" 21 | }, 22 | "devDependencies": { 23 | "jest": "^26.6.3", 24 | "jscs": "^3.0.7", 25 | "postcss": "^8.0.0" 26 | }, 27 | "peerDependencies": { 28 | "postcss": "^8.0.0" 29 | }, 30 | "scripts": { 31 | "pretest": "jscs index.js test", 32 | "test": "jest", 33 | "postpublish": "git push --follow-tags" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/__snapshots__/test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`postcss-each doesn't replace other variables 1`] = ` 4 | " 5 | .icon-foo, .foo { 6 | background: url(\\"$(bg).png\\"); 7 | } 8 | .icon-bar, .bar { 9 | background: url(\\"$(bg).png\\"); 10 | } 11 | " 12 | `; 13 | 14 | exports[`postcss-each iterates and assigns multiple values 1`] = ` 15 | " 16 | .icon-a-x-0 { 17 | background: url(\\"a.png\\"); 18 | color: x; 19 | } 20 | .icon-b-y-1 { 21 | background: url(\\"b.png\\"); 22 | color: y; 23 | } 24 | .icon-c-z-2 { 25 | background: url(\\"c.png\\"); 26 | color: z; 27 | } 28 | " 29 | `; 30 | 31 | exports[`postcss-each iterates short names 1`] = ` 32 | " 33 | .icon-foo { 34 | background: url(\\"foo.png\\"); 35 | } 36 | " 37 | `; 38 | 39 | exports[`postcss-each iterates through given values 1`] = ` 40 | " 41 | .icon-foo { 42 | background: url(\\"foo.png\\"); 43 | } 44 | .icon-bar { 45 | background: url(\\"bar.png\\"); 46 | } 47 | " 48 | `; 49 | 50 | exports[`postcss-each iterates through one value 1`] = ` 51 | " 52 | .icon-foo { 53 | background: url(\\"foo.png\\"); 54 | } 55 | " 56 | `; 57 | 58 | exports[`postcss-each iterates value and index 1`] = ` 59 | " 60 | .icon-foo { 61 | background: url(\\"foo_0.png\\"); 62 | } 63 | .icon-bar { 64 | background: url(\\"bar_1.png\\"); 65 | } 66 | " 67 | `; 68 | 69 | exports[`postcss-each performs nested iteration 1`] = ` 70 | " 71 | .icon-foo { 72 | border: abc; 73 | border: xyz 74 | } 75 | .icon-bar { 76 | border: abc; 77 | border: xyz 78 | } 79 | .a { 80 | hello: a; 81 | } 82 | .a-x { 83 | color: purple; 84 | blah: u; 85 | blah: v 86 | } 87 | .a-y { 88 | color: purple; 89 | blah: u; 90 | blah: v 91 | } 92 | .b { 93 | hello: b; 94 | } 95 | .b-x { 96 | color: purple; 97 | blah: u; 98 | blah: v 99 | } 100 | .b-y { 101 | color: purple; 102 | blah: u; 103 | blah: v 104 | } 105 | " 106 | `; 107 | 108 | exports[`postcss-each performs nested iteration with parent's variables 1`] = ` 109 | " 110 | .icon-foo { 111 | border: foo-abc; 112 | border: xyz 113 | } 114 | .icon-bar { 115 | border: bar-abc; 116 | border: xyz 117 | } 118 | " 119 | `; 120 | 121 | exports[`postcss-each respects multiline expressions 1`] = ` 122 | " 123 | .foo { 124 | background: url(\\"foo.png\\") 125 | } 126 | .bar { 127 | background: url(\\"bar.png\\") 128 | } 129 | " 130 | `; 131 | 132 | exports[`postcss-each respects multiple properties 1`] = ` 133 | " 134 | .icon-foo { 135 | background: url(\\"foo.png\\"); 136 | content: \\"foo\\"; 137 | } 138 | .icon-bar { 139 | background: url(\\"bar.png\\"); 140 | content: \\"bar\\"; 141 | } 142 | " 143 | `; 144 | 145 | exports[`postcss-each respects multiple selectors 1`] = ` 146 | " 147 | .icon-foo, .foo { 148 | background: url(\\"foo.png\\"); 149 | } 150 | .icon-bar, .bar { 151 | background: url(\\"bar.png\\"); 152 | } 153 | " 154 | `; 155 | 156 | exports[`postcss-each respects properties with \`in\` substring 1`] = ` 157 | " 158 | .print { 159 | background: url(\\"print.png\\"); 160 | } 161 | .bar { 162 | background: url(\\"bar.png\\"); 163 | } 164 | " 165 | `; 166 | -------------------------------------------------------------------------------- /test/cases/multiline-expressions.css: -------------------------------------------------------------------------------- 1 | @each $icon 2 | in foo { 3 | .$(icon) { 4 | background: url("$(icon).png") 5 | } 6 | } 7 | @each $icon 8 | in 9 | bar { 10 | .$(icon) { 11 | background: url("$(icon).png") 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/cases/multiple-assignment.css: -------------------------------------------------------------------------------- 1 | @each $foo, $bar, $i in (a, b, c), (x, y, z) { 2 | .icon-$(foo)-$(bar)-$(i) { 3 | background: url("$(foo).png"); 4 | color: $bar; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/cases/multiple-properties.css: -------------------------------------------------------------------------------- 1 | @each $icon in foo, bar { 2 | .icon-$(icon) { 3 | background: url("$(icon).png"); 4 | content: "$(icon)"; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/cases/multiple-selectors.css: -------------------------------------------------------------------------------- 1 | @each $icon in foo, bar { 2 | .icon-$(icon), .$(icon) { 3 | background: url("$(icon).png"); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/cases/multiple-values.css: -------------------------------------------------------------------------------- 1 | @each $icon in foo, bar { 2 | .icon-$(icon) { 3 | background: url("$(icon).png"); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/cases/nested-iteration-parent-vars.css: -------------------------------------------------------------------------------- 1 | @each $icon in foo, bar { 2 | .icon-$(icon) { 3 | @each $thing in $(icon)-abc, xyz { 4 | border: $(thing); 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/cases/nested-iteration.css: -------------------------------------------------------------------------------- 1 | @each $icon in foo, bar { 2 | .icon-$(icon) { 3 | @each $thing in abc, xyz { 4 | border: $(thing); 5 | } 6 | } 7 | } 8 | @each $foo in a, b { 9 | .$(foo) { 10 | hello: $(foo); 11 | } 12 | @each $bar in x, y { 13 | .$(foo)-$(bar) { 14 | color: purple; 15 | @each $baz in u, v { 16 | blah: $(baz); 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/cases/one-value.css: -------------------------------------------------------------------------------- 1 | @each $icon in foo { 2 | .icon-$(icon) { 3 | background: url("$(icon).png"); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/cases/other-variables.css: -------------------------------------------------------------------------------- 1 | @each $icon in foo, bar { 2 | .icon-$(icon), .$(icon) { 3 | background: url("$(bg).png"); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/cases/short-names.css: -------------------------------------------------------------------------------- 1 | @each $i in foo { 2 | .icon-$(i) { 3 | background: url("$(i).png"); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/cases/value-with-index.css: -------------------------------------------------------------------------------- 1 | @each $val, $i in foo, bar { 2 | .icon-$(val) { 3 | background: url("$(val)_$(i).png"); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/cases/with-in-substring.css: -------------------------------------------------------------------------------- 1 | @each $icon in print, bar { 2 | .$(icon) { 3 | background: url("$(icon).png"); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | const postcss = require('postcss'); 2 | const assert = require('assert'); 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const plugin = require('../'); 6 | 7 | const casesPath = path.join(__dirname, '/cases'); 8 | 9 | const cases = { 10 | 'multiple-values': 'iterates through given values', 11 | 'one-value': 'iterates through one value', 12 | 'short-names': 'iterates short names', 13 | 'value-with-index': 'iterates value and index', 14 | 'multiple-assignment': 'iterates and assigns multiple values', 15 | 'multiple-selectors': 'respects multiple selectors', 16 | 'with-in-substring': 'respects properties with `in` substring', 17 | 'multiline-expressions': 'respects multiline expressions', 18 | 'multiple-properties': 'respects multiple properties', 19 | 'other-variables': 'doesn\'t replace other variables', 20 | 'nested-iteration': 'performs nested iteration', 21 | 'nested-iteration-parent-vars': 'performs nested iteration with parent\'s variables', 22 | }; 23 | 24 | function test(input, opts, done) { 25 | const result = postcss([plugin(opts)]).process(input); 26 | expect(result.css).toMatchSnapshot(); 27 | }; 28 | 29 | function css(name) { 30 | const fileName = path.join(casesPath, name + '.css'); 31 | return fs.readFileSync(fileName).toString(); 32 | } 33 | 34 | describe('postcss-each', () => { 35 | it('expects valid syntax', () => { 36 | const missedIn = () => test('@each $icon foo, bar {}'); 37 | const missedVar = () => test('@each in foo, bar {}'); 38 | const missedValues = () => test('@each $icon in {}'); 39 | 40 | assert.throws(missedIn, /Missed "in" keyword in @each/); 41 | assert.throws(missedVar, /Missed variable name in @each/); 42 | assert.throws(missedValues, /Missed values list in @each/); 43 | }); 44 | 45 | for (let caseName in cases) { 46 | const description = cases[caseName]; 47 | it(description, () => test(css(caseName))); 48 | } 49 | }); 50 | --------------------------------------------------------------------------------