├── .gitattributes ├── .gitignore ├── .npmignore ├── .eslintrc.json ├── .github └── workflows │ └── npm-publish.yml ├── package.json ├── LICENSE ├── README.md ├── index.js └── test └── test.js /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | node_modules/ 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | .github/workflows/ 3 | node_modules/ 4 | npm-debug.log 5 | .eslintrc.json 6 | test/ 7 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "commonjs": true, 5 | "es2021": true, 6 | "jest": true 7 | }, 8 | "extends": "standard", 9 | "overrides": [ 10 | ], 11 | "parserOptions": { 12 | "ecmaVersion": "latest" 13 | }, 14 | "rules": { 15 | "comma-dangle": [2, "always-multiline"], 16 | "space-before-function-paren": [2, "never"], 17 | "semi": [2, "always"] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - '*' 5 | 6 | jobs: 7 | publish: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3 11 | - uses: actions/setup-node@v3 12 | with: 13 | node-version: "18" 14 | - run: npm install 15 | - run: npm run lint 16 | - run: npm test 17 | - uses: JS-DevTools/npm-publish@v2 18 | with: 19 | token: ${{ secrets.NPM_TOKEN }} 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "css-color-extractor", 3 | "version": "1.1.4", 4 | "description": "Extract from colors from CSS.", 5 | "main": "index.js", 6 | "keywords": [ 7 | "css", 8 | "color" 9 | ], 10 | "author": "Rob Sanchez ", 11 | "license": "MIT", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/rsanchez/css-color-extractor.git" 15 | }, 16 | "bugs": { 17 | "url": "https://github.com/rsanchez/css-color-extractor/issues" 18 | }, 19 | "homepage": "https://github.com/rsanchez/css-color-extractor", 20 | "dependencies": { 21 | "color": "^4.2.3", 22 | "postcss": ">=5.0.2" 23 | }, 24 | "scripts": { 25 | "lint": "eslint index.js", 26 | "fix": "eslint index.js --fix", 27 | "test": "jest" 28 | }, 29 | "devDependencies": { 30 | "eslint": "^8.41.0", 31 | "eslint-config-standard": "^17.1.0", 32 | "eslint-plugin-import": "^2.27.5", 33 | "eslint-plugin-n": "^16.0.0", 34 | "eslint-plugin-promise": "^6.1.1", 35 | "jest": "^29.5.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright 2015 Rob Sanchez 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 | # CSS Color Extractor 2 | 3 | Extract colors (named, hex, rgb, rgba, hsl, and hsla) from CSS. 4 | 5 | This tool is useful if you are re-skinning a site with a new color scheme and need a starting point for a new stylesheet. 6 | 7 | Powers http://www.css-color-extractor.com. 8 | 9 | ```css 10 | .foo { 11 | color: red; 12 | border: 1px solid #ab560f; 13 | font-size: 16px; 14 | background-image: linear-gradient(to-bottom, red, blue); 15 | } 16 | 17 | .bar { 18 | color: rgba(0, 128, 255, 0.5); 19 | } 20 | 21 | .baz { 22 | display: block; 23 | } 24 | ``` 25 | 26 | ``` 27 | red 28 | #ab560f 29 | blue 30 | rgba(0, 128, 255, 0.5) 31 | ``` 32 | 33 | This module looks at the following CSS properties for colors: 34 | 35 | * `color` 36 | * `background` 37 | * `background-color` 38 | * `background-image` 39 | * `border` 40 | * `border-top` 41 | * `border-right` 42 | * `border-bottom` 43 | * `border-left` 44 | * `border-color` 45 | * `border-top-color` 46 | * `border-right-color` 47 | * `border-bottom-color` 48 | * `border-left-color` 49 | * `outline` 50 | * `outline-color` 51 | * `text-decoration` 52 | * `text-decoration-color` 53 | * `text-shadow` 54 | * `box-shadow` 55 | * `fill` 56 | * `stroke` 57 | * `stop-color` 58 | * `flood-color` 59 | * `lighting-color` 60 | 61 | ## Installation 62 | 63 | [![NPM version](https://badge.fury.io/js/css-color-extractor.svg)](https://www.npmjs.org/package/css-color-extractor) 64 | 65 | [Use npm](https://www.npmjs.org/doc/cli/npm-install.html). 66 | 67 | ``` 68 | npm install css-color-extractor 69 | ``` 70 | 71 | ## Usage 72 | 73 | ```javascript 74 | var extractor = require('css-color-extractor'); 75 | 76 | var options = { 77 | withoutGrey: false, // set to true to remove rules that only have grey colors 78 | withoutMonochrome: false, // set to true to remove rules that only have grey, black, or white colors 79 | colorFormat: null, // transform colors to one of the following formats: hexString, hexaString, rgbString, percentString, hslString, hwbString, or keyword 80 | allColors: false, // set to true to get all colors instead of unique colors 81 | sort: null, // set to "hue" to sort colors in order of hue, or to "frequency" to sort colors by how many times they appear in the css 82 | }; 83 | 84 | // extract from a full stylesheet 85 | extractor.fromCss('a { color: red; } p { color: blue; }'); 86 | // => ['red', 'blue'] 87 | 88 | // extract from a string 89 | extractor.fromString('1px solid blue'); 90 | // => ['blue'] 91 | 92 | // extract from a declaration 93 | extractor.fromDecl({ prop: 'color', value: '1px solid blue' }); 94 | // => ['blue'] 95 | ``` 96 | 97 | ## CLI 98 | 99 | Install the CLI tool: 100 | 101 | ``` 102 | npm install -g css-color-extractor-cli 103 | ``` 104 | 105 | Extract colors as a list to stdout: 106 | 107 | ``` 108 | css-color-extractor input.css 109 | ``` 110 | 111 | Extract colors from stdin: 112 | 113 | ``` 114 | cat input.css | css-color-extractor 115 | ``` 116 | 117 | Use the `--without-grey` or `--without-monochrome` flag(s): 118 | 119 | ``` 120 | css-color-extractor input.css --without-grey 121 | ``` 122 | 123 | Use the `--color-format` option to transform color output format (`hexString`, `hexaString`, `rgbString`, `percentString`, `hslString`, `hwbString`, or `keyword`): 124 | 125 | ``` 126 | css-color-extractor input.css --color-format=hslString 127 | ``` 128 | 129 | Use the `--sort` option to sort the list of colors (`hue` or `frequency`): 130 | 131 | ``` 132 | css-color-extractor input.css --sort=hue 133 | ``` 134 | 135 | Use the `--inverse` option to remove colors from rules: 136 | 137 | ``` 138 | css-color-extractor input.css output.css --inverse 139 | ``` 140 | 141 | Extract colors to file: 142 | 143 | ``` 144 | css-color-extractor input.css output.txt 145 | ``` 146 | 147 | Extract colors to CSS format (includes original CSS selectors): 148 | 149 | ``` 150 | css-color-extractor input.css output.css 151 | 152 | # or to stdout 153 | css-color-extractor input.css --format=css 154 | ``` 155 | 156 | ```css 157 | .foo { 158 | color: red; 159 | border: 1px solid #ab560f; 160 | font-size: 16px; 161 | background-image: linear-gradient(to-bottom, red, blue); 162 | } 163 | 164 | .bar { 165 | color: rgba(0, 128, 255, 0.5); 166 | } 167 | 168 | .baz { 169 | display: block; 170 | } 171 | ``` 172 | 173 | Yields: 174 | ```css 175 | .foo { 176 | color: red; 177 | border-color: #ab560f; 178 | background-image: linear-gradient(to-bottom, red, blue); 179 | } 180 | 181 | .bar { 182 | color: rgba(0, 128, 255, 0.5); 183 | } 184 | ``` 185 | 186 | Extract colors to JSON format: 187 | 188 | ``` 189 | css-color-extractor input.css output.json 190 | 191 | # or to stdout 192 | css-color-extractor input.css --format=json 193 | ``` 194 | 195 | ```css 196 | .foo { 197 | color: red; 198 | border: 1px solid #ab560f; 199 | font-size: 16px; 200 | background-image: linear-gradient(to-bottom, red, blue); 201 | } 202 | 203 | .bar { 204 | color: rgba(0, 128, 255, 0.5); 205 | } 206 | 207 | .baz { 208 | display: block; 209 | } 210 | ``` 211 | 212 | Yields: 213 | ```js 214 | ["red","#ab560f","blue","rgba(0, 128, 255, 0.5)"] 215 | ``` 216 | 217 | Extract colors to HTML format (page of color swatches): 218 | 219 | ``` 220 | css-color-extractor input.css output.html 221 | 222 | # or to stdout 223 | css-color-extractor input.css --format=html 224 | ``` 225 | 226 | ```css 227 | .foo { 228 | color: yellow; 229 | border: 1px solid #ab560f; 230 | font-size: 16px; 231 | background-image: linear-gradient(to-bottom, red, blue); 232 | } 233 | 234 | .bar { 235 | color: rgba(0, 128, 255, 0.5); 236 | } 237 | 238 | .baz { 239 | display: block; 240 | } 241 | ``` 242 | 243 | Yields: 244 | 245 | ```html 246 | 247 | 248 | 249 | Colors 250 | 251 | 252 |
253 |
    254 |
  • yellow
  • 255 |
  • #ab560f
  • 256 |
  • rgba(0, 128, 255, 0.5)
  • 257 |
  • blue
  • 258 |
259 |
260 | 261 | 262 | ``` 263 | 264 | ## License 265 | 266 | Copyright (c) 2015 [Rob Sanchez](https://github.com/rsanchez) 267 | 268 | Licensed under [the MIT License](./LICENSE). 269 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const postcss = require('postcss'); 4 | const Color = require('color'); 5 | 6 | /** 7 | * @typedef Options 8 | * @property {boolean} withoutGrey 9 | * @property {boolean} withoutMonochrome 10 | * @property {boolean} allColors 11 | * @property {string|null} colorFormat 12 | * @property {"hue"|"frequency"|null} sort 13 | */ 14 | 15 | function CssColorExtractor() { 16 | const propertiesWithColors = [ 17 | 'color', 18 | 'background', 19 | 'background-color', 20 | 'background-image', 21 | 'border', 22 | 'border-top', 23 | 'border-right', 24 | 'border-bottom', 25 | 'border-left', 26 | 'border-color', 27 | 'border-top-color', 28 | 'border-right-color', 29 | 'border-bottom-color', 30 | 'border-left-color', 31 | 'outline', 32 | 'outline-color', 33 | 'text-decoration', 34 | 'text-decoration-color', 35 | 'text-shadow', 36 | 'box-shadow', 37 | 'fill', 38 | 'stroke', 39 | 'stop-color', 40 | 'flood-color', 41 | 'lighting-color', 42 | ]; 43 | const colorFormats = [ 44 | 'hexString', 45 | 'hexaString', 46 | 'rgbString', 47 | 'percentString', 48 | 'hslString', 49 | 'hwbString', 50 | 'keyword', 51 | ]; 52 | 53 | /** 54 | * @param {string} property 55 | * @returns {boolean} 56 | */ 57 | function doesPropertyAllowColor(property) { 58 | return propertiesWithColors.indexOf(property) > -1; 59 | } 60 | 61 | /** 62 | * @param {Color} color 63 | * @returns {boolean} 64 | */ 65 | function isColorGrey(color) { 66 | const red = color.red(); 67 | 68 | // we only need to test one color 69 | // since isColorMonochrome assures that all 70 | // three rgb colors are equal 71 | 72 | return isColorMonochrome(color) && red > 0 && red < 255; 73 | } 74 | 75 | /** 76 | * @param {Color} color 77 | * @returns {boolean} 78 | */ 79 | function isColorMonochrome(color) { 80 | const hsl = color.hsl().object(); 81 | 82 | return hsl.h === 0 && hsl.s === 0; 83 | } 84 | 85 | /** 86 | * @param {string} format 87 | * @returns {boolean} 88 | */ 89 | function isValidColorFormat(format) { 90 | return colorFormats.indexOf(format) > -1; 91 | } 92 | 93 | /** 94 | * @param {string} value the original string color value extracted from css 95 | * @param {Options} options 96 | * @returns {string} 97 | */ 98 | function serializeColor(value, options) { 99 | if (!options.colorFormat || !isValidColorFormat(options.colorFormat)) { 100 | return value; 101 | } 102 | const color = new Color(value); 103 | let colorFormat = options.colorFormat; 104 | if (!color[options.colorFormat]) { 105 | colorFormat = colorFormat.replace(/String$/, ''); 106 | } 107 | const formatted = color[colorFormat](); 108 | if (typeof formatted === 'string') { 109 | return formatted; 110 | } 111 | return formatted.string(); 112 | } 113 | 114 | /** 115 | * @param {Partial} options 116 | * @returns {Options} 117 | */ 118 | function defaultOptions(options) { 119 | return Object.assign({ 120 | withoutGrey: false, 121 | withoutMonochrome: false, 122 | allColors: false, 123 | colorFormat: null, 124 | sort: null, 125 | }, options); 126 | } 127 | 128 | /** 129 | * @param {string[]} colors 130 | * @param {Partial} sortOptions 131 | * @returns {string[]} 132 | */ 133 | function sortColors(colors, sortOptions) { 134 | const options = defaultOptions(sortOptions); 135 | colors = colors.map((value) => serializeColor(value, options)); 136 | if (options.sort === 'hue') { 137 | colors = colors.sort((a, b) => { 138 | return new Color(a).hue() - new Color(b).hue(); 139 | }); 140 | } 141 | if (options.sort === 'frequency') { 142 | const frequencyMap = new Map(); 143 | colors.forEach((value) => { 144 | frequencyMap.set(value, (frequencyMap.get(value) || 0) + 1); 145 | }); 146 | colors = colors.sort((a, b) => { 147 | return frequencyMap.get(b) - frequencyMap.get(a); 148 | }); 149 | } 150 | return options.allColors ? colors : unique(colors); 151 | } 152 | 153 | /** 154 | * @param {string[]} items 155 | * @returns {string[]} 156 | */ 157 | function unique(items) { 158 | return [...new Set(items)]; 159 | } 160 | 161 | /** 162 | * @param {string} string 163 | * @param {Partial} options 164 | * @returns {string[]} 165 | */ 166 | function extractColorsFromString(string, options) { 167 | /** @type {string[]} */ 168 | let colors = []; 169 | 170 | const { 171 | withoutMonochrome, 172 | withoutGrey, 173 | colorFormat, 174 | } = defaultOptions(options); 175 | 176 | postcss.list.comma(string).forEach(function(items) { 177 | postcss.list.space(items).forEach(function(item) { 178 | const regex = new RegExp( 179 | '^' + 180 | '(-webkit-|-moz-|-o-)?' + 181 | '(repeating-)?' + 182 | '(radial|linear)-gradient\\((.*?)\\)' + 183 | '$', 184 | ); 185 | 186 | const match = item.match(regex); 187 | 188 | if (match) { 189 | colors = colors.concat.apply( 190 | colors, 191 | postcss.list.comma(match[4]).map(postcss.list.space), 192 | ); 193 | } else { 194 | colors.push(item); 195 | } 196 | }); 197 | }); 198 | 199 | return colors.filter((value) => { 200 | let color; 201 | 202 | // check if it is a valid color 203 | try { 204 | color = new Color(value); 205 | } catch (e) { 206 | return false; 207 | } 208 | 209 | if (withoutMonochrome && isColorMonochrome(color)) { 210 | return false; 211 | } 212 | 213 | if (withoutGrey && isColorGrey(color)) { 214 | return false; 215 | } 216 | 217 | if (colorFormat === 'keyword') { 218 | // convert back to rgb to see if keyword is an exact match 219 | const keywordColor = new Color(color.keyword()); 220 | if (keywordColor.rgbNumber() !== color.rgbNumber()) { 221 | return false; 222 | } 223 | } 224 | 225 | return true; 226 | }); 227 | } 228 | 229 | /** 230 | * @param {postcss.Declaration} decl 231 | * @param {Partial} options 232 | * @returns {string[]} 233 | */ 234 | function extractColorsFromDecl(decl, options) { 235 | if (!doesPropertyAllowColor(decl.prop)) { 236 | return []; 237 | } 238 | 239 | return extractColorsFromString(decl.value, options); 240 | } 241 | 242 | /** 243 | * @param {string} css 244 | * @param {Partial} options 245 | * @returns {string[]} 246 | */ 247 | function extractColorsFromCss(css, options) { 248 | let colors = []; 249 | 250 | postcss.parse(css).walkDecls(function(decl) { 251 | colors = colors.concat(extractColorsFromDecl(decl, options)); 252 | }); 253 | 254 | return colors; 255 | } 256 | 257 | /** @type {(decl: postcss.Declaration, options: Partial) => string[]} */ 258 | this.fromDecl = (decl, options) => sortColors(extractColorsFromDecl(decl, options), options); 259 | 260 | /** @type {(css: string, options: Partial) => string[]} */ 261 | this.fromCss = (css, options) => sortColors(extractColorsFromCss(css, options), options); 262 | 263 | /** @type {(string: string, options: Partial) => string[]} */ 264 | this.fromString = (string, options) => sortColors(extractColorsFromString(string, options), options); 265 | } 266 | 267 | module.exports = new CssColorExtractor(); 268 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | const extractor = require('../'); 2 | const { describe, expect, it } = require('@jest/globals'); 3 | 4 | const test = function(input, output, opts, done) { 5 | const result = extractor.fromCss(input, opts); 6 | expect(result).toEqual(output); 7 | done(); 8 | }; 9 | 10 | describe('postcss-colors-only', function() { 11 | it('should extract named color.', function(done) { 12 | test( 13 | 'a { color: red; } p { display: block; }', 14 | ['red'], 15 | {}, 16 | done, 17 | ); 18 | }); 19 | 20 | it('should extract three-letter hex color.', function(done) { 21 | test( 22 | 'a { color: #123; } p { display: block; }', 23 | ['#123'], 24 | {}, 25 | done, 26 | ); 27 | }); 28 | 29 | it('should extract six-letter hex color.', function(done) { 30 | test( 31 | 'a { color: #123123; } p { display: block; }', 32 | ['#123123'], 33 | {}, 34 | done, 35 | ); 36 | }); 37 | 38 | it('should extract rgb color.', function(done) { 39 | test( 40 | 'a { color: rgb(1, 2, 3); } p { display: block; }', 41 | ['rgb(1, 2, 3)'], 42 | {}, 43 | done, 44 | ); 45 | }); 46 | 47 | it('should extract rgba color.', function(done) { 48 | test( 49 | 'a { color: rgba(1, 2, 3, 0.5); } p { display: block; }', 50 | ['rgba(1, 2, 3, 0.5)'], 51 | {}, 52 | done, 53 | ); 54 | }); 55 | 56 | it('should extract hsl color.', function(done) { 57 | test( 58 | 'a { color: hsl(1, 2%, 3%); } p { display: block; }', 59 | ['hsl(1, 2%, 3%)'], 60 | {}, 61 | done, 62 | ); 63 | }); 64 | 65 | it('should extract hsla color.', function(done) { 66 | test( 67 | 'a { color: hsla(1, 2%, 3%, 0.5); } p { display: block; }', 68 | ['hsla(1, 2%, 3%, 0.5)'], 69 | {}, 70 | done, 71 | ); 72 | }); 73 | 74 | it('should extract background-color.', function(done) { 75 | test( 76 | 'a { background-color: red; } p { display: block; }', 77 | ['red'], 78 | {}, 79 | done, 80 | ); 81 | }); 82 | 83 | it('should extract border-color.', function(done) { 84 | test( 85 | 'a { border-color: red; } p { display: block; }', 86 | ['red'], 87 | {}, 88 | done, 89 | ); 90 | }); 91 | 92 | it('should extract border-top-color.', function(done) { 93 | test( 94 | 'a { border-top-color: red; } p { display: block; }', 95 | ['red'], 96 | {}, 97 | done, 98 | ); 99 | }); 100 | 101 | it('should extract border-right-color.', function(done) { 102 | test( 103 | 'a { border-right-color: red; } p { display: block; }', 104 | ['red'], 105 | {}, 106 | done, 107 | ); 108 | }); 109 | 110 | it('should extract border-bottom-color.', function(done) { 111 | test( 112 | 'a { border-bottom-color: red; } p { display: block; }', 113 | ['red'], 114 | {}, 115 | done, 116 | ); 117 | }); 118 | 119 | it('should extract border-left-color.', function(done) { 120 | test( 121 | 'a { border-left-color: red; } p { display: block; }', 122 | ['red'], 123 | {}, 124 | done, 125 | ); 126 | }); 127 | 128 | it('should extract background-image.', function(done) { 129 | test( 130 | 'a { background-image: linear-gradient(to bottom, red, blue); } ' + 131 | 'p { display: block; }', 132 | ['red', 'blue'], 133 | {}, 134 | done, 135 | ); 136 | }); 137 | 138 | it('should extract linear-gradient.', function(done) { 139 | test( 140 | 'a { background: linear-gradient(90deg, rgba(2,0,36,1) 0%, rgba(9,9,121,1) 35%, rgba(0,212,255,1) 100%) }', 141 | ['rgba(2,0,36,1)', 'rgba(9,9,121,1)', 'rgba(0,212,255,1)'], 142 | {}, 143 | done, 144 | ); 145 | }); 146 | 147 | it('should extract outline-color.', function(done) { 148 | test( 149 | 'a { outline-color: red; } p { display: block; }', 150 | ['red'], 151 | {}, 152 | done, 153 | ); 154 | }); 155 | 156 | it('should extract text-decoration.', function(done) { 157 | test( 158 | 'a { text-decoration: overline blue; } p { display: block; }', 159 | ['blue'], 160 | {}, 161 | done, 162 | ); 163 | }); 164 | 165 | it('should extract text-decoration-color.', function(done) { 166 | test( 167 | 'a { text-decoration-color: black; } p { display: block; }', 168 | ['black'], 169 | {}, 170 | done, 171 | ); 172 | }); 173 | 174 | it('should extract text-shadow.', function(done) { 175 | test( 176 | 'a { text-shadow: 1px 1px 2px black; } p { display: block; }', 177 | ['black'], 178 | {}, 179 | done, 180 | ); 181 | }); 182 | 183 | it('should extract box-shadow.', function(done) { 184 | test( 185 | 'a { box-shadow: 10px 5px 5px black; } p { display: block; }', 186 | ['black'], 187 | {}, 188 | done, 189 | ); 190 | }); 191 | 192 | it('should extract complex background.', function(done) { 193 | test( 194 | 'a { background: red url(../foo.jpg) no-repeat center center; } ' + 195 | 'p { display: block; }', 196 | ['red'], 197 | {}, 198 | done, 199 | ); 200 | }); 201 | 202 | it('should extract rule with multiple colors.', function(done) { 203 | test( 204 | 'a { background: red url(../foo.jpg), blue url(../bar.jpg); } ' + 205 | 'p { display: block; }', 206 | ['red', 'blue'], 207 | {}, 208 | done, 209 | ); 210 | }); 211 | 212 | it('should extract outline.', function(done) { 213 | test( 214 | 'a { outline: 1px solid white; } p { display: block; }', 215 | ['white'], 216 | {}, 217 | done, 218 | ); 219 | }); 220 | 221 | it('should extract border.', function(done) { 222 | test( 223 | 'a { border: 1px solid white; } p { display: block; }', 224 | ['white'], 225 | {}, 226 | done, 227 | ); 228 | }); 229 | 230 | it('should not extract border 0.', function(done) { 231 | test( 232 | 'a { border: 0; } p { display: block; }', 233 | [], 234 | {}, 235 | done, 236 | ); 237 | }); 238 | 239 | it('should extract border-top.', function(done) { 240 | test( 241 | 'a { border-top: 1px solid white; } p { display: block; }', 242 | ['white'], 243 | {}, 244 | done, 245 | ); 246 | }); 247 | 248 | it('should extract border-right.', function(done) { 249 | test( 250 | 'a { border-right: 1px solid white; } p { display: block; }', 251 | ['white'], 252 | {}, 253 | done, 254 | ); 255 | }); 256 | 257 | it('should extract border-bottom.', function(done) { 258 | test( 259 | 'a { border-bottom: 1px solid white; } p { display: block; }', 260 | ['white'], 261 | {}, 262 | done, 263 | ); 264 | }); 265 | 266 | it('should extract border-left.', function(done) { 267 | test( 268 | 'a { border-left: 1px solid white; } p { display: block; }', 269 | ['white'], 270 | {}, 271 | done, 272 | ); 273 | }); 274 | 275 | it('should extract fill.', function(done) { 276 | test( 277 | 'svg { fill: red; } p { display: block; }', 278 | ['red'], 279 | {}, 280 | done, 281 | ); 282 | }); 283 | 284 | it('should omit grey, but not black or white.', function(done) { 285 | test( 286 | 'a { color: red; } p { color: grey; } h1 { color: black; }', 287 | ['red', 'black'], 288 | { withoutGrey: true }, 289 | done, 290 | ); 291 | }); 292 | 293 | it('should omit grey', function(done) { 294 | test( 295 | 'a { color: red; } p { color: grey; }', 296 | ['red'], 297 | { withoutGrey: true }, 298 | done, 299 | ); 300 | }); 301 | 302 | it('should omit gray', function(done) { 303 | test( 304 | 'a { color: red; } p { color: gray; }', 305 | ['red'], 306 | { withoutGrey: true }, 307 | done, 308 | ); 309 | }); 310 | 311 | it('should omit lightgrey', function(done) { 312 | test( 313 | 'a { color: red; } p { color: lightgrey; }', 314 | ['red'], 315 | { withoutGrey: true }, 316 | done, 317 | ); 318 | }); 319 | 320 | it('should omit lightgray', function(done) { 321 | test( 322 | 'a { color: red; } p { color: lightgray; }', 323 | ['red'], 324 | { withoutGrey: true }, 325 | done, 326 | ); 327 | }); 328 | 329 | it('should omit dimgrey', function(done) { 330 | test( 331 | 'a { color: red; } p { color: dimgrey; }', 332 | ['red'], 333 | { withoutGrey: true }, 334 | done, 335 | ); 336 | }); 337 | 338 | it('should omit dimgray', function(done) { 339 | test( 340 | 'a { color: red; } p { color: dimgray; }', 341 | ['red'], 342 | { withoutGrey: true }, 343 | done, 344 | ); 345 | }); 346 | 347 | it('should omit darkgrey', function(done) { 348 | test( 349 | 'a { color: red; } p { color: darkgrey; }', 350 | ['red'], 351 | { withoutGrey: true }, 352 | done, 353 | ); 354 | }); 355 | 356 | it('should omit darkgray', function(done) { 357 | test( 358 | 'a { color: red; } p { color: darkgray; }', 359 | ['red'], 360 | { withoutGrey: true }, 361 | done, 362 | ); 363 | }); 364 | 365 | it('should omit three-letter hex grey', function(done) { 366 | test( 367 | 'a { color: red; } p { color: #111; }', 368 | ['red'], 369 | { withoutGrey: true }, 370 | done, 371 | ); 372 | }); 373 | 374 | it('should omit six-letter hex grey', function(done) { 375 | test( 376 | 'a { color: red; } p { color: #121212; border-color: #111111; }', 377 | ['red'], 378 | { withoutGrey: true }, 379 | done, 380 | ); 381 | }); 382 | 383 | it('should omit rgb grey', function(done) { 384 | test( 385 | 'a { color: red; } p { color: rgb(1, 1, 1); }', 386 | ['red'], 387 | { withoutGrey: true }, 388 | done, 389 | ); 390 | }); 391 | 392 | it('should omit rgba grey', function(done) { 393 | test( 394 | 'a { color: red; } p { color: rgba(1, 1, 1, 0.5); }', 395 | ['red'], 396 | { withoutGrey: true }, 397 | done, 398 | ); 399 | }); 400 | 401 | it('should omit hsl grey', function(done) { 402 | test( 403 | 'a { color: red; } p { color: hsl(0, 0, 1%); }', 404 | ['red'], 405 | { withoutGrey: true }, 406 | done, 407 | ); 408 | }); 409 | 410 | it('should omit hsla grey', function(done) { 411 | test( 412 | 'a { color: red; } p { color: hsla(0, 0, 1%, 0.5); }', 413 | ['red'], 414 | { withoutGrey: true }, 415 | done, 416 | ); 417 | }); 418 | 419 | it('should omit grey', function(done) { 420 | test( 421 | 'a { color: red; } p { color: grey; }', 422 | ['red'], 423 | { withoutMonochrome: true }, 424 | done, 425 | ); 426 | }); 427 | 428 | it('should omit white', function(done) { 429 | test( 430 | 'a { color: red; } p { color: white; }', 431 | ['red'], 432 | { withoutMonochrome: true }, 433 | done, 434 | ); 435 | }); 436 | 437 | it('should omit three-letter hex white', function(done) { 438 | test( 439 | 'a { color: red; } p { color: #fFf; }', 440 | ['red'], 441 | { withoutMonochrome: true }, 442 | done, 443 | ); 444 | }); 445 | 446 | it('should omit six-letter hex white', function(done) { 447 | test( 448 | 'a { color: red; } p { color: #fFfFfF; }', 449 | ['red'], 450 | { withoutMonochrome: true }, 451 | done, 452 | ); 453 | }); 454 | 455 | it('should omit rgb white', function(done) { 456 | test( 457 | 'a { color: red; } p { color: rgba(255, 255, 255); }', 458 | ['red'], 459 | { withoutMonochrome: true }, 460 | done, 461 | ); 462 | }); 463 | 464 | it('should omit rgba white', function(done) { 465 | test( 466 | 'a { color: red; } p { color: rgba(255, 255, 255, 0.5); }', 467 | ['red'], 468 | { withoutMonochrome: true }, 469 | done, 470 | ); 471 | }); 472 | 473 | it('should omit hsl white', function(done) { 474 | test( 475 | 'a { color: red; } p { color: hsl(0, 0, 100%); }', 476 | ['red'], 477 | { withoutMonochrome: true }, 478 | done, 479 | ); 480 | }); 481 | 482 | it('should omit hsla white', function(done) { 483 | test( 484 | 'a { color: red; } p { color: hsla(0, 0, 100%, 0.5); }', 485 | ['red'], 486 | { withoutMonochrome: true }, 487 | done, 488 | ); 489 | }); 490 | 491 | it('should omit black', function(done) { 492 | test( 493 | 'a { color: red; } p { color: black; }', 494 | ['red'], 495 | { withoutMonochrome: true }, 496 | done, 497 | ); 498 | }); 499 | 500 | it('should omit three-letter hex black', function(done) { 501 | test( 502 | 'a { color: red; } p { color: #000; }', 503 | ['red'], 504 | { withoutMonochrome: true }, 505 | done, 506 | ); 507 | }); 508 | 509 | it('should omit six-letter hex black', function(done) { 510 | test( 511 | 'a { color: red; } p { color: #000000; }', 512 | ['red'], 513 | { withoutMonochrome: true }, 514 | done, 515 | ); 516 | }); 517 | 518 | it('should omit rgb black', function(done) { 519 | test( 520 | 'a { color: red; } p { color: rgba(0, 0, 0); }', 521 | ['red'], 522 | { withoutMonochrome: true }, 523 | done, 524 | ); 525 | }); 526 | 527 | it('should omit rgba black', function(done) { 528 | test( 529 | 'a { color: red; } p { color: rgba(0, 0, 0, 0.5); }', 530 | ['red'], 531 | { withoutMonochrome: true }, 532 | done, 533 | ); 534 | }); 535 | 536 | it('should omit hsl black', function(done) { 537 | test( 538 | 'a { color: red; } p { color: hsl(0, 0, 0%); }', 539 | ['red'], 540 | { withoutMonochrome: true }, 541 | done, 542 | ); 543 | }); 544 | 545 | it('should omit hsla black', function(done) { 546 | test( 547 | 'a { color: red; } p { color: hsla(0, 0, 0%, 0.5); }', 548 | ['red'], 549 | { withoutMonochrome: true }, 550 | done, 551 | ); 552 | }); 553 | 554 | it('should read nested @media rules', function(done) { 555 | test( 556 | 'a { color: red; } @media (screen-only) { a { color: blue; } ' + 557 | 'p { display: block; } }', 558 | ['red', 'blue'], 559 | {}, 560 | done, 561 | ); 562 | }); 563 | 564 | it('should output rgbString format', function(done) { 565 | test( 566 | 'a { color: #123123; }', 567 | ['rgb(18, 49, 35)'], 568 | { colorFormat: 'rgbString' }, 569 | done, 570 | ); 571 | }); 572 | 573 | it('should output hslString format', function(done) { 574 | test( 575 | 'a { color: #00FF99; }', 576 | ['hsl(156, 100%, 50%)'], 577 | { colorFormat: 'hslString' }, 578 | done, 579 | ); 580 | }); 581 | 582 | it('should output percentString format', function(done) { 583 | test( 584 | 'a { color: #123123; }', 585 | ['rgb(7%, 19%, 14%)'], 586 | { colorFormat: 'percentString' }, 587 | done, 588 | ); 589 | }); 590 | 591 | it('should output hexString format', function(done) { 592 | test( 593 | 'a { color: rgb(255, 255, 255); }', 594 | ['#FFFFFF'], 595 | { colorFormat: 'hexString' }, 596 | done, 597 | ); 598 | }); 599 | 600 | it('should output hexaString format', function(done) { 601 | test( 602 | 'a { color: rgb(255, 255, 255); }', 603 | ['#FFFFFFFF'], 604 | { colorFormat: 'hexaString' }, 605 | done, 606 | ); 607 | }); 608 | 609 | it('should output hexaString format', function(done) { 610 | test( 611 | 'a { color: rgba(255, 255, 255, 0.3); }', 612 | ['#FFFFFF4D'], 613 | { colorFormat: 'hexaString' }, 614 | done, 615 | ); 616 | }); 617 | 618 | it('should output hexaString format', function(done) { 619 | test( 620 | 'a { color: #FFFFFF4D; }', 621 | ['#FFFFFF4D'], 622 | { colorFormat: 'hexaString' }, 623 | done, 624 | ); 625 | }); 626 | 627 | it('should output keyword format', function(done) { 628 | test( 629 | 'a { color: rgb(255, 255, 255); }', 630 | ['white'], 631 | { colorFormat: 'keyword' }, 632 | done, 633 | ); 634 | }); 635 | 636 | it('should omit non-keyword colors', function(done) { 637 | test( 638 | 'a { color: #a94c1b; }', 639 | [], 640 | { colorFormat: 'keyword' }, 641 | done, 642 | ); 643 | }); 644 | 645 | it('should return unique colors', function(done) { 646 | test( 647 | 'a { color: #123123; } p { color: #123123; } ' + 648 | 'button { background-color: #fff; }', 649 | ['#123123', '#fff'], 650 | {}, 651 | done, 652 | ); 653 | }); 654 | 655 | it('should return unique colors without options', function(done) { 656 | test( 657 | 'a { color: #123123; } p { color: #123123; } ' + 658 | 'button { background-color: #fff; }', 659 | ['#123123', '#fff'], 660 | null, 661 | done, 662 | ); 663 | }); 664 | 665 | it('should return all colors', function(done) { 666 | test( 667 | 'a { color: #123123; } p { color: #123123; } ' + 668 | 'button { background-color: #fff; }', 669 | ['#123123', '#123123', '#fff'], 670 | { allColors: true }, 671 | done, 672 | ); 673 | }); 674 | 675 | it('should sort by frequency', function(done) { 676 | test( 677 | 'a { color: #123123; } p { color: #123123; } ' + 678 | 'button { background-color: #fff; }' + 679 | 'div { background-color: #000; color: #000 }' + 680 | 'input { background-color: #000; }', 681 | ['#000', '#123123', '#fff'], 682 | { sort: 'frequency' }, 683 | done, 684 | ); 685 | }); 686 | 687 | it('should sort by hue', function(done) { 688 | test( 689 | 'a { color: #0000FF; } p { color: #FF0000; } ' + 690 | 'button { background-color: #00FF00; }', 691 | ['#FF0000', '#00FF00', '#0000FF'], 692 | { sort: 'hue' }, 693 | done, 694 | ); 695 | }); 696 | }); 697 | --------------------------------------------------------------------------------