diff --git a/bin/atomizer b/bin/atomizer index d645ddbb..43fdadba 100755 --- a/bin/atomizer +++ b/bin/atomizer @@ -13,47 +13,52 @@ process.title = 'atomizer'; var path = require('path'); var fs = require('fs'); var chalk = require('chalk'); -var atomizer = require('../src/atomizer'); -var utils = require('../src/lib/utils'); +var Atomizer = require('../src/atomizer'); var _ = require('lodash'); var content = ''; var config = {}; -var parsedConfig = {}; -var recursive = false; +var classnames = []; var params = require('minimist')(process.argv.slice(2), { - "boolean": ['rtl', 'help', 'verbose', 'R' ], + "boolean": ['rtl', 'help', 'verbose', 'R'], "string": ['n'] }); -function parseFiles (files, dir) { +var atomizer = new Atomizer({ verbose: !!params.verbose }); + +function parseFiles (files, recursive, dir) { var classNames = []; for (var i=0, iLen=files.length; i < iLen; i++) { - classNames = _.union(classNames, parseFile(files[i], dir)); + classNames = _.union(classNames, parseFile(files[i], recursive, dir)); } return classNames; } -function parseFile (file, dir) { +function parseFile (file, recursive, dir) { var classNames = [], fileContents, - filepath = dir ? path.resolve(dir, file) : path.resolve(file), - relative = path.relative(process.cwd(), filepath), + filepath, + relative, + stat; + + if (file) { + filepath = dir ? path.resolve(dir, file) : path.resolve(file); + relative = path.relative(process.cwd(), filepath); stat = fs.statSync(filepath); - if (stat.isFile()) { - console.warn('Parsing file ' + chalk.cyan(relative) + ' for Atomic CSS classes'); - fileContents = fs.readFileSync(filepath, {encoding: 'utf-8'}); - classNames = atomizer.parse(fileContents); - } else if (stat.isDirectory()) { - if (!dir || dir && recursive) { - console.warn('Inspecting directory ' + chalk.cyan(path.relative(process.cwd(), filepath))); - classNames = parseFiles(fs.readdirSync(filepath), filepath); + if (stat.isFile()) { + console.warn('Parsing file ' + chalk.cyan(relative) + ' for Atomic CSS classes'); + fileContents = fs.readFileSync(filepath, {encoding: 'utf-8'}); + classNames = atomizer.findClassNames(fileContents); + } else if (stat.isDirectory()) { + if (!dir || dir && recursive) { + console.warn('Inspecting directory ' + chalk.cyan(path.relative(process.cwd(), filepath))); + classNames = parseFiles(fs.readdirSync(filepath), filepath); + } } } - return classNames; } @@ -94,16 +99,14 @@ if (configFile) { // Generate config from parsed src files var filesToParse = params._ || []; if (filesToParse.length) { - // Recursive parsing? - recursive = !!params.R; - parsedConfig = atomizer.getConfig(parseFiles(filesToParse), config, !!params.verbose); + classnames = parseFiles(filesToParse, !!params.R); } -// Merge the static config with the generated config -config = _.merge(parsedConfig, config, utils.handleMergeArrays); +// Finalize the config +config = atomizer.getConfig(classnames, config); // Create the CSS -content = atomizer.createCSS(config, options); +content = atomizer.getCss(config, options); // Output the CSS var outfile = params.o || params.outfile; diff --git a/examples/basic-config.js b/examples/basic-config.js index 9dc8f4d9..9b454155 100644 --- a/examples/basic-config.js +++ b/examples/basic-config.js @@ -1,13 +1,42 @@ module.exports = { - // pattern - 'display': { - 'b': true + 'custom': { + uh: '79px', + primary: '#f6a1e1', + l: '$START', + r: '$END' }, - - // pattern - 'border-top': { - custom: [ - {suffix: '1', values: ['1px solid #ccc']} - ] - } + breakPoints: { + 'sm': '@media(min-width:500px)', + 'md': '@media(min-width:900px)', + 'lg': '@media(min-width:1200px)' + }, + 'classNames': [ + // normal + 'Td-u', + 'Td-u:h', + 'Td-u--sm', + // custom + 'foo>W-uh', + 'H-uh', + 'C-primary', + // rtl + 'Fl-start', + 'Fl-end', + 'Fl-l', + 'Fl-r', + // more lengthy then the previous one + 'W-1/12', + 'W-2/12', + 'W-3/12', + 'W-4/12', + 'W-5/12', + 'W-6/12', + 'W-7/12', + 'W-8/12', + 'W-9/12', + 'W-10/12', + 'W-11/12', + 'W-12/12', + 'W-1/12--sm' + ] }; \ No newline at end of file diff --git a/package.json b/package.json index ccabd158..ce0023ef 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "atomizer", - "version": "2.0.0-alpha.3", + "version": "2.0.0-beta.2", "description": "A tool for creating Atomic CSS, a collection of single purpose styling units for maximum reuse", "main": "./lib/atomic.js", "contributors": [ @@ -31,16 +31,17 @@ "absurd": "~0.3.34", "minimist": "~1.1.0", "chalk": "~1.0.0", - "lodash": "~3.5.0" + "lodash": "~3.5.0", + "xregexp": "^2.0.0" }, "devDependencies": { - "mocha": "~2.2.0", "chai": "~2.1.0", - "sinon": "~1.13.0", - "sinon-chai": "~2.7.0", + "coveralls": "~2.11.2", "istanbul": "~0.3.5", "jshint": "~2.6.0", - "coveralls": "~2.11.2" + "mocha": "~2.2.0", + "sinon": "~1.13.0", + "sinon-chai": "~2.7.0" }, "repository": { "type": "git", diff --git a/src/atomizer-ts.js b/src/atomizer-ts.js new file mode 100644 index 00000000..f479daf6 --- /dev/null +++ b/src/atomizer-ts.js @@ -0,0 +1,61 @@ +/** + * Future refactor with Atomizer with type declarations. + * Not using TypeScript until 1.5 officially comes out, + * so we can write as close to ES6 as possible (including modules). + * For now, we just declare the interfance and class to serve as + * our implementation contract. + */ +var Atomizer = (function () { + function Atomizer(rules, options) { + this.rules = rules; + this.verbose = options && options.verbose || false; + } + Atomizer.prototype.findClassNames = function (src) { + return []; + }; + Atomizer.prototype.getCss = function (classNames, config, options) { + return; + }; + Atomizer.prototype.parseClassNames = function (classNames, config) { + return {}; + }; + Atomizer.prototype.getCssFromParseTree = function (parseTree) { + return ''; + }; + Atomizer.grammar = { + 'DESC': '[^\\s]+_', + 'DIRECT': '[^\\s]+>', + 'PROP': '(W-|H-|...)', + 'SIGN': '(?:neg)?', + 'NUMBER': '[0-9]+(?:\\.[0-9]+)?', + 'UNIT': '(?:[a-zA-Z%]+)?', + 'HEX': '[0-9a-f]{3}(?:[0-9a-f]{3})?', + 'CUSTOM': '[a-zA-Z0-9%\\.]+\\b', + 'MOD': '--[a-z]+' + }; + Atomizer.regex = new RegExp([ + '\\b(', + '(?:', + Atomizer.grammar['DESC'], + '|', + Atomizer.grammar['DIRECT'], + ')?', + Atomizer.grammar['PROP'], + '(?:', + Atomizer.grammar['SIGN'], + Atomizer.grammar['NUMBER'], + Atomizer.grammar['UNIT'], + '|', + Atomizer.grammar['HEX'], + '|', + Atomizer.grammar['CUSTOM'], + ')', + '(?:', + Atomizer.grammar['MOD'], + ')?', + ')' + ].join('')); + return Atomizer; +})(); +// 1.5 +// export Atomizer; diff --git a/src/atomizer.js b/src/atomizer.js index 9267798b..d263a504 100644 --- a/src/atomizer.js +++ b/src/atomizer.js @@ -4,288 +4,722 @@ * See the accompanying LICENSE file for terms. */ +/** + * @TODO: + * - Try using immutable.js for rules. + * - don't require the entire lodash lib, just what we're using. + * - implement getConfig() so we can export a merged config. + * - validate config? maybe we need it. + * - GRAMMAR/SYNTAX needs to handle some edge cases, specifically in regards to word boundaries. + * - check how much memory this program is using, check if we could potentially run out of memory + * because of the lengthy regex. + * - replace Absurd() with something simpler, it does too much and it's slow. + */ + 'use strict'; var _ = require('lodash'); -var Absurd = require('absurd'); -var AtomicBuilder = require('./lib/AtomicBuilder.js'); var objectAssign = require('object-assign'); -var rules = require('./rules.js'); -var utils = require('./lib/utils'); +var Absurd = require('absurd'); +var XRegExp = require('xregexp').XRegExp; + +var RULES = require('./rules.js').concat(require('./helpers.js')); + +var PSEUDOS = { + ':active': ':a', + ':checked': ':c', + ':default': ':d', + ':disabled': ':di', + ':empty': ':e', + ':enabled': ':en', + ':first': ':fi', + ':first-child': ':fc', + ':first-of-type': ':fot', + ':fullscreen': ':fs', + ':focus': ':f', + ':hover': ':h', + ':indeterminate': ':ind', + ':in-range': ':ir', + ':invalid': ':inv', + ':last-child': ':lc', + ':last-of-type': ':lot', + ':left': ':l', + ':link': ':li', + ':only-child': ':oc', + ':only-of-type': ':oot', + ':optional': ':o', + ':out-of-range': ':oor', + ':read-only': ':ro', + ':read-write': ':rw', + ':required': ':req', + ':right': ':r', + ':root': ':rt', + ':scope': ':s', + ':target': ':t', + ':valid': ':va', + ':visited': ':vi' +}; +var PSEUDOS_INVERTED = _.invert(PSEUDOS); +var PSEUDO_REGEX = []; +for (var pseudo in PSEUDOS) { + PSEUDO_REGEX.push(pseudo); + PSEUDO_REGEX.push(PSEUDOS[pseudo]); +} +PSEUDO_REGEX = '(?:' + PSEUDO_REGEX.join('|') + ')'; + +// regular grammar to match valid atomic classes +var GRAMMAR = { + 'BOUNDARY' : '(?:^|\\s|"|\'|\{)', + 'PARENT' : '[^\\s:>_]+', + 'PARENT_SEP' : '[>_]', + 'FRACTION' : '(?[0-9]+)\\/(?[1-9](?:[0-9]+)?)', + 'PARAMS' : '\\((?(?:.*?,?)+)\\)', + 'SIGN' : 'neg', + 'NUMBER' : '[0-9]+(?:\\.[0-9]+)?', + 'UNIT' : '[a-zA-Z%]+', + 'HEX' : '[0-9a-f]{3}(?:[0-9a-f]{3})?', + 'IMPORTANT' : '!', + // https://regex101.com/r/mM2vT9/7 + 'NAMED' : '(\\w+(?:(?:-(?!\\-))?\\w*)*)', + 'PSEUDO' : PSEUDO_REGEX, + 'BREAKPOINT' : '--(?[a-z]+)' +}; +GRAMMAR.PARENT_SELECTOR = [ + // parent (any character that is not a space) + '(?', + GRAMMAR.PARENT, + ')', + // followed by optional pseudo class + '(?', + GRAMMAR.PSEUDO, + ')?', + // followed by either a descendant or direct symbol + '(?', + GRAMMAR.PARENT_SEP, + ')' +].join(''); -var atomicRegex; -var verbose = false; +/* +// ----------------------------------- +// INTERFACE +// ----------------------------------- + +// Atomizer Options +// options for the behavior of the atomizer class (not the CSS output) +interface AtomizerOptions { + verbose:boolean; +} -/** - * Escapes special regular expression characters - * @param {string} str The regexp string. - * @return {string} The escaped regexp string. - */ -function escapeRegExp(str) { - return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); +// Atomizer Rules +// rules are expected to be in the following format +interface AtomizerRules { + [index:number]:AtomizerRule +} +interface AtomizerRule { + allowCustom:boolean; + allowSuffixToValue:boolean; + id:string; + name:string; + prefix:string; + properties:string; + type:string; } -/** - * Returns a regular expression with atomic classes based on the rules from atomizer. - * Making it as a function for better separation (code style). - * @return {RegExp} The regular expression containing the atomic classes. - */ -function getAtomicRegex(rules) { - var regexes = []; +// AtomizerConfig +// the config that contains additional info to create atomic classes +interface AtomizerConfig { + custom?: {[index:string]:string}; + breakPoints?: {[index:string]:string}; + classNames: string[]; +} - rules.forEach(function (pattern) { - var prefix = pattern.prefix || ''; - prefix = prefix.replace('.', ''); +// CssOptions +// general options that affect the CSS output +interface CssOptions { + namespace?:string; + rtl?:boolean; +} - if (pattern.rules) { - pattern.rules.forEach(function (rule) { - regexes.push(escapeRegExp(prefix + rule.suffix) + '\\b'); - }); - } else { - // custom-only patterns with no rules - } - if (pattern.prefix) { - regexes.push('\\b' + escapeRegExp(prefix) + '(?:(?:neg)?[0-9]+(?:\.[0-9]+)?(?:[a-zA-Z%]+)?|[0-9a-f]{3}(?:[0-9a-f]{3})?|[a-zA-Z0-9%\.]+\\b)'); - } - }); - return new RegExp('(' + regexes.join('|') + ')', 'g'); +// AtomicTree +// the parse tree is generated after a class is parsed. +// it's an object where its keys are mapped to AtomizerRules.ids +// and value is an array of objects containing structured data about +// the class name. +interface AtomicTree { + [index:string]:AtomicTreeArray; +} +interface AtomicTreeArray { + [index:number]:AtomicTreeObject; +} +interface AtomicTreeObject { + breakPoint?:string; + className:string; + context?:AtomicTreeContext; + pseudo?:string; + value:AtomicTreeValue; } +interface AtomicTreeContext { + directParent:boolean; + parent:string; +} +interface AtomicTreeValue { + percentage?:number; + fraction:string; + color:string; + value:string; +} +*/ /** - * Get an atomic config rule given an atomic class name - * @param {string} className An atomic class name - * @param {object} currentConfig The current config. - * @param {array} warnings An array of warnings generated while processing custom config rules - * @return {object} The config rule for the given class name. + * constructor */ -function getConfigRule(className, currentConfig, warnings) { - var sepIndex = className.indexOf('-') + 1; - var prefix = '.' + className.substring(0, sepIndex); - var suffix = className.substring(sepIndex); - var configRule = {}; - var value; - - // iterate rules to find the pattern that the className belongs to - rules.some(function (pattern) { - var patternRulesLength = 0; - - // filter to the prefix we're looking for - if (pattern.prefix === prefix) { - // set the id in the config rule - configRule[pattern.id] = {}; - - // if the pattern has rules, let's find the suffix - if (pattern.rules) { - patternRulesLength = pattern.rules.length; - pattern.rules.some(function (rule, index) { - // found the suffix, place it in the config - if (rule.suffix === suffix) { - configRule[pattern.id][suffix] = true; - return true; - } - // it's a custom suffix - else if (patternRulesLength === index + 1) { - configRule = handleCustomConfigRule(configRule, className, pattern, suffix, currentConfig, warnings); - return true; - } - }); - } - // no pattern.rules, then it's a custom suffix - else { - configRule = handleCustomConfigRule(configRule, className, pattern, suffix, currentConfig, warnings); - } - return true; +function Atomizer(options/*:AtomizerOptions*/, rules/*AtomizerRules*/) { + this.verbose = options && options.verbose || false; + this.rules = []; + this.rulesMap = {}; + this.helpersMap = {}; + + // add rules + this.addRules(rules || RULES); +} + +Atomizer.prototype.addRules = function(rules/*:AtomizerRules*/)/*:void*/ { + + rules.forEach(function (rule) { + if (this.rulesMap.hasOwnProperty(rule.prefix)) { + throw new Error('Rule ' + rule.prefix + ' already exists'); } - }); - return configRule; -} + // push new rule to this.rules and update rulesMap + this.rules.push(rule); + + if (rule.type === 'pattern') { + this.rulesMap[rule.prefix] = this.rules.length - 1; + } else { + this.helpersMap[rule.prefix] = this.rules.length - 1; + } + }, this); + + // invalidates syntax + this.syntax = null; +}; /** - * Used by getConfigRule to handle custom config rules - * @param {object} configRule The config rule object being built. - * @param {string} className The class name of the custom class to be evaluated. - * @param {object} pattern The pattern that matches this class in atomic css. - * @param {string} suffix The suffix of the class. - * @param {object} currentConfig An existing config to merge with - * @param {array} warnings An array of warnings generated while processing custom config rules - * @return {object} The custom config rule. + * getSyntax() + * we combine the regular expressions here. since we're NOT doing a lexical + * analysis of the entire document we need to use regular grammar for this. + * @private */ -function handleCustomConfigRule(configRule, className, pattern, suffix, currentConfig, warnings) { - var value; - - if (pattern.allowSuffixToValue && ( - utils.isPercentage(suffix) || - utils.isLength(suffix) || - utils.isHex(suffix) || - utils.isInteger(suffix) || - utils.isFloat(suffix))) { - - if (!configRule[pattern.id].custom) { - configRule[pattern.id].custom = []; +Atomizer.prototype.getSyntax = function () { + var syntax; + var helperRegex; + var propRegex; + var helpersKeys; + var rulesKeys; + var mainSyntax; + + if (this.syntax) { + return this.syntax; + } else { + helpersKeys = Object.keys(this.helpersMap); + rulesKeys = Object.keys(this.rulesMap); + + // helpers regex + if (helpersKeys.length) { + helperRegex = [ + // prefix + '(?' + helpersKeys.join('|') + ')', + // required param () + GRAMMAR.PARAMS + ].join(''); + mainSyntax = helperRegex; + } + // rules regex + if (rulesKeys.length) { + propRegex = [ + // prefix + '(?' + rulesKeys.join('|') + ')', + // required value + '(?', + '(?', + GRAMMAR.FRACTION, + ')', + '|', + '(?', + GRAMMAR.HEX, + '(?!', + GRAMMAR.UNIT, + ')', + ')', + '|', + '(?', + GRAMMAR.SIGN, + ')?', + '(?', + GRAMMAR.NUMBER, + ')', + '(?', + GRAMMAR.UNIT, + ')?', + '|', + '(?', + GRAMMAR.NAMED, + ')', + ')', + '(?', + GRAMMAR.IMPORTANT, + ')?', + ].join(''); + mainSyntax = propRegex; } - value = utils.isHex(suffix) ? '#' + suffix : suffix; - configRule[pattern.id].custom.push({ - suffix: suffix, - values: [value] - }); - if (verbose) { - console.warn('Found `' + className + '`, config has been added.'); + if (helpersKeys.length && rulesKeys.length) { + mainSyntax = ['(?:', helperRegex , '|', propRegex,')'].join(''); } - } else { - if (!currentConfig[pattern.id] || !currentConfig[pattern.id].custom) { - warnMissingClassInConfig(className, pattern.id, suffix, warnings); - } else if (currentConfig[pattern.id].custom.every(function (custom) { - return custom.suffix !== suffix; - })) { - warnMissingClassInConfig(className, pattern.id, suffix, warnings); - }; - return false; + + syntax = [ + // word boundary + GRAMMAR.BOUNDARY, + // optional parent + '(?', + GRAMMAR.PARENT_SELECTOR, + ')?', + mainSyntax, + // optional pseudo + '(?', + GRAMMAR.PSEUDO, + ')?', + // optional modifier + '(?:', + GRAMMAR.BREAKPOINT, + ')?' + ].join(''); + + this.syntax = XRegExp(syntax, 'g'); + + return this.syntax; } +}; - return configRule; -} +/** + * findClassNames + */ +Atomizer.prototype.findClassNames = function (src/*:string*/)/*:string[]*/ { + // using object to remove dupes + var classNamesObj = {}; + var className; + var syntaxRegex = this.getSyntax(); + var match = syntaxRegex.exec(src); + + while (match !== null) { + // strip boundary character + className = match[0].substr(1); + // assign to classNamesObj as key and give it a counter + classNamesObj[className] = classNamesObj[className] ? classNamesObj[className] + 1 : 1; + // run regex again + match = syntaxRegex.exec(src); + } + // return an array of the matched class names + return _.keys(classNamesObj); +}; /** - * Used to log warning messages about missing classes in the config - * @param {string} className The missing class name. - * @param {string} id The id of the pattern. - * @param {string} suffix The suffix of the class. - * @param {array} warnings An array of warnings to be displayed to the user - * @void + * Get Atomizer config given an array of class names and an optional config object + * examples: + * + * getConfig(['Op-1', 'D-n:h', 'Fz-heading'], { + * custom: { + * heading: '80px' + * }, + * breakPoints: { + * 'sm': '@media(min-width:500px)', + * 'md': '@media(min-width:900px)', + * 'lg': '@media(min-width:1200px)' + * }, + * classNames: ['D-b'] + * }, { + * rtl: true + * }); + * + * getConfig(['Op-1', 'D-n:h']); */ -function warnMissingClassInConfig(className, patternId, suffix, warnings) { - warnings.push([ - 'Warning: Class `' + className + '` is ambiguous, and must be manually added to your config file:', - '\'' + patternId + '\'' + ':' + '{', - ' custom: [', - ' {suffix: \'' + suffix + '\', values: [\'YOUR-CUSTOM-VALUE\']}', - ' ]', - '}' - ].join("\n")); -} +Atomizer.prototype.getConfig = function (classNames/*:string[]*/, config/*:AtomizerConfig*/)/*:AtomizerConfig*/ { + config = config || { classNames: [] }; + // merge classnames with config + config.classNames = _.union(classNames || [], config.classNames); + return config; +}; -module.exports = { - - /** - * Merge atomizer configs into a single config - * @param {Array} configs An array of Atomizer config objects - * @return {object} An atomizer config object - */ - mergeConfigs: function (configs) { - return _.isArray(configs) && configs.length > 0 ? _.merge.apply(null, configs.concat(utils.handleMergeArrays)) : {}; - }, - - /** - * Look for atomic class names in text and add to class names object. - * @param {string} src The text to be parsed. - * @param {object} classNamesObj (Optional) A hash of classnames -> number instances found - * @return {array} An array of class names. - */ - parse: function (src, classNamesObj) { - classNamesObj = classNamesObj || {}; - - if (!atomicRegex) { - atomicRegex = getAtomicRegex(rules); +/** + * Get CSS given an array of class names, a config and css options. + * examples: + * + * getCss({ + * custom: { + * heading: '80px' + * }, + * breakPoints: { + * 'sm': '@media(min-width:500px)', + * 'md': '@media(min-width:900px)', + * 'lg': '@media(min-width:1200px)' + * }, + * classNames: ['D-b', 'Op-1', 'D-n:h', 'Fz-heading'] + * }, { + * rtl: true + * }); + * + */ +Atomizer.prototype.getCss = function (config/*:AtomizerConfig*/, options/*:CSSOptions*/)/*:string*/ { + var matches; + var tree/*:AtomicTree*/ = {}; + var csso = {}; + var cssoHelpers = {}; + var absurd = Absurd(); + var content = ''; + var warnings = []; + var isVerbose = !!this.verbose; + var syntaxRegex = this.getSyntax(); + var breakPoints; + + options = objectAssign({}, { + // require: [], + // morph: null, + banner: '', + namespace: null, + rtl: false + }, options); + + // validate config.breakPoints + if (config && config.breakPoints) { + if (!_.isObject(config.breakPoints)) { + throw new TypeError('`config.breakPoints` must be an Object'); } - - var match = atomicRegex.exec(src); - while (match !== null) { - classNamesObj[match[0]] = classNamesObj[match[0]] ? classNamesObj[match[0]] + 1 : 1; - match = atomicRegex.exec(src); + /* istanbul ignore else */ + if (_.size(config.breakPoints) > 0) { + for(var bp in config.breakPoints) { + if (!/^@media/.test(config.breakPoints[bp])) { + throw new Error('Breakpoint `' + bp + '` must start with `@media`.'); + } else { + breakPoints = config.breakPoints; + } + } } + } + + // each match is a valid class name + config.classNames.forEach(function (className) { + var match = XRegExp.exec(className, syntaxRegex); + var namedFound = false; + var rule; + var treeo; + var ruleIndex; - return _.keys(classNamesObj); - }, - - /** - * Get config object given an array of atomic class names. - * @param {array} classNames Array of atomic class names. - * @param {object} currentConfig The current config. - * @param {boolean} verboseLogging Verbose logging (default = false) - * @return {object} The atomic config object. - */ - getConfig: function (classNames, currentConfig, verboseLogging) { - var config = {}, - warnings = []; - - verbose = !!verboseLogging; - - for (var i = 0, iLen = classNames.length; i < iLen; i++) { - config = _.merge(config, getConfigRule(classNames[i], currentConfig, warnings), utils.handleMergeArrays); - // config = this.mergeConfigs([config, getConfigRule(classNames[i], currentConfig, warnings)]); + if (!match) { + return ''; } - // Merge the existing config with the new config - config = this.mergeConfigs([config, currentConfig]); + ruleIndex = match.prop ? this.rulesMap[match.prop] : this.helpersMap[match.helper]; - // Now that we've processed all the configuration, notify the user - // if any custom classnames were found that were too ambiguous to - // have their config auto-generated. - if (warnings.length) { - warnings.forEach(function (w) { - console.warn(w); - }); + // get the rule that this class name belongs to. + // this is why we created the dictionary + // as it will return the index given an prefix. + rule = this.rules[ruleIndex]; + treeo = { + className: match[0] + }; + + if (!tree[rule.prefix]) { + tree[rule.prefix] = []; } - return config; - }, - - /** - * createCSS() - * - * Converts configuration JSON into CSS - */ - createCSS: function (config, options) { - var content; - // TODO: Verify these are good defaults - var breakPoints = { - 'sm': '767px', - 'md': '992px', - 'lg': '1200px' - }; + if (match.parentSelector) { + treeo.parentSelector = match.parentSelector; + } + if (match.parent) { + treeo.parent = match.parent; + } + if (match.parentPseudo) { + treeo.parentPseudo = match.parentPseudo; + } + if (match.parentSep) { + treeo.parentSep = match.parentSep; + } + if (match.value) { + // is this a valid value? + if (rule.allowSuffixToValue) { + treeo.value = match.value; + } else { + match.named = match.value; + } + } + if (match.params) { + treeo.params = match.params.split(','); + } + if (match.fraction) { + // multiplying by 100 then by 10000 on purpose (instead of just multiplying by 1M), + // making clear the steps involved: + // percentage: (numerator / denominator * 100) + // 4 decimal places: (Math.round(percentage * 10000) / 10000) + treeo.value = Math.round(match.numerator / match.denominator * 100 * 10000) / 10000 + '%'; + } + if (match.sign) { + treeo.value = treeo.value.replace(GRAMMAR.SIGN, '-'); + } - options = objectAssign({}, { - require: [], - morph: null, - banner: '', - namespace: '#atomic', - rtl: false - }, options); + if (match.hex) { + treeo.value = '#' + match.hex; + } + if (match.named) { + treeo.named = match.named; + + // check if the named suffix matches any of + // the suffixes registered in rules. + if (rule.rules) { + // iterate rules + rule.rules.some(function (keywordRule, index) { + // if we find it, then add declaration + if (keywordRule.suffix === match.named) { + // build declaration (iterate prop the value) + rule.properties.forEach(function (property) { + keywordRule.values.forEach(function (value) { + /* istanbul ignore else */ + if (!treeo.declaration) { + treeo.declaration = {}; + } + treeo.declaration[Atomizer.replaceConstants(property, options.rtl)] = Atomizer.replaceConstants(value, options.rtl); + }); + }); + namedFound = true; + return true; + } + }); + } + // check if named suffix was passed in the config + if (!namedFound && config && _.isObject(config.custom) && config.custom.hasOwnProperty(match.named)) { + treeo.value = config.custom[match.named]; + } else { + if (isVerbose) { + warnings.push([ + 'Warning: Class `' + className + '` is ambiguous, and must be manually added to your config file:', + '"custom": {', + ' "' + match.named + '": ', + '}' + ].join("\n")); + } + treeo.value = null; + } + } + if (match.valuePseudo) { + treeo.valuePseudo = match.valuePseudo; + } - if (!config) { - throw new Error('No configuration provided.'); + if (match.breakPoint) { + treeo.breakPoint = match.breakPoint; + } + if (match.important) { + treeo.value = treeo.value + ' !important'; } - // Set defaults - if (!config.breakPoints) { - config.breakPoints = breakPoints; + tree[rule.prefix].push(treeo); + + }, this); + + // throw warnings + if (isVerbose && warnings.length > 0) { + warnings.forEach(function (warning) { + console.warn(warning); + }); + } + + // write CSSO + // start by iterating rules (we need to follow the order that the rules were declared) + this.rules.forEach(function (rule) { + var className; + var treeCurrent; + + // check if we have a class name that matches this rule + if (tree[rule.prefix]) { + tree[rule.prefix].forEach(function(treeo) { + var breakPoint = breakPoints && breakPoints[treeo.breakPoint]; + + // this is where we start writing the class name, properties and values + className = Atomizer.escapeSelector(Atomizer.replaceConstants(treeo.className, options.rtl)); + + // handle parent classname + if (treeo.parentSelector) { + className = [ + Atomizer.escapeSelector(treeo.parent), + Atomizer.getPseudo(treeo.parentPseudo), + treeo.parentSep !== '>' ? ' ' : treeo.parentSep, + '.', + className + ].join(''); + } + + // handle pseudo in values + if (treeo.valuePseudo) { + className = [ + className, + Atomizer.getPseudo(treeo.valuePseudo) + ].join(''); + } + + // add the dot for the class + className = ['.', className].join(''); + + // fix the comma problem in Absurd + // @TODO temporary until we replace Absurd + // See also the return of this method. + className = className.replace(',', '__COMMA__'); + + // finaly, create the object + + // helper rules doesn't have the same format as patterns + if (rule.type === 'helper') { + cssoHelpers[className] = {}; + + if (breakPoint) { + cssoHelpers[className][breakPoint] = {}; + } + if (!rule.declaration) { + throw new Error('Declaration key is expected in a helper class. Helper class: ' + rule.prefix); + } + + if (breakPoint) { + cssoHelpers[className][breakPoint] = rule.declaration; + } else { + cssoHelpers[className] = rule.declaration; + } + + // we have params in declaration + if (treeo.params) { + treeo.params.forEach(function (param, index) { + if (breakPoint) { + for (var prop in cssoHelpers[className][breakPoint]) { + cssoHelpers[className][breakPoint][prop] = cssoHelpers[className][breakPoint][prop].replace('$' + index, param); + } + } else { + for (var prop in cssoHelpers[className]) { + cssoHelpers[className][prop] = cssoHelpers[className][prop].replace('$' + index, param); + } + } + }); + } + if (rule.rules) { + _.merge(csso, rule.rules); + } + } else/* if (type === 'pattern')*/ { + csso[className] = {}; + + if (breakPoint) { + csso[className][breakPoint] = {}; + } + + // named classes have their property/value already assigned + if (treeo.declaration) { + if (breakPoint) { + csso[className][breakPoint] = treeo.declaration; + } else { + csso[className] = treeo.declaration; + } + } + // a custom class name not declared in the config might not have values + else if (treeo.value) { + rule.properties.forEach(function (property) { + var value = Atomizer.replaceConstants(treeo.value, options.rtl); + property = Atomizer.replaceConstants(property, options.rtl); + if (breakPoint) { + csso[className][breakPoint][property] = value; + } else { + csso[className][property] = value; + } + }); + } + } + }); } + }); + + // Pass some options through to Absurd + // if (options.morph) { + // api.morph(options.morph); + // } - var api = Absurd(); + // if (options.require.length > 0) { + // api.import(options.require); + // } + + if (options.namespace) { + var cssoNew = {}; + cssoNew[options.namespace] = csso; + csso = cssoNew; + } + if (options.helpersNS) { + var cssoHelpersNew = {}; + cssoHelpersNew[options.helpersNS] = cssoHelpers; + cssoHelpers = cssoHelpersNew; + } - api.morph(options.morph); + _.merge(csso, cssoHelpers); - if (options.require.length > 0) { - api.import(options.require); + // send CSSO to absurd + absurd.add(csso); + absurd.compile(function(err, result) { + /* istanbul ignore if else */ + if (err) { + throw new Error('Failed to compile atomic css:' + err); } + content = options.banner + result; + }, options); - var atomicBuilder = new AtomicBuilder(rules, config, options); - var build = atomicBuilder.getBuild(); + // fix the comma problem in Absurd + content = content.replace(/__COMMA__/g, ','); - api.add(build); + return content; +}; - api.compile(function(err, result) { - /* istanbul ignore if else */ - if (err) { - throw new Error('Failed to compile atomic css:' + err); - } - content = options.banner + result; - }, options); +/** + * get non abbreviated pseudo class string given abbreviated or non abbreviated form + */ +Atomizer.getPseudo = function (pseudoName/*:string*/)/*:string*/ { + return PSEUDOS[pseudoName] ? pseudoName : PSEUDOS_INVERTED[pseudoName]; +}; + +/** + * Escape CSS selectors with a backslash + * e.g. ".W-100%" => ".W-100\%" + */ +Atomizer.escapeSelector = function (str/*:string*/)/*:string*/ { + if (!str && str !== 0) { + throw new TypeError('str must be present'); + } - return content; + if (str.constructor !== String) { + return str; } + // TODO: maybe find a better regex? (-?) is here because '-' is considered a word boundary + // so we get it and put it back to the string. + return str.replace(/\b(-?)([^-_a-zA-Z0-9\s]+)/g, function (str, dash, characters) { + return dash + characters.split('').map(function (character) { + return ['\\', character].join(''); + }).join(''); + }); }; + +/** + * Replace LTR/RTL placeholders with actual left/right strings + */ +Atomizer.replaceConstants = function (str, rtl) { + var start = rtl ? 'right' : 'left'; + var end = rtl ? 'left' : 'right'; + + if (!str || str.constructor !== String) { + return str; + } + + return str.replace(/\$START/g, start).replace(/\$END/g, end); +}; + +module.exports = Atomizer; diff --git a/src/atomizer.ts b/src/atomizer.ts new file mode 100644 index 00000000..0c61a0a1 --- /dev/null +++ b/src/atomizer.ts @@ -0,0 +1,125 @@ +/** + * Future refactor with Atomizer with type declarations. + * Not using TypeScript until 1.5 officially comes out, + * so we can write as close to ES6 as possible (including modules). + * For now, we just declare the interfance and class to serve as + * our implementation contract. + */ + +interface Config { + globals?: {[index:string]:string}; + breakPoints?: {[index:string]:string}; + classNames: string[]; +} +// ParseTree: Object +interface ParseTree { + [index:string]:ParseTreeArray; +} +interface ParseTreeArray { + [index:number]:ParseTreeObject; +} +interface ParseTreeObject { + breakPoint?:string; + className:string; + context?:ParseTreeContext; + pseudo?:string; + value:ParseTreeValue; +} +interface ParseTreeContext { + directParent:boolean; + parent:string; +} +interface ParseTreeValue { + percentage?:number; + fraction:string; + color:string; + value:string; +} +interface CssOptions { + rtl:boolean; +} +interface AtomizerOptions { + verbose:boolean; +} +interface AtomizerRules { + [index:number]:AtomizerRule +} +interface AtomizerRule { + allowCustom:boolean; + allowSuffixToValue:boolean; + id:string; + name:string; + prefix:string; + properties:string; + type:string; +} + +class Atomizer { + + verbose: boolean; + rules: AtomizerRules; + + static grammar = { + 'DESC' : '[^\\s]+_', + 'DIRECT' : '[^\\s]+>', + 'PROP' : '(W-|H-|...)', + 'SIGN' : '(?:neg)?', + 'NUMBER' : '[0-9]+(?:\\.[0-9]+)?', + 'UNIT' : '(?:[a-zA-Z%]+)?', + 'HEX' : '[0-9a-f]{3}(?:[0-9a-f]{3})?', + 'CUSTOM' : '[a-zA-Z0-9%\\.]+\\b', + 'MOD' : '--[a-z]+' + }; + + static regex = new RegExp([ + '\\b(', + // descendant or direct + '(?:', + Atomizer.grammar['DESC'], + '|', + Atomizer.grammar['DIRECT'], + ')?', + // property + Atomizer.grammar['PROP'], + // value + '(?:', + // + Atomizer.grammar['SIGN'], + Atomizer.grammar['NUMBER'], + Atomizer.grammar['UNIT'], + '|', + Atomizer.grammar['HEX'], + '|', + Atomizer.grammar['CUSTOM'], + ')', + // modifier + '(?:', + Atomizer.grammar['MOD'], + ')?', + ')' + ].join('')); + + constructor(rules:AtomizerRules , options:AtomizerOptions) { + this.rules = rules; + this.verbose = options && options.verbose || false; + } + + findClassNames(src:string) : string[] { + return []; + } + + getCss(classNames:string[], config:Config, options:CssOptions):string { + return + } + + private parseClassNames(classNames:string[], config:Config):ParseTree { + return {}; + } + + private getCssFromParseTree(parseTree:ParseTree):string { + return ''; + } +} + +// 1.5 +// export Atomizer; diff --git a/src/helpers.js b/src/helpers.js new file mode 100644 index 00000000..11e3eb34 --- /dev/null +++ b/src/helpers.js @@ -0,0 +1,37 @@ +module.exports = [ + { + type: 'helper', + name: 'Line clamp', + prefix: 'LineClamp', + declaration: { + '-webkit-line-clamp': '$0', + 'max-height': '$1' + }, + rules: { + '[class*=LineClamp]': { + 'display': '-webkit-box', + '-webkit-box-orient': 'vertical', + 'overflow': 'hidden' + }, + 'a[class*=LineClamp]': { + 'display': 'inline-block', + '*display': 'inline', + 'zoom': 1 + }, + /** + * Fix WebKit bug that displays ellipsis in middle of text inside *LINKS* + * see: https://twitter.com/thierrykoblentz/status/443899465842176000 + * 1. removes that hack out of the flow (bug reported by Fonda) + */ + 'a[class*=LineClamp]:after': { + 'content': '"."', + 'font-size': 0, + 'visibility': 'hidden', + 'display': 'inline-block', /* 1 */ + 'overflow': 'hidden', /* 1 */ + 'height': 0, /* 1 */ + 'width': 0 /* 1 */ + } + } + } +]; \ No newline at end of file diff --git a/src/lib/AtomicBuilder.js b/src/lib/AtomicBuilder.js deleted file mode 100644 index bedf395c..00000000 --- a/src/lib/AtomicBuilder.js +++ /dev/null @@ -1,503 +0,0 @@ -/* - * Copyright (c) 2015, Yahoo Inc. All rights reserved. - * Copyrights licensed under the New BSD License. - * See the accompanying LICENSE file for terms. - */ - -'use strict'; - -var _ = require('lodash'); -var utils = require('./utils'); - -/** - * AtomicBuilder manage build object given an atomic object and a config object. - * @class - * @param {Array} atomicObjs An array of atomic objects describing atomic.css. - * It is the reference to the config object that tells - * which rules it wants from this list. - * @param {Object} configObj Tells what it wants from the atomic.css (the atomic - * rules) and it is used to to generate a custom build - * of atomic.css. - * @param {Object} optionsObj Options used in rendering the CSS. - */ -function AtomicBuilder (atomicObjs, configObj, optionsObj) { - this.loadObjects(atomicObjs); - this.loadConfig(configObj); - this.loadOptions(optionsObj); - - // create our build obj - this.build = {}; - - // populate build - this.run(); -} - -/** - * Adds an atomic object array to the instance. - * - * @method loadObjects - * @param {Array} objs (Required) The array of atomic objects to be loaded - */ -AtomicBuilder.prototype.loadObjects = function (objs) { - if (!objs) { - throw new Error('`objs` param is required'); - } - if (objs.constructor !== Array) { - throw new TypeError('The `objs` param must be an Array.'); - } - this.atomicObjs = objs; -}; - -/** - * Adds a config object and stores additional information to the instance. - * - * @method loadConfig - * @param {Object} config (Required) The config object to be loaded - */ -AtomicBuilder.prototype.loadConfig = function (config) { - if (!config) { - throw new Error('`config` param is required'); - } - if (config.constructor !== Object) { - throw new TypeError('The `config` param must be an Object.'); - } - if (config.breakPoints) { - if (config.breakPoints.constructor !== Object) { - throw new TypeError('`config.breakPoints` must be an Object'); - } - this.mediaQueries = {}; - if (config.breakPoints.sm) { - this.mediaQueries.sm = '@media(min-width:' + config.breakPoints.sm + ')'; - } - if (config.breakPoints.md) { - this.mediaQueries.md = '@media(min-width:' + config.breakPoints.md + ')'; - } - if (config.breakPoints.lg) { - this.mediaQueries.lg = '@media(min-width:' + config.breakPoints.lg + ')'; - } - } - this.configObj = config; -}; - -/** - * Loads options - * - * @method loadOptions - * @param {Object} config (Required) The options object to be loaded - */ -AtomicBuilder.prototype.loadOptions = function (options) { - if (!options) { - throw new Error('`options` param is required'); - } - if (options.constructor !== Object) { - throw new TypeError('The `options` param must be an Object.'); - } - this.optionsObj = options; -}; - -/** - * Starts the building process of the final CSS by iterating through the atomic object array - * and checking them against the config object. - * - * @method run - */ -AtomicBuilder.prototype.run = function () { - - var atomicObjs = this.atomicObjs, - configObj = this.configObj, - self = this; - - // iterate atomicObjs because we need them to be in order - atomicObjs.forEach(function (atomicObj) { - var currentConfigObj; - - // check if we have some basic required keys in the atomic object - if (!atomicObj.id || atomicObj.id.constructor !== String) { - throw new TypeError('Key `id` of atomic object is required and must be a String. Object: ' + atomicObj); - } - if (!atomicObj.type || atomicObj.type.constructor !== String) { - throw new TypeError('Key `type` of atomic object is required and must be a String. Object: ' + atomicObj.id); - } - - // return if this group is not wanted by the config - currentConfigObj = configObj[atomicObj.id]; - if (!currentConfigObj) { - return; - } - - // if the atomic object is a pattern - if (atomicObj.type === 'pattern') { - // if `rules` has been passed - if (atomicObj.rules) { - if (atomicObj.rules.constructor !== Array) { - throw new TypeError('`atomicObj.rules` must be an Array. AtomicObject id: ' + atomicObj.id); - } - // add each rule, if present in the config - atomicObj.rules.forEach(function (rule) { - // check if rule is wanted by the config - if (currentConfigObj[rule.suffix]) { - self.addPatternRule(rule, atomicObj, currentConfigObj[rule.suffix]); - } - }); - } - // if `fraction` has been passed - if (currentConfigObj.fraction) { - if (!atomicObj.allowFraction) { - throw new Error('Fraction has been passed but it is not allowed for this rule. Config key: ' + atomicObj.id + '.'); - } - self.addFractionRules(currentConfigObj.fraction, atomicObj); - } - // if `custom` has been passed - if (currentConfigObj.custom) { - if (!atomicObj.allowCustom) { - throw new Error('Custom has been passed but it is not allowed for this rule. Config key: ' + atomicObj.id + '.'); - } - if (currentConfigObj.custom.constructor !== Array) { - throw new TypeError('`Config ' + atomicObj.id + '.custom` must be an Array.'); - } - currentConfigObj.custom.forEach(function (rule) { - self.addPatternRule(rule, atomicObj, currentConfigObj); - }); - } - // if `custom-auto-suffix` has been passed - if (currentConfigObj['custom-auto-suffix']) { - if (!atomicObj.allowCustomAutoSuffix) { - throw new Error('`custom-auto-suffix` has been passed but it is not allowed for this rule. Config key: ' + atomicObj.id + '.'); - } - if (currentConfigObj['custom-auto-suffix'].constructor !== Array) { - throw new TypeError('`Config ' + atomicObj.id + '.custom` must be an Array.'); - } - currentConfigObj['custom-auto-suffix'].forEach(function (rule, index) { - if (rule.constructor !== Object) { - throw new TypeError('`custom-auto-suffix` rule must be an Object.'); - } - rule.suffix = atomicObj.suffixType === 'numerical' ? index + 1 : String.fromCharCode(97 + index); - self.addPatternRule(rule, atomicObj, currentConfigObj); - }); - } - } - // if the atomic object is a rule - else if (atomicObj.type === 'rule') { - // check if `rule` is present - if (!atomicObj.rule || atomicObj.rule.constructor !== Object) { - throw new TypeError('Key `rule` of atomic object is required and must be an Object. Object: ' + atomicObj.id); - } - self.addRule(atomicObj.rule, atomicObj.id); - } - // type is not any of the ones listed above - else { - throw new Error('Key `type` must be a `rule` or a `pattern`.'); - } - }); -}; - -/** - * Add a rule written in pattern format to the build obj. - * - * @method addPatternRules - * @param {Object} rule (Required) The rule object containg the following keys: `suffix` and `values`. - * @param {String} rule.suffix (Required) The suffix of the rule. - * @param {Array} rule.values (Required) An array of values that will be mapped to the properties array. - * @param {Object} atomicObj (Required) The atomic object that is being evaluated. - * @param {String} atomicObj.id (Required) The 'id' of pattern. - * @param {String} atomicObj.prefix (Required) The prefix string of the class name. - * @param {Array} atomicObj.properties (Required) The array of CSS properties to be added to this pattern. - */ -AtomicBuilder.prototype.addPatternRule = function (rule, atomicObj, currentConfigObj) { - var self = this, - className, - suffix; - - // validate rule - if (!rule || rule.constructor !== Object) { - throw new TypeError('The `rule` param is required and must be an Object.'); - } - if (!rule.values || rule.values.constructor !== Array) { - throw new TypeError('The `rule.values` param is required and must be an Array.'); - } - // auto suffix does not require a suffix - if (!rule.suffix || (rule.suffix.constructor !== String && rule.suffix.constructor !== Number)) { - throw new TypeError('The `rule.suffix` param is required and must be a String or a Number.'); - } - // validate atomicObj - if (!atomicObj || atomicObj.constructor !== Object) { - throw new TypeError('The `atomicObj` param is required and must be an Object.'); - } - if (!atomicObj.id || atomicObj.id.constructor !== String) { - throw new TypeError('The `atomicObj.id` param is required and must be a String.'); - } - if (!atomicObj.prefix || atomicObj.prefix.constructor !== String) { - throw new TypeError('The `atomicObj.prefix` param is required and must be a String.'); - } - if (!atomicObj.properties || atomicObj.properties.constructor !== Array) { - throw new TypeError('The `atomicObj.properties` param is required and must be an Array.'); - } - - if (!currentConfigObj || (currentConfigObj.constructor !== Object && currentConfigObj.constructor !== Boolean)) { - throw new TypeError('The `currentConfigObj` param is required and must be an Object or a Boolean.'); - } - - suffix = rule.suffix; - className = atomicObj.prefix + suffix; - - // iterate properties - atomicObj.properties.forEach(function (property) { - var values = rule.values; - - // values could be specified in the config as well - if (currentConfigObj.values && currentConfigObj.values.constructor === Array) { - values = currentConfigObj.values; - } - // iterate values - values.forEach(function (value) { - var breakPoints; - - // add BreakPoints - if (rule.breakPoints && rule.breakPoints.constructor === Array) { - breakPoints = rule.breakPoints; - } - if (currentConfigObj.breakPoints && currentConfigObj.breakPoints.constructor === Array) { - breakPoints = currentConfigObj.breakPoints; - } - - // finally, assign - self.addCssRule(className, property, value, breakPoints); - }); - }); -}; - -/** - * Parses an object of type "rule" - * - * @method addRule - * @param {Object} rule (Required) The rule to be added. - * @param {String} id (Required) The id of the rule to be added. - * @param {Object} breakPoints (Optional) The breakPoints object to be grouped in media queries. - * @return {Boolean} True if the rule has been added, false otherwise. - */ -AtomicBuilder.prototype.addRule = function (rule, id) { - var configObj = this.configObj, - breakPoints; - - if (!rule || rule.constructor !== Object) { - throw new TypeError('The `rule` param is required and must be an Object.'); - } - if (!id || id.constructor !== String) { - throw new TypeError('The `id` param is required and must be a String.'); - } - if (!configObj || configObj.constructor !== Object) { - throw new TypeError('Expecting config object to be set in this instance.'); - } - - // check if this rule is wanted by the config - if (!configObj[id]) { - return; - } - - // iterate through the rule so we can send it to addCssRule - for (var className in rule) { - /* istanbul ignore else */ - if (rule.hasOwnProperty(className)) { - for (var property in rule[className]) { - /* istanbul ignore else */ - if (rule[className].hasOwnProperty(property)) { - if (configObj[id].constructor === Object && configObj[id].breakPoints) { - breakPoints = configObj[id].breakPoints; - } - this.addCssRule(className, property, rule[className][property], breakPoints); - } - } - } - } -}; - -/** - * Adds fraction rules (in percentage values) that are written in pattern format to the build obj. - * - * @method addFractionRules - * @param {Object} fractionObj (Required) An object containing information about the fraction rules to be added. - * @param {Integer} fractionObj.denominator (Required) The denominator of the fraction (how many equal parts it will be divided). - * @param {Array} fractionObj.breakPoints (Optional) An array containing the media queries that will be added to each rule. - * @param {String} atomicObj (Required) The atomic object containing the following keys: - * @param {String} atomicObj.id (Required) The 'id' of the pattern. - * @param {Array} atomicObj.properties (Required) The array of properties of the pattern. - * @param {String} atomicObj.prefix (Required) The prefix string of the class name. - */ -AtomicBuilder.prototype.addFractionRules = function (fractionObj, atomicObj) { - var denominator, - className = '', - value = '', - self = this; - - if (!fractionObj || fractionObj.constructor !== Object) { - throw new TypeError('fractionObj in config must be an Object.'); - } - denominator = fractionObj.denominator; - if (!denominator || !utils.isInteger(denominator)) { - throw new TypeError('fractionObj.denominator in config must be a Number.'); - } - if (!atomicObj || atomicObj.constructor !== Object) { - throw new TypeError('The `atomicObj` param is required and must be a String.'); - } - if (!atomicObj.id || atomicObj.id.constructor !== String) { - throw new TypeError('The `atomicObj.id` param is required and must be a String.'); - } - if (!atomicObj.properties || atomicObj.properties.constructor !== Array) { - throw new TypeError('The `atomicObj.properties` param is required and must be an Array.'); - } - if (!atomicObj.prefix || atomicObj.prefix.constructor !== String) { - throw new TypeError('The `atomicObj.prefix` param is required and must be a String.'); - } - - function add(property) { - self.addCssRule(className, property, value, fractionObj.breakPoints); - } - - for (var i = 1; i <= denominator; i += 1) { - className = atomicObj.prefix + i + '\/' + denominator; - // multiplying by 100 then by 10000 on purpose to show more clearly that we want: - // percentage: (i / denominator * 100) - // 4 decimal places: (Math.round(percentage * 10000) / 10000) - value = Math.round(i / denominator * 100 * 10000) / 10000 + '%'; - atomicObj.properties.forEach(add); - } -}; - -/** - * Add CSS rule to the build object - * - * @method addCssRule - * @param {String} className (Required) The class name of the rule. - * @param {String} property (Required) The property of the rule. - * @param {String} value (Required) The value associated with the property passed. - * @param {Array} breakPoints (Optional) An array of breakpoints if required by this rule. - * @return {Boolean} True if the rule has been added, false otherwise. - */ -AtomicBuilder.prototype.addCssRule = function (className, property, value, breakPoints) { - var build = {}, - mqs = this.mediaQueries || {}; - - if (!className || className.constructor !== String) { - throw new TypeError('addCssRule(): `className` param is required and must be a String.'); - } - if (!property || property.constructor !== String) { - throw new TypeError('addCssRule(): `property` param is required and must be a String.'); - } - if (!value && value !== 0) { - throw new TypeError('addCssRule(): `value` param is required.'); - } - - className = this.escapeSelector(this.placeConstants(className)); - property = this.placeConstants(property); - value = this.placeConstants(value); - - build[className] = {}; - build[className][property] = value; - - if (breakPoints) { - breakPoints.forEach(function (breakPoint) { - var bpClassName = className + '--' + breakPoint; - - // check if breakPoint is valid - if (!mqs[breakPoint]) { - throw new Error('Breakpoint is not valid: ' + breakPoint + '. Valid breakPoint values: ' + Object.keys(mqs)); - } - - build[bpClassName] = {}; - build[bpClassName][mqs[breakPoint]] = {}; - build[bpClassName][mqs[breakPoint]][property] = value; - }); - } - - _.merge(this.build, build); - return true; -}; - -/** - * Escape CSS selectors with a backslash - * e.g. ".W-100%" => ".W-100\%" - * - * @param {String} str The string to be processed. - * @return {String} The processed string. - */ -AtomicBuilder.prototype.escapeSelector = function (str) { - if (!str && str !== 0) { - throw new TypeError('str must be present'); - } - - if (str.constructor !== String) { - return str; - } - - // TODO: maybe find a better regex? (-?) is here because '-' is considered a word boundary - // so we get it and put it back to the string. - return str.replace(/\b(-?)([^-_a-zA-Z0-9\s]+)/g, function (str, dash, characters) { - return dash + characters.split('').map(function (character) { - return ['\\', character].join(''); - }).join(''); - }); -}; - -/** - * Replace symbols such as $START and $END with the constants set in the config. - * e.g. border-$START => "border-left" - * - * @param {String} str The string to be processed. - * @return {String} The processed string. - */ -AtomicBuilder.prototype.placeConstants = function (str) { - var configObj = this.configObj; - var optionsObj = this.optionsObj; - var start = optionsObj && optionsObj.rtl ? 'right' : 'left'; - var end = optionsObj && optionsObj.rtl ? 'left' : 'right'; - - if (!str && str !== 0) { - throw new TypeError('str must be present'); - } - if (str.constructor !== String) { - return str; - } - if (!configObj || configObj.constructor !== Object) { - throw new TypeError('configObj is required and must be an Object.'); - } - str = str.replace(/\$START/g, start).replace(/\$END/g, end); - return str; -}; - -/** - * Cleans up the build of this instance (no CSS rules). - * - * @method flush - * @return {Object} The build object - */ -AtomicBuilder.prototype.flush = function () { - this.build = {}; -}; - -/** - * Returns the build object - * - * @method getBuild - * @return {Object} The build object - */ -AtomicBuilder.prototype.getBuild = function () { - var build = {}, - configObj = this.configObj, - namespace; - - if (!configObj || configObj.constructor !== Object) { - throw new TypeError('Expecting config object to be set in this instance.'); - } - - namespace = this.optionsObj.namespace; - - if (namespace) { - build[namespace] = this.build; - } else { - build = this.build; - } - - return build; -}; - -module.exports = AtomicBuilder; \ No newline at end of file diff --git a/src/lib/utils.js b/src/lib/utils.js deleted file mode 100644 index 718ef093..00000000 --- a/src/lib/utils.js +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright (c) 2015, Yahoo Inc. All rights reserved. - * Copyrights licensed under the New BSD License. - * See the accompanying LICENSE file for terms. - */ -var _ = require('lodash'); - -var utils = exports; - -utils.getNumber = function(value) { - var out = parseFloat(value, 10); - if (isNaN(out)) { - throw new TypeError('Argument `length` is not a valid number.'); - } - return out; -}; - -utils.getUnit = function(value) { - if (isNaN(parseFloat(value, 10))) { - throw new TypeError('Argument `number` is not a number.'); - } - return value.replace(/^[\d\.\s]+/, ''); -}; - -utils.isLength = function(value) { - return parseInt(value, 10) === 0 || (/^-?(?:\d+)?\.?\b\d+[a-z]+$/.test(value) && ['em', 'ex', 'ch', 'rem', 'vh', 'vw', 'vmin', 'vmax', 'px', 'mm', 'cm', 'in', 'pt', 'pc'].indexOf(utils.getUnit(value)) >= 0); -}; - -utils.isPercentage = function(value) { - return /^-?(?:\d+)?\.?\b\d+%$/.test(value); -}; - -utils.isInteger = function(value) { - value = new Number(value); // typecast to Number - return !isNaN(value) && (value % 1) === 0; -}; - -utils.isFloat = function(value) { - return (!isNaN(value) && value.toString().indexOf('.') !== -1); -}; - -utils.isHex = function(value) { - return /^#?[0-9a-f]{3}(?:[0-9a-f]{3})?$/i.test(value); -}; - -utils.isRgb = function(value) { - return /^rgb\(\s*(0|[1-9]\d?|1\d\d?|2[0-4]\d|25[0-5])\s*,\s*(0|[1-9]\d?|1\d\d?|2[0-4]\d|25[0-5])\s*,\s*(0|[1-9]\d?|1\d\d?|2[0-4]\d|25[0-5])\s*\)$/.test(value); -}; - -utils.isRgba = function(value) { - return /^rgba\(\s*(0|[1-9]\d?|1\d\d?|2[0-4]\d|25[0-5])\s*,\s*(0|[1-9]\d?|1\d\d?|2[0-4]\d|25[0-5])\s*,\s*(0|[1-9]\d?|1\d\d?|2[0-4]\d|25[0-5])\s*,\s*(0?\.[0-9]*|[01]|1\.0+)\s*\)$/.test(value); -}; - -utils.isHsl = function(value) { - return /^hsl\(\s*(0|[1-9]\d?|[12]\d\d|3[0-5]\d)\s*,\s*((0|[1-9]\d?|100)%)\s*,\s*((0|[1-9]\d?|100)%)\s*\)$/.test(value); -}; - -utils.isHsla = function(value) { - return /^hsla\(\s*(0|[1-9]\d?|[12]\d\d|3[0-5]\d)\s*,\s*((0|[1-9]\d?|100)%)\s*,\s*((0|[1-9]\d?|100)%)\s*,\s*(0?\.[0-9]*|[01]|1\.0+)\s*\)$/.test(value); -}; - -utils.isColorKeyword = function(value) { - var keywords = [ - 'transparent', - 'currentColor', - - 'aliceblue', - 'antiquewhite', - 'aqua', - 'aquamarine', - 'azure', - 'beige', - 'bisque', - 'black', - 'blanchedalmond', - 'blue', - 'blueviolet', - 'brown', - 'burlywood', - 'cadetblue', - 'chartreuse', - 'chocolate', - 'coral', - 'cornflowerblue', - 'cornsilk', - 'crimson', - 'cyan', - 'darkblue', - 'darkcyan', - 'darkgoldenrod', - 'darkgray', - 'darkgreen', - 'darkgrey', - 'darkkhaki', - 'darkmagenta', - 'darkolivegreen', - 'darkorange', - 'darkorchid', - 'darkred', - 'darksalmon', - 'darkseagreen', - 'darkslateblue', - 'darkslategray', - 'darkslategrey', - 'darkturquoise', - 'darkviolet', - 'deeppink', - 'deepskyblue', - 'dimgray', - 'dimgrey', - 'dodgerblue', - 'firebrick', - 'floralwhite', - 'forestgreen', - 'fuchsia', - 'gainsboro', - 'ghostwhite', - 'gold', - 'goldenrod', - 'gray', - 'green', - 'greenyellow', - 'grey', - 'honeydew', - 'hotpink', - 'indianred', - 'indigo', - 'ivory', - 'khaki', - 'lavender', - 'lavenderblush', - 'lawngreen', - 'lemonchiffon', - 'lightblue', - 'lightcoral', - 'lightcyan', - 'lightgoldenrodyellow', - 'lightgray', - 'lightgreen', - 'lightgrey', - 'lightpink', - 'lightsalmon', - 'lightseagreen', - 'lightskyblue', - 'lightslategray', - 'lightslategrey', - 'lightsteelblue', - 'lightyellow', - 'lime', - 'limegreen', - 'linen', - 'magenta', - 'maroon', - 'mediumaquamarine', - 'mediumblue', - 'mediumorchid', - 'mediumpurple', - 'mediumseagreen', - 'mediumslateblue', - 'mediumspringgreen', - 'mediumturquoise', - 'mediumvioletred', - 'midnightblue', - 'mintcream', - 'mistyrose', - 'moccasin', - 'navajowhite', - 'navy', - 'oldlace', - 'olive', - 'olivedrab', - 'orange', - 'orangered', - 'orchid', - 'palegoldenrod', - 'palegreen', - 'paleturquoise', - 'palevioletred', - 'papayawhip', - 'peachpuff', - 'peru', - 'pink', - 'plum', - 'powderblue', - 'purple', - 'red', - 'rosybrown', - 'royalblue', - 'saddlebrown', - 'salmon', - 'sandybrown', - 'seagreen', - 'seashell', - 'sienna', - 'silver', - 'skyblue', - 'slateblue', - 'slategray', - 'slategrey', - 'snow', - 'springgreen', - 'steelblue', - 'tan', - 'teal', - 'thistle', - 'tomato', - 'turquoise', - 'violet', - 'wheat', - 'white', - 'whitesmoke', - 'yellow', - 'yellowgreen' - ]; - return keywords.indexOf(value) >= 0 ? true : false; -}; - -utils.isColor = function(value) { - return utils.isHex(value) || utils.isColorKeyword(value) || utils.isRgb(value) || utils.isRgba(value) || utils.isHsl(value) || this.isHsla(value); -}; - -utils.indexOf = function(listOfValidItems) { - if (listOfValidItems.constructor !== Array) { - throw new Error('Argument must be an Array.'); - } - return function(value) { - return listOfValidItems.indexOf(value) >= 0 ? true : false; - }; -}; - -/** - * helper function to handle merging array of objects - * @param {mixed} a Data of the first merge param - * @param {mixed} b Data of the second merge param - * @return {mixed} The merged object - */ -utils.handleMergeArrays = function (a, b) { - if (_.isArray(a) && _.isArray(b)) { - a.forEach(function(item){ - if(_.findIndex(b, item) === -1) { - b.push(item); - } - }); - return b; - } -}; diff --git a/src/rules.js b/src/rules.js index d1e488ee..9e2c619e 100644 --- a/src/rules.js +++ b/src/rules.js @@ -10,6 +10,7 @@ * - look for top/right/bottom/left rules in the "offset" section * - we do *not* use left and right as keywords for class names, instead we use "start" and "end" * - T-Shirt sizes follow http://www.americanapparel.net/sizing/default.asp?chart=mu.shirts + * - Rules is written as an array because ORDER is important for the CSS generation * **/ @@ -148,9 +149,8 @@ module.exports = [ // all edges { type: 'pattern', - id: 'border', name: 'Border', - prefix: '.Bd-', + prefix: 'Bd-', properties: ['border'], allowCustom: true, allowCustomAutoSuffix: true, @@ -159,9 +159,8 @@ module.exports = [ // top { type: 'pattern', - id: 'border-top', name: 'Border top', - prefix: '.Bdt-', + prefix: 'Bdt-', properties: ['border-top'], allowCustom: true, allowCustomAutoSuffix: true, @@ -170,9 +169,8 @@ module.exports = [ // end { type: 'pattern', - id: 'border-end', name: 'Border end', - prefix: '.Bdend-', + prefix: 'Bdend-', properties: ['border-$END'], allowCustom: true, allowCustomAutoSuffix: true, @@ -181,9 +179,8 @@ module.exports = [ // bottom { type: 'pattern', - id: 'border-bottom', name: 'Border bottom', - prefix: '.Bdb-', + prefix: 'Bdb-', properties: ['border-bottom'], allowCustom: true, allowCustomAutoSuffix: true, @@ -192,9 +189,8 @@ module.exports = [ // start { type: 'pattern', - id: 'border-start', name: 'Border start', - prefix: '.Bdstart-', + prefix: 'Bdstart-', properties: ['border-$START'], allowCustom: true, allowCustomAutoSuffix: true, @@ -203,9 +199,8 @@ module.exports = [ // X axis { type: 'pattern', - id: 'border-x', name: 'Border X', - prefix: '.Bdx-', + prefix: 'Bdx-', properties: ['border-$START', 'border-$END'], allowCustom: true, allowCustomAutoSuffix: true, @@ -214,9 +209,8 @@ module.exports = [ // Y axis { type: 'pattern', - id: 'border-y', name: 'Border Y', - prefix: '.Bdy-', + prefix: 'Bdy-', properties: ['border-top', 'border-bottom'], allowCustom: true, allowCustomAutoSuffix: true, @@ -231,9 +225,8 @@ module.exports = [ // all edges { type: 'pattern', - id: 'border-color', name: 'Border color', - prefix: '.Bdc-', + prefix: 'Bdc-', properties: ['border-color'], allowCustom: true, allowCustomAutoSuffix: true, @@ -246,9 +239,8 @@ module.exports = [ // top { type: 'pattern', - id: 'border-top-color', name: 'Border top color', - prefix: '.Bdtc-', + prefix: 'Bdtc-', properties: ['border-top-color'], allowCustom: true, allowCustomAutoSuffix: true, @@ -261,9 +253,8 @@ module.exports = [ // end { type: 'pattern', - id: 'border-end-color', name: 'Border end color', - prefix: '.Bdendc-', + prefix: 'Bdendc-', properties: ['border-$END-color'], allowCustom: true, allowCustomAutoSuffix: true, @@ -276,9 +267,8 @@ module.exports = [ // bottom { type: 'pattern', - id: 'border-bottom-color', name: 'Border bottom color', - prefix: '.Bdbc-', + prefix: 'Bdbc-', properties: ['border-bottom-color'], allowCustom: true, allowCustomAutoSuffix: true, @@ -291,9 +281,8 @@ module.exports = [ // start { type: 'pattern', - id: 'border-start-color', name: 'Border start color', - prefix: '.Bdstartc-', + prefix: 'Bdstartc-', properties: ['border-$START-color'], allowCustom: true, allowCustomAutoSuffix: true, @@ -312,9 +301,8 @@ module.exports = [ // all edges { type: 'pattern', - id: 'border-style', name: 'Border style', - prefix: '.Bds-', + prefix: 'Bds-', properties: ['border-style'], allowCustom: true, allowCustomAutoSuffix: false, @@ -335,9 +323,8 @@ module.exports = [ // top { type: 'pattern', - id: 'border-top-style', name: 'Border top style', - prefix: '.Bdts-', + prefix: 'Bdts-', properties: ['border-top-style'], allowCustom: true, allowCustomAutoSuffix: true, @@ -358,9 +345,8 @@ module.exports = [ // end { type: 'pattern', - id: 'border-end-style', name: 'Border end style', - prefix: '.Bdends-', + prefix: 'Bdends-', properties: ['border-$END-style'], allowCustom: true, allowCustomAutoSuffix: true, @@ -381,9 +367,8 @@ module.exports = [ // bottom { type: 'pattern', - id: 'border-bottom-style', name: 'Border bottom style', - prefix: '.Bdbs-', + prefix: 'Bdbs-', properties: ['border-bottom-style'], allowCustom: true, allowCustomAutoSuffix: true, @@ -404,9 +389,8 @@ module.exports = [ // start { type: 'pattern', - id: 'border-start-style', name: 'Border start style', - prefix: '.Bdstarts-', + prefix: 'Bdstarts-', properties: ['border-$START-style'], allowCustom: true, allowCustomAutoSuffix: true, @@ -433,9 +417,8 @@ module.exports = [ // all edges { type: 'pattern', - id: 'border-width', name: 'Border width', - prefix: '.Bdw-', + prefix: 'Bdw-', properties: ['border-width'], allowCustom: true, allowCustomAutoSuffix: true, @@ -449,9 +432,8 @@ module.exports = [ // top { type: 'pattern', - id: 'border-top-width', name: 'Border top width', - prefix: '.Bdtw-', + prefix: 'Bdtw-', properties: ['border-top-width'], allowCustom: true, allowCustomAutoSuffix: true, @@ -465,9 +447,8 @@ module.exports = [ // end { type: 'pattern', - id: 'border-end-width', name: 'Border end width', - prefix: '.Bdendw-', + prefix: 'Bdendw-', properties: ['border-$END-width'], allowCustom: true, allowCustomAutoSuffix: true, @@ -481,9 +462,8 @@ module.exports = [ // bottom { type: 'pattern', - id: 'border-bottom-width', name: 'Border bottom width', - prefix: '.Bdbw-', + prefix: 'Bdbw-', properties: ['border-bottom-width'], allowCustom: true, allowCustomAutoSuffix: true, @@ -497,9 +477,8 @@ module.exports = [ // start { type: 'pattern', - id: 'border-start-width', name: 'Border start width', - prefix: '.Bdsw-', + prefix: 'Bdsw-', properties: ['border-$START-width'], allowCustom: true, allowCustomAutoSuffix: true, @@ -518,9 +497,8 @@ module.exports = [ */ { type: 'pattern', - id: 'border-radius', name: 'Border radius', - prefix: '.Bdrs-', + prefix: 'Bdrs-', properties: ['border-radius'], allowCustom: true, allowSuffixToValue: true @@ -528,9 +506,8 @@ module.exports = [ // top-right { type: 'pattern', - id: 'border-top-right-radius', name: 'Border radius top right', - prefix: '.Bdrstend-', + prefix: 'Bdrstend-', properties: ['border-top-$END-radius'], allowCustom: true, allowSuffixToValue: true @@ -538,9 +515,8 @@ module.exports = [ // bottom-right { type: 'pattern', - id: 'border-bottom-right-radius', name: 'Border radius bottom right', - prefix: '.Bdrsbend-', + prefix: 'Bdrsbend-', properties: ['border-bottom-$END-radius'], allowCustom: true, allowSuffixToValue: true @@ -548,9 +524,8 @@ module.exports = [ // bottom-left { type: 'pattern', - id: 'border-bottom-left-radius', name: 'Border radius bottom left', - prefix: '.Bdrsbstart-', + prefix: 'Bdrsbstart-', properties: ['border-bottom-$START-radius'], allowCustom: true, allowSuffixToValue: true @@ -558,9 +533,8 @@ module.exports = [ // top-left { type: 'pattern', - id: 'border-top-left-radius', name: 'Border radius top left', - prefix: '.Bdrststart-', + prefix: 'Bdrststart-', properties: ['border-top-$START-radius'], allowCustom: true, allowSuffixToValue: true @@ -575,9 +549,8 @@ module.exports = [ /* background */ { type: 'pattern', - id: 'background', name: 'Background', - prefix: '.Bg-', + prefix: 'Bg-', properties: ['background'], allowCustom: true, allowCustomAutoSuffix: true, @@ -591,9 +564,8 @@ module.exports = [ /* background-image */ { type: 'pattern', - id: 'background-image', name: 'Background image', - prefix: '.Bgi-', + prefix: 'Bgi-', properties: ['background-image'], allowCustom: true, allowCustomAutoSuffix: true, @@ -606,9 +578,8 @@ module.exports = [ /* background-color */ { type: 'pattern', - id: 'background-color', name: 'Background color', - prefix: '.Bgc-', + prefix: 'Bgc-', properties: ['background-color'], allowCustom: true, allowCustomAutoSuffix: true, @@ -618,9 +589,8 @@ module.exports = [ /* background-clip */ { type: 'pattern', - id: 'background-clip', name: 'Background clip', - prefix: '.Bgcp-', + prefix: 'Bgcp-', properties: ['background-clip'], rules: [ {suffix: 'bb', values: ['border-box']}, @@ -631,9 +601,8 @@ module.exports = [ /* background-origin */ { type: 'pattern', - id: 'background-origin', name: 'Background origin', - prefix: '.Bgo-', + prefix: 'Bgo-', properties: ['background-origin'], rules: [ {suffix: 'bb', values: ['border-box']}, @@ -644,9 +613,8 @@ module.exports = [ /* background-size (length would be customized) */ { type: 'pattern', - id: 'background-size', name: 'Background size', - prefix: '.Bgz-', + prefix: 'Bgz-', properties: ['background-size'], allowCustom: true, allowSuffixToValue: true, @@ -659,9 +627,8 @@ module.exports = [ /* background-attachment */ { type: 'pattern', - id: 'background-attachment', name: 'Background attachment', - prefix: '.Bga-', + prefix: 'Bga-', properties: ['background-attachment'], rules: [ {suffix: 'f', values: ['fixed']}, @@ -672,9 +639,8 @@ module.exports = [ /* background-position *4 corners only* (s=start and e=end) */ { type: 'pattern', - id: 'background-position', name: 'Background position', - prefix: '.Bgp-', + prefix: 'Bgp-', properties: ['background-position'], rules: [ {suffix: 's_t', values: ['$START 0']}, @@ -686,9 +652,8 @@ module.exports = [ /* background-repeat */ { type: 'pattern', - id: 'background-repeat', name: 'Background repeat', - prefix: '.Bgr-', + prefix: 'Bgr-', properties: ['background-repeat'], rules: [ {suffix: 'n', values: ['no-repeat']}, @@ -706,9 +671,8 @@ module.exports = [ */ { type: 'pattern', - id: 'border-collapse', name: 'Border collapse', - prefix: '.Bdcl-', + prefix: 'Bdcl-', properties: ['border-collapse'], rules: [ {suffix: 'c', values: ['collapse']}, @@ -724,9 +688,8 @@ module.exports = [ */ { type: 'pattern', - id: 'box-sizing', name: 'Box sizing', - prefix: '.Bxz-', + prefix: 'Bxz-', properties: ['box-sizing'], rules: [ {suffix: 'cb', values: ['content-box']}, @@ -743,10 +706,9 @@ module.exports = [ */ { type: 'pattern', - id: 'box-shadow', name: 'Box shadow', properties: ['box-shadow'], - prefix: '.Bxsh-', + prefix: 'Bxsh-', allowCustom: true, allowCustomAutoSuffix: true, allowSuffixToValue: false, @@ -762,9 +724,8 @@ module.exports = [ */ { type: 'pattern', - id: 'clear', name: 'Clear', - prefix: '.Cl-', + prefix: 'Cl-', properties: ['clear'], rules: [ {suffix: 'n', values: ['none']}, @@ -782,9 +743,8 @@ module.exports = [ */ { type: 'pattern', - id: 'color', name: 'color', - prefix: '.C-', + prefix: 'C-', properties: ['color'], allowCustom: true, allowCustomAutoSuffix: true, @@ -803,9 +763,8 @@ module.exports = [ */ { type: 'pattern', - id: 'cursor', name: 'Cursor', - prefix: '.Cur-', + prefix: 'Cur-', properties: ['cursor'], rules: [ {suffix: 'a', values: ['auto']}, @@ -836,9 +795,8 @@ module.exports = [ */ { type: 'pattern', - id: 'display', name: 'Display', - prefix: '.D-', + prefix: 'D-', properties: ['display'], rules: [ {suffix: 'n', values: ['none']}, @@ -872,9 +830,8 @@ module.exports = [ */ { type: 'pattern', - id: 'flex', name: 'Flex', - prefix: '.Flx-', + prefix: 'Flx-', properties: ['flex'], allowCustom: true, allowSuffixToValue: false, @@ -892,9 +849,8 @@ module.exports = [ // // { // type: 'pattern', - // id: 'flex-align', - // name: 'Flex align', - // prefix: '.Fla-', + // // name: 'Flex align', + // prefix: 'Fla-', // properties: ['flex-align'], // rules: [ // {suffix: 's', values: ['start']}, @@ -906,9 +862,8 @@ module.exports = [ // }, { type: 'pattern', - id: 'align-self', name: 'Align self', - prefix: '.As-', + prefix: 'As-', properties: ['align-self'], rules: [ {suffix: 'a', values: ['auto']}, @@ -923,9 +878,8 @@ module.exports = [ /* FLEX-DIRECTION */ { type: 'pattern', - id: 'flex-direction', name: 'Flex direction', - prefix: '.Fld-', + prefix: 'Fld-', properties: ['flex-direction'], rules: [ {suffix: 'r', values: ['row']}, @@ -938,9 +892,8 @@ module.exports = [ /* FLEX-FLOW */ { type: 'pattern', - id: 'flex-flow', name: 'Flex flow', - prefix: '.Flf-', + prefix: 'Flf-', properties: ['flex-flow'], rules: [ {suffix: 'r', values: ['row']}, @@ -961,9 +914,8 @@ module.exports = [ // // { // type: 'pattern', - // id: 'flex-item-align', - // name: 'Flex item align', - // prefix: '.Flia-', + // // name: 'Flex item align', + // prefix: 'Flia-', // properties: ['flex-item-align'], // rules: [ // {suffix: 'a', values: ['auto']}, @@ -976,9 +928,8 @@ module.exports = [ // }, { type: 'pattern', - id: 'align-items', name: 'Align items', - prefix: '.Ai-', + prefix: 'Ai-', properties: ['align-items'], rules: [ // flex-start | flex-end | center | baseline | stretch @@ -999,9 +950,8 @@ module.exports = [ // // { // type: 'pattern', - // id: 'flex-line-pack', - // name: 'Flex line pack', - // prefix: '.Fllp-', + // // name: 'Flex line pack', + // prefix: 'Fllp-', // properties: ['flex-line-pack'], // rules: [ // {suffix: 's', values: ['start']}, @@ -1014,9 +964,8 @@ module.exports = [ // }, { type: 'pattern', - id: 'align-content', name: 'Align content', - prefix: '.Ac-', + prefix: 'Ac-', properties: ['align-content'], rules: [ {suffix: 'fs', values: ['flex-start']}, @@ -1036,16 +985,14 @@ module.exports = [ // // { // type: 'pattern', - // id: 'flex-order', - // name: 'Flex order', - // prefix: '.Flo-', + // // name: 'Flex order', + // prefix: 'Flo-', // properties: ['flex-order'] // }, { type: 'pattern', - id: 'order', name: 'Order', - prefix: '.Or-', + prefix: 'Or-', properties: ['order'], allowCustom: true, allowSuffixToValue: true @@ -1059,9 +1006,8 @@ module.exports = [ // // { // type: 'pattern', - // id: 'flex-pack', - // name: 'Flex pack', - // prefix: '.Flp-', + // // name: 'Flex pack', + // prefix: 'Flp-', // properties: ['flex-pack'], // rules: [ // {suffix: 's', values: ['start']}, @@ -1073,9 +1019,8 @@ module.exports = [ // }, { type: 'pattern', - id: 'justify-content', name: 'Justify content', - prefix: '.Jc-', + prefix: 'Jc-', properties: ['justify-content'], rules: [ {suffix: 'fs', values: ['flex-start']}, @@ -1089,9 +1034,8 @@ module.exports = [ /* FLEX-WRAP */ { type: 'pattern', - id: 'flex-wrap', name: 'Flex-wrap', - prefix: '.Flw-', + prefix: 'Flw-', properties: ['flex-wrap'], rules: [ {suffix: 'nw', values: ['nowrap']}, @@ -1107,9 +1051,8 @@ module.exports = [ */ { type: 'pattern', - id: 'float', name: 'Float', - prefix: '.Fl-', + prefix: 'Fl-', properties: ['float'], rules: [ {suffix: 'n', values: ['none']}, @@ -1126,9 +1069,8 @@ module.exports = [ */ { type: 'pattern', - id: 'font-family', name: 'Font family', - prefix: '.Ff-', + prefix: 'Ff-', properties: ['font-family'], rules: [ {suffix: 'c', values: ['"Monotype Corsiva", "Comic Sans MS", cursive']}, @@ -1146,9 +1088,8 @@ module.exports = [ */ { type: 'pattern', - id: 'font-weight', name: 'Font weight', - prefix: '.Fw-', + prefix: 'Fw-', properties: ['font-weight'], rules: [ {suffix: '100', values: ['100']}, @@ -1174,9 +1115,8 @@ module.exports = [ */ { type: 'pattern', - id: 'font-size', name: 'Font size', - prefix: '.Fz-', + prefix: 'Fz-', properties: ['font-size'], allowCustom: true, allowCustomAutoSuffix: true, @@ -1190,9 +1130,8 @@ module.exports = [ */ { type: 'pattern', - id: 'font-style', name: 'Font style', - prefix: '.Fs-', + prefix: 'Fs-', properties: ['font-style'], rules: [ {suffix: 'n', values: ['normal']}, @@ -1211,9 +1150,8 @@ module.exports = [ // https://developer.mozilla.org/en-US/docs/Web/CSS/font-variant { type: 'pattern', - id: 'font-variant', name: 'Font variant', - prefix: '.Fv-', + prefix: 'Fv-', properties: ['font-variant'], rules: [ {suffix: 'n', values: ['normal']}, @@ -1228,9 +1166,8 @@ module.exports = [ */ { type: 'pattern', - id: 'height', name: 'Height', - prefix: '.H-', + prefix: 'H-', properties: ['height'], allowCustom: true, allowSuffixToValue: true, @@ -1255,9 +1192,8 @@ module.exports = [ */ { type: 'pattern', - id: 'hyphens', name: 'Hyphens', - prefix: '.Hy-', + prefix: 'Hy-', properties: ['hyphens'], rules: [ {suffix: 'a', values: ['auto']}, @@ -1274,9 +1210,8 @@ module.exports = [ */ { type: 'pattern', - id: 'list-style-type', name: 'List style type', - prefix: '.List-', + prefix: 'List-', properties: ['list-style-type'], rules: [ {suffix: 'n', values: ['none']}, @@ -1306,9 +1241,8 @@ module.exports = [ // TODO: Validate URI { type: 'pattern', - id: 'list-style-image', name: 'List style image', - prefix: '.Lisi-', + prefix: 'Lisi-', properties: ['list-style-image'], allowCustom: true, allowSuffixToValue: false, @@ -1325,9 +1259,8 @@ module.exports = [ */ { type: 'pattern', - id: 'line-height', name: 'Line height', - prefix: '.Lh-', + prefix: 'Lh-', properties: ['line-height'], allowCustom: true, allowSuffixToValue: true, @@ -1345,9 +1278,8 @@ module.exports = [ // all edges { type: 'pattern', - id: 'margin', name: 'Margin (all edges)', - prefix: '.M-', + prefix: 'M-', properties: ['margin'], allowCustom: true, allowSuffixToValue: true, @@ -1360,9 +1292,8 @@ module.exports = [ // top { type: 'pattern', - id: 'margin-top', name: 'Margin top', - prefix: '.Mt-', + prefix: 'Mt-', properties: ['margin-top'], allowCustom: true, allowSuffixToValue: true, @@ -1375,9 +1306,8 @@ module.exports = [ // end { type: 'pattern', - id: 'margin-end', name: 'Margin end', - prefix: '.Mend-', + prefix: 'Mend-', properties: ['margin-$END'], allowCustom: true, allowSuffixToValue: true, @@ -1390,9 +1320,8 @@ module.exports = [ // bottom { type: 'pattern', - id: 'margin-bottom', name: 'Margin bottom', - prefix: '.Mb-', + prefix: 'Mb-', properties: ['margin-bottom'], allowCustom: true, allowSuffixToValue: true, @@ -1405,9 +1334,8 @@ module.exports = [ // start { type: 'pattern', - id: 'margin-start', name: 'Margin start', - prefix: '.Mstart-', + prefix: 'Mstart-', properties: ['margin-$START'], allowCustom: true, allowSuffixToValue: true, @@ -1420,9 +1348,8 @@ module.exports = [ // X axis { type: 'pattern', - id: 'margin-x', name: 'Margin (X axis)', - prefix: '.Mx-', + prefix: 'Mx-', properties: ['margin-$START', 'margin-$END'], allowCustom: true, allowSuffixToValue: true, @@ -1435,9 +1362,8 @@ module.exports = [ // Y axis { type: 'pattern', - id: 'margin-y', name: 'Margin (Y axis)', - prefix: '.My-', + prefix: 'My-', properties: ['margin-top', 'margin-bottom'], allowCustom: true, allowSuffixToValue: true, @@ -1454,9 +1380,8 @@ module.exports = [ */ { type: 'pattern', - id: 'max-height', name: 'Max height', - prefix: '.Mah-', + prefix: 'Mah-', properties: ['max-height'], allowCustom: true, allowSuffixToValue: true, @@ -1476,9 +1401,8 @@ module.exports = [ */ { type: 'pattern', - id: 'max-width', name: 'Max width', - prefix: '.Maw-', + prefix: 'Maw-', properties: ['max-width'], allowCustom: true, allowSuffixToValue: true, @@ -1498,9 +1422,8 @@ module.exports = [ */ { type: 'pattern', - id: 'min-height', name: 'Min height', - prefix: '.Mih-', + prefix: 'Mih-', properties: ['min-height'], allowCustom: true, allowSuffixToValue: true, @@ -1520,9 +1443,8 @@ module.exports = [ */ { type: 'pattern', - id: 'min-width', name: 'Min width', - prefix: '.Miw-', + prefix: 'Miw-', properties: ['min-width'], allowCustom: true, allowSuffixToValue: true, @@ -1542,9 +1464,8 @@ module.exports = [ */ { type: 'pattern', - id: 'outline', name: 'Outline', - prefix: '.O-', + prefix: 'O-', properties: ['outline'], allowCustom: true, allowSuffixToValue: false, @@ -1562,9 +1483,8 @@ module.exports = [ // top { type: 'pattern', - id: 'top', name: 'Top', - prefix: '.T-', + prefix: 'T-', properties: ['top'], allowCustom: true, allowSuffixToValue: true, @@ -1576,9 +1496,8 @@ module.exports = [ // end { type: 'pattern', - id: 'end', name: 'End', - prefix: '.End-', + prefix: 'End-', properties: ['$END'], allowCustom: true, allowSuffixToValue: true, @@ -1590,9 +1509,8 @@ module.exports = [ // bottom { type: 'pattern', - id: 'bottom', name: 'Bottom', - prefix: '.B-', + prefix: 'B-', properties: ['bottom'], allowCustom: true, allowSuffixToValue: true, @@ -1604,9 +1522,8 @@ module.exports = [ // start { type: 'pattern', - id: 'start', name: 'Start', - prefix: '.Start-', + prefix: 'Start-', properties: ['$START'], allowCustom: true, allowSuffixToValue: true, @@ -1622,9 +1539,8 @@ module.exports = [ */ { type: 'pattern', - id: 'opacity', name: 'Opacity', - prefix: '.Op-', + prefix: 'Op-', properties: ['opacity'], allowCustom: true, allowSuffixToValue: true, @@ -1641,9 +1557,8 @@ module.exports = [ */ { type: 'pattern', - id: 'overflow', name: 'Overflow', - prefix: '.Ov-', + prefix: 'Ov-', properties: ['overflow'], rules: [ {suffix: 'a', values: ['auto']}, @@ -1660,9 +1575,8 @@ module.exports = [ */ { type: 'pattern', - id: 'overflow-x', name: 'Overflow (X axis)', - prefix: '.Ovx-', + prefix: 'Ovx-', properties: ['overflow-x'], rules: [ {suffix: 'a', values: ['auto']}, @@ -1679,9 +1593,8 @@ module.exports = [ */ { type: 'pattern', - id: 'overflow-y', name: 'Overflow (Y axis)', - prefix: '.Ovy-', + prefix: 'Ovy-', properties: ['overflow-y'], rules: [ {suffix: 'a', values: ['auto']}, @@ -1698,9 +1611,8 @@ module.exports = [ */ { type: 'pattern', - id: 'overflow-scrolling', name: 'Overflow scrolling', - prefix: '.Ovs-', + prefix: 'Ovs-', properties: ['-webkit-overflow-scrolling'], rules: [ {suffix: 'a', values: ['auto']}, @@ -1716,9 +1628,8 @@ module.exports = [ // all edges { type: 'pattern', - id: 'padding', name: 'Padding (all edges)', - prefix: '.P-', + prefix: 'P-', properties: ['padding'], allowCustom: true, allowSuffixToValue: true, @@ -1730,9 +1641,8 @@ module.exports = [ // top { type: 'pattern', - id: 'padding-top', name: 'Padding top', - prefix: '.Pt-', + prefix: 'Pt-', properties: ['padding-top'], allowCustom: true, allowSuffixToValue: true, @@ -1744,9 +1654,8 @@ module.exports = [ // end { type: 'pattern', - id: 'padding-end', name: 'Padding end', - prefix: '.Pend-', + prefix: 'Pend-', properties: ['padding-$END'], allowCustom: true, allowSuffixToValue: true, @@ -1758,9 +1667,8 @@ module.exports = [ // bottom { type: 'pattern', - id: 'padding-bottom', name: 'Padding bottom', - prefix: '.Pb-', + prefix: 'Pb-', properties: ['padding-bottom'], allowCustom: true, allowSuffixToValue: true, @@ -1772,9 +1680,8 @@ module.exports = [ // start { type: 'pattern', - id: 'padding-start', name: 'Padding start', - prefix: '.Pstart-', + prefix: 'Pstart-', properties: ['padding-$START'], allowCustom: true, allowSuffixToValue: true, @@ -1786,9 +1693,8 @@ module.exports = [ // X axis { type: 'pattern', - id: 'padding-x', name: 'Padding (X axis)', - prefix: '.Px-', + prefix: 'Px-', properties: ['padding-$START', 'padding-$END'], allowCustom: true, allowSuffixToValue: true, @@ -1800,9 +1706,8 @@ module.exports = [ // Y axis { type: 'pattern', - id: 'padding-y', name: 'Padding (Y axis)', - prefix: '.Py-', + prefix: 'Py-', properties: ['padding-top', 'padding-bottom'], allowCustom: true, allowSuffixToValue: true, @@ -1818,9 +1723,8 @@ module.exports = [ */ { type: 'pattern', - id: 'pointer-events', name: 'Pointer events', - prefix: '.Pe-', + prefix: 'Pe-', properties: ['pointer-events'], rules: [ {suffix: 'a', values: ['auto']}, @@ -1843,9 +1747,8 @@ module.exports = [ */ { type: 'pattern', - id: 'position', name: 'Position', - prefix: '.Pos-', + prefix: 'Pos-', properties: ['position'], rules: [ {suffix: 'a', values: ['absolute']}, @@ -1863,9 +1766,8 @@ module.exports = [ */ { type: 'pattern', - id: 'table-layout', name: 'Table layout', - prefix: '.Tbl-', + prefix: 'Tbl-', properties: ['table-layout'], rules: [ {suffix: 'a', values: ['auto']}, @@ -1880,9 +1782,8 @@ module.exports = [ */ { type: 'pattern', - id: 'text-align', name: 'Text align', - prefix: '.Ta-', + prefix: 'Ta-', properties: ['text-align'], rules: [ {suffix: 'c', values: ['center']}, @@ -1902,9 +1803,8 @@ module.exports = [ */ { type: 'pattern', - id: 'text-align-last', name: 'Text align last', - prefix: '.Tal-', + prefix: 'Tal-', properties: ['text-align-last'], rules: [ {suffix: 'a', values: ['auto']}, @@ -1924,9 +1824,8 @@ module.exports = [ */ { type: 'pattern', - id: 'text-decoration', name: 'Text decoration', - prefix: '.Td-', + prefix: 'Td-', properties: ['text-decoration'], rules: [ {suffix: 'l', values: ['line-through']}, @@ -1943,9 +1842,8 @@ module.exports = [ */ { type: 'pattern', - id: 'text-rendering', name: 'Text rendering', - prefix: '.Tren-', + prefix: 'Tren-', properties: ['text-rendering'], rules: [ {suffix: 'a', values: ['auto']}, @@ -1963,9 +1861,8 @@ module.exports = [ */ { type: 'pattern', - id: 'text-replace', name: 'Text replace', - prefix: '.Tr-', + prefix: 'Tr-', properties: ['text-replace'], allowCustom: true, allowSuffixToValue: false, @@ -1981,9 +1878,8 @@ module.exports = [ */ { type: 'pattern', - id: 'text-transform', name: 'Text transform', - prefix: '.Tt-', + prefix: 'Tt-', properties: ['text-transform'], rules: [ {suffix: 'n', values: ['none']}, @@ -2000,9 +1896,8 @@ module.exports = [ */ { type: 'pattern', - id: 'text-shadow', name: 'Text shadow', - prefix: '.Tsh-', + prefix: 'Tsh-', properties: ['text-shadow'], allowCustom: true, allowCustomAutoSuffix: true, @@ -2037,9 +1932,8 @@ module.exports = [ */ { type: 'pattern', - id: 'transition', name: 'Transition', - prefix: '.Trs-', + prefix: 'Trs-', properties: ['transition'], allowCustom: true, allowCustomAutoSuffix: false, @@ -2047,9 +1941,8 @@ module.exports = [ }, { type: 'pattern', - id: 'transition-delay', name: 'Transition delay', - prefix: '.Trsde-', + prefix: 'Trsde-', properties: ['transition-delay'], allowCustom: true, allowCustomAutoSuffix: false, @@ -2060,9 +1953,8 @@ module.exports = [ }, { type: 'pattern', - id: 'transition-duration', name: 'Transition duration', - prefix: '.Trsdu-', + prefix: 'Trsdu-', properties: ['transition-duration'], allowCustom: true, allowCustomAutoSuffix: false, @@ -2073,9 +1965,8 @@ module.exports = [ }, { type: 'pattern', - id: 'transition-property', name: 'Transition property', - prefix: '.Trsp-', + prefix: 'Trsp-', properties: ['transition-property'], allowCustom: true, allowCustomAutoSuffix: false, @@ -2086,9 +1977,8 @@ module.exports = [ }, { type: 'pattern', - id: 'transition-timing-function', name: 'Transition timing function', - prefix: '.Trstf-', + prefix: 'Trstf-', properties: ['transition-timing-function'], // for cubic-bezier allowCustom: true, @@ -2112,9 +2002,8 @@ module.exports = [ */ { type: 'pattern', - id: 'user-select', name: 'User select', - prefix: '.Us-', + prefix: 'Us-', properties: ['user-select'], rules: [ {suffix: 'a', values: ['all']}, @@ -2133,9 +2022,8 @@ module.exports = [ */ { type: 'pattern', - id: 'vertical-align', name: 'Vertical align', - prefix: '.Va-', + prefix: 'Va-', properties: ['vertical-align'], allowCustom: true, allowSuffixToValue: true, @@ -2158,9 +2046,8 @@ module.exports = [ */ { type: 'pattern', - id: 'visibility', name: 'Visibility', - prefix: '.V-', + prefix: 'V-', properties: ['visibility'], rules: [ {suffix: 'v', values: ['visible']}, @@ -2176,9 +2063,8 @@ module.exports = [ */ { type: 'pattern', - id: 'white-space', name: 'White space', - prefix: '.Whs-', + prefix: 'Whs-', properties: ['white-space'], rules: [ {suffix: 'n', values: ['normal']}, @@ -2196,9 +2082,8 @@ module.exports = [ */ { type: 'pattern', - id: 'white-space-collapse', name: 'White space collapse', - prefix: '.Whsc-', + prefix: 'Whsc-', properties: ['white-space-collapse'], rules: [ {suffix: 'n', values: ['normal']}, @@ -2216,9 +2101,8 @@ module.exports = [ */ { type: 'pattern', - id: 'width', name: 'Width', - prefix: '.W-', + prefix: 'W-', properties: ['width'], allowCustom: true, allowCustomAutoSuffix: true, @@ -2243,9 +2127,8 @@ module.exports = [ */ { type: 'pattern', - id: 'word-break', name: 'Word break', - prefix: '.Wob-', + prefix: 'Wob-', properties: ['word-break'], rules: [ {suffix: 'ba', values: ['break-all']}, @@ -2261,9 +2144,8 @@ module.exports = [ */ { type: 'pattern', - id: 'word-wrap', name: 'Word wrap', - prefix: '.Wow-', + prefix: 'Wow-', properties: ['word-wrap'], rules: [ {suffix: 'bw', values: ['break-word']}, @@ -2278,9 +2160,8 @@ module.exports = [ */ { type: 'pattern', - id: 'z-index', name: 'Z index', - prefix: '.Z-', + prefix: 'Z-', properties: ['z-index'], allowCustom: true, allowCustomAutoSuffix: true, diff --git a/tests/AtomicBuilder.js b/tests/AtomicBuilder.js deleted file mode 100644 index 00547e94..00000000 --- a/tests/AtomicBuilder.js +++ /dev/null @@ -1,1746 +0,0 @@ -/*globals describe,it,afterEach */ -'use strict'; - -var chai = require('chai'); -var sinon = require('sinon'); -var sinonChai = require('sinon-chai'); -var expect = chai.expect; -chai.use(sinonChai); - -var AtomicBuilder = require('../src/lib/AtomicBuilder'); -var atomicBuilder; - -describe('AtomicBuilder', function () { - afterEach(function () { - // restore original methods - var methodName, method; - for(methodName in AtomicBuilder.prototype) { - if (AtomicBuilder.prototype.hasOwnProperty(methodName)) { - method = AtomicBuilder.prototype[methodName]; - if (method.restore) { - method.restore(); - } - } - } - }); - - // ------------------------------------------------------- - // constructor() - // ------------------------------------------------------- - describe('constructor()', function () { - it('should set build object, load atomic objects and config and run', function () { - var mock = sinon.mock(AtomicBuilder.prototype); - - // mock methods - mock.expects('loadObjects').once(); - mock.expects('loadConfig').once(); - mock.expects('loadOptions').once(); - mock.expects('run').once(); - - // execute - atomicBuilder = new AtomicBuilder([], {}, {}); - - // assert - expect(atomicBuilder.build).to.deep.equal({}); - mock.verify(); - }); - }); - - // ------------------------------------------------------- - // loadObjects() - // ------------------------------------------------------- - describe('loadObjects()', function () { - it('throws if objs param is empty', function () { - // execute and assert - expect(function () { - AtomicBuilder.prototype.loadObjects(); - }).to.throw(Error); - }); - it('throws if objs param is not an array', function () { - // execute and assert - expect(function () { - AtomicBuilder.prototype.loadObjects('foo'); - }).to.throw(TypeError); - }); - it('should store atomic objects', function () { - var atomicObjs = [{ - type: 'pattern', - id: 'padding-x', - name: 'Horizontal padding', - prefix: '.Px-', - allowCustom: true, - properties: ['padding-left', 'padding-right'] - }]; - - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadConfig'); - sinon.stub(AtomicBuilder.prototype, 'run'); - - // execute - atomicBuilder = new AtomicBuilder(atomicObjs, {}, {}); - - // assert - expect(atomicBuilder.atomicObjs).to.deep.equal(atomicObjs); - }); - }); - - describe('loadOptions()', function () { - it('throws if objs param is empty', function () { - // execute and assert - expect(function () { - AtomicBuilder.prototype.loadOptions(); - }).to.throw(Error); - }); - it('throws if objs param is not an object', function () { - // execute and assert - expect(function () { - AtomicBuilder.prototype.loadOptions('foo'); - }).to.throw(TypeError); - expect(function () { - AtomicBuilder.prototype.loadOptions([]); - }).to.throw(TypeError); - }); - }); - - // ------------------------------------------------------- - // loadConfig() - // ------------------------------------------------------- - describe('loadConfig()', function () { - it('throws if objs param is empty', function () { - // execute and assert - expect(function () { - AtomicBuilder.prototype.loadConfig(); - }).to.throw(Error); - }); - it('throws if objs param is not an array', function () { - // execute and assert - expect(function () { - AtomicBuilder.prototype.loadConfig('foo'); - }).to.throw(TypeError); - }); - it('should store the config', function () { - var config = { - 'font-weight': { - 'n': true, - 'b': true - } - }; - - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - - // execute - atomicBuilder = new AtomicBuilder({}, config, {}); - - // assert - expect(atomicBuilder.configObj).to.deep.equal(config); - }); - it('should handle empty breakPoints', function () { - var config = { - breakPoints: {} - }; - var expected = {}; - - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - - // execute - atomicBuilder = new AtomicBuilder({}, config, {}); - - // assert - expect(atomicBuilder.mediaQueries).to.deep.equal(expected); - }); - it('throws if breakPoints is not an object', function () { - var config = { - breakPoints: 'foo' - }; - - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - - // execute and assert - expect(function () { - var atomicBuilder = new AtomicBuilder({}, config, {}); - }).to.throw(TypeError); - }); - it('should store the `sm` breakPoint as mediaQuery', function () { - var config = { - breakPoints: { - sm: '200px' - } - }; - var expected = { - sm: '@media(min-width:200px)' - }; - - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - - // execute - atomicBuilder = new AtomicBuilder({}, config, {}); - - // assert - expect(atomicBuilder.mediaQueries).to.deep.equal(expected); - }); - it('should store the `md` breakPoint as mediaQuery', function () { - var config = { - breakPoints: { - md: '300px' - } - }; - var expected = { - md: '@media(min-width:300px)' - }; - - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - - // execute - atomicBuilder = new AtomicBuilder({}, config, {}); - - // assert - expect(atomicBuilder.mediaQueries).to.deep.equal(expected); - }); - it('should store the `lg` breakPoint as mediaQuery', function () { - var config = { - breakPoints: { - lg: '500px' - } - }; - var expected = { - lg: '@media(min-width:500px)' - }; - - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - - // execute - atomicBuilder = new AtomicBuilder({}, config, {}); - - // assert - expect(atomicBuilder.mediaQueries).to.deep.equal(expected); - }); - it('should store all breakPoints as mediaQueries', function () { - var config = { - breakPoints: { - sm: '200px', - md: '300px', - lg: '500px' - } - }; - var expected = { - sm: '@media(min-width:200px)', - md: '@media(min-width:300px)', - lg: '@media(min-width:500px)' - }; - - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - - // execute - atomicBuilder = new AtomicBuilder({}, config, {}); - - // assert - expect(atomicBuilder.mediaQueries).to.deep.equal(expected); - }); - }); - - // ------------------------------------------------------- - // flush() - // ------------------------------------------------------- - describe('flush()', function () { - it('should clean build object', function () { - var atomicBuilder = new AtomicBuilder([], {}, {}); - // set something in the build - atomicBuilder.build = { - '.foo': { - 'font-weight': 'bold' - } - }; - // flush it - atomicBuilder.flush(); - - // check if it was flushed - expect(Object.keys(atomicBuilder.build).length).to.equal(0); - }); - }); - - // ------------------------------------------------------- - // addPatternRule() - // ------------------------------------------------------- - describe('addPatternRule()', function () { - // default params to send in tests - var rule = {suffix: 'foo', values: ['bold']}; - var id = 'foo'; - var properties = ['font-weight']; - var prefix = '.Fw-'; - var atomicObj = { - id: id, - properties: properties, - prefix: prefix - }; - - // tests - it('throws if `rules` is missing', function () { - // execute and assert - expect(function () { - AtomicBuilder.prototype.addPatternRule(); - }).to.throw(TypeError); - }); - it('throws if `rules` is not an object', function () { - // execute and assert - expect(function () { - AtomicBuilder.prototype.addPatternRule('foo'); - }).to.throw(TypeError); - }); - it('throws if `rules.values` is missing', function () { - // execute and assert - expect(function () { - AtomicBuilder.prototype.addPatternRule({ - suffix: rule.suffix - }); - }).to.throw(TypeError); - }); - it('throws if `rules.values` is not an array', function () { - // execute and assert - expect(function () { - AtomicBuilder.prototype.addPatternRule({ - suffix: rule.suffix, - values: 'values' - }); - }).to.throw(TypeError); - }); - it('throws if `rules.suffix` is missing', function () { - // execute and assert - expect(function () { - AtomicBuilder.prototype.addPatternRule({ - values: ['values'] - }); - }).to.throw(TypeError); - }); - it('throws if `rules.suffix` is not a string', function () { - // execute and assert - expect(function () { - AtomicBuilder.prototype.addPatternRule({ - suffix: [rule.suffix], - values: ['values'] - }); - }).to.throw(TypeError); - }); - it('throws if `atomicObj` is missing', function () { - // execute and assert - expect(function () { - AtomicBuilder.prototype.addPatternRule(rule); - }).to.throw(TypeError); - }); - it('throws if `atomicObj.id` is missing', function () { - // execute and assert - expect(function () { - AtomicBuilder.prototype.addPatternRule(rule, { - prefix: prefix, - properties: properties - }); - }).to.throw(TypeError); - }); - it('throws if `atomicObj.id` is not a string', function () { - // execute and assert - expect(function () { - AtomicBuilder.prototype.addPatternRule(rule, { - id: [id], - prefix: prefix, - properties: properties - }); - }).to.throw(TypeError); - }); - it('throws if `atomicObj.prefix` is missing', function () { - // execute and assert - expect(function () { - AtomicBuilder.prototype.addPatternRule(rule, { - id: id, - properties: properties - }); - }).to.throw(TypeError); - }); - it('throws if `atomicObj.prefix` is not a string', function () { - // execute and assert - expect(function () { - AtomicBuilder.prototype.addPatternRule(rule, { - id: id, - prefix: [prefix], - properties: properties - }); - }).to.throw(TypeError); - }); - it('throws if `atomicObj.properties` is missing', function () { - // execute and assert - expect(function () { - AtomicBuilder.prototype.addPatternRule(rule, { - id: id, - prefix: prefix - }); - }).to.throw(TypeError); - }); - it('throws if `atomicObj.properties` is not an array', function () { - // execute and assert - expect(function () { - AtomicBuilder.prototype.addPatternRule(rule, { - id: id, - prefix: prefix, - properties: {} - }); - }).to.throw(TypeError); - }); - it('throws if currentConfigObj is missing', function () { - // execute and assert - expect(function () { - AtomicBuilder.prototype.addPatternRule(rule, { - id: id, - prefix: prefix, - properties: properties - }); - }).to.throw(TypeError); - }); - it('should use config values and breakPoints if passed by the config', function (done) { - var expectedValue = 'custom-value'; - var expectedBreakPoints = ['sm', 'md', 'lg']; - - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadConfig'); - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - sinon.stub(AtomicBuilder.prototype, 'addCssRule', function (className, property, value, breakPoints) { - expect(value).to.equal(expectedValue); - expect(breakPoints).to.equal(expectedBreakPoints); - done(); - }); - - // instantiation & setup - var atomicBuilder = new AtomicBuilder(); - - // execute - atomicBuilder.addPatternRule({ - suffix: 'foo', values: ['bold'], breakPoints: expectedBreakPoints - }, { - id: id, - prefix: prefix, - properties: properties, - }, { - values: [expectedValue], - breakPoints: expectedBreakPoints - }); - }); - it('should call addCssRule() to add pattern to the build object if currentConfigObj is true', function () { - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadConfig'); - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - - // instantiation & setup - var atomicBuilder = new AtomicBuilder(); - var mock = sinon.mock(atomicBuilder); - - // set expectations - mock.expects('addCssRule').once(); - - // execute - atomicBuilder.addPatternRule(rule, atomicObj, true); - - // assert - mock.verify(); - }); - }); - - // ------------------------------------------------------- - // addFractionRules() - // ------------------------------------------------------- - describe('addFractionRules()', function () { - // default params to send in tests - var fractionObj = { - denominator: 3 - }; - var id = 'width'; - var properties = ['width']; - var prefix = '.W-'; - var atomicObj = { - id: id, - prefix: prefix, - properties: properties - }; - - // tests - it('throws if `fractionObj` is not an object', function () { - // execute and assert - expect(function () { - AtomicBuilder.prototype.addFractionRules('foo', atomicObj); - }).to.throw(TypeError); - }); - it('throws if `fractionObj.denominator` is missing', function () { - // execute and assert - expect(function () { - AtomicBuilder.prototype.addFractionRules({}, atomicObj); - }).to.throw(TypeError); - }); - it('throws if `fractionObj.denominator` is not an integer', function () { - // execute and assert - expect(function () { - AtomicBuilder.prototype.addFractionRules({ - denominator: 1.2 - }, atomicObj); - }).to.throw(TypeError); - }); - it('throws if `atomicObj` is missing', function () { - // execute and assert - expect(function () { - AtomicBuilder.prototype.addFractionRules({ - denominator: 12 - }); - }).to.throw(TypeError); - }); - it('throws if `atomicObj` is not an object', function () { - // execute and assert - expect(function () { - AtomicBuilder.prototype.addFractionRules({ - denominator: 12 - }, 'atomicObj'); - }).to.throw(TypeError); - }); - it('throws if `atomicObj.id` is missing', function () { - // execute and assert - expect(function () { - AtomicBuilder.prototype.addFractionRules(fractionObj, { - prefix: prefix, - properties: properties - }); - }).to.throw(TypeError); - }); - it('throws if `atomicObj.id` is not a string', function () { - // execute and assert - expect(function () { - AtomicBuilder.prototype.addFractionRules(fractionObj, { - id: [id], - prefix: prefix, - properties: properties - }); - }).to.throw(TypeError); - }); - it('throws if `atomicObj.properties` is missing', function () { - // execute and assert - expect(function () { - AtomicBuilder.prototype.addFractionRules(fractionObj, { - id: id, - prefix: prefix - }); - }).to.throw(TypeError); - }); - it('throws if `atomicObj.properties` is not an array', function () { - // execute and assert - expect(function () { - AtomicBuilder.prototype.addFractionRules(fractionObj, { - id: id, - prefix: prefix, - properties: {} - }); - }).to.throw(TypeError); - }); - it('throws if `atomicObj.prefix` is missing', function () { - // execute and assert - expect(function () { - AtomicBuilder.prototype.addFractionRules(fractionObj, { - id: id, - properties: properties - }); - }).to.throw(TypeError); - }); - it('throws if `atomicObj.prefix` is not a string', function () { - // execute and assert - expect(function () { - AtomicBuilder.prototype.addFractionRules(fractionObj, { - id: id, - prefix: [prefix], - properties: properties - }); - }).to.throw(TypeError); - }); - it('should call addCssRule() to add pattern to the build object', function () { - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadConfig'); - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - - // instantiation & setup - var atomicBuilder = new AtomicBuilder(); - var mock = sinon.mock(atomicBuilder); - - // set expectations - mock.expects('addCssRule').thrice(); - - // execute - atomicBuilder.addFractionRules(fractionObj, atomicObj); - - // assert - mock.verify(); - }); - }); - - // ------------------------------------------------------- - // addRule() - // ------------------------------------------------------- - describe('addRule()', function () { - var rule = { - '.Foo': { - 'font-weight': 'bold' - } - }; - var id = 'foo'; - - // tests - it('throws if `rule` is missing', function () { - // execute and assert - expect(function () { - AtomicBuilder.prototype.addRule(); - }).to.throw(TypeError); - }); - it('throws if `rule` is not an object', function () { - // execute and assert - expect(function () { - AtomicBuilder.prototype.addRule('foo'); - }).to.throw(TypeError); - }); - it('throws if `id` is missing', function () { - // execute and assert - expect(function () { - AtomicBuilder.prototype.addRule({}); - }).to.throw(TypeError); - }); - it('throws if `id` is not a string', function () { - // execute and assert - expect(function () { - AtomicBuilder.prototype.addRule({},{}); - }).to.throw(TypeError); - }); - it('throws if configObj is not set in the instance', function () { - // execute and assert - expect(function () { - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadConfig'); - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - - // execute - var atomicBuilder = new AtomicBuilder(); - atomicBuilder.addRule(rule, id); - }).to.throw(TypeError); - }); - it('should not call addCssrule() if rule is not wanted by the config', function () { - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadConfig'); - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - - // instantiation & setup - var atomicBuilder = new AtomicBuilder(); - var mock = sinon.mock(atomicBuilder); - - // set config - atomicBuilder.configObj = { - 'foo': false - }; - - // set expectations - mock.expects('addCssRule').never(); - - // execute - atomicBuilder.addRule(rule, id); - - // assert - mock.verify(); - }); - it('should send expected arguments to addCssRule()', function (done) { - var expectedClassName = '.Foo'; - var expectedProperty = 'font-weight'; - var expectedValue = 'bold'; - var expectedBreakPoints = ['sm', 'md', 'lg']; - - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadConfig'); - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - sinon.stub(AtomicBuilder.prototype, 'addCssRule', function (className, property, value, breakPoints) { - expect(className).to.equal(expectedClassName); - expect(property).to.equal(expectedProperty); - expect(value).to.equal(expectedValue); - expect(breakPoints).to.deep.equal(expectedBreakPoints); - done(); - }); - - // instantiation & setup - var atomicBuilder = new AtomicBuilder(); - - // execute - atomicBuilder.configObj = { - 'foo': { - breakPoints: ['sm', 'md', 'lg'], - } - }; - atomicBuilder.addRule({ - '.Foo': { - 'font-weight': 'bold' - } - }, 'foo'); - }); - it('should call addCssRule() if rule is wanted by the config', function () { - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadConfig'); - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - - // instantiation & setup - var atomicBuilder = new AtomicBuilder(); - var mock = sinon.mock(atomicBuilder); - - // set config - atomicBuilder.configObj = { - 'foo': true - }; - - // set expectations - mock.expects('addCssRule').atLeast(1); - - // execute - atomicBuilder.addRule(rule, id); - - // assert - mock.verify(); - }); - }); - - // ------------------------------------------------------- - // addCssRule() - // ------------------------------------------------------- - describe('addCssRule()', function () { - var className = '.Foo'; - var property = 'font-weight'; - var value = 'bold'; - var breakPoints = ['sm', 'md', 'lg']; - - it('throws if className has not been passed or is invalid', function () { - // execute and assert - expect(function () { - AtomicBuilder.prototype.addCssRule(); - }).to.throw(TypeError); - // execute and assert - expect(function () { - AtomicBuilder.prototype.addCssRule({}); - }).to.throw(TypeError); - }); - it('throws if property has not been passed or is invalid', function () { - // execute and assert - expect(function () { - AtomicBuilder.prototype.addCssRule(className); - }).to.throw(TypeError); - // execute and assert - expect(function () { - AtomicBuilder.prototype.addCssRule(className, {}); - }).to.throw(TypeError); - }); - it('throws if value has not been passed', function () { - // execute and assert - expect(function () { - AtomicBuilder.prototype.addCssRule(className, property); - }).to.throw(TypeError); - }); - it('throws if breakPoints are not valid', function () { - expect(function () { - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadConfig'); - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - sinon.stub(AtomicBuilder.prototype, 'placeConstants', function (str) { - return str; - }); - - // execute - var atomicBuilder = new AtomicBuilder(); - - // add mediaQueries - atomicBuilder.mediaQueries = { - sm: '@media(min-width:100px)', - md: '@media(min-width:200px)', - lg: '@media(min-width:300px)' - }; - atomicBuilder.addCssRule(className, property, value, ['invalidbp']); - }).to.throw(Error); - }); - it('adds breakPoint rules if breakPoints have been passed', function () { - var expected = { - '.Foo': { - 'font-weight': 'bold' - }, - '.Foo--sm': { - '@media(min-width:100px)': { - 'font-weight': 'bold' - } - }, - '.Foo--md': { - '@media(min-width:200px)': { - 'font-weight': 'bold' - } - }, - '.Foo--lg': { - '@media(min-width:300px)': { - 'font-weight': 'bold' - } - } - }; - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadConfig'); - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - sinon.stub(AtomicBuilder.prototype, 'placeConstants', function (str) { - return str; - }); - - // execute - var atomicBuilder = new AtomicBuilder(); - - // add mediaQueries - atomicBuilder.mediaQueries = { - sm: '@media(min-width:100px)', - md: '@media(min-width:200px)', - lg: '@media(min-width:300px)' - }; - - expect(atomicBuilder.addCssRule(className, property, value, breakPoints)).to.be.true; - expect(atomicBuilder.build).to.deep.equal(expected); - }); - it('adds rule if all params are valid and `breakPoints` has not been passed', function () { - var expected = { - '.Foo': { - 'font-weight': 'bold' - } - }; - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadConfig'); - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - sinon.stub(AtomicBuilder.prototype, 'placeConstants', function (str) { - return str; - }); - - // execute - var atomicBuilder = new AtomicBuilder(); - - expect(atomicBuilder.addCssRule(className, property, value)).to.be.true; - expect(atomicBuilder.build).to.deep.equal(expected); - }); - }); - - // ------------------------------------------------------- - // escapeSelector() - // ------------------------------------------------------- - describe('escapeSelector()', function () { - it('throws if str has not been passed', function () { - // execute and assert - expect(function () { - AtomicBuilder.prototype.escapeSelector(); - }).to.throw(TypeError); - }); - it('returns the original string if the param is not a string', function () { - // execute and assert - expect(AtomicBuilder.prototype.escapeSelector(123)).to.equal(123); - }); - it('returns the processed string if passed', function () { - // execute and assert - expect(AtomicBuilder.prototype.escapeSelector('#atomic .selector-50%')).to.equal('#atomic .selector-50\\%'); - expect(AtomicBuilder.prototype.escapeSelector('#atomic .selector-50%/50%')).to.equal('#atomic .selector-50\\%\\/50\\%'); - expect(AtomicBuilder.prototype.escapeSelector('#atomic .selector-50%::something')).to.equal('#atomic .selector-50\\%\\:\\:something'); - expect(AtomicBuilder.prototype.escapeSelector('#atomic .selector-.50%:.:something')).to.equal('#atomic .selector-\\.50\\%\\:\\.\\:something'); - }); - }); - - // ------------------------------------------------------- - // placeConstants() - // ------------------------------------------------------- - describe('placeConstants()', function () { - it('throws if str has not been passed', function () { - // execute and assert - expect(function () { - AtomicBuilder.prototype.placeConstants(); - }).to.throw(TypeError); - }); - it('throws if config is not set', function () { - // execute and assert - expect(function () { - AtomicBuilder.prototype.placeConstants('test'); - }).to.throw(TypeError); - }); - it('returns the original string if the param is not a string', function () { - // execute and assert - expect(AtomicBuilder.prototype.placeConstants(123)).to.equal(123); - }); - it('returns the original string if the param is not a string', function () { - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadConfig'); - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - - // execute - var atomicBuilder = new AtomicBuilder(); - - // assert - expect(atomicBuilder.placeConstants(123)).equal(123); - }); - it('returns the processed string if passed, in ltr mode', function () { - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadConfig'); - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - - // execute - var atomicBuilder = new AtomicBuilder(); - atomicBuilder.configObj = {}; - atomicBuilder.optionsObj = { - rtl: false - }; - - // assert - expect(atomicBuilder.placeConstants('test-$START')).equal('test-left'); - expect(atomicBuilder.placeConstants('test-$END')).equal('test-right'); - expect(atomicBuilder.placeConstants('test-$START-$END')).equal('test-left-right'); - }); - it('returns the processed string if passed, in rtl mode', function () { - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadConfig'); - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - - // execute - var atomicBuilder = new AtomicBuilder(); - atomicBuilder.configObj = {}; - atomicBuilder.optionsObj = { - rtl: true - }; - - // assert - expect(atomicBuilder.placeConstants('test-$START')).equal('test-right'); - expect(atomicBuilder.placeConstants('test-$END')).equal('test-left'); - expect(atomicBuilder.placeConstants('test-$START-$END')).equal('test-right-left'); - }); - }); - - // ------------------------------------------------------- - // getBuild() - // ------------------------------------------------------- - describe('getBuild()', function () { - it('throws if configObj is not set in the instance', function () { - // execute and assert - expect(function () { - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadConfig'); - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - - // execute - var atomicBuilder = new AtomicBuilder(); - atomicBuilder.getBuild(); - }).to.throw(TypeError); - }); - it('should return the build with no namespace if none is defined', function () { - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadConfig'); - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - - // execute - var atomicBuilder = new AtomicBuilder(); - atomicBuilder.build = { - 'foo': { - 'font-weight': 'bold' - } - }; - atomicBuilder.configObj = { - 'foo': true - }; - atomicBuilder.optionsObj = { - 'foo': true - }; - var result = atomicBuilder.getBuild(); - - // assert - expect(result).to.deep.equal(atomicBuilder.build); - }); - it('should return the build with namespace if defined', function () { - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadConfig'); - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - - // execute - var atomicBuilder = new AtomicBuilder(); - atomicBuilder.build = { - 'foo': { - 'font-weight': 'bold' - } - }; - atomicBuilder.configObj = { - 'foo': true - }; - atomicBuilder.optionsObj = { - namespace: '.baz' - }; - var result = atomicBuilder.getBuild(); - var expected = {}; - expected[atomicBuilder.optionsObj.namespace] = atomicBuilder.build; - - // assert - expect(result).to.deep.equal(expected); - }); - }); - - // ------------------------------------------------------- - // run() - // ------------------------------------------------------- - describe('run()', function () { - it('throws if `id` in atomicObj is missing', function () { - // execute and assert - expect(function () { - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadConfig'); - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - - // instantiation & setup - var atomicBuilder = new AtomicBuilder(); - atomicBuilder.atomicObjs = [{}]; - - // restore the method we're testing - AtomicBuilder.prototype.run.restore(); - - // execute - atomicBuilder.run(); - }).to.throw(TypeError); - }); - it('throws if `id` in atomicObj is not a string', function () { - // execute and assert - expect(function () { - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadConfig'); - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - - // instantiation & setup - var atomicBuilder = new AtomicBuilder(); - atomicBuilder.atomicObjs = [{ - id: {} - }]; - - // restore the method we're testing - AtomicBuilder.prototype.run.restore(); - - // execute - atomicBuilder.run(); - }).to.throw(TypeError); - }); - it('throws if `type` in atomicObj is missing', function () { - // execute and assert - expect(function () { - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadConfig'); - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - - // instantiation & setup - var atomicBuilder = new AtomicBuilder(); - atomicBuilder.atomicObjs = [{ - id: 'foo' - }]; - - // restore the method we're testing - AtomicBuilder.prototype.run.restore(); - - // execute - atomicBuilder.run(); - }).to.throw(TypeError); - }); - it('throws if `type` in atomicObj is not a string', function () { - // execute and assert - expect(function () { - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadConfig'); - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - - // instantiation & setup - var atomicBuilder = new AtomicBuilder(); - atomicBuilder.atomicObjs = [{ - id: 'foo', - type: {} - }]; - - // restore the method we're testing - AtomicBuilder.prototype.run.restore(); - - // execute - atomicBuilder.run(); - }).to.throw(TypeError); - }); - - it('does nothing if rule is not wanted by the config', function () { - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadConfig'); - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - - // instantiation & setup - var atomicBuilder = new AtomicBuilder(); - var mock = sinon.mock(atomicBuilder); - - // set config and atomicObjs before running - atomicBuilder.configObj = {}; - atomicBuilder.atomicObjs = [{ - type: 'pattern', - id: 'font-weight', - name: 'Font weight', - prefix: '.Fw-', - properties: ['font-weight'], - rules: [ - {suffix: 'b', values: 'bold'} - ] - }]; - - // restore the method we're testing - AtomicBuilder.prototype.run.restore(); - - // set expectations - mock.expects('addPatternRule').never(); - - // execute - atomicBuilder.run(); - - // assert - mock.verify(); - }); - - it('throws if ryle `type` is not a `pattern` nor a `rule`.', function () { - expect(function () { - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadConfig'); - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - - // instantiation & setup - var atomicBuilder = new AtomicBuilder(); - - // set config and atomicObjs before running - atomicBuilder.configObj = { - 'font-weight': {} - }; - atomicBuilder.atomicObjs = [{ - type: 'not-pattern-nor-rule', - id: 'font-weight', - name: 'Font weight', - prefix: '.Fw-', - properties: ['font-weight'], - rules: [ - {suffix: 'b', values: 'bold'} - ] - }]; - - // restore the method we're testing - AtomicBuilder.prototype.run.restore(); - - // execute - atomicBuilder.run(); - }).to.throw(Error); - }); - - // ------------------------------------------------------- - // type: pattern - // ------------------------------------------------------- - describe('type: `pattern`', function () { - it('throws if `rules` in atomicObj has been passed but it is not an array', function () { - // execute and assert - expect(function () { - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadConfig'); - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - - // instantiation & setup - var atomicBuilder = new AtomicBuilder(); - atomicBuilder.configObj = { - 'foo': {} - }; - atomicBuilder.atomicObjs = [{ - id: 'foo', - type: 'pattern', - rules: 'abc' - }]; - - // restore the method we're testing - AtomicBuilder.prototype.run.restore(); - - // execute - atomicBuilder.run(); - }).to.throw(TypeError); - }); - it('throws if configObj has fraction but it is not allowed', function () { - // execute and assert - expect(function () { - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadConfig'); - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - - // instantiation & setup - var atomicBuilder = new AtomicBuilder(); - atomicBuilder.configObj = { - 'width': { - fraction: {} - } - }; - atomicBuilder.atomicObjs = [{ - type: 'pattern', - id: 'width', - name: 'Width', - prefix: '.W-', - properties: ['width'] - }]; - - // restore the method we're testing - AtomicBuilder.prototype.run.restore(); - - // execute - atomicBuilder.run(); - }).to.throw(Error); - }); - it('should execute addFractionRules() once if atomicObj is a pattern and has fraction', function () { - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadConfig'); - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - - // instantiation & setup - var atomicBuilder = new AtomicBuilder(); - var mock = sinon.mock(atomicBuilder); - - // set config and atomicObjs before running - atomicBuilder.configObj = { - 'width': { - fraction: {} - } - }; - atomicBuilder.atomicObjs = [{ - type: 'pattern', - id: 'width', - name: 'Width', - prefix: '.W-', - properties: ['width'], - allowFraction: true - }]; - - // restore the method we're testing - AtomicBuilder.prototype.run.restore(); - - // set expectations - mock.expects('addFractionRules').once(); - - // execute - atomicBuilder.run(); - - // assert - mock.verify(); - }); - it('should execute addPatternRule() once if atomicObj is a pattern', function () { - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadConfig'); - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - - // instantiation & setup - var atomicBuilder = new AtomicBuilder(); - var mock = sinon.mock(atomicBuilder); - - // set config and atomicObjs before running - atomicBuilder.configObj = { - 'font-weight': { - b: true - } - }; - atomicBuilder.atomicObjs = [{ - type: 'pattern', - id: 'font-weight', - name: 'Font weight', - prefix: '.Fw-', - properties: ['font-weight'], - rules: [ - {suffix: 'n', values: ['normal']}, - {suffix: 'b', values: ['bold']} - ] - }]; - - // restore the method we're testing - AtomicBuilder.prototype.run.restore(); - - // set expectations - mock.expects('addPatternRule').once(); - - // execute - atomicBuilder.run(); - - // assert - mock.verify(); - }); - - // CUSTOM PATTERN - it('throws if custom has been passed in the config but atomic object does not allow it', function () { - expect(function () { - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadConfig'); - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - - // instantiation & setup - var atomicBuilder = new AtomicBuilder(); - - // set config and atomicObjs before running - atomicBuilder.configObj = { - 'font-weight': { - b: true, - custom: [ - { suffix: '100', values: ['100'] } - ] - } - }; - atomicBuilder.atomicObjs = [{ - type: 'pattern', - id: 'font-weight', - name: 'Font weight', - prefix: '.Fw-', - properties: ['font-weight'], - rules: [ - {suffix: 'n', values: ['normal']}, - {suffix: 'b', values: ['bold']} - ] - }]; - - // restore the method we're testing - AtomicBuilder.prototype.run.restore(); - - // execute - atomicBuilder.run(); - }).to.throw(Error); - }); - it('throws if custom has been passed in the config AND it is allowed, but it\'s not an array', function () { - expect(function () { - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadConfig'); - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - - // instantiation & setup - var atomicBuilder = new AtomicBuilder(); - - // set config and atomicObjs before running - atomicBuilder.configObj = { - 'font-weight': { - b: true, - custom: 'not an array' - } - }; - atomicBuilder.atomicObjs = [{ - type: 'pattern', - id: 'font-weight', - name: 'Font weight', - prefix: '.Fw-', - properties: ['font-weight'], - allowCustom: true, - rules: [ - {suffix: 'n', values: ['normal']}, - {suffix: 'b', values: ['bold']} - ] - }]; - - // restore the method we're testing - AtomicBuilder.prototype.run.restore(); - - // execute - atomicBuilder.run(); - }).to.throw(TypeError); - }); - it('should execute addPatternRule() once if atomicObj is a pattern and has custom values on an atomic object that allows custom values', function () { - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadConfig'); - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - - // instantiation & setup - var atomicBuilder = new AtomicBuilder(); - var mock = sinon.mock(atomicBuilder); - - // set config and atomicObjs before running - atomicBuilder.configObj = { - 'font-weight': { - b: true, - custom: [ - { suffix: '100', values: ['100'] } - ] - } - }; - atomicBuilder.atomicObjs = [{ - type: 'pattern', - id: 'font-weight', - name: 'Font weight', - prefix: '.Fw-', - properties: ['font-weight'], - allowCustom: true - }]; - - // restore the method we're testing - AtomicBuilder.prototype.run.restore(); - - // set expectations - mock.expects('addPatternRule').once(); - - // execute - atomicBuilder.run(); - - // assert - mock.verify(); - }); - - // CUSTOM AUTO SUFFIX PATTERN - it('throws if custom-auto-suffix has been passed in the config but rule does not allow it', function () { - expect(function () { - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadConfig'); - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - - // instantiation & setup - var atomicBuilder = new AtomicBuilder(); - - // set config and atomicObjs before running - atomicBuilder.configObj = { - 'font-weight': { - 'b': true, - 'custom-auto-suffix': [ - {values: ['100']} - ] - } - }; - atomicBuilder.atomicObjs = [{ - type: 'pattern', - id: 'font-weight', - name: 'Font weight', - prefix: '.Fw-', - properties: ['font-weight'], - rules: [ - {suffix: 'n', values: ['normal']}, - {suffix: 'b', values: ['bold']} - ] - }]; - - // restore the method we're testing - AtomicBuilder.prototype.run.restore(); - - // execute - atomicBuilder.run(); - }).to.throw(Error); - }); - it('throws if custom has been passed in the config AND it is allowed, but it\'s not an array', function () { - expect(function () { - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadConfig'); - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - - // instantiation & setup - var atomicBuilder = new AtomicBuilder(); - - // set config and atomicObjs before running - atomicBuilder.configObj = { - 'font-weight': { - b: true, - 'custom-auto-suffix': 'not an array' - } - }; - atomicBuilder.atomicObjs = [{ - type: 'pattern', - id: 'font-weight', - name: 'Font weight', - prefix: '.Fw-', - properties: ['font-weight'], - allowCustom: true, - allowCustomAutoSuffix: true, - rules: [ - {suffix: 'n', values: ['normal']}, - {suffix: 'b', values: ['bold']} - ] - }]; - - // restore the method we're testing - AtomicBuilder.prototype.run.restore(); - - // execute - atomicBuilder.run(); - }).to.throw(TypeError); - }); - it('throws if custom has been passed in the config AND it is allowed, but it\'s not an array of objects', function () { - expect(function () { - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadConfig'); - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - - // instantiation & setup - var atomicBuilder = new AtomicBuilder(); - - // set config and atomicObjs before running - atomicBuilder.configObj = { - 'font-weight': { - b: true, - 'custom-auto-suffix': ['not an array of objects'] - } - }; - atomicBuilder.atomicObjs = [{ - type: 'pattern', - id: 'font-weight', - name: 'Font weight', - prefix: '.Fw-', - properties: ['font-weight'], - allowCustom: true, - allowCustomAutoSuffix: true, - rules: [ - {suffix: 'n', values: ['normal']}, - {suffix: 'b', values: ['bold']} - ] - }]; - - // restore the method we're testing - AtomicBuilder.prototype.run.restore(); - - // execute - atomicBuilder.run(); - }).to.throw(TypeError); - }); - it('should execute addPatternRule() once if atomicObj is a pattern and has custom auto prefix values on an atomic object that allows them', function (done) { - var assertions = 0; - - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadConfig'); - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - sinon.stub(AtomicBuilder.prototype, 'addPatternRule', function (rule) { - switch(rule.values[0]) { - case '100': - expect(rule.suffix).to.equal('a'); - break; - case '200': - expect(rule.suffix).to.equal('b'); - break; - case '11px': - expect(rule.suffix).to.equal(1); - break; - case '12px': - expect(rule.suffix).to.equal(2); - break; - } - assertions++; - if (assertions === 4) { - done(); - } - }); - - // instantiation & setup - var atomicBuilder = new AtomicBuilder(); - - // set config and atomicObjs before running - atomicBuilder.configObj = { - 'font-weight': { - 'custom-auto-suffix': [ - {values: ['100']}, - {values: ['200']} - ] - }, - 'font-size': { - 'custom-auto-suffix': [ - {values: ['11px']}, - {values: ['12px']} - ] - } - }; - atomicBuilder.atomicObjs = [ - { - type: 'pattern', - id: 'font-weight', - name: 'Font weight', - prefix: '.Fw-', - properties: ['font-weight'], - allowCustom: true, - allowCustomAutoSuffix: true - }, - { - type: 'pattern', - id: 'font-size', - name: 'Font size', - prefix: '.Fz-', - properties: ['font-size'], - suffixType: 'numerical', - allowCustom: true, - allowCustomAutoSuffix: true - } - ]; - - // restore the method we're testing - AtomicBuilder.prototype.run.restore(); - - // execute - atomicBuilder.run(); - }); - }); - - // ------------------------------------------------------- - // type: rule - // ------------------------------------------------------- - describe('type: `rule`', function () { - it('throws if `rule` in atomicObj is missing', function () { - // execute and assert - expect(function () { - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadConfig'); - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - - // instantiation & setup - var atomicBuilder = new AtomicBuilder(); - atomicBuilder.configObj = { - 'foo': {} - }; - atomicBuilder.atomicObjs = [{ - id: 'foo', - type: 'rule' - }]; - - // restore the method we're testing - AtomicBuilder.prototype.run.restore(); - - // execute - atomicBuilder.run(); - }).to.throw(TypeError); - }); - it('throws if `rule` in atomicObj is not an object', function () { - // execute and assert - expect(function () { - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadConfig'); - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - - // instantiation & setup - var atomicBuilder = new AtomicBuilder(); - atomicBuilder.configObj = { - 'foo': {} - }; - atomicBuilder.atomicObjs = [{ - id: 'foo', - type: 'rule', - rule: 'abc' - }]; - - // restore the method we're testing - AtomicBuilder.prototype.run.restore(); - - // execute - atomicBuilder.run(); - }).to.throw(TypeError); - }); - it('should execute addRule() once if atomicObj is a rule', function () { - // stub methods - sinon.stub(AtomicBuilder.prototype, 'loadConfig'); - sinon.stub(AtomicBuilder.prototype, 'loadObjects'); - sinon.stub(AtomicBuilder.prototype, 'loadOptions'); - sinon.stub(AtomicBuilder.prototype, 'run'); - - // instantiation & setup - var atomicBuilder = new AtomicBuilder(); - var mock = sinon.mock(atomicBuilder); - - // set config and atomicObjs before running - atomicBuilder.configObj = { - 'foo': true - }; - atomicBuilder.atomicObjs = [{ - type: 'rule', - id: 'foo', - rule: { - '.Foo': { - 'font-weight': 'bold' - } - } - }]; - - // restore the method we're testing - AtomicBuilder.prototype.run.restore(); - - // set expectations - mock.expects('addRule').once(); - - // execute - atomicBuilder.run(); - - // assert - mock.verify(); - }); - }); - }); -}); \ No newline at end of file diff --git a/tests/atomizer.js b/tests/atomizer.js index e84e8f06..84e320ae 100644 --- a/tests/atomizer.js +++ b/tests/atomizer.js @@ -2,378 +2,485 @@ 'use strict'; var expect = require('chai').expect; -var objectAssign = require('object-assign'); +var Atomizer = require('../src/atomizer'); -var atomizer = require('../src/atomizer'); - -// default config -var defaultConfig; - -describe('createCss()', function () { - beforeEach(function () { - defaultConfig = {}; - }); - it ('throws if no configuration is provided', function () { - expect(function () { - atomizer.createCSS(); - }).to.throw(Error); - }); - it ('imports different absurdjs objects if passed as an option', function () { - var result = atomizer.createCSS(defaultConfig, { - require: [__dirname + '/fixtures/fz.js'] +describe('Atomizer()', function () { + describe('constructor()', function () { + it('instantiate without any params', function () { + var atomizer = new Atomizer(); + expect(atomizer).to.be.an.Object; + }); + it('instantiate with the assigned params', function () { + var options = { + verbose: true + }; + var rules = [{ + type: 'pattern', + id: 'border', + name: 'Border', + prefix: 'Bd-', + properties: ['border'] + }]; + var atomizer = new Atomizer(options, rules); + expect(atomizer.rules).to.deep.equal(rules); + expect(atomizer.verbose).to.equal(true); }); - var expected = [ - 'body {', - ' margin: 20px;', - '}', - '' - ].join('\n'); - expect(result).to.equal(expected); - }); - it ('should escape illegal characters', function () { - var result; - var config = { - 'height': { - custom: [ - {suffix: '55%', values: ['55%']} - ] - } - }; - var expected = [ - '#atomic .H-55\\% {', - ' height: 55%;', - '}', - '' - ].join('\n'); - - config = objectAssign(defaultConfig, config); - - result = atomizer.createCSS(config); - - expect(result).to.equal(expected); - }); - it ('should return rules within media queries if breakPoints is specified', function () { - var result; - var config = { - display: { - b: { - breakPoints: ['sm', 'md'] - } - }, - 'padding-end': { - custom: [ - {suffix: 'foo', values: ['10px'], breakPoints: ['sm', 'md', 'lg']} - ] - } - }; - var expected = [ - '#atomic .D-b {', - ' display: block;', - '}', - '#atomic .Pend-foo {', - ' padding-right: 10px;', - '}', - '@media(min-width:767px) {', - ' #atomic .D-b--sm {', - ' display: block;', - ' }', - ' #atomic .Pend-foo--sm {', - ' padding-right: 10px;', - ' }', - '}', - '@media(min-width:992px) {', - ' #atomic .D-b--md {', - ' display: block;', - ' }', - ' #atomic .Pend-foo--md {', - ' padding-right: 10px;', - ' }', - '}', - '@media(min-width:1200px) {', - ' #atomic .Pend-foo--lg {', - ' padding-right: 10px;', - ' }', - '}', - '' - ].join('\n'); - - config = objectAssign(defaultConfig, config); - - result = atomizer.createCSS(config); - - expect(result).to.equal(expected); }); - it ('should return rules in the same order they were declared in rules.js even if config was declared in a different order', function () { - var result; - var config = { - 'padding-end': { - custom: [ - {suffix: 'foo', values: ['10px']} - ] - }, - display: { - b: true - } - }; - var expected = [ - '#atomic .D-b {', - ' display: block;', - '}', - '#atomic .Pend-foo {', - ' padding-right: 10px;', - '}', - '' - ].join('\n'); - - config = objectAssign(defaultConfig, config); - - result = atomizer.createCSS(config); - - expect(result).to.equal(expected); + describe('findClassNames()', function () { + it('returns an array of valid atomic class names', function () { + var atomizer = new Atomizer(); + // duplicate P-55px to make sure we get only one + var result = atomizer.findClassNames('
'); + var expected = ['P-55px', 'H-100%', 'test:h>Op-1:h', 'C-the-best-border-color']; + expect(result).to.deep.equal(expected); + }); }); - it ('should not return a rule if it was set to false in the config', function () { - var result; - var config = { - display: { - b: false, - ib: true - } - }; - var expected = [ - '#atomic .D-ib {', - ' display: inline-block;', - '}', - '' - ].join('\n'); - - config = objectAssign(defaultConfig, config); - - result = atomizer.createCSS(config); - - expect(result).to.equal(expected); + describe('addRules()', function () { + it('throws if a rule with the same prefix already exists', function () { + var rules = [{ + type: 'pattern', + id: 'border', + name: 'Border', + prefix: 'Bd-', + properties: ['border'] + }]; + var atomizer = new Atomizer(null, rules); + expect(function() { + atomizer.addRules([{prefix: 'Bd-'}]); + }).to.throw(); + }); + it('adds a new rule to the atomizer instance and resets the syntax', function () { + var atomizer = new Atomizer(); + var myRules = [{ + type: 'pattern', + id: 'foo', + name: 'foo', + prefix: 'Foo-', + properties: ['foo'] + }]; + atomizer.addRules(myRules); + + expect(atomizer.rules[atomizer.rules.length - 1]).to.deep.equal(myRules[0]); + expect(atomizer.rulesMap).to.have.ownProperty('Foo-'); + expect(atomizer.syntax).to.be.null; + }); }); - // it ('should throw if there\'s nothing to transform into CSS', function () { - // var result; - // var config = { - // display: { - // b: false - // } - // }; - - // expect(function () { - // atomizer.createCSS(config, { namespace: '' }); - // }).to.throw(Error); - // }); -}); - -describe('parse()', function () { - it('Properly identifies Atomic classes found in text', function () { - var result; - var markup = '
Foobar
'; - var classnameObj = {}; - var classnames = atomizer.parse(markup, classnameObj); - expect(classnames.sort()).to.deep.equal(['Ovs-t', 'Bgo-bb'].sort()); - expect(classnameObj).to.deep.equal({'Bgo-bb': 2, 'Ovs-t': 1}); + describe('getSyntax()', function () { + it('returns the same syntax if syntax has not changed', function () { + var rules = [{ + type: 'pattern', + id: 'border', + name: 'Border', + prefix: 'Bd-', + properties: ['border'] + }]; + var atomizer = new Atomizer(null, rules); + var syntax = atomizer.getSyntax(); + var result = atomizer.getSyntax(); + + expect(syntax).to.equal(result); + }); + it('returns a new syntax if syntax has changed', function () { + var rules = [{ + type: 'pattern', + id: 'border', + name: 'Border', + prefix: 'Bd-', + properties: ['border'] + }]; + var atomizer = new Atomizer(null, rules); + var syntax = atomizer.getSyntax(); + atomizer.addRules([{ + type: 'pattern', + id: 'foo', + name: 'Foo', + prefix: 'Foo-', + properties: ['foo'] + }]); + var result = atomizer.getSyntax(); + expect(syntax).to.not.equal(result); + }); }); - - it('Properly identifies custom value classes found in text', function () { - var result; - var markup = '
Foobar
'; - var classnameObj = {}; - var classnames = atomizer.parse(markup, classnameObj); - expect(classnames.sort()).to.deep.equal(['Fz-3em','Bgc-1', 'C-07f', "P-10px", "M-100%", 'Lh-1.2', 'Z-3'].sort()); - expect(classnameObj).to.deep.equal({'Fz-3em': 1, 'Bgc-1': 1, 'C-07f': 1, "P-10px": 1, "M-100%": 1, 'Lh-1.2': 1, 'Z-3': 1}); + describe('getConfig()', function () { + it ('returns a valid config object when given classes and no config', function () { + var atomizer = new Atomizer(); + var classNames = ['P-55px']; + var expected = { + classNames: ['P-55px'] + }; + var result = atomizer.getConfig(classNames); + expect(result).to.deep.equal(expected); + + result = atomizer.getConfig(classNames, {}); + expect(result).to.deep.equal(expected); + }); + it ('returns a valid config object when given classes and existing config', function () { + var atomizer = new Atomizer(); + var classNames = ['P-55px', 'D-b']; + var existingConfig = { + custom: { + heading: '80px' + }, + classNames: ['M-10px', 'D-ib'] + }; + var expected = { + custom: { + heading: '80px' + }, + classNames: ['P-55px', 'D-b', 'M-10px', 'D-ib'] + }; + var result = atomizer.getConfig(classNames, existingConfig); + expect(result).to.deep.equal(expected); + }); + it ('returns a valid config object when given no arguments', function () { + var atomizer = new Atomizer(); + var expected = { + classNames: [] + }; + var result = atomizer.getConfig(); + expect(result).to.deep.equal(expected); + }); }); -}); - -describe('getConfig()', function () { - beforeEach(function () { - defaultConfig = { - 'border': { - custom: [ - { - "suffix": "happyblue", - "values": [ '#F00' ] + describe('getCss()', function () { + it ('returns css by reading an array of class names', function () { + var atomizer = new Atomizer(); + var config = { + classNames: ['P-55px', 'H-100%', 'M-a', 'test:h>Op-1:h', 'test:h_Op-1:h', 'Op-1', 'Op-1!', 'C-333', 'Mt-neg10px', 'W-1/3'] + }; + var expected = [ + '.C-333 {', + ' color: #333;', + '}', + '.H-100\\% {', + ' height: 100%;', + '}', + '.M-a {', + ' margin: auto;', + '}', + '.Mt-neg10px {', + ' margin-top: -10px;', + '}', + '.test:hover>.test\\:h\\>Op-1\\:h:hover, .test:hover .test\\:h_Op-1\\:h:hover, .Op-1 {', + ' opacity: 1;', + '}', + '.Op-1\\! {', + ' opacity: 1 !important;', + '}', + '.P-55px {', + ' padding: 55px;', + '}', + '.W-1\\/3 {', + ' width: 33.3333%;', + '}\n' + ].join('\n'); + var result = atomizer.getCss(config); + expect(result).to.equal(expected); + }); + it ('returns expected css of a helper class', function () { + // set rules here so if helper change, we don't fail the test + var atomizer = new Atomizer(null, [ + // params + { + type: 'helper', + name: 'Foo', + prefix: 'Foo', + declaration: { + 'param0': '$0', + 'param1': '$1' }, - { - "suffix": "2", - "values": [ '#444' ] + rules: { + 'rule': { + 'foo': 'bar' + } } - ] - } - }; - }); - - it('should return valid configuration when provided Atomic classnames', function () { - var config; - var classNames = ['Bd-1', 'Bd-2', 'Fz-3em', 'Fz-4em', 'Lh-1.2', 'Z-3', 'Bgcp-bb', 'C-07f', "P-10px", "M-100%"]; - var expectedConfig = { - 'background-clip': { bb: true }, - color: { - custom: [ - { - "suffix": "07f", - "values": [ "#07f" ] + }, + // empty + { + type: 'helper', + name: 'Bar', + prefix: 'Bar', + declaration: { + 'bar': 'foo' } - ] - }, - padding: { - custom: [ + } + ]); + var config = { + classNames: ['Foo(1,10px)', 'Bar()'] + }; + var expected = [ + 'rule {', + ' foo: bar;', + '}', + '.Foo\\(1\\,10px\\) {', + ' param0: 1;', + ' param1: 10px;', + '}', + '.Bar\\(\\) {', + ' bar: foo;', + '}\n' + ].join('\n'); + var result = atomizer.getCss(config); + expect(result).to.equal(expected); + }); + it ('throws if helper class doesn\'t have a declaration', function () { + expect(function () { + // set rules here so if helper change, we don't fail the test + var atomizer = new Atomizer(null, [ + // empty { - "suffix": "10px", - "values": [ "10px" ] + type: 'helper', + name: 'Bar', + prefix: 'Bar' } - ] - }, - margin: { - custom: [ - { - "suffix": "100%", - "values": [ "100%" ] + ]); + var config = { + classNames: ['Bar()'] + }; + atomizer.getCss(config); + }).to.throw(); + }); + it ('returns expected css value declared in custom', function () { + var atomizer = new Atomizer(); + var config = { + custom: { + 'brand-color': '#400090' + }, + classNames: ['C-brand-color', 'C-custom'] + }; + var expected = [ + '.C-brand-color {', + ' color: #400090;', + '}\n' + ].join('\n'); + var result = atomizer.getCss(config); + expect(result).to.equal(expected); + }); + it ('returns expected css value declared in custom when using numeric keys', function () { + var atomizer = new Atomizer(); + var config = { + custom: { + '1': '10px solid #ccc' + }, + classNames: ['Bdt-1'] + }; + var expected = [ + '.Bdt-1 {', + ' border-top: 10px solid #ccc;', + '}\n' + ].join('\n'); + var result = atomizer.getCss(config); + expect(result).to.equal(expected); + }); + it ('returns expected css value with breakpoints', function () { + var atomizer = new Atomizer(null, [ + { + type: 'pattern', + name: 'Display', + prefix: 'D-', + properties: ['display'], + rules: [ + {suffix: 'n', values: ['none']} + ] + }, + { + type: 'pattern', + name: 'Padding (all edges)', + prefix: 'P-', + properties: ['padding'] + }, + { + type: 'helper', + name: 'Foo', + prefix: 'Foo', + declaration: { + foo: 'bar' } - ] - }, - "line-height": { - custom: [ - { - "suffix": "1.2", - "values": [ "1.2" ] + }, + { + type: 'helper', + name: 'Bar', + prefix: 'Bar', + declaration: { + bar: '$0' } - ] - }, - "z-index": { - custom: [ - { - "suffix": "3", - "values": [ "3" ] - } - ] - }, - "font-size": { - custom: [ - { - "suffix": "4em", - "values": [ "4em" ] - }, - { - "suffix": "3em", - "values": [ "3em" ] + } + ]); + var config = { + custom: { + "foo": "10px" + }, + breakPoints: { + sm: '@media(min-width:400px)' + }, + classNames: ['D-n--sm', 'P-foo--sm', 'Foo()--sm', 'Bar(10px)--sm'] + }; + var expected = [ + '@media(min-width:400px) {', + ' .D-n--sm {', + ' display: none;', + ' }', + ' .P-foo--sm {', + ' padding: 10px;', + ' }', + ' .Foo\\(\\)--sm {', + ' foo: bar;', + ' }', + ' .Bar\\(10px\\)--sm {', + ' bar: 10px;', + ' }', + '}\n' + ].join('\n'); + var result = atomizer.getCss(config); + expect(result).to.equal(expected); + }); + it ('throws if breakpoints aren\'t valid', function () { + var atomizer = new Atomizer(); + var config = { + breakPoints: { + sm: '400px' + }, + classNames: ['D-n--sm'] + }; + expect(function() { + atomizer.getCss(config); + }).to.throw(); + }); + it ('throws if breakpoints aren\'t passed as an object', function () { + var atomizer = new Atomizer(); + var config = { + breakPoints: '400px', + classNames: ['D-n--sm'] + }; + expect(function() { + atomizer.getCss(config); + }).to.throw(); + }); + it ('returns namespaced css when a namespace is specified in options', function () { + var atomizer = new Atomizer(null, [ + { + type: 'pattern', + name: 'color', + prefix: 'C-', + properties: ['color'] + }, + { + type: 'helper', + name: 'foo', + prefix: 'Foo', + declaration: { + 'font-weight': 'bold' } - ] - } - }; - - config = atomizer.getConfig(classNames, {}, true); // To cover a particular use case - expect(config).to.deep.equal(expectedConfig); - + } + ]); + var config = { + custom: { + 'brand-color': '#400090' + }, + classNames: ['C-brand-color', 'Foo()'] + }; + var expected = [ + '#atomic .C-brand-color {', + ' color: #400090;', + '}', + '.atomic .Foo\\(\\) {', + ' font-weight: bold;', + '}\n' + ].join('\n'); + var result = atomizer.getCss(config, {namespace: '#atomic', helpersNS: '.atomic'}); + expect(result).to.equal(expected); + }); + it ('ignores invalid classnames', function () { + var atomizer = new Atomizer(); + var config = { + classNames: ['XXXXX-1'] + }; + var expected = ''; + var result = atomizer.getCss(config); + expect(result).to.equal(expected); + }); + it ('warns the user if an ambiguous class is provided and verbose flag is true', function (done) { + var atomizer = new Atomizer({verbose: true}); + var config = { + classNames: ['C-foo'] + }; + var expected = ''; + // mock console.warn + console.temp = console.warn; + console.warn = function (text) { + var expected = "Class `C-foo` is ambiguous"; + expect(text).to.contain(expected); + done(); + }; + var result = atomizer.getCss(config); + console.warn = console.temp; + expect(result).to.equal(expected); + }); }); - - it('should ignore illegal units of measure', function () { - var expectedConfig = defaultConfig; - var config; - - config = atomizer.getConfig(['Bdrs-1foo'], defaultConfig); - expect(config).to.deep.equal(expectedConfig); - - config = atomizer.getConfig(['Bdrs-foo'], defaultConfig); - expect(config).to.deep.equal(expectedConfig); + // ------------------------------------------------------- + // getPseudo() + // ------------------------------------------------------- + describe('getPseudo()', function () { + it('returns undefined if undefined has been passed', function () { + // execute and assert + expect(Atomizer.getPseudo()).to.be.undefined; + }); + it('returns non abbreviated form if abbreviated form has been passed', function () { + // execute and assert + expect(Atomizer.getPseudo(':h')).to.equal(':hover'); + }); + it('returns non abbreviated form if non abbreviated form has been passed', function () { + // execute and assert + expect(Atomizer.getPseudo(':hover')).to.equal(':hover'); + }); }); - - it('should merge the default config with the newly created config', function () { - var config; - var classNames = ['Bd-1', 'Bd-2', 'C-07f', 'Bgcp-bb', 'Fz-3em']; - var expectedConfig = { - 'background-clip': { - bb: true - }, - color: { - custom: [ - { - "suffix": "07f", - "values": [ "#07f" ] - } - ] - }, - "font-size": { - custom: [ - { - "suffix": "3em", - "values": [ "3em" ] - } - ] - } - }; - - config = atomizer.getConfig(classNames, defaultConfig); - expect(config).to.deep.equal(atomizer.mergeConfigs([defaultConfig, expectedConfig])); + // ------------------------------------------------------- + // escapeSelector() + // ------------------------------------------------------- + describe('escapeSelector()', function () { + it('throws if str has not been passed', function () { + // execute and assert + expect(function () { + Atomizer.escapeSelector(); + }).to.throw(TypeError); + }); + it('returns the original string if the param is not a string', function () { + // execute and assert + expect(Atomizer.escapeSelector(123)).to.equal(123); + }); + it('returns the processed string if passed', function () { + // execute and assert + expect(Atomizer.escapeSelector('#atomic .selector-50%')).to.equal('#atomic .selector-50\\%'); + expect(Atomizer.escapeSelector('#atomic .selector-50%/50%')).to.equal('#atomic .selector-50\\%\\/50\\%'); + expect(Atomizer.escapeSelector('#atomic .selector-50%::something')).to.equal('#atomic .selector-50\\%\\:\\:something'); + expect(Atomizer.escapeSelector('#atomic .selector-.50%:.:something')).to.equal('#atomic .selector-\\.50\\%\\:\\.\\:something'); + }); }); -}); - -describe('mergeConfigs()', function () { - it('should deep merge two config objects', function () { - var primaryConfig = { - 'background-clip': { - bb: true - }, - color: { - custom: [ - { - "suffix": "07f", - "values": [ "#07f" ] - } - ] - } - }; - - var secondaryConfig = { - 'background-clip': { - pb: true, - cb: true - }, - "padding-top": { - custom: [ - { - "suffix": "foo", - "values": [ "231px" ] - } - ] - } - }; + // ------------------------------------------------------- + // replaceConstants() + // ------------------------------------------------------- + describe('replaceConstants()', function () { + it('returns the original string if the param is not a string', function () { + var obj = {}; + var arr = []; + // execute and assert + expect(Atomizer.replaceConstants(123)).to.equal(123); + expect(Atomizer.replaceConstants(obj)).to.equal(obj); + expect(Atomizer.replaceConstants(arr)).to.equal(arr); + expect(Atomizer.replaceConstants(null)).to.equal(null); + expect(Atomizer.replaceConstants(undefined)).to.equal(undefined); - var expectedConfig = { - 'background-clip': { - bb: true, - pb: true, - cb: true - }, - color: { - custom: [ - { - "suffix": "07f", - "values": [ "#07f" ] - } - ] - }, - "padding-top": { - custom: [ - { - "suffix": "foo", - "values": [ "231px" ] - } - ] - } - }; - - var config = atomizer.mergeConfigs([primaryConfig, secondaryConfig]); - - expect(config).to.deep.equal(expectedConfig); - }); - it('should return an empty object if argument is not an array', function () { - var config = atomizer.mergeConfigs(); - expect(config).to.deep.equal({}); - }); - it('should return an empty object if argument is an empty array', function () { - var config = atomizer.mergeConfigs([]); - expect(config).to.deep.equal({}); + }); + it('returns the processed string if passed, in ltr mode', function () { + // assert + expect(Atomizer.replaceConstants('test-$START', false)).equal('test-left'); + expect(Atomizer.replaceConstants('test-$END', false)).equal('test-right'); + expect(Atomizer.replaceConstants('test-$START-$END', false)).equal('test-left-right'); + }); + it('returns the processed string if passed, in rtl mode', function () { + // assert + expect(Atomizer.replaceConstants('test-$START', true)).equal('test-right'); + expect(Atomizer.replaceConstants('test-$END', true)).equal('test-left'); + expect(Atomizer.replaceConstants('test-$START-$END', true)).equal('test-right-left'); + }); }); }); diff --git a/tests/utils.js b/tests/utils.js deleted file mode 100644 index 8cd92136..00000000 --- a/tests/utils.js +++ /dev/null @@ -1,383 +0,0 @@ -/*globals describe,it,afterEach */ -'use strict'; - -var chai = require('chai'); -var sinon = require('sinon'); -var sinonChai = require('sinon-chai'); -var expect = chai.expect; -chai.use(sinonChai); - -var utils = require('../src/lib/utils'); - -describe('utils', function () { - - afterEach(function () { - // restore original methods - var methodName, method; - for(methodName in utils) { - if (utils.hasOwnProperty(methodName)) { - method = utils[methodName]; - if (method.restore) { - method.restore(); - } - } - } - }); - - describe('getNumber()', function() { - it('throws if invalid length', function () { - expect(function () { - utils.getNumber('a100em'); - }).to.throw(TypeError); - }); - it('should return number from a valid length', function () { - var expected = 100; - var lengths = ['100em', '100ex', '100ch', '100rem', '100vh', '100vw', '100vmin', '100vmax', '100px', '100mm', '100cm', '100in', '100pt', '100pc']; - lengths.forEach(function (length){ - expect(utils.getNumber(length)).to.equal(expected); - }); - }); - }); - describe('getUnit()', function() { - it('throws if invalid length', function () { - expect(function () { - utils.getUnit('a100em'); - }).to.throw(TypeError); - }); - it('should return unit from a valid length', function () { - var expected = ['em', 'ex', 'ch', 'rem', 'vh', 'vw', 'vmin', 'vmax', 'px', 'mm', 'cm', 'in', 'pt', 'pc']; - var lengths = ['100em', '100ex', '100ch', '100rem', '100vh', '100vw', '100vmin', '100vmax', '100px', '100mm', '100cm', '100in', '100pt', '100pc']; - lengths.forEach(function (length){ - expect(expected.indexOf(utils.getUnit(length))).to.be.above(-1); - }); - }); - }); - describe('isLength()', function() { - it('should return true if length is valid', function () { - // stub getUnit - sinon.stub(utils, 'getUnit').returns('em'); - expect(utils.isLength('100em')).to.be.true; - expect(utils.isLength(0)).to.be.true; - }); - it('should return false if length is invalid', function () { - // stub getUnit - sinon.stub(utils, 'getUnit').returns('em'); - expect(utils.isLength('abc100em')).to.be.false; - }); - }); - describe('isPercentage()', function() { - it('should return true if percentage is valid', function () { - expect(utils.isPercentage('100%')).to.be.true; - expect(utils.isPercentage('99.5%')).to.be.true; - }); - it('should return false if percentage is invalid', function () { - expect(utils.isPercentage('10%0%')).to.be.false; - expect(utils.isPercentage('a100%')).to.be.false; - expect(utils.isPercentage('10a0%')).to.be.false; - }); - }); - describe('isInteger()', function() { - it('should return true if integer is valid', function () { - expect(utils.isInteger(100)).to.be.true; - expect(utils.isInteger(1)).to.be.true; - }); - it('should return false if integer is invalid', function () { - expect(utils.isInteger(99.5)).to.be.false; - expect(utils.isInteger('a99')).to.be.false; - expect(utils.isInteger('99a')).to.be.false; - }); - }); - describe('isFloat()', function() { - it('should return true if integer is valid', function () { - expect(utils.isFloat(100.5)).to.be.true; - expect(utils.isFloat(0.5)).to.be.true; - }); - it('should return false if integer is invalid', function () { - expect(utils.isFloat(99)).to.be.false; - expect(utils.isFloat('a.99')).to.be.false; - expect(utils.isFloat('99.a')).to.be.false; - }); - }); - describe('isHex()', function() { - it('should return true if hex is valid', function () { - expect(utils.isHex('#000')).to.be.true; - expect(utils.isHex('#000000')).to.be.true; - }); - it('should return false if hex is invalid', function () { - expect(utils.isHex('#0000')).to.be.false; - expect(utils.isHex('red')).to.be.false; - expect(utils.isHex('99')).to.be.false; - }); - }); - describe('isRgb()', function() { - it('should return true if rgb is valid', function () { - expect(utils.isRgb('rgb(0, 255, 1)')).to.be.true; - expect(utils.isRgb('rgb(0,255,1)')).to.be.true; - expect(utils.isRgb('rgb( 0, 255, 1 )')).to.be.true; - }); - it('should return false if rgb is invalid', function () { - expect(utils.isRgb('rgb(0, 256, 1)')).to.be.false; - expect(utils.isRgb('rgb(0, 255 1)')).to.be.false; - expect(utils.isRgb('rgb(1)')).to.be.false; - }); - }); - describe('isRgba()', function() { - it('should return true if rgba is valid', function () { - expect(utils.isRgba('rgba(0, 255, 1, .74)')).to.be.true; - expect(utils.isRgba('rgba(0,255,1, 1)')).to.be.true; - expect(utils.isRgba('rgba( 0, 255, 1 , 0.04)')).to.be.true; - expect(utils.isRgba('rgba( 0, 255, 1 , 1.0)')).to.be.true; - expect(utils.isRgba('rgba( 0, 255, 1 , 0.0)')).to.be.true; - expect(utils.isRgba('rgba( 0, 255, 1 , 0.0000004)')).to.be.true; - expect(utils.isRgba('rgba( 0, 255, 1 , .0)')).to.be.true; - }); - it('should return false if rgba is invalid', function () { - expect(utils.isRgba('rgba(0, 256, 1, 0.4)')).to.be.false; - expect(utils.isRgba('rgba(0, 255, 1)')).to.be.false; - expect(utils.isRgba('rgba(0, 255 1, .2)')).to.be.false; - expect(utils.isRgba('rgba(0, 255, 1, 1.1)')).to.be.false; - expect(utils.isRgba('rgba(0, 255, 1, -1.0)')).to.be.false; - }); - }); - describe('isHsl()', function() { - it('should return true if hsl is valid', function () { - expect(utils.isHsl('hsl(0, 57%, 1%)')).to.be.true; - expect(utils.isHsl('hsl(0, 57%, 100%)')).to.be.true; - expect(utils.isHsl('hsl(50,100%,57%)')).to.be.true; - expect(utils.isHsl('hsl(120,100%,57%)')).to.be.true; - expect(utils.isHsl('hsl( 359, 57%, 30% )')).to.be.true; - }); - it('should return false if hsl is invalid', function () { - expect(utils.isHsl('hsl(360, 100%, 56%)')).to.be.false; - expect(utils.isHsl('hsl(0, 100%, 56)')).to.be.false; - expect(utils.isHsl('hsl(-1, 101%, 100%)')).to.be.false; - expect(utils.isHsl('hsl(0, 50, 100%)')).to.be.false; - expect(utils.isHsl('hsl(1)')).to.be.false; - }); - }); - describe('isHsla()', function() { - it('should return true if hsla is valid', function () { - expect(utils.isHsla('hsla(0, 100%, 1%, .74)')).to.be.true; - expect(utils.isHsla('hsla(0, 100%, 57%, .74)')).to.be.true; - expect(utils.isHsla('hsla(50,100%,57%, 1)')).to.be.true; - expect(utils.isHsla('hsla(120,100%,57%, 1)')).to.be.true; - expect(utils.isHsla('hsla(359,100%,57%, 1)')).to.be.true; - expect(utils.isHsla('hsla( 0, 100%, 57% , 0.04)')).to.be.true; - expect(utils.isHsla('hsla( 0, 100%, 57% , 1.0)')).to.be.true; - expect(utils.isHsla('hsla( 0, 100%, 57% , 0.0)')).to.be.true; - expect(utils.isHsla('hsla( 0, 100%, 57% , 0.0000004)')).to.be.true; - expect(utils.isHsla('hsla( 0, 100%, 57% , .0)')).to.be.true; - }); - it('should return false if hsla is invalid', function () { - expect(utils.isHsla('hsla(360, 100%, 57%, .74)')).to.be.false; - expect(utils.isHsla('hsla(-1, 100%, 57%, .74)')).to.be.false; - expect(utils.isHsla('hsla(0, 101%, 57%, .74)')).to.be.false; - expect(utils.isHsla('hsla(0, 57%, 101%, .74)')).to.be.false; - expect(utils.isHsla('hsla(0, 100%, 1, 0.4)')).to.be.false; - expect(utils.isHsla('hsla(0, 100%, 100%)')).to.be.false; - expect(utils.isHsla('hsla(0, 100% 100%, .2)')).to.be.false; - expect(utils.isHsla('hsla(0, 100%, 100%, 1.1)')).to.be.false; - expect(utils.isHsla('hsla(0, 100%, 100%, -1.0)')).to.be.false; - }); - }); - describe('isColorKeyword()', function() { - it('should return true if color keyword is valid', function () { - - var validKeywords = [ - 'transparent', - 'currentColor', - - 'aliceblue', - 'antiquewhite', - 'aqua', - 'aquamarine', - 'azure', - 'beige', - 'bisque', - 'black', - 'blanchedalmond', - 'blue', - 'blueviolet', - 'brown', - 'burlywood', - 'cadetblue', - 'chartreuse', - 'chocolate', - 'coral', - 'cornflowerblue', - 'cornsilk', - 'crimson', - 'cyan', - 'darkblue', - 'darkcyan', - 'darkgoldenrod', - 'darkgray', - 'darkgreen', - 'darkgrey', - 'darkkhaki', - 'darkmagenta', - 'darkolivegreen', - 'darkorange', - 'darkorchid', - 'darkred', - 'darksalmon', - 'darkseagreen', - 'darkslateblue', - 'darkslategray', - 'darkslategrey', - 'darkturquoise', - 'darkviolet', - 'deeppink', - 'deepskyblue', - 'dimgray', - 'dimgrey', - 'dodgerblue', - 'firebrick', - 'floralwhite', - 'forestgreen', - 'fuchsia', - 'gainsboro', - 'ghostwhite', - 'gold', - 'goldenrod', - 'gray', - 'green', - 'greenyellow', - 'grey', - 'honeydew', - 'hotpink', - 'indianred', - 'indigo', - 'ivory', - 'khaki', - 'lavender', - 'lavenderblush', - 'lawngreen', - 'lemonchiffon', - 'lightblue', - 'lightcoral', - 'lightcyan', - 'lightgoldenrodyellow', - 'lightgray', - 'lightgreen', - 'lightgrey', - 'lightpink', - 'lightsalmon', - 'lightseagreen', - 'lightskyblue', - 'lightslategray', - 'lightslategrey', - 'lightsteelblue', - 'lightyellow', - 'lime', - 'limegreen', - 'linen', - 'magenta', - 'maroon', - 'mediumaquamarine', - 'mediumblue', - 'mediumorchid', - 'mediumpurple', - 'mediumseagreen', - 'mediumslateblue', - 'mediumspringgreen', - 'mediumturquoise', - 'mediumvioletred', - 'midnightblue', - 'mintcream', - 'mistyrose', - 'moccasin', - 'navajowhite', - 'navy', - 'oldlace', - 'olive', - 'olivedrab', - 'orange', - 'orangered', - 'orchid', - 'palegoldenrod', - 'palegreen', - 'paleturquoise', - 'palevioletred', - 'papayawhip', - 'peachpuff', - 'peru', - 'pink', - 'plum', - 'powderblue', - 'purple', - 'red', - 'rosybrown', - 'royalblue', - 'saddlebrown', - 'salmon', - 'sandybrown', - 'seagreen', - 'seashell', - 'sienna', - 'silver', - 'skyblue', - 'slateblue', - 'slategray', - 'slategrey', - 'snow', - 'springgreen', - 'steelblue', - 'tan', - 'teal', - 'thistle', - 'tomato', - 'turquoise', - 'violet', - 'wheat', - 'white', - 'whitesmoke', - 'yellow', - 'yellowgreen' - ]; - - validKeywords.forEach(function (keyword) { - expect(utils.isColorKeyword(keyword)).to.be.true; - }); - }); - it('should return false if color keyword is invalid', function () { - expect(utils.isColorKeyword('superRandomBrightColor')).to.be.false; - }); - }); - describe('isColor()', function() { - it('should return true if color is valid', function () { - expect(utils.isColor('transparent')).to.be.true; - expect(utils.isColor('blue')).to.be.true; - expect(utils.isColor('#000')).to.be.true; - expect(utils.isColor('#000000')).to.be.true; - expect(utils.isColor('rgb(244,255,2)')).to.be.true; - expect(utils.isColor('rgba(244,255,2,.5)')).to.be.true; - expect(utils.isColor('hsl(120,100%,50%)')).to.be.true; - expect(utils.isColor('hsla(120,100%,50%,.5)')).to.be.true; - }); - it('should return false if color is invalid', function () { - expect(utils.isColor('superRandomBrightColor')).to.be.false; - expect(utils.isColor('#0000')).to.be.false; - expect(utils.isColor('rgb(244,256,0)')).to.be.false; - expect(utils.isColor('rgba(244,255,2,1.5)')).to.be.false; - expect(utils.isColor('hsl(360,100%,50%)')).to.be.false; - expect(utils.isColor('hsla(120,101%,50%,.5)')).to.be.false; - }); - }); - describe('indexOf()', function() { - it('throws if argument is not an array', function () { - expect(function() { - utils.indexOf('[1, 2, 3]'); - }).to.throw(Error); - }); - it('should return a function if argument is an array', function () { - expect(utils.indexOf([1, 2, 3]).constructor).to.be.instanceOf(Function); - }); - it('should return a function that performs an indexOf of the passed array and returns false if value is not an index of the array', function () { - expect(utils.indexOf([1, 2, 3]).call('undefined', 4)).to.be.false; - }); - it('should return a function that performs an indexOf of the passed array and returns true if value is not an index of the array', function () { - expect(utils.indexOf([1, 2, 3]).call('undefined', 1)).to.be.true; - }); - }); -});