├── .gitignore ├── .travis.yml ├── lib ├── helpers.js └── register.js ├── package.json ├── LICENSE ├── README.md ├── index.js └── tests.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .idea/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | install: 5 | - npm install 6 | - npm install -g codecov 7 | script: 8 | - ./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha tests.js --report lcovonly -- -R spec && codecov -------------------------------------------------------------------------------- /lib/helpers.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | /** 3 | * Delay the calculation of a variable until later 4 | * 5 | * @param {Function} fn 6 | * @param {Array} args 7 | * @returns {{type: string, fn: *, args: *}} 8 | */ 9 | defer(fn, args) { 10 | return { 11 | type: 'deferredFunction', 12 | fn: fn, 13 | args: args 14 | }; 15 | }, 16 | 17 | /** 18 | * Auto-escape icon to prep for browser 19 | * 20 | * @param {string} icon 21 | * @returns {string} 22 | */ 23 | icon(icon) { 24 | return `'\\${icon}'`; 25 | } 26 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "postcss-variables", 3 | "version": "1.1.1", 4 | "description": "PostCSS plugin that converts variables into CSS", 5 | "main": "index.js", 6 | "keywords": [ 7 | "postcss", 8 | "css", 9 | "postcss-plugin", 10 | "variables", 11 | "vars" 12 | ], 13 | "scripts": { 14 | "test": "mocha ./tests.js" 15 | }, 16 | "repository": "nathanhood/postcss-variables", 17 | "author": "Nathan Hood", 18 | "license": "MIT", 19 | "dependencies": { 20 | "postcss": "^6.0.9" 21 | }, 22 | "devDependencies": { 23 | "chai": "^4.1.1", 24 | "istanbul": "^0.4.5", 25 | "mocha": "^3.5.0", 26 | "postcss-js-mixins": "^2.5.2", 27 | "postcss-wee-syntax": "3.0.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright 2016 Nathan Hood 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. -------------------------------------------------------------------------------- /lib/register.js: -------------------------------------------------------------------------------- 1 | module.exports = (variables = {}) => { 2 | let vars; 3 | 4 | /** 5 | * Retrieve property value from vars object 6 | * 7 | * @param {string} keys 8 | * @returns {*} 9 | */ 10 | function getProp(keys) { 11 | let segs = keys.toString().split('.'), 12 | key = segs.shift().replace(/^\$/, ''), 13 | value = null; 14 | 15 | // Confirm that string is variable reference 16 | if (! /^\$/.test(keys)) { 17 | return keys; 18 | } 19 | 20 | if (vars[key]) { 21 | value = vars[key]; 22 | 23 | while (segs.length && value.hasOwnProperty(segs[0])) { 24 | value = value[segs[0]]; 25 | segs.shift(); 26 | } 27 | } 28 | 29 | return value; 30 | } 31 | 32 | /** 33 | * Execute deferred function to calculate variable value 34 | * 35 | * @param {Function} fn 36 | * @param {Array} args 37 | * @returns {*} 38 | */ 39 | function evalFn(fn, args) { 40 | return fn.apply(null, args.map(arg => { 41 | let result = arg; 42 | 43 | if (typeof arg === 'string') { 44 | result = getProp(arg); 45 | } 46 | 47 | return result; 48 | })); 49 | } 50 | 51 | /** 52 | * Traverse entire object structure recursively 53 | * 54 | * @param {Object|Array} obj 55 | */ 56 | function traverse(obj) { 57 | let props = Object.keys(obj), 58 | i; 59 | 60 | for (i = 0; i < props.length; i++) { 61 | let value = obj[props[i]], 62 | type = typeof value; 63 | 64 | if (type === 'object') { 65 | if (value.type === 'deferredFunction') { 66 | obj[props[i]] = evalFn(value.fn, value.args); 67 | } else { 68 | traverse(value); 69 | } 70 | } else if (type === 'string') { 71 | obj[props[i]] = getProp(value); 72 | } 73 | } 74 | } 75 | 76 | return () => { 77 | vars = Object.assign({}, variables); 78 | 79 | traverse(vars); 80 | 81 | return vars; 82 | }; 83 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PostCSS Variables 2 | 3 | [![Build Status](https://travis-ci.org/nathanhood/postcss-variables.svg?branch=master)](https://travis-ci.org/nathanhood/postcss-variables) 4 | [![codecov](https://codecov.io/gh/nathanhood/postcss-variables/branch/master/graph/badge.svg)](https://codecov.io/gh/nathanhood/postcss-variables) 5 | [![npm version](https://badge.fury.io/js/postcss-variables.svg)](https://badge.fury.io/js/postcss-variables) 6 | 7 | 8 | 9 | Converts variables into CSS. 10 | 11 | ```scss 12 | /* before (nesting requires postcss-nested) */ 13 | 14 | $dir: assets/icons; 15 | $color: blue; 16 | 17 | .block { 18 | background: url('$(dir)/foo.png'); 19 | &__elem { 20 | $color: green; 21 | color: $color; 22 | } 23 | &__elem2 { 24 | color: $color; 25 | } 26 | } 27 | 28 | /* after */ 29 | 30 | .block { 31 | background: url('assets/icons/foo.png'); 32 | &__elem { 33 | color: green; 34 | } 35 | &__elem2 { 36 | color: blue; 37 | } 38 | } 39 | ``` 40 | 41 | 42 | ## Usage 43 | 44 | Add PostCSS Variables to your build tool: 45 | 46 | ```bash 47 | npm install postcss-variables --save-dev 48 | ``` 49 | 50 | #### Node 51 | 52 | ```js 53 | require('postcss-variables')({ /* options */ }).process(YOUR_CSS); 54 | ``` 55 | 56 | #### PostCSS 57 | 58 | Add [PostCSS](https://github.com/postcss/postcss) to your build tool: 59 | 60 | ```bash 61 | npm install postcss --save-dev 62 | ``` 63 | 64 | Load PostCSS Variables as a PostCSS plugin: 65 | 66 | ```js 67 | postcss([ 68 | require('postcss-variables')({ /* options */ }) 69 | ]); 70 | ``` 71 | 72 | ## Options 73 | 74 | ### `globals` 75 | 76 | Type: `Object` 77 | Default: `{}` 78 | 79 | Specifies your own global variables. 80 | 81 | ```js 82 | require('postcss-variables')({ 83 | globals: { 84 | siteWidth: '960px', 85 | colors: { 86 | primary: '#fff', 87 | secondary: '#000' 88 | } 89 | } 90 | }); 91 | ``` 92 | 93 | ```css 94 | /* before */ 95 | 96 | .hero { 97 | color: $colors.primary; 98 | max-width: $siteWidth; 99 | } 100 | 101 | /* after */ 102 | 103 | .hero { 104 | color: #fff; 105 | max-width: 960px; 106 | } 107 | ``` 108 | 109 | ## Advanced 110 | 111 | When creating your global variables, you may want to eliminate duplication by referencing an existing property to define your new variable. You can do this by referencing variables like you would in your stylesheet. Here is the basic idea: 112 | 113 | ```js 114 | let vars = { 115 | colors: { 116 | primary: '#fff' 117 | }, 118 | heading: { 119 | color: '$colors.primary' 120 | } 121 | }; 122 | ``` 123 | 124 | In certain circumstances, you may want to create a base variables file that you would want to be able to override. This would be a use-case if you were using this plugin inside of some kind of framework. 125 | If you are using functions to calculate global variables, you may want to delay the function execution until after you had a chance to override your variables. This can be done by using the `defer` method. 126 | 127 | ```js 128 | function darken(color, pct) { 129 | // Do something to calculate darker hex value 130 | return result 131 | } 132 | 133 | module.exports = { 134 | colors: { 135 | white: '#fff', 136 | gray: defer(darken, ['$colors.white', 35]) 137 | } 138 | }; 139 | ``` 140 | 141 | This is what a full example would look like in order to use these features: 142 | 143 | ```js 144 | /* variables.js */ 145 | const { defer } = require('postcss-variables/lib/helpers'); 146 | const register = require('postcss-variables/lib/register'); 147 | 148 | function darken(color, pct) { 149 | // Do something to calculate darker hex value 150 | return result 151 | } 152 | 153 | let vars = { 154 | colors: { 155 | primary: '#fff', 156 | gray: defer(darken, ['$colors.white', 35]) 157 | }, 158 | heading: { 159 | color: '$colors.primary' 160 | } 161 | }; 162 | 163 | module.exports = register(vars); 164 | ``` -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const postcss = require('postcss'); 2 | 3 | module.exports = postcss.plugin('postcss-variables', (opts = {}) => { 4 | let globals = opts.globals || {}, 5 | result; 6 | 7 | const isVariableDeclaration = /^\$[\w-]+$/; 8 | const variablesInString = /(^|[^\\])\$(?:\(([A-z][\w-.]*)\)|([A-z][\w-.]*))/g; 9 | 10 | /** 11 | * Split comma separated arguments 12 | * 'black, linear-gradient(white, black)' => ['black', 'linear-gradient(white, black)'] 13 | * 14 | * @param {string} string 15 | * @param {boolean} [first] 16 | * @returns {*} 17 | */ 18 | function getArrayedString(string, first) { 19 | let array = postcss.list 20 | .comma(String(string)); 21 | 22 | return first && array.length === 1 ? array[0] : array; 23 | } 24 | 25 | /** 26 | * Retrieve variable, traversing up parent containers as necessary 27 | * 28 | * @param {postcss.Container} node 29 | * @param {string} name 30 | * @returns {*} 31 | */ 32 | function getVariable(node, name) { 33 | let segs = name.toString().split('.'), 34 | key = segs.shift(), 35 | value = null; 36 | 37 | if (node.variables && node.variables[key]) { 38 | value = node.variables[key]; 39 | 40 | while (segs.length && value.hasOwnProperty(segs[0])) { 41 | value = value[segs[0]]; 42 | segs.shift(); 43 | } 44 | } 45 | 46 | if (value === null || segs.length) { 47 | value = node.parent && getVariable(node.parent, name); 48 | } 49 | 50 | return value; 51 | } 52 | 53 | /** 54 | * Parse out variables from passed in string 55 | * Replace with corresponding values 56 | * 'Hello $name' => 'Hello VALUE' 57 | * 58 | * @param {postcss.Container} node 59 | * @param {string} string 60 | * @returns {*} 61 | */ 62 | function getVariableTransformedString(node, string) { 63 | return string.replace(variablesInString, (match, before, name1, name2) => { 64 | let prop = name1 || name2, 65 | value = getVariable(node, prop); 66 | 67 | if (value === undefined) { 68 | node.warn(result, 'Undefined variable $' + prop); 69 | 70 | return match; 71 | } 72 | 73 | return before + formatValue(value); 74 | }); 75 | } 76 | 77 | /** 78 | * Format array of values with comma and space between 79 | * 80 | * @param {array|string|number} value 81 | * @returns {string|*} 82 | */ 83 | function formatValue(value) { 84 | return Array.isArray(value) ? value.join(', ') : value; 85 | } 86 | 87 | /** 88 | * Set variable on node 89 | * 90 | * @param {postcss.Container} node 91 | * @param {string} name 92 | * @param {*} value 93 | */ 94 | function setVariable(node, name, value) { 95 | node.variables = node.variables || {}; 96 | 97 | node.variables[name] = getArrayedString(value, true); 98 | } 99 | 100 | /** 101 | * Action to be taken on each declaration 102 | * 103 | * @param {postcss.Declaration} node 104 | * @param {postcss.Container} parent 105 | * @param {number} nodeCount 106 | * @returns {*} 107 | */ 108 | function eachDecl(node, parent, nodeCount) { 109 | // ie - $name: value 110 | if (isVariableDeclaration.test(node.prop)) { 111 | node.value = getVariableTransformedString(parent, node.value); 112 | 113 | // Set variable on parent node 114 | setVariable(parent, node.prop.slice(1), node.value); 115 | 116 | node.remove(); 117 | 118 | --nodeCount; 119 | } else { 120 | node.prop = getVariableTransformedString(parent, node.prop); 121 | node.value = getVariableTransformedString(parent, node.value); 122 | } 123 | 124 | return nodeCount; 125 | } 126 | 127 | /** 128 | * Action to be taken on each rule 129 | * 130 | * @param {postcss.Container} node 131 | * @param {postcss.Container} parent 132 | * @param {number} index 133 | * @returns {*} 134 | */ 135 | function eachRule(node, parent, nodeCount) { 136 | node.selector = getVariableTransformedString(parent, node.selector); 137 | 138 | return nodeCount; 139 | } 140 | 141 | /** 142 | * Action to be taken on each atRule (ie - @name PARAMS) 143 | * 144 | * @param {postcss.Container} node 145 | * @param {postcss.Container} parent 146 | * @param {number} nodeCount 147 | * @returns {*} 148 | */ 149 | function eachAtRule(node, parent, nodeCount) { 150 | node.params = getVariableTransformedString(parent, node.params); 151 | 152 | return nodeCount; 153 | } 154 | 155 | function eachMixin(node, parent, nodeCount) { 156 | node.arguments.ordered = node.arguments.ordered.map(arg => { 157 | return getVariableTransformedString(parent, arg); 158 | }); 159 | 160 | Object.keys(node.arguments.named).forEach(arg => { 161 | node.arguments.named[arg] = getVariableTransformedString(parent, node.arguments.named[arg]); 162 | }); 163 | 164 | return nodeCount; 165 | } 166 | 167 | /** 168 | * Traverse every node 169 | * 170 | * @param {postcss.Container} parent 171 | */ 172 | function each(parent) { 173 | let index = -1; 174 | let node; 175 | 176 | while (node = parent.nodes[++index]) { 177 | if (node.type === 'decl') { 178 | index = eachDecl(node, parent, index); 179 | } else if (node.type === 'rule') { 180 | index = eachRule(node, parent, index); 181 | } else if (node.type === 'atrule') { 182 | index = eachAtRule(node, parent, index); 183 | } else if (node.type === 'mixin') { 184 | index = eachMixin(node, parent, index); 185 | } 186 | 187 | if (node.nodes) { 188 | each(node); 189 | } 190 | } 191 | } 192 | 193 | return (root, res) => { 194 | result = res; 195 | 196 | // Initialize each global variable 197 | root.variables = Object.assign({}, globals); 198 | 199 | // Begin processing each css node 200 | each(root); 201 | }; 202 | }); -------------------------------------------------------------------------------- /tests.js: -------------------------------------------------------------------------------- 1 | const expect = require('chai').expect; 2 | const postcss = require('postcss'); 3 | const plugin = require('./'); 4 | const register = require('./lib/register'); 5 | const { defer, icon } = require('./lib/helpers'); 6 | 7 | function process(input, expected, opts = {}) { 8 | return postcss([ plugin(opts) ]).process(input) 9 | .then((result) => { 10 | expect(result.css).to.equal(expected); 11 | expect(result.warnings().length).to.equal(0); 12 | }); 13 | } 14 | 15 | function processFail(input, expected, opts = {}) { 16 | return postcss([ plugin(opts) ]).process(input) 17 | .then((result) => { 18 | expect(result.css).to.equal(expected); 19 | expect(result.warnings().length).to.equal(1); 20 | }); 21 | } 22 | 23 | function processMixins(input, expected, opts = {}, errors = 0) { 24 | return postcss([ plugin(opts) ]).process(input, { 25 | syntax: require('postcss-wee-syntax') 26 | }) 27 | .then((result) => { 28 | expect(result.css).to.equal(expected); 29 | expect(result.warnings().length).to.equal(errors); 30 | }); 31 | } 32 | 33 | describe('Declarations', () => { 34 | it('should resolve values', () => { 35 | return process( 36 | `$size: 10px; 37 | a { 38 | width: $size; 39 | height: $size; 40 | }`, 41 | `a { 42 | width: 10px; 43 | height: 10px; 44 | }` 45 | ); 46 | }); 47 | 48 | it('should resolve nested values', () => { 49 | return process( 50 | `$size: 10px; 51 | .block { 52 | &__elem { 53 | width: $size; 54 | } 55 | height: $size; 56 | }`, 57 | `.block { 58 | &__elem { 59 | width: 10px; 60 | } 61 | height: 10px; 62 | }` 63 | ); 64 | }); 65 | 66 | it('should resolve multiple variables in the same declaration', () => { 67 | return process( 68 | `$fontStyle: italic; 69 | $fontSize: 2rem; 70 | $fontWeight: bold; 71 | a { 72 | font: $fontStyle $fontSize $fontWeight; 73 | }`, 74 | `a { 75 | font: italic 2rem bold; 76 | }` 77 | ); 78 | }); 79 | 80 | it('should resolve variable properties', () => { 81 | return process( 82 | `$decl: color; 83 | .block { 84 | $(decl): blue; 85 | }`, 86 | `.block { 87 | color: blue; 88 | }` 89 | ); 90 | }); 91 | 92 | it('should resolve variables as part of string values', () => { 93 | return process( 94 | `$path: /img/icons; 95 | .block { 96 | background-image: url('$(path)/share.svg'); 97 | }`, 98 | `.block { 99 | background-image: url('/img/icons/share.svg'); 100 | }` 101 | ); 102 | }); 103 | 104 | it('should resolve variables with variables as values', () => { 105 | return process( 106 | `$baseDir: /img; 107 | $path: $(baseDir)/share.png; 108 | body { 109 | background-image: url('$(path)'); 110 | }`, 111 | `body { 112 | background-image: url('/img/share.png'); 113 | }` 114 | ); 115 | }); 116 | }); 117 | 118 | describe('Variable scope', () => { 119 | it('should be by block', () => { 120 | return process( 121 | `$size: 10px; 122 | .block { 123 | &__elem { 124 | $size: 20px; 125 | width: $size; 126 | } 127 | height: $size; 128 | }`, 129 | `.block { 130 | &__elem { 131 | width: 20px; 132 | } 133 | height: 10px; 134 | }` 135 | ); 136 | }); 137 | 138 | it('should not be visible to an outside block', () => { 139 | return processFail( 140 | `.block { 141 | &__elem { 142 | $size: 20px; 143 | width: $size; 144 | } 145 | height: $size; 146 | }`, 147 | `.block { 148 | &__elem { 149 | width: 20px; 150 | } 151 | height: $size; 152 | }` 153 | ); 154 | }); 155 | 156 | it('should resolve globals passed as plugin options', () => { 157 | return process( 158 | `a { 159 | color: $color; 160 | font-size: $fontSize; 161 | }`, 162 | `a { 163 | color: blue; 164 | font-size: 20px; 165 | }`, 166 | { 167 | globals: { 168 | color: 'blue', 169 | fontSize: '20px' 170 | } 171 | } 172 | ); 173 | }); 174 | 175 | it('should resolve nested global properties with dot notation', () => { 176 | return process( 177 | `.block { 178 | color: $colors.primary; 179 | &__elem { 180 | $colors: blue; 181 | width: $components.elem.width; 182 | &.-modifier { 183 | background-image: url('/img/icon.png'); 184 | color: $colors; 185 | width: $components.elem.modified.width; 186 | } 187 | } 188 | } 189 | .block2 { 190 | color: $colors.primary; 191 | }`, 192 | `.block { 193 | color: #fff; 194 | &__elem { 195 | width: 10px; 196 | &.-modifier { 197 | background-image: url('/img/icon.png'); 198 | color: blue; 199 | width: 20px; 200 | } 201 | } 202 | } 203 | .block2 { 204 | color: #fff; 205 | }`, 206 | { 207 | globals: { 208 | colors: { 209 | primary: '#fff' 210 | }, 211 | components: { 212 | elem: { 213 | width: '10px', 214 | modified: { 215 | width: '20px' 216 | } 217 | } 218 | } 219 | } 220 | } 221 | ); 222 | }); 223 | }); 224 | 225 | describe('Variable lists', () => { 226 | it('should be resolved with comma and space separating arguments', () => { 227 | return process( 228 | `$fontStyle: italic; 229 | $fontSize: 2rem; 230 | $fontFamily: "Open Sans", Arial, sans-serif; 231 | a { 232 | font: $fontStyle $fontSize $fontFamily; 233 | }`, 234 | `a { 235 | font: italic 2rem "Open Sans", Arial, sans-serif; 236 | }` 237 | ); 238 | }); 239 | }); 240 | 241 | describe('Rules', () => { 242 | it('should resolve variable selectors', () => { 243 | return process( 244 | `$sel: .block; 245 | $(sel) { 246 | color: blue; 247 | }`, 248 | `.block { 249 | color: blue; 250 | }` 251 | ); 252 | }); 253 | 254 | it('should resolve nested variable selectors', () => { 255 | return process( 256 | `$sel: elem; 257 | .block { 258 | &__$(sel) { 259 | color: blue; 260 | } 261 | }`, 262 | `.block { 263 | &__elem { 264 | color: blue; 265 | } 266 | }` 267 | ); 268 | }); 269 | }); 270 | 271 | describe('At-Rules', () => { 272 | it('should resolve params', () => { 273 | return process( 274 | `$condition: min-width; 275 | $size: 743px; 276 | @media ($condition: $size) { 277 | font-size: 20rem; 278 | }`, 279 | `@media (min-width: 743px) { 280 | font-size: 20rem; 281 | }` 282 | ); 283 | }); 284 | 285 | // TODO: Investigate why quotes are stripped from @import url('/$path/screen.css') 286 | // TODO: Not this plugin (see test below). Looks to be postcss itself. 287 | it('should resolve @import path', () => { 288 | return process( 289 | `$path: ./path/to; 290 | @import '$(path)/style.css'`, 291 | `@import './path/to/style.css'` 292 | ); 293 | }); 294 | 295 | // TODO: Finish @rule tests (font-face, supports, etc) 296 | }); 297 | 298 | describe('Mixins', () => { 299 | it('should resolve params passed into mixins (postcss-js-mixins)', () => { 300 | return processMixins( 301 | `$color: #000; 302 | .block { 303 | mixin($color); 304 | mixin($colors.primary); 305 | }`, 306 | `.block { 307 | mixin(#000); 308 | mixin(#fff); 309 | }`, 310 | { 311 | globals: { 312 | colors: { 313 | primary: '#fff' 314 | } 315 | } 316 | } 317 | ); 318 | }); 319 | 320 | it('should resolve mixture of params passed into mixin', () => { 321 | return processMixins( 322 | `$color: #000; 323 | .block { 324 | mixin($color, url('string.png'), 10, 30px, $colors.primary); 325 | }`, 326 | `.block { 327 | mixin(#000, url('string.png'), 10, 30px, #fff); 328 | }`, 329 | { 330 | globals: { 331 | colors: { 332 | primary: '#fff' 333 | } 334 | } 335 | } 336 | ); 337 | }); 338 | 339 | it('should resolve key: value params passed into mixin', () => { 340 | return processMixins( 341 | `$color: #000; 342 | .block { 343 | mixin(color: $color, 10, 30px); 344 | }`, 345 | `.block { 346 | mixin(10, 30px, color: #000); 347 | }` 348 | ); 349 | }); 350 | }); 351 | 352 | describe('Registering and deferring variables', () => { 353 | it('should return function', () => { 354 | expect(typeof register()).to.equal('function'); 355 | }); 356 | 357 | it('should return evaluated object when executed', () => { 358 | let vars = { 359 | prop1: 'test', 360 | prop2: '$prop1' 361 | }, 362 | results = register(vars)(); 363 | 364 | expect(JSON.stringify(results)).to.equal('{"prop1":"test","prop2":"test"}'); 365 | }); 366 | 367 | it('should evaluate dot notated variable references', () => { 368 | let vars = { 369 | colors: { 370 | primary: '#000' 371 | }, 372 | input: { 373 | color: '$colors.primary' 374 | } 375 | }, 376 | results = register(vars)(); 377 | 378 | expect(results.input.color).to.equal('#000'); 379 | }); 380 | 381 | it('should be able to override properties that are referenced by other properties', () => { 382 | let vars = { 383 | colors: { 384 | primary: '#000' 385 | }, 386 | input: { 387 | color: '$colors.primary' 388 | } 389 | }; 390 | 391 | // Override variable 392 | vars.colors.primary = '#eee'; 393 | 394 | let results = register(vars)(); 395 | 396 | expect(results.input.color).to.equal('#eee'); 397 | expect(results.colors.primary).to.equal('#eee'); 398 | }); 399 | 400 | it('should be able to defer property function execution', () => { 401 | let colorFn = (val) => { 402 | return val; 403 | }, 404 | vars = { 405 | colors: { 406 | primary: '#fff' 407 | }, 408 | input: { 409 | color: defer(colorFn, ['$colors.primary']) 410 | } 411 | }; 412 | 413 | // Make overriding change that affects function outcome 414 | vars.colors.primary = '#000'; 415 | 416 | let results = register(vars)(); 417 | 418 | expect(results.input.color).to.equal('#000'); 419 | }); 420 | }); 421 | 422 | describe('Helpers: icon', () => { 423 | it('should escape and wrap icon in quotes', () => { 424 | expect(icon('e803')).to.equal("'\\e803'"); 425 | expect(icon("e803")).to.equal("'\\e803'"); 426 | }); 427 | }); --------------------------------------------------------------------------------