diff --git a/.bithoundrc b/.bithoundrc index 0b51624a..92cdd0bb 100644 --- a/.bithoundrc +++ b/.bithoundrc @@ -1,6 +1,14 @@ { "ignore": [ "dist/*.js", - "example/*.js" + "example/*.js", + "plugins/postcss/dist/*.js", + "plugins/postcss/node_modules/**/*.js", + "plugins/prefix-browser/dist/*.js", + "plugins/prefix-browser/node_modules/**/*.js", + "plugins/prefix-browser/src/extracted/*.js", + "plugins/prefix-browser/upstream/**/*.js", + "plugins/prefix-iso/**/*.js", + "plugins/prefix-server/**/*.js" ] } \ No newline at end of file diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..e0a0ff7a --- /dev/null +++ b/.eslintignore @@ -0,0 +1,14 @@ +coverage +dist +example +gh_pages +modules +plugins/postcss/dist +plugins/postcss/node_modules +plugins/prefix-browser/dist +plugins/prefix-browser/node_modules +plugins/prefix-browser/src/extracted +plugins/prefix-browser/test-utils/exposed.js +plugins/prefix-browser/upstream +plugins/prefix-iso +plugins/prefix-server \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js index 43d342ee..d68e4145 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,22 +1,24 @@ module.exports = { rules: { indent: [ - 2, 2 + "error", 2 ], quotes: [ - 2, + "error", "single", "avoid-escape" ], "linebreak-style": [ - 2, + "error", "unix" ], semi: [ - 2, + "error", "never" ], - "no-trailing-spaces": [2] + "no-trailing-spaces": "error", + "comma-dangle": "error", + "no-native-reassign": "error" }, parserOptions: { ecmaVersion: 6, diff --git a/.gitignore b/.gitignore index 415ada59..3091757a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ node_modules -gh_pages +coverage \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 2fbd0ea9..4c6e220b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ language: node_js node_js: - - '0.10' - - '0.11' - - '5.0' -script: npm run cover \ No newline at end of file + - node +cache: yarn +script: "yarn install-plugins && yarn run travis" \ No newline at end of file diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 00000000..679eb5a8 --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,62 @@ +# `j2c` development + +If you want to install `j2c` to work on it, first fork the repo and clone the fork locally + +```BASH +$ git clone https://github.com/yourAccount/j2c +``` + +Then add `j2css/j2c` as a remote for pulling, then create your work branch. My advice is to never work on the master branch directly as it will make it easier to pull remote changes and handle merge conflicts (unless you know your way around git, then do whatever you like). + +```BASH +$ cd j2c +$ git remote add j2css https://github.com/j2css/j2c.git +$ git checkout -b your-work-branch-here +``` + +Now install the dependencies and the plugin dependencies ([yarn](https://yarnpkg.com/) is recommended but you can use NPM as well): + +```BASH +$ yarn # resp. npm install +$ yarn run install-plugins # resp. npm run install-plugins +``` + +Now run the full QA suite to verify that everything works fine. + +```BASH +$ yarn run all # runs `lint`, `build`, `cover` (which runs the tests) and `check-coverage` +``` + +While developing, you'll probably want to use `yarn run dev` instead of `all`. It only runs the `build` and `test` tasks. + +While `yarn run build` will only build the core, the `test`, `lint` and `cover` tasks work on the whole project, plugins included. This ensures that changes in the core do not break plugins. + +## working on a plugin. + +The plugins are set up in distinct folders with their own `package.json` and dedicated `build` and `dev` scripts, and they rely on the main tasks to run the tests, linting and coverage. + +When working in a plugin directory the `yarn run go x` command (or `yarn go x`) will be forwarded to the scripts at the `j2c` root (it literally calls `cd ../.. && yarn run x`). + +The test suites of the plugins depend on the `j2c` version at the root of the directory, not on the `npm` dependency. + +## rebasing on top of upstream changes + +If you want to submit a PR but other changes that were committed in the mean time, you can rebase your changes on top of them. + +```BASH +$ git checkout master # resp. your target branch +$ git pull --rebase j2css # the --rebase option gets rids of ugly merge commits +$ git checkout your-work-branch-here +$ git rebase master +``` + +If there are conflicts: + +1. resolve potential conflicts in your text editor (identify the problem files using `git diff` or `git status`) +2. then run + +```BASH +$ git add previously-conflictual-file +$ git rebase continue +``` +rinse and repeat until there are no conflicts anymore. \ No newline at end of file diff --git a/HISTORY.md b/HISTORY.md index 3e9931e2..3be7fcb1 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,21 +1,97 @@ # Change log -## v0.11.1 (2016-03-8) and v0.11.2 - -- Patch bump to fix what `npm` installs by default (I erronously published `v1.0.0-x` on without `--tag next`, twice) Note to self, don't publish in the -wee hours. +## v1.0.0 (WIP) + +### Breaking changes +- The exported object is a factory, users have to create instances + manually as `var j2c = new J2c(options)`. +- Local scope is now per instance rather than per sheet. + - `j2c.sheet()` and `j2c.inline()` return plain strings rather than + `String` objects. + - `j2c.names` holds the local -> global mappings for use in the (v)DOM. +- syntax changes for properties and nested sub-properties. + tl;dr: `{foo_bat: {baz: 5}}` must now be written `{fooBar: {Baz: 5}}` + - underscores are not converted to dashes anymore (use CamelCase) + - sub-properties must start with an upper case letter + - the `*` hack is supported for old IE. +- Removed the partial auto-prefix insertion from the core. There's now a + fully fledged prefix plugin for the browser, and Autoprefixer can be + used on the server by using the PostCSS adapter. +- Revamped the plugin system. + - Removed `$postprocess` plugins that were taking and returning the full + buffer. + - Introduced `filter` system that can patch values on the fly on their + way between the compiler and the buffer. + - Introduced `sink` plugin that allow to subvert the sheet building + process to implement systems like Radium or Descartes. + - Introduced `at` filters that can handle at-rules beside the ones that + j2c supports out of the box. +- Revamped the poorly named `@extend` into `@adopt`, based on @tivac's + Modular CSS `composes`. The semantics are too distant from either `@extend` + or `composes` to use their name witout introducing potential confusion, + hence the new name. + + So: `j2c.sheet({'@adopt .foo': ['.bar']})` where foo is a local class that + adopts the behavior of the global `.bar`. + +### Additions +- Added a namespace mechanism to scope the styles per component wihtout having to + use BEM-like classes or having to create a distinct `j2c` instance for each + component. +- Prefixed at-rules are treated like their unprefixed counterparts. +- Added `@local` as a counterpart to `@global`. +- Consecutive, identical selectors are deduped in the output. Using mixins + won't cause duplicate selectors anymore. + +### Fixes +- `& > &` with a selector list as parent will perform their cartesian product + like SASS and LESS do. +- Robust parsing of selectors. Comas, ampersands and class-like parts + in strings are now ignored by the compiler. + + ```JS + j2c.sheet({'p':{'[foo="&,.bar"]': {color: 'red'}}}) + ``` + + produces + + ```CSS + p[foo="&,.bar"] {color:red} + ``` + + Whereas previous versions would produce + + ```CSS + [foo="p,p.bar_j2c_ubv4pc_knzcqw_ngjme7_1usit1c_9"] {color: red;} + ``` + + Likewise, `':not(p, a, ul, li)'` (a CSS4 addition) will not be split. + +### Size + +2.4Kb mingzipped (+ ~700 bytes). + +## v0.11.1 (2016-03-8) and v0.11.2 (2016-03-17) + +- Patch bump to fix what `npm` installs by default (I erronously published ` + v1.0.0-x` on without `--tag next`, twice) Note to self, don't publish in + the wee hours. ## v0.11.0 -- Refactor the innards so that the source order is completely respected in the output, including conditional at-rules. +- Refactor the innards so that the source order is completely respected in + the output, including conditional at-rules. - Speed ++ - Autoprefix all animation-* and transition. -- Some error messages are now inserted in the result as -error-prefixed at-rules and :pseudo-classes. +- Some error messages are now inserted in the result as -error-prefixed + at-rules and :pseudo-classes. ## v0.10.0 -- At long last allow element selectors without a prepended space at the top level -- Autoprefix animation and animation-name with -webkit- too like we do for `@keyframes`. +- At long last allow element selectors without a prepended space at the top + level +- Autoprefix animation and animation-name with -webkit- too like we do for + `@keyframes`. - Tweaks and cleanup ## v0.9.0 @@ -40,21 +116,26 @@ wee hours. ## v0.8.0 -- classes and animations/@keyframes names are now localized by default in `j2c.sheet()`. +- classes and animations/@keyframes names are now localized by default in + `j2c.sheet()`. - introduced `@global{}` and `:global()` to reach global names - dropped `j2c.scoped()` which is obsoleted by the above. - dropped bulk auto-prefixing. - better at-rules handling. - support for autoDeCamelization of property-names. -- New signature for `j2c.sheet([namespace1, namespace2, ] source)` where `namespace` is an object -with plain -> localized names for classes and animations. -- allow to create custom j2c instances that can be extended with plugins *[needs docs]*. +- New signature for `j2c.sheet([namespace1, namespace2, ] source)` where + `namespace` is an object with plain -> localized names for classes and + animations. +- allow to create custom j2c instances that can be extended with plugins + *[needs docs]*. - Hardened the test suite. -- Bug fix: the source order is now respected in the output, with the caveat that, for nested selector, the children appear in source before the parent. +- Bug fix: the source order is now respected in the output, with the caveat + that, for nested selector, the children appear in source before the parent. ## v0.7.3 -- Identical to `v0.7.2`. Somehow a bad commit ended up on NPM for that version. +- Identical to `v0.7.2`. Somehow a bad commit ended up on NPM for that + version. ## v0.7.2 diff --git a/PLUGINS.md b/PLUGINS.md new file mode 100644 index 00000000..85d0befc --- /dev/null +++ b/PLUGINS.md @@ -0,0 +1,184 @@ +# Notes for plugin authors + +A plugin can be an object with one or more of the following fields: `atrule`, `filter` `set` and `sink` or an array of zero or more plugins (nested arrays are thus supported). + +The plugins can only be registered when a j2c instance is created using `var j2c = new J2c({plugins: [plugin1, ...]}) + +## Overview of the types of plugins + +`j2c` is at heart a simple compiler made of a frontend and a backend. + +- The frontend that walks a tree made of objects and arrays (as if it were an AST) and calls the backend when necessary to emit atrules, rules and declarations. + +- A backend is made of a sink and optional filters. Filters intercept and modify the values sent to the sink. The default backend is made of a sink that fills a buffer with strings and `join()`s them on completion. + +`j2c` plugin can either tap into the frontend (by implementing custom at-rules), and replace the sink, augment it with filters and/or set additional properties and methods on the instance. + +## `set` plugins: set additional properties and methods on the instance. + +A `set` plugin is an object with a `set` property whose value is an object. The keys and values of that last object will be copied onto the instance that is being built using a `_.defaults()` semantics, meaning that native `j2c` methods can't be overwritten. If there are more than one `set` plugin, they are processed from the last to the first one, so the properties of the last plugin will prevail on those of the previous ones. + +## `sink` plugins: replace the very end of the backend + +A `j2c` backend must provide the following API: + +```JS +{ + init() {}, + // called on `j2c.sheet()/j2c.inline()` invocations before + // the sheet is processed. + done() {}, + // when the tree traversal is finished, to finalize + // the style sheet + err(message) {}, + // when an error is noticed, allows to display errors + // in context. + raw(txt) {}, + // inserts `txt` as is into the buffer + decl(property, value), + // inserts a declaration into the sheet + rule(selector) {}, + // opens a rule with a given selector. At this point in the pipeline, + // the selector is fully formed. Class names have been localized and + // nested selectors have been merged. + _rule() {}, + // signals the end of a ruleset + atrule(name, kind, param, block) + // when an atrule is encountered. + // `name` is the rule name in full includin the '@' and the + // prefix when there's one. e.g. '@-webkit-keyframes' + // `kind` is the type of at-rule, without '@' nor prefix + // e.g. 'keyframes' + // `param` is the optional parameter + // `block` is a tells whether the at-rule takes a block or + // not. Values: + // - falsy for @namespace, @charset and @import + // - 'decl' if the block contains declarations like @font-face + // - 'rule' if the block contains rules like @media or @keyframes + _atrule(){} + // called to signal the end of an atrule block +} +``` + +`j2c.inline()` uses the same API but restricted to `init()`, `done()`, `err(msg)` and `decl(property, value)`. + +As mentioned above, The default sink pushes the various arguments into a buffer and `.join()`s it on `done()` and returns the resulting string. Smarter sinks that deduplicate rules or insert them one at a time using sheet.insertRule() on the fly for speed. + +A filter plugin is an object with a `sink` property whose value is a function that returns an array of one or two backends. The first backend is intended for `j2c.sheet()`, the second, optional, for `j2c.inline()`. In the absence of a second sink `j2c` creates one out of the methods of the first one (`init`, `done`, `err`, `raw` and `decl`). +The function is called once when a `j2c` instance is created. + +### Example + +The following sink will produce a string with CSS indented by 4 spaces: + +```JS +export const prettySink = {sink: function(){ + var buf, err, indent + return [{ + init: function(){buf=[], err=0, indent = ''}, + done: function (raw) { + if (err) throw new Error('j2c error, see below\n' + buf.join('')) + return raw ? buf : buf.join('') + }, + err: function(msg) { + err = 1 + buf.push('/* +++ ERROR +++ ' + msg + ' */\n') + }, + raw: function(txt) { + buf.push(txt, '\n') + }, + atrule: function(rule, kind, param, takesBlock) { + buf.push(indent, rule, param && ' ', param, takesBlock ? ' {' : ';', '\n') + if (takesBlock) indent = indent + ' ' + }, + // close atrule + _atrule: function() { + indent = indent.slice(4) + buf.push(indent, '}', '\n') + }, + rule: function(selector) { + buf.push(indent, selector, ' {', '\n') + indent = indent + ' ' + }, + + // close rule + _rule: function() { + indent = indent.slice(4) + buf.push(indent, '}', '\n') + }, + decl: function (prop, value) {buf.push(indent, prop, prop && ': ', value, ';', '\n')} + }] +}} +``` + +## `filter` plugins: augment a backend. + +Be sure to read the `sink` section first. Understanding the `sink` API is necessary to write `filter` plugins. + +A filter plugin receive the next backend as an arguement and return a new one, augmented with some capabilites. The filter methods must call the methods of the backend it recieved as an arguement. The methods are optional. When a method is missing `j2c` automatically defers to the next one. + +It is made of an object with a `filter` property whose value is a function that takes two parameters: `next` and `inline`. `next` is the next part of the backend (a `sink`, possibly augmented by previous filters). `inline` is truthy for `j2c.inlne()` filters and falsy for `j2c.sheet()` filters. The plugin function is called twice when the `j2c` instance is created, once for `inline` and once for `sheet`. The fields of its return value should not be mutated once the wrapper function has returned. + +Example + +Here's a simple filter that makes each declaration `!important` + +```JS +export const importantFilter = {filter: function(next) { + return { + decl: function(prop, value) { + next.decl( + prop, + /!important$/.test(value) + ? value + : value + ' !important' + ) + } + } +}} +``` + +Calling the next methods rather than returning values has advantages and drawbacks. On the pro side: + +- it allows to forward more than one value without allocating anything on the heap. +- it allows to call the next method several times (the prefix plugin, turns `flex-flow:...` into `-webkit-box-direction:...` plus `-webkit-box-orient:...` by calling `next.decl()` twice, for example). +- it allows to buffer the calls and re-create an AST, as done by the PostCSS plugin. The various methods of the filter build a tree. On `done()`, the PostCSS processor is called and the resulting tree is walked. The walkers then call the `next.x` handlers. +- The functions are generally small and can be inlined by the JIT compilers. Unless it materializes the AST as the PostCSS plugin does, the engine iterates only once over the source tree. + +The main drawback is that it allows one to call the `next` functions in an incorrect order and to produce an invalid style sheet. + +## `atrule` plugins. + +**WIP (TODO document the `declarations()` and `rules()` and `state.localizeReplacer` functions)** + +`atrule` plugins allow one to handle custom at-rules that take precedence over the default ones. + +An `atrule` plugin is an object with a `atrule` property whose value is a function with the following signature: + +```JS +function atrulePlugin( + state, + backend, + rule, + argOrBlock, + selectorPrefix, + local, + nestingDepth +):bool {} +``` + +If it should return `true` if it handled the rule it was given and `false` otherwise. + +- `state` contains the state-related functions and properties of the `j2c` instance. a `localize()` method that registers a new local name an returns it mangled, a `localizeReplacer` used as a second argument to `aString.replace(regexp, replacer)` and a `names` object that contains the plain => mangled local names mapping. It also holds an array with the at-rules plugins. + +- `backend` is the backend whose methods are described in the `sink` section. + +- `[match, name, kind, param] = rule` is an array, the result of a regexp match. For example, `"@-webkit-keyframes foo"` will map to `["@-webkit-keyframes foo", "@-webkit-keyframes", "keyframes", "foo"]`, and `"@namespace"` will map to `["@namespace", "@namespace", "namespace", ""]`. + +- `argOrBlock` is either the argument for at-rules that don't take blocks: `@import`, `@namespace` and `@charset`, or the nested block for rules that take one (all of the others). + +- `selectorPrefix` is the selector at this point in the tree. Since `j2c` supports nested selectors and at-rules, the final selector emitted by `backend.rule()` may be longer, augmented by sub-selectors defined in the `argOrBlock` sub-tree. + +- `local` is a boolean true when we are in local scope, false otherwise + +- `nestingDepth` is a number that reflects how deep we are in the tree. 0 at the root, then incremented at each level. Arrays in the tree don't increment the depth. \ No newline at end of file diff --git a/README.md b/README.md index 1e3d8d02..6b071b2f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ +# Warning, WIP: these docs haven't been updated to match the changes since v0.11 + +See HISTORY.md for a high level overview of the differences. + # j2c [![npm][npm_img]][npm_url] ![.min.gz][size_img] [![Join the chat at https://gitter.im/j2css/j2c][gitter_img]][gitter_url] @@ -7,44 +11,78 @@ [![bitHound Score][bithound_img]][bithound_url] [![downloads][monthly_img]][monthly_url] +[trav_img]: https://travis-ci.org/j2css/j2c.svg?branch=master +[trav_url]: https://travis-ci.org/j2css/j2c +[cov_img]: https://coveralls.io/repos/j2css/j2c/badge.svg?branch=master +[cov_url]: https://coveralls.io/r/j2css/j2c?branch=master +[npm_img]: https://img.shields.io/npm/v/j2c.svg +[npm_url]: https://npmjs.com/package/j2c +[size_img]: https://badges.herokuapp.com/size/npm/j2c/dist/j2c.global.min.js.gz?label=.min.gz +[deps_img]: https://david-dm.org/j2css/j2c.svg +[deps_url]: https://david-dm.org/j2css/j2c +[bithound_img]: https://www.bithound.io/github/j2css/j2c/badges/score.svg +[bithound_url]: https://www.bithound.io/github/j2css/j2c/ +[gitter_img]: https://badges.gitter.im/Join%20Chat.svg +[gitter_url]: https://gitter.im/j2css/j2c + + +A lean (2.2KB), no hassle CSS in JS solution. + +`j2c`: + +- supports all CSS features, and then some (most notably, local scope). +- scales from standalone ` + + +

j2c-plugin-prefix-browser in action in your browser

+ + + diff --git a/plugins/prefix-browser/tests/test-detector-atrules.js b/plugins/prefix-browser/tests/test-detector-atrules.js new file mode 100644 index 00000000..189265cd --- /dev/null +++ b/plugins/prefix-browser/tests/test-detector-atrules.js @@ -0,0 +1,170 @@ +var o = require('../test-utils/ospec-instance') + +var cleanupIfNeeded = require('../test-utils/misc').cleanupIfNeeded +var exposed = require('../test-utils/exposed') +var mocks = require('../test-utils/mocks') +var upToDate = require('../test-utils/misc').upToDate + +var hasCleanState = exposed.hasCleanState +var init = exposed.init +var finalize = exposed.finalize +var detectAtrules = exposed.detectAtrules +var blankFixers = exposed.blankFixers + +var referenceFixers = Object.keys(blankFixers()) + + +o.spec('detectAtrules', function() { + var fixers + o.beforeEach(function() { + o(hasCleanState()).equals(true)('detector utils state isn\'t clean') + fixers = blankFixers() + }) + o.afterEach(function() { + cleanupIfNeeded(exposed) + o(Object.keys(fixers)).deepEquals(referenceFixers) + }) + + o('build up to date', function() { + o(upToDate(__dirname, '../src/detectors/atrules.js')).equals(true) + }) + + o('works without options', function(){ + mocks(global) + init() + detectAtrules(fixers) + finalize() + + o(fixers.hasDppx).equals(null) + o(fixers.hasPixelRatio).equals(false) + o(fixers.hasPixelRatioFraction).equals(false) + o(fixers.atrules).deepEquals({}) + }) + o('works with a prefix manually imposed and no options', function(){ + mocks(global) + init() + fixers.prefix = '-o-' + fixers.prefixes = ['-o-'] + detectAtrules(fixers) + finalize() + + o(fixers.hasDppx).equals(false) + o(fixers.hasPixelRatio).equals(false) + o(fixers.hasPixelRatioFraction).equals(false) + o(fixers.atrules).deepEquals({}) + o('min-resolution' in fixers.properties).equals(false) + }) + o('works with a prefix manually imposed and prefixed @keyframes, @viewport and @document', function(){ + mocks(global, { + rules: ['@-o-keyframes name{}', '@-o-viewport {}', '@-o-document regexp("."){}'] + }) + init() + fixers.prefix = '-o-' + fixers.prefixes = ['-o-'] + detectAtrules(fixers) + finalize() + + o(fixers.hasDppx).equals(false) + o(fixers.hasPixelRatio).equals(false) + o(fixers.hasPixelRatioFraction).equals(false) + o(fixers.atrules).deepEquals({'@keyframes':'@-o-keyframes', '@viewport': '@-o-viewport', '@document': '@-o-document'}) + o('min-resolution' in fixers.properties).equals(false) + }) + o('works with two prefixes manually imposed and prefixed @keyframes, @viewport and @document', function(){ + mocks(global, { + rules: ['@-o-keyframes name{}', '@-ms-viewport {}', '@-o-document regexp("."){}'] + }) + init() + fixers.prefix = '-o-' + fixers.prefixes = ['-o-', '-ms-'] + detectAtrules(fixers) + finalize() + + o(fixers.hasDppx).equals(false) + o(fixers.hasPixelRatio).equals(false) + o(fixers.hasPixelRatioFraction).equals(false) + o(fixers.atrules).deepEquals({'@keyframes':'@-o-keyframes', '@viewport': '@-ms-viewport', '@document': '@-o-document'}) + o('min-resolution' in fixers.properties).equals(false) + }) + o('favours unprefixed rules', function(){ + mocks(global, { + rules: ['@-o-keyframes name{}', '@-o-viewport {}', '@-o-document regexp("."){}', '@keyframes name{}', '@viewport {}', '@document regexp("."){}'] + }) + init() + fixers.prefix = '-o-' + detectAtrules(fixers) + finalize() + + o(fixers.hasDppx).equals(false) + o(fixers.hasPixelRatio).equals(false) + o(fixers.hasPixelRatioFraction).equals(false) + o(fixers.atrules).deepEquals({}) + o('min-resolution' in fixers.properties).equals(false) + }) + o('detects dppx', function(){ + mocks(global, { + rules: ['@media (resolution:1dppx){}'] + }) + init() + fixers.prefix = '-o-' + detectAtrules(fixers) + finalize() + + o(fixers.hasDppx).equals(true) + o(fixers.hasPixelRatio).equals(false) + o(fixers.hasPixelRatioFraction).equals(false) + o(fixers.atrules).deepEquals({}) + o('min-resolution' in fixers.properties).equals(false) + }) + o('detects -webkit-device-pixel-ratio', function(){ + mocks(global, { + rules: ['@media (-webkit-device-pixel-ratio:1){}'] + }) + init() + fixers.prefix = '-webkit-' + detectAtrules(fixers) + finalize() + + o(fixers.hasDppx).equals(false) + o(fixers.hasPixelRatio).equals(true) + o(fixers.hasPixelRatioFraction).equals(false) + o(fixers.atrules).deepEquals({}) + o(fixers.properties['resolution']).equals('-webkit-device-pixel-ratio') + o(fixers.properties['min-resolution']).equals('-webkit-min-device-pixel-ratio') + o(fixers.properties['max-resolution']).equals('-webkit-max-device-pixel-ratio') + }) + o('detects -moz-device-pixel-ratio', function(){ + mocks(global, { + rules: ['@media (-moz-device-pixel-ratio:1){}', '@media (min--moz-device-pixel-ratio:1){}'] + }) + init() + fixers.prefix = '-moz-' + detectAtrules(fixers) + finalize() + + o(fixers.hasDppx).equals(false) + o(fixers.hasPixelRatio).equals(true) + o(fixers.hasPixelRatioFraction).equals(false) + o(fixers.atrules).deepEquals({}) + o(fixers.properties['resolution']).equals('-moz-device-pixel-ratio') + o(fixers.properties['min-resolution']).equals('min--moz-device-pixel-ratio') + o(fixers.properties['max-resolution']).equals('max--moz-device-pixel-ratio') + }) + o('detects -o-device-pixel-ratio', function(){ + mocks(global, { + rules: ['@media (-o-device-pixel-ratio:1/1){}'] + }) + init() + fixers.prefix = '-o-' + detectAtrules(fixers) + finalize() + + o(fixers.hasDppx).equals(false) + o(fixers.hasPixelRatio).equals(false) + o(fixers.hasPixelRatioFraction).equals(true) + o(fixers.atrules).deepEquals({}) + o(fixers.properties['resolution']).equals('-o-device-pixel-ratio') + o(fixers.properties['min-resolution']).equals('-o-min-device-pixel-ratio') + o(fixers.properties['max-resolution']).equals('-o-max-device-pixel-ratio') + }) +}) diff --git a/plugins/prefix-browser/tests/test-detector-functions.js b/plugins/prefix-browser/tests/test-detector-functions.js new file mode 100644 index 00000000..78fece84 --- /dev/null +++ b/plugins/prefix-browser/tests/test-detector-functions.js @@ -0,0 +1,91 @@ +var o = require('../test-utils/ospec-instance') + +var cleanupIfNeeded = require('../test-utils/misc').cleanupIfNeeded +var exposed = require('../test-utils/exposed') +var mocks = require('../test-utils/mocks') +var upToDate = require('../test-utils/misc').upToDate + +var hasCleanState = exposed.hasCleanState +var init = exposed.init +var finalize = exposed.finalize +var detectFunctions = exposed.detectFunctions +var blankFixers = exposed.blankFixers + +var referenceFixers = Object.keys(blankFixers()) + + +o.spec('detectFunctions', function() { + var fixers + o.beforeEach(function() { + o(hasCleanState()).equals(true)('detector utils state isn\'t clean') + fixers = blankFixers() + }) + o.afterEach(function() { + cleanupIfNeeded(exposed) + o(Object.keys(fixers)).deepEquals(referenceFixers) + fixers = null + }) + + o('build up to date', function() { + o(upToDate(__dirname, '../src/detectors/functions.js')).equals(true) + }) + + o('works when no prefix are supported', function(){ + mocks(global, {properties: {backgroundImage:'red', width: '0px'}}) + init() + detectFunctions(fixers) + finalize() + + o(fixers.functions.length).equals(0) + }) + o('works with a prefix but no valid functions', function(){ + mocks(global, {properties: {backgroundImage:'red', width: '0px'}}) + init() + fixers.prefix = '-o-' + detectFunctions(fixers) + finalize() + + o(fixers.functions.length).equals(0) + }) + o('works with a prefix and unprefixed functions', function(){ + mocks(global, {properties: { + backgroundImage: ['linear-gradient(red, teal)', 'radial-gradient(red, teal)', 'element(#foo)', 'cross-fade(url(a.png), url(b.png), 50%)'], + width: 'calc(1px + 5%)' + }}) + init() + fixers.prefix = '-o-' + detectFunctions(fixers) + finalize() + + o(fixers.functions.length).equals(0) + }) + o('works with a prefix and prefixed functions', function(){ + mocks(global, {properties: { + backgroundImage: ['-o-linear-gradient(red, teal)', '-o-radial-gradient(red, teal)', '-o-element(#foo)', '-o-cross-fade(url(a.png), url(b.png), 50%)'], + width: '-o-calc(1px + 5%)', + zIndex: '0' + }}) + init() + fixers.prefix = '-o-' + detectFunctions(fixers) + finalize() + + o(fixers.functions.sort()).deepEquals(['calc', 'cross-fade', 'element', 'linear-gradient', 'radial-gradient']) + }) + + o('works with a prefix and both prefixed and unprefixed functions', function(){ + mocks(global, {properties: { + backgroundImage: [ + 'linear-gradient(red, teal)', 'radial-gradient(red, teal)', 'element(#foo)', 'cross-fade(url(a.png), url(b.png), 50%)', + '-o-linear-gradient(red, teal)', '-o-radial-gradient(red, teal)', '-o-element(#foo)', '-o-cross-fade(url(a.png), url(b.png), 50%)' + ], + width: ['calc(1px + 5%)', '-o-calc(1px + 5%)'] + }}) + init() + fixers.prefix = '-o-' + detectFunctions(fixers) + finalize() + + o(fixers.functions.length).equals(0) + }) +}) diff --git a/plugins/prefix-browser/tests/test-detector-keywords.js b/plugins/prefix-browser/tests/test-detector-keywords.js new file mode 100644 index 00000000..1b7a65d4 --- /dev/null +++ b/plugins/prefix-browser/tests/test-detector-keywords.js @@ -0,0 +1,187 @@ +var o = require('../test-utils/ospec-instance') + +var cleanupIfNeeded = require('../test-utils/misc').cleanupIfNeeded +var exposed = require('../test-utils/exposed') +var mocks = require('../test-utils/mocks') +var upToDate = require('../test-utils/misc').upToDate + +var blankFixers = exposed.blankFixers +var hasCleanState = exposed.hasCleanState +var camelCase= exposed.camelCase +var deCamelCase= exposed.deCamelCase +var detectKeywords = exposed.detectKeywords +var finalize = exposed.finalize +var init = exposed.init + +var referenceFixers = Object.keys(blankFixers()) + +// ----------------------------------- +// define options and expected results + +var noFlexSrc = { + 'color': ['initial'], + 'cursor': [ 'grab', 'grabbing', 'zoom-in', 'zoom-out'], + 'display' : ['grid', 'inline-grid'], + 'position': ['sticky'], + 'width, columnWidth, height, maxHeight, maxWidth, minHeight, minWidth': ['contain-floats', 'fill-available', 'fit-content', 'max-content', 'min-content'] +} + +var unprefixed = {}, prefixed = {}, both = {}, result = {} +var properties = {noFlex: {}, flex: {}, box: {}, flexbox: {}} +var emptyResult = [ + 'cursor', 'display', 'position', 'width', 'columnWidth', 'height', + 'maxHeight', 'maxWidth', 'minHeight', 'minWidth' +].reduce(function(acc, k){ + acc[deCamelCase(k)] = {} + return acc +}, {}) + +unprefixed.noFlex = Object.keys(noFlexSrc).reduce(function(acc, kk){ + kk.split(', ').forEach(function(k) { + acc[k] = noFlexSrc[kk] + }) + return acc +}, {}) +unprefixed.flex = {display:['flex', 'inline-flex']} +unprefixed.box = {display:['box', 'inline-box']} +unprefixed.flexbox = {display:['flexbox', 'inline-flexbox']} + +;['noFlex', 'flex', 'box', 'flexbox'].forEach(function(flexType) { + prefixed[flexType] = Object.keys(unprefixed[flexType]).reduce(function(acc, k) { + acc[k] = unprefixed[flexType][k].map(function(v) {return '-o-' + v}) + return acc + }, {}) + + both[flexType] = Object.keys(unprefixed[flexType]).reduce(function(acc, k) { + acc[k] = unprefixed[flexType][k].concat(prefixed[flexType][k]) + return acc + }, {}) + + result[flexType] = Object.keys(emptyResult).reduce(function(acc, k) { + if (k !== 'color' && camelCase(k) in unprefixed[flexType]) acc[k] = unprefixed[flexType][camelCase(k)].reduce(function(acc2, v){ + acc2[v] = '-o-' + v + return acc2 + }, {}) + else acc[k] = {} + return acc + }, {}) +}) + + +Object.assign(properties.flexbox, exposed.flex2012Props) +Object.keys(exposed.flex2012Props).forEach(function(p) { + result.flexbox[p] = exposed.flex2012Values +}) +result.flexbox.display.flex = result.flexbox.display.flexbox +result.flexbox.display['inline-flex'] = result.flexbox.display['inline-flexbox'] + + +Object.assign( + properties.box, + Object.keys(exposed.flex2009Props).reduce(function(acc, k){ + acc[k] = '-o-' + exposed.flex2009Props[k] + return acc + }, {}) +) +Object.keys(exposed.flex2009Props).forEach(function(p) { + result.box[p] = exposed.flex2009Values +}) +result.box.display.flex = result.box.display.box +result.box.display['inline-flex'] = result.box.display['inline-box'] + + +// ----------------------------------- + +o.spec('detectKeywords', function() { + var fixers + o.beforeEach(function() { + o(hasCleanState()).equals(true)('detector utils state isn\'t clean') + fixers = blankFixers() + }) + o.afterEach(function() { + cleanupIfNeeded(exposed) + o(Object.keys(fixers)).deepEquals(referenceFixers) + fixers = null + }) + + o('build up to date', function() { + + o(upToDate(__dirname, '../src/detectors/keywords.js')).equals(true) + }) + + o('works when no prefix are supported', function(){ + mocks(global) + init() + detectKeywords(fixers) + finalize() + + o(fixers.keywords).deepEquals({}) + o(fixers.initial).equals(null) + }) + o('works with a prefix but no valid keyword', function(){ + mocks(global) + init() + fixers.prefix = '-o-' + fixers.prefixes = ['-o-'] + detectKeywords(fixers) + finalize() + + o(fixers.keywords).deepEquals(emptyResult) + o(fixers.initial).equals(null) + }) + + ;['noFlex', 'flex', 'box', 'flexbox'].forEach(function(flexType) { + o('works with a prefix and unprefixed keywords (' + flexType + ')', function(){ + mocks(global, {properties: unprefixed[flexType]}) + init() + fixers.prefix = '-o-' + fixers.prefixes = ['-o-'] + detectKeywords(fixers) + finalize() + + o(fixers.keywords).deepEquals(emptyResult) + o(fixers.initial).equals(null) + }) + o('works with a prefix and prefixed keywords (' + flexType + ')', function(){ + mocks(global, {properties: prefixed[flexType]}) + init() + fixers.prefix = '-o-' + fixers.prefixes = ['-o-'] + detectKeywords(fixers) + finalize() + o(fixers.keywords).deepEquals(result[flexType]) + o(fixers.initial).equals(flexType === 'noFlex' ? '-o-initial' : null) + }) + + o('works with a prefix and both prefixed and unprefixed keywords (' + flexType + ')', function(){ + mocks(global, {properties: both[flexType]}) + init() + fixers.prefix = '-o-' + fixers.prefixes = ['-o-'] + detectKeywords(fixers) + finalize() + + o(fixers.keywords).deepEquals(emptyResult) + o(fixers.initial).equals(null) + }) + }) + o('works for secondary prefixes', function(){ + mocks(global, {properties: {display: ['flex', '-ms-grid', '-o-inline-grid']}}) + init() + fixers.prefix = '-o-' + fixers.prefixes = ['-o-', '-ms-'] + detectKeywords(fixers) + finalize() + o(fixers.keywords.display).deepEquals({grid: '-ms-grid', 'inline-grid': '-o-inline-grid'}) + o(fixers.initial).equals(null) + }) + o('detects a lack of flexbox support', function() { + mocks(global, {properties: {display: ['grid']}}) + init() + fixers.prefix = '-ms-' + fixers.prefixes = ['-ms-'] + detectKeywords(fixers) + finalize() + o(fixers.jsFlex).equals(true) + }) +}) diff --git a/plugins/prefix-browser/tests/test-detector-prefix.js b/plugins/prefix-browser/tests/test-detector-prefix.js new file mode 100644 index 00000000..a2c99e6c --- /dev/null +++ b/plugins/prefix-browser/tests/test-detector-prefix.js @@ -0,0 +1,124 @@ +var o = require('../test-utils/ospec-instance') + +var cleanupIfNeeded = require('../test-utils/misc').cleanupIfNeeded +var exposed = require('../test-utils/exposed') +var mocks = require('../test-utils/mocks') +var upToDate = require('../test-utils/misc').upToDate + +var init = exposed.init +var hasCleanState = exposed.hasCleanState +var finalize = exposed.finalize +var detectPrefix = exposed.detectPrefix +var blankFixers = exposed.blankFixers + +var referenceFixers = Object.keys(blankFixers()) + + +o.spec('detectPrefix', function() { + var fixers + o.beforeEach(function() { + o(hasCleanState()).equals(true)('detector utils state isn\'t clean') + fixers = blankFixers() + }) + o.afterEach(function() { + cleanupIfNeeded(exposed) + // no key was added at run time (that would cause deopts) + o(Object.keys(fixers)).deepEquals(referenceFixers) + fixers = null + }) + + o('build up to date', function() { + o(upToDate(__dirname, '../src/detectors/prefix.js')).equals(true) + }) + + o('no properties', function() { + mocks(global) + init() + detectPrefix(fixers) + finalize() + + o(fixers.prefix).equals('') + }) + ;[true, false].forEach(function(computedStyleAsArray) { + o('properties without a prefix ('+ (computedStyleAsArray ? 'computed style as array' : 'computed style as object') +')', function() { + mocks(global, { + computedStyleAsArray: computedStyleAsArray, + properties: { + color: 'red', + width: '0' + } + }) + init() + detectPrefix(fixers) + finalize() + + o(fixers.prefix).equals('') + o(fixers.prefixes).deepEquals([]) + }) + o('properties with a single prefix ('+ (computedStyleAsArray ? 'computed style as array' : 'computed style as object') +')', function() { + mocks(global, { + computedStyleAsArray: computedStyleAsArray, + properties: { + MozColor: 'red', + width: '0' + } + }) + init() + detectPrefix(fixers) + finalize() + + o(fixers.prefix).equals('-moz-') + o(fixers.prefixes).deepEquals(['-moz-']) + }) + o('properties with two prefixes, majority prefix first ('+ (computedStyleAsArray ? 'computed style as array' : 'computed style as object') +')', function() { + mocks(global, { + computedStyleAsArray: computedStyleAsArray, + properties: { + MozColor: 'red', + MozMargin: '0', + OMargin: '0', + width: '0' + } + }) + init() + detectPrefix(fixers) + finalize() + + o(fixers.prefix).equals('-moz-') + o(fixers.prefixes).deepEquals(['-moz-', '-o-']) + }) + o('properties with two prefixes, majority prefix last ('+ (computedStyleAsArray ? 'computed style as array' : 'computed style as object') +')', function() { + mocks(global, { + computedStyleAsArray: computedStyleAsArray, + properties: { + OMargin: '0', + MozColor: 'red', + MozMargin: '0', + width: '0' + } + }) + init() + detectPrefix(fixers) + finalize() + + o(fixers.prefix).equals('-moz-') + o(fixers.prefixes).deepEquals(['-moz-', '-o-']) + }) + o('edge hack detection('+ (computedStyleAsArray ? 'computed style as array' : 'computed style as object') +')', function() { + mocks(global, { + computedStyleAsArray: computedStyleAsArray, + properties: { + WebkitColor: 'red', + width: '0' + }, + rules: ['_:-ms-lang(x), _:-webkit-full-screen{}'] + }) + init() + detectPrefix(fixers) + finalize() + + o(fixers.prefix).equals('-webkit-') + o(fixers.prefixes).deepEquals(['-webkit-', '-ms-']) + }) + }) +}) diff --git a/plugins/prefix-browser/tests/test-detector-properties.js b/plugins/prefix-browser/tests/test-detector-properties.js new file mode 100644 index 00000000..2a4d6c0a --- /dev/null +++ b/plugins/prefix-browser/tests/test-detector-properties.js @@ -0,0 +1,69 @@ +var o = require('../test-utils/ospec-instance') + +var cleanupIfNeeded = require('../test-utils/misc').cleanupIfNeeded +var exposed = require('../test-utils/exposed') +var mocks = require('../test-utils/mocks') +var upToDate = require('../test-utils/misc').upToDate + +var init = exposed.init +var hasCleanState = exposed.hasCleanState +var finalize = exposed.finalize +// needed because the reference is updated by the init() function. +function supportedProperty (p) {return exposed.supportedProperty(p)} +var blankFixers = exposed.blankFixers + +var referenceFixers = Object.keys(blankFixers()) + + +o.spec('supportedProperty', function() { + var fixers + o.beforeEach(function() { + o(hasCleanState()).equals(true)('detector utils state isn\'t clean') + fixers = blankFixers() + }) + o.afterEach(function() { + cleanupIfNeeded(exposed) + o(Object.keys(fixers)).deepEquals(referenceFixers) + fixers = null + }) + + o('build up to date', function() { + o(upToDate(__dirname, '../src/detectors/core.js')).equals(true) + }) + + o('works without options', function(){ + mocks(global) + init() + finalize() + + o(supportedProperty('foo')).equals(false) + }) + o('works with empty options', function(){ + mocks(global, {}) + init() + finalize() + + o(supportedProperty('foo')).equals(false) + }) + o('detects simple property', function(){ + mocks(global, {properties: {foo: '0', zIndex:'0'}}) + init() + finalize() + + o(supportedProperty('foo')).equals(true) + }) + o('detects property with a dash', function(){ + mocks(global, {properties: {fooBar: '0'}}) + init() + finalize() + + o(supportedProperty('foo-bar')).equals(true) + }) + o('detects property with a prefix', function(){ + mocks(global, {properties: {FooBar: '0'}}) + init() + finalize() + + o(supportedProperty('-foo-bar')).equals(true) + }) +}) diff --git a/plugins/prefix-browser/tests/test-detector-selectors.js b/plugins/prefix-browser/tests/test-detector-selectors.js new file mode 100644 index 00000000..e083dc1b --- /dev/null +++ b/plugins/prefix-browser/tests/test-detector-selectors.js @@ -0,0 +1,214 @@ +var o = require('../test-utils/ospec-instance') + +var cleanupIfNeeded = require('../test-utils/misc').cleanupIfNeeded +var exposed = require('../test-utils/exposed') +var mocks = require('../test-utils/mocks') +var upToDate = require('../test-utils/misc').upToDate + +var init = exposed.init +var hasCleanState = exposed.hasCleanState +var finalize = exposed.finalize +var detectSelectors = exposed.detectSelectors +var blankFixers = exposed.blankFixers + +var referenceFixers = Object.keys(blankFixers()) +'::input-placeholder','::placeholder', ':input-placeholder',':placeholder', + +o.spec('detectSelectors', function() { + var fixers + o.beforeEach(function() { + o(hasCleanState()).equals(true)('detector utils state isn\'t clean') + fixers = blankFixers() + }) + o.afterEach(function() { + cleanupIfNeeded(exposed) + o(Object.keys(fixers)).deepEquals(referenceFixers) + fixers = null + }) + + o('build up to date', function() { + o(upToDate(__dirname, '../src/detectors/selectors.js')).equals(true) + }) + + o('works when no prefix are supported', function(){ + mocks(global) + init() + detectSelectors(fixers) + finalize() + + o(fixers.selectorList.length).equals(0) + o(fixers.selectorMap).deepEquals({}) + }) + o('works with a prefix but no valid selector', function(){ + mocks(global) + init() + fixers.prefix = '-o-' + detectSelectors(fixers) + finalize() + + o(fixers.selectorList.length).equals(0) + o(fixers.selectorMap).deepEquals({}) + }) + o('works with a prefix and unprefixed selectors', function(){ + mocks(global, {rules: [ + ':any-link{}', ':read-only{}', ':read-write{}', '::selection{}' + ]}) + init() + fixers.prefix = '-o-' + detectSelectors(fixers) + finalize() + + o(fixers.selectorList.length).equals(0) + o(fixers.selectorMap).deepEquals({}) + }) + o('works with a prefix and prefixed selectors', function(){ + mocks(global, {rules: [ + ':-o-any-link{}',':-o-read-only{}', ':-o-read-write{}', '::-o-selection{}' + ]}) + init() + fixers.prefix = '-o-' + detectSelectors(fixers) + finalize() + + o(fixers.selectorList.sort()).deepEquals(['::selection', ':any-link', ':read-only', ':read-write']) + o(fixers.selectorMap).deepEquals({ + '::selection': '::-o-selection', + ':any-link': ':-o-any-link', + ':read-only': ':-o-read-only', + ':read-write': ':-o-read-write' + }) + }) + + o('works with a prefix and both prefixed and unprefixed selectors', function(){ + mocks(global, {rules: [ + ':any-link{}', ':read-only{}', ':read-write{}', '::selection{}', + ':-o-any-link{}',':-o-read-only{}', ':-o-read-write{}', '::-o-selection{}' + ]}) + init() + fixers.prefix = '-o-' + detectSelectors(fixers) + finalize() + + o(fixers.selectorList.length).equals(0) + }) + o(':placeholder', function() { + mocks(global, {rules: [':placeholder{}']}) + init() + fixers.prefix = '-o-' + detectSelectors(fixers) + finalize() + + o(fixers.selectorList).deepEquals([]) + o(fixers.selectorMap).deepEquals({}) + }) + o('::placeholder', function() { + mocks(global, {rules: [':placeholder{}']}) + init() + fixers.prefix = '-o-' + detectSelectors(fixers) + finalize() + + o(fixers.selectorList).deepEquals([]) + o(fixers.selectorMap).deepEquals({}) + }) + o(':input-placeholder', function() { + mocks(global, {rules: [':placeholder{}']}) + init() + fixers.prefix = '-o-' + detectSelectors(fixers) + finalize() + + o(fixers.selectorList).deepEquals([]) + o(fixers.selectorMap).deepEquals({}) + }) + o('::input-placeholder', function() { + mocks(global, {rules: [':placeholder{}']}) + init() + fixers.prefix = '-o-' + detectSelectors(fixers) + finalize() + + o(fixers.selectorList).deepEquals([]) + o(fixers.selectorMap).deepEquals({}) + }) + o(':-o-placeholder', function() { + mocks(global, {rules: [':-o-placeholder{}']}) + init() + fixers.prefix = '-o-' + detectSelectors(fixers) + finalize() + + o(fixers.selectorList).deepEquals(['::placeholder']) + o(fixers.selectorMap).deepEquals({'::placeholder': ':-o-placeholder'}) + }) + o('::-o-placeholder', function() { + mocks(global, {rules: ['::-o-placeholder{}']}) + init() + fixers.prefix = '-o-' + detectSelectors(fixers) + finalize() + + o(fixers.selectorList).deepEquals(['::placeholder']) + o(fixers.selectorMap).deepEquals({'::placeholder': '::-o-placeholder'}) + }) + o(':-o-input-placeholder', function() { + mocks(global, {rules: [':-o-input-placeholder{}']}) + init() + fixers.prefix = '-o-' + detectSelectors(fixers) + finalize() + + o(fixers.selectorList).deepEquals(['::placeholder']) + o(fixers.selectorMap).deepEquals({'::placeholder': ':-o-input-placeholder'}) + }) + o('::-o-input-placeholder', function() { + mocks(global, {rules: ['::-o-input-placeholder{}']}) + init() + fixers.prefix = '-o-' + detectSelectors(fixers) + finalize() + + o(fixers.selectorList).deepEquals(['::placeholder']) + o(fixers.selectorMap).deepEquals({'::placeholder': '::-o-input-placeholder'}) + }) + o('both ::placeholder and :-o-placeholder', function() { + mocks(global, {rules: [':-o-placeholder{}', '::placeholder{}']}) + init() + fixers.prefix = '-o-' + detectSelectors(fixers) + finalize() + + o(fixers.selectorList).deepEquals([]) + o(fixers.selectorMap).deepEquals({}) + }) + o('both ::placeholder and ::-o-placeholder', function() { + mocks(global, {rules: ['::-o-placeholder{}', '::placeholder{}']}) + init() + fixers.prefix = '-o-' + detectSelectors(fixers) + finalize() + + o(fixers.selectorList).deepEquals([]) + o(fixers.selectorMap).deepEquals({}) + }) + o('both ::placeholder and :-o-input-placeholder', function() { + mocks(global, {rules: [':-o-input-placeholder{}', '::placeholder{}']}) + init() + fixers.prefix = '-o-' + detectSelectors(fixers) + finalize() + + o(fixers.selectorList).deepEquals([]) + o(fixers.selectorMap).deepEquals({}) + }) + o('both ::placeholder and ::-o-input-placeholder', function() { + mocks(global, {rules: ['::-o-input-placeholder{}', '::placeholder{}']}) + init() + fixers.prefix = '-o-' + detectSelectors(fixers) + finalize() + + o(fixers.selectorList).deepEquals([]) + o(fixers.selectorMap).deepEquals({}) + }) +}) diff --git a/plugins/prefix-browser/tests/test-detector-utils.js b/plugins/prefix-browser/tests/test-detector-utils.js new file mode 100644 index 00000000..ce00cb4e --- /dev/null +++ b/plugins/prefix-browser/tests/test-detector-utils.js @@ -0,0 +1,40 @@ +var o = require('../test-utils/ospec-instance') + +var cleanupIfNeeded = require('../test-utils/misc').cleanupIfNeeded +var exposed = require('../test-utils/exposed') +var mocks = require('../test-utils/mocks') +var upToDate = require('../test-utils/misc').upToDate + +var camelCase = exposed.camelCase +var cleanupDetectorUtils = exposed.cleanupDetectorUtils +var deCamelCase = exposed.deCamelCase +var hasCleanState = exposed.hasCleanState +var init = exposed.init + +o.spec('core', function() { + o('build up to date', function() { + o(upToDate(__dirname, '../src/detectors/core.js')).equals(true) + }) + o.spec('camel case conversion', function() { + o('camelCase', function() { + o(camelCase('foo')).equals('foo') + o(camelCase('f-oo')).equals('fOo') + o(camelCase('-foo')).equals('Foo') + o(camelCase('foo')).equals('foo') + o(camelCase('f--oo')).equals('fOo') + }) + o('deCamelCase', function() { + o(deCamelCase('foo')).equals('foo') + o(deCamelCase('fOo')).equals('f-oo') + o(deCamelCase('Foo')).equals('-foo') + }) + }) + o('state cleanup', function() { + mocks(global) + init() + cleanupDetectorUtils() + o(hasCleanState()).equals(true)('detector utils state isn\'t clean') + cleanupIfNeeded(exposed) + }) +}) + diff --git a/plugins/prefix-browser/tests/test-detectors-webkit-compat.js b/plugins/prefix-browser/tests/test-detectors-webkit-compat.js new file mode 100644 index 00000000..6f0a2012 --- /dev/null +++ b/plugins/prefix-browser/tests/test-detectors-webkit-compat.js @@ -0,0 +1,65 @@ +var o = require('../test-utils/ospec-instance') + +var cleanupIfNeeded = require('../test-utils/misc').cleanupIfNeeded +var exposed = require('../test-utils/exposed') +var mocks = require('../test-utils/mocks') +var upToDate = require('../test-utils/misc').upToDate + +var hasCleanState = exposed.hasCleanState +var init = exposed.init +var finalize = exposed.finalize +var detectWebkitCompat = exposed.detectWebkitCompat +var blankFixers = exposed.blankFixers + +var referenceFixers = Object.keys(blankFixers()) + + +o.spec('Webkit prefies for Web compat', function() { + var fixers + o.beforeEach(function() { + o(hasCleanState()).equals(true)('detector utils state isn\'t clean') + fixers = blankFixers() + }) + o.afterEach(function() { + cleanupIfNeeded(exposed) + o(Object.keys(fixers)).deepEquals(referenceFixers) + fixers = null + }) + + o('build up to date', function() { + o(upToDate(__dirname, '../src/detectors/functions.js')).equals(true) + }) + + o('detects that `background-clip:text` needs a prefix', function(){ + mocks(global, {properties: {backgroundClip: ['padding-box', 'border-box', 'margin-box'], WebkitBackgroundClip: 'text'}}) + init() + detectWebkitCompat(fixers) + finalize() + + o(fixers.WkBCTxt).equals(true) + }) + o('ignore `-webkit-background-clip:text` when the unprefixed version works', function(){ + mocks(global, {properties: {backgroundClip: ['padding-box', 'border-box', 'margin-box', 'text'], WebkitBackgroundClip: 'text'}}) + init() + detectWebkitCompat(fixers) + finalize() + + o(fixers.WkBCTxt).equals(false) + }) + o('detect `-webkit-text-stroke`', function() { + mocks(global, {properties: {WebkitTextStroke: 'thin red'}}) + init() + detectWebkitCompat(fixers) + finalize() + + o(fixers.properties['text-stroke']).equals('-webkit-text-stroke') + }) + o('ignores `-webkit-text-stroke` if `text-stroke` is supported', function() { + mocks(global, {properties: {WebkitTextStroke: 'thin red', textStroke: 'thin red'}}) + init() + detectWebkitCompat(fixers) + finalize() + + o(fixers.properties).deepEquals({}) + }) +}) \ No newline at end of file diff --git a/plugins/prefix-browser/tests/test-integration.js b/plugins/prefix-browser/tests/test-integration.js new file mode 100644 index 00000000..2ffdd8c3 --- /dev/null +++ b/plugins/prefix-browser/tests/test-integration.js @@ -0,0 +1,60 @@ +var o = require('../test-utils/ospec-instance') + +var cleanupIfNeeded = require('../test-utils/misc').cleanupIfNeeded +var exposed = require('../test-utils/exposed') +var sink = require('../../../test-utils/sinks').simple +var mocks = require('../test-utils/mocks') + +var J2c = require('../../..') + +var blankFixers = exposed.blankFixers +var initBrowser = exposed.initBrowser +var hasCleanState = exposed.hasCleanState +var prefixPlugin = exposed.createPrefixPlugin + +var referenceFixers = Object.keys(blankFixers()) + +o.spec('plugin-prefix-browser integration', function() { + var fixers + + o.beforeEach(function() { + o(hasCleanState()).equals(true)('detector utils state isn\'t clean') + fixers = blankFixers() + }) + o.afterEach(function() { + cleanupIfNeeded(exposed) + o(Object.keys(fixers)).deepEquals(referenceFixers) + fixers = null + }) + + o('works', function(){ + mocks(global) + initBrowser() + + fixers.prefix = '-o-' + fixers.properties = {animation: '-o-animation'} + + var j2c = J2c({plugins: [prefixPlugin(), sink]}) + + j2c.setPrefixDb(fixers) + + var css = j2c.sheet({'@global': { + '@keyframes foo': { + 'from, to': {width: 0} + }, + '.bar' :{ + animation: 'baz 1sec' + } + }}) + o(css).deepEquals([ + ['atrule', '@keyframes', 'keyframes', 'foo', 'rule'], + ['rule', 'from, to'], + ['decl', 'width', '0'], + ['_rule'], + ['_atrule'], + ['rule', '.bar'], + ['decl', '-o-animation', 'baz 1sec'], + ['_rule'] + ]) + }) +}) \ No newline at end of file diff --git a/plugins/prefix-browser/tests/test-plugin-at-media-params.js b/plugins/prefix-browser/tests/test-plugin-at-media-params.js new file mode 100644 index 00000000..d7cb0973 --- /dev/null +++ b/plugins/prefix-browser/tests/test-plugin-at-media-params.js @@ -0,0 +1,106 @@ +var o = require('../test-utils/ospec-instance') + +var cleanupIfNeeded = require('../test-utils/misc').cleanupIfNeeded +var exposed = require('../test-utils/exposed') +var makeSink = require('../test-utils/misc').makeSink + +var blankFixers = exposed.blankFixers +var createPrefixPlugin = exposed.createPrefixPlugin +var hasCleanState = exposed.hasCleanState + +var referenceFixers = Object.keys(blankFixers()) + + + +o.spec('plugin @media parameters', function() { + var fixers + + o.beforeEach(function() { + o(hasCleanState()).equals(true)('detector utils state isn\'t clean') + fixers = blankFixers() + }) + o.afterEach(function() { + cleanupIfNeeded(exposed) + o(Object.keys(fixers)).deepEquals(referenceFixers) + }) + + o('works with a blank fixer object', function() { + var plugin = createPrefixPlugin() + plugin.set.setPrefixDb(fixers) + var sink = makeSink() + var methods = plugin.filter(sink) + + methods.atrule('@media', 'media', 'screen', true) + + o(sink.buffer).deepEquals([['atrule', '@media', 'media', 'screen', true]]) + }) + + o('works with no dppx support', function() { + fixers.hasDppx = false + fixers.prefix = '-dummy-' + + var plugin = createPrefixPlugin() + plugin.set.setPrefixDb(fixers) + var sink = makeSink() + var methods = plugin.filter(sink) + + methods.atrule('@media', 'media', 'screen', true) + methods.atrule('@media', 'media', '(resolution:2dppx) and (min-resolution:1dppx) and (max-resolution:2.5dppx)', true) + methods.atrule('@media', 'media', '(resolution:1dppx) and (min-resolution:2dppx) and (max-resolution:1.5dppx)', true) + + o(sink.buffer).deepEquals([ + ['atrule', '@media', 'media', 'screen', true], + ['atrule', '@media', 'media', '(resolution:192dpi) and (min-resolution:96dpi) and (max-resolution:240dpi)', true], + ['atrule', '@media', 'media', '(resolution:96dpi) and (min-resolution:192dpi) and (max-resolution:144dpi)', true] + ]) + }) + o('works with dppx support', function() { + fixers.hasDppx = true + fixers.prefix = '-dummy-' + + var plugin = createPrefixPlugin() + plugin.set.setPrefixDb(fixers) + var sink = makeSink() + var methods = plugin.filter(sink) + + methods.atrule('@media', 'media', '(resolution:2dppx) and (min-resolution:1dppx) and (max-resolution:2.5dppx)', true) + + o(sink.buffer).deepEquals([['atrule', '@media', 'media', '(resolution:2dppx) and (min-resolution:1dppx) and (max-resolution:2.5dppx)', true]]) + }) + o('works in webkit-like configuration', function() { + fixers.hasDppx = false + fixers.hasPixelRatio = true + fixers.prefix = '-webkit-' + fixers.properties['resolution'] = '-webkit-device-pixel-ratio' + fixers.properties['min-resolution'] = '-webkit-min-device-pixel-ratio' + fixers.properties['max-resolution'] = '-webkit-max-device-pixel-ratio' + + + var plugin = createPrefixPlugin() + plugin.set.setPrefixDb(fixers) + var sink = makeSink() + var methods = plugin.filter(sink) + + methods.atrule('@media', 'media', '(resolution:2dppx) and (min-resolution:1dppx) and (max-resolution:2.5dppx)', true) + + o(sink.buffer).deepEquals([['atrule', '@media', 'media', '(-webkit-device-pixel-ratio:2) and (-webkit-min-device-pixel-ratio:1) and (-webkit-max-device-pixel-ratio:2.5)', true]]) + }) + o('works in opera-like configuration', function() { + fixers.hasDppx = false + fixers.hasPixelRatioFraction = true + fixers.prefix = '-o-' + fixers.properties['resolution'] = '-o-device-pixel-ratio' + fixers.properties['min-resolution'] = '-o-min-device-pixel-ratio' + fixers.properties['max-resolution'] = '-o-max-device-pixel-ratio' + + + var plugin = createPrefixPlugin() + plugin.set.setPrefixDb(fixers) + var sink = makeSink() + var methods = plugin.filter(sink) + + methods.atrule('@media', 'media', '(resolution:2dppx) and (min-resolution:1dppx) and (max-resolution:2.51dppx)', true) + + o(sink.buffer).deepEquals([['atrule', '@media', 'media', '(-o-device-pixel-ratio:20/10) and (-o-min-device-pixel-ratio:10/10) and (-o-max-device-pixel-ratio:25/10)', true]]) + }) +}) \ No newline at end of file diff --git a/plugins/prefix-browser/tests/test-plugin-at-supports-params.js b/plugins/prefix-browser/tests/test-plugin-at-supports-params.js new file mode 100644 index 00000000..10b91970 --- /dev/null +++ b/plugins/prefix-browser/tests/test-plugin-at-supports-params.js @@ -0,0 +1,98 @@ +var o = require('../test-utils/ospec-instance') + +var cleanupIfNeeded = require('../test-utils/misc').cleanupIfNeeded +var exposed = require('../test-utils/exposed') +var makeSink = require('../test-utils/misc').makeSink +var mocks = require('../test-utils/mocks') + +var blankFixers = exposed.blankFixers +var createPrefixPlugin = exposed.createPrefixPlugin +var hasCleanState = exposed.hasCleanState +var initBrowser = exposed.initBrowser + +var referenceFixers = Object.keys(blankFixers()) + + + +o.spec('plugin @supports parameters', function() { + var fixers + + o.beforeEach(function() { + o(hasCleanState()).equals(true)('detector utils state isn\'t clean') + fixers = blankFixers() + }) + o.afterEach(function() { + cleanupIfNeeded(exposed) + o(Object.keys(fixers)).deepEquals(referenceFixers) + }) + + o('works with a blank fixer object', function() { + mocks(global) + initBrowser() + + var plugin = createPrefixPlugin() + plugin.set.setPrefixDb(fixers) + var sink = makeSink() + var methods = plugin.filter(sink) + + methods.atrule('@supports', 'supports', '(foo:bar)', true) + + o(sink.buffer).deepEquals([['atrule', '@supports', 'supports', '(foo:bar)', true]]) + }) + o('adds prefixes adequately (simple example)', function() { + mocks(global, { + properties: { + OFoo: 'bar' + } + }) + initBrowser() + + var sink = makeSink() + var methods = createPrefixPlugin().filter(sink) + + methods.atrule('@supports', 'supports', '(foo: foo)', true) + + o(sink.buffer).deepEquals([ + ['atrule', '@supports', 'supports', '(-o-foo:foo)', true] + ]) + }) + o('adds prefixes adequately (complex example)', function() { + mocks(global, { + properties: { + OFoo: 'bar', + backgroundImage: ['-o-linear-gradient(red, teal)'], + display:['-o-box', '-o-grid'], + width: '-o-calc(1px + 5%)', + color: '-o-initial' + } + }) + initBrowser() + + var sink = makeSink() + var methods = createPrefixPlugin().filter(sink) + + methods.atrule('@supports', 'supports', + '(foo: foo) and ' + + '(transition: foo cubic-bezier(calc(2 * var(--foo)),foo,calc(1 + var(--ofo)))) and ' + + '(transition-property: bar,foo) and ' + + '(display: flex) and ' + + '(display: grid) and ' + + '(background-image: linear-gradient(40deg, rgb(0, 0, calc(3 * var(--oof))))) and ' + + '(foo: initial)', + true) + + o(sink.buffer).deepEquals([ + [ + 'atrule', '@supports', 'supports', + '(-o-foo:foo) and ' + + '(transition:-o-foo cubic-bezier(-o-calc(2 * var(--foo)),foo,-o-calc(1 + var(--ofo)))) and ' + + '(transition-property:bar,-o-foo) and ' + + '(display:-o-box) and ' + + '(display:-o-grid) and ' + + '(background-image:-o-linear-gradient(50deg, rgb(0, 0, -o-calc(3 * var(--oof))))) and ' + + '(-o-foo:-o-initial)', + true + ] + ]) + }) +}) \ No newline at end of file diff --git a/plugins/prefix-browser/tests/test-plugin-atrules.js b/plugins/prefix-browser/tests/test-plugin-atrules.js new file mode 100644 index 00000000..5c1d4dbc --- /dev/null +++ b/plugins/prefix-browser/tests/test-plugin-atrules.js @@ -0,0 +1,64 @@ +var o = require('../test-utils/ospec-instance') + +var cleanupIfNeeded = require('../test-utils/misc').cleanupIfNeeded +var exposed = require('../test-utils/exposed') +var makeSink = require('../test-utils/misc').makeSink + +var blankFixers = exposed.blankFixers +var createPrefixPlugin = exposed.createPrefixPlugin +var hasCleanState = exposed.hasCleanState + +var referenceFixers = Object.keys(blankFixers()) + + + +o.spec('plugin.atrules', function() { + var fixers + + o.beforeEach(function() { + o(hasCleanState()).equals(true)('detector utils state isn\'t clean') + fixers = blankFixers() + }) + o.afterEach(function() { + cleanupIfNeeded(exposed) + o(Object.keys(fixers)).deepEquals(referenceFixers) + }) + + o('works with a blank fixer object', function() { + var plugin = createPrefixPlugin() + plugin.set.setPrefixDb(fixers) + var sink = makeSink() + var methods = plugin.filter(sink) + + methods.atrule('@keyframes', 'keyframes', 'foo', true) + + o(sink.buffer).deepEquals([['atrule', '@keyframes', 'keyframes', 'foo', true]]) + }) + o('with a prefix and hasAtrules set to true, leaves unknowed rules alone', function() { + fixers.prefix = '-o-' + fixers.hasAtrules = true + + var plugin = createPrefixPlugin() + plugin.set.setPrefixDb(fixers) + var sink = makeSink() + var methods = plugin.filter(sink) + + methods.atrule('@keyframes', 'keyframes', 'foo', true) + + o(sink.buffer).deepEquals([['atrule', '@keyframes', 'keyframes', 'foo', true]]) + }) + o('modifies known rules according to fixers.atrules', function() { + fixers.atrules['@foo'] = '@-o-foo' + fixers.prefix = '-o-' + fixers.hasAtrules = true + + var plugin = createPrefixPlugin() + plugin.set.setPrefixDb(fixers) + var sink = makeSink() + var methods = plugin.filter(sink) + + methods.atrule('@foo', 'foo', 'bar', true) + + o(sink.buffer).deepEquals([['atrule', '@-o-foo', 'foo', 'bar', true]]) + }) +}) \ No newline at end of file diff --git a/plugins/prefix-browser/tests/test-plugin-misc.js b/plugins/prefix-browser/tests/test-plugin-misc.js new file mode 100644 index 00000000..13c33dff --- /dev/null +++ b/plugins/prefix-browser/tests/test-plugin-misc.js @@ -0,0 +1,64 @@ +var o = require('../test-utils/ospec-instance') + +var upToDate = require('../test-utils/misc').upToDate + +var cleanupIfNeeded = require('../test-utils/misc').cleanupIfNeeded +var exposed = require('../test-utils/exposed') +var makeSink = require('../test-utils/misc').makeSink +var mocks = require('../test-utils/mocks') + +var blankFixers = exposed.blankFixers +var createPrefixPlugin = exposed.createPrefixPlugin +var hasCleanState = exposed.hasCleanState +var initBrowser = exposed.initBrowser + +var referenceFixers = Object.keys(blankFixers()) + +o.spec('plugin misc tests', function() { + var fixers + + o.beforeEach(function() { + o(hasCleanState()).equals(true)('detector utils state isn\'t clean') + fixers = blankFixers() + }) + o.afterEach(function() { + cleanupIfNeeded(exposed) + o(Object.keys(fixers)).deepEquals(referenceFixers) + fixers = null + }) + + o('build up to date', function() { + o(upToDate(__dirname, '../src/plugin.js')).equals(true) + o(upToDate(__dirname, '../src/fixers.js')).equals(true) + }) + + o('handles raw declarations and bad values gracefully', function() { + mocks(global, {properties: {'-o-foo': 'bar'}}) + initBrowser() + + fixers.prefix = '-o-' + + var plugin = createPrefixPlugin() + plugin.set.setPrefixDb(fixers) + var sink = makeSink() + var methods = plugin.filter(sink) + + o(fixers.properties).deepEquals({}) + methods.decl('foo', 5) + methods.decl('bar', null) + methods.decl('-baz', global) + methods.decl(null, global) + methods.decl('', 'qux') + + o(sink.buffer).deepEquals([ + ['decl', '-o-foo', '5'], + ['decl', 'bar', null], + ['decl', '-baz', global], + ['decl', null, global], + ['decl', '', 'qux'] + ]) + o(fixers.properties).deepEquals({ + 'foo': '-o-foo', 'bar': 'bar', '': '' + }) + }) +}) \ No newline at end of file diff --git a/plugins/prefix-browser/tests/test-plugin-properties.js b/plugins/prefix-browser/tests/test-plugin-properties.js new file mode 100644 index 00000000..42833115 --- /dev/null +++ b/plugins/prefix-browser/tests/test-plugin-properties.js @@ -0,0 +1,317 @@ +var o = require('../test-utils/ospec-instance') + +var cleanupIfNeeded = require('../test-utils/misc').cleanupIfNeeded +var exposed = require('../test-utils/exposed') +var makeSink = require('../test-utils/misc').makeSink +var mocks = require('../test-utils/mocks') + +var blankFixers = exposed.blankFixers +var createPrefixPlugin = exposed.createPrefixPlugin +var hasCleanState = exposed.hasCleanState +var initBrowser = exposed.initBrowser + +var referenceFixers = Object.keys(blankFixers()) + + + +o.spec('plugin.decl for properties', function() { + var fixers + + o.beforeEach(function() { + o(hasCleanState()).equals(true)('detector utils state isn\'t clean') + fixers = blankFixers() + }) + o.afterEach(function() { + cleanupIfNeeded(exposed) + o(Object.keys(fixers)).deepEquals(referenceFixers) + fixers = null + }) + + o('it leaves unknowned properties as is', function() { + mocks(global, {properties: {'-o-foo': null, 'foo': null}}) + initBrowser() + + fixers.prefix = '-o-' + + var plugin = createPrefixPlugin() + plugin.set.setPrefixDb(fixers) + plugin.set.setPrefixDb(fixers) // set it a second time to exercise the cache branch. + var sink = makeSink() + var methods = plugin.filter(sink) + + methods.decl('foo', 'bar') + + o(sink.buffer).deepEquals([['decl', 'foo', 'bar']]) + o(fixers.properties).deepEquals({'foo': 'foo'}) + }) + o('adds prefixes when necessary', function() { + mocks(global, {properties: {'-o-foo': 'bar'}}) + initBrowser() + + fixers.prefix = '-o-' + + var plugin = createPrefixPlugin() + plugin.set.setPrefixDb(fixers) + var sink = makeSink() + var methods = plugin.filter(sink) + + o(fixers.properties).deepEquals({}) + + methods.decl('foo', 'bar') + methods.decl('baz', 'qux') + + o(sink.buffer).deepEquals([ + ['decl', '-o-foo', 'bar'], + ['decl', 'baz', 'qux'] + ]) + + o(fixers.properties).deepEquals({ + 'foo': '-o-foo', + 'baz': 'baz' + }) + }) + o('doesn\'t prefix when both prefix an unprefixed are supported', function() { + mocks(global, {properties: {'-o-foo': 'bar', 'foo': 'bar'}}) + initBrowser() + + fixers.prefix = '-o-' + + var plugin = createPrefixPlugin() + plugin.set.setPrefixDb(fixers) + var sink = makeSink() + var methods = plugin.filter(sink) + + o(fixers.properties).deepEquals({}) + + methods.decl('foo', 'bar') + methods.decl('baz', 'qux') + + o(sink.buffer).deepEquals([ + ['decl', 'foo', 'bar'], + ['decl', 'baz', 'qux'] + ]) + + o(fixers.properties).deepEquals({ + 'foo': 'foo', + 'baz': 'baz' + }) + }) + o('with jsFlex, display:flex becomes -js-display:flex', function() { + mocks(global) + initBrowser() + + fixers.prefix = '-o-' + fixers.jsFlex = true + + var plugin = createPrefixPlugin() + plugin.set.setPrefixDb(fixers) + var sink = makeSink() + var methods = plugin.filter(sink) + + + methods.decl('display', 'flex') + methods.decl('display', 'inline-flex') + + o(fixers.properties).deepEquals({}) + + methods.decl('display', 'block') + + o(sink.buffer).deepEquals([ + ['decl', '-js-display', 'flex'], + ['decl', '-js-display', 'inline-flex'], + ['decl', 'display', 'block'] + ]) + + o(fixers.properties).deepEquals({display: 'display'}) + }) + o('with flexbox 2009, `flex-direction` becomes box-orient + box-direction', function() { + mocks(global) + initBrowser() + + fixers.prefix = '-o-' + fixers.properties['box-orient'] = '-o-box-orient' + fixers.properties['box-direction'] = '-o-box-direction' + fixers.flexbox2009 = true + + var plugin = createPrefixPlugin() + plugin.set.setPrefixDb(fixers) + var sink = makeSink() + var methods = plugin.filter(sink) + + o(fixers.properties).deepEquals({ + 'box-orient': '-o-box-orient', + 'box-direction': '-o-box-direction' + }) + + methods.decl('flex-direction', 'column-reverse') + + o(sink.buffer).deepEquals([ + ['decl', '-o-box-orient', 'block-axis'], + ['decl', '-o-box-direction', 'reverse'] + ]) + + o(fixers.properties).deepEquals({ + 'box-orient': '-o-box-orient', + 'box-direction': '-o-box-direction' + }) + }) + o('with flexbox 2009, `flex-flow` becomes box-orient + box-direction + box-lines', function() { + mocks(global) + initBrowser() + + fixers.prefix = '-o-' + fixers.properties['box-orient'] = '-o-box-orient' + fixers.properties['box-direction'] = '-o-box-direction' + fixers.properties['flex-wrap'] = '-o-box-lines' + fixers.keywords['flex-wrap'] = {wrap:'multiple'} + fixers.hasKeywords = true + fixers.flexbox2009 = true + + var plugin = createPrefixPlugin() + plugin.set.setPrefixDb(fixers) + var sink = makeSink() + var methods = plugin.filter(sink) + + o(fixers.properties).deepEquals({ + 'box-orient': '-o-box-orient', + 'box-direction': '-o-box-direction', + 'flex-wrap': '-o-box-lines' + }) + // The extra space between the values is intentional + methods.decl('flex-flow', 'column-reverse wrap') + + o(sink.buffer).deepEquals([ + ['decl', '-o-box-orient', 'block-axis'], + ['decl', '-o-box-direction', 'reverse'], + ['decl', '-o-box-lines', 'multiple'] + ]) + + o(fixers.properties).deepEquals({ + 'box-orient': '-o-box-orient', + 'box-direction': '-o-box-direction', + 'flex-wrap': '-o-box-lines' + }) + }) + o('with flexbox 2009, `flex-flow` (no wrap value) becomes box-orient + box-direction', function() { + mocks(global) + initBrowser() + + fixers.prefix = '-o-' + fixers.properties['box-orient'] = '-o-box-orient' + fixers.properties['box-direction'] = '-o-box-direction' + fixers.properties['flex-wrap'] = '-o-box-lines' + fixers.keywords['flex-wrap'] = {wrap:'multiple'} + fixers.hasKeywords = true + fixers.flexbox2009 = true + + var plugin = createPrefixPlugin() + plugin.set.setPrefixDb(fixers) + var sink = makeSink() + var methods = plugin.filter(sink) + + o(fixers.properties).deepEquals({ + 'box-orient': '-o-box-orient', + 'box-direction': '-o-box-direction', + 'flex-wrap': '-o-box-lines' + }) + + methods.decl('flex-flow', 'row') + + o(sink.buffer).deepEquals([ + ['decl', '-o-box-orient', 'inline-axis'], + ['decl', '-o-box-direction', 'normal'] + ]) + + o(fixers.properties).deepEquals({ + 'box-orient': '-o-box-orient', + 'box-direction': '-o-box-direction', + 'flex-wrap': '-o-box-lines' + }) + }) + o('with flexbox 2009, `flex-flow` (no direction) box-lines', function() { + mocks(global) + initBrowser() + + fixers.prefix = '-o-' + fixers.properties['box-orient'] = '-o-box-orient' + fixers.properties['box-direction'] = '-o-box-direction' + fixers.properties['flex-wrap'] = '-o-box-lines' + fixers.keywords['flex-wrap'] = {nowrap:'single'} + fixers.hasKeywords = true + fixers.flexbox2009 = true + + var plugin = createPrefixPlugin() + plugin.set.setPrefixDb(fixers) + var sink = makeSink() + var methods = plugin.filter(sink) + + o(fixers.properties).deepEquals({ + 'box-orient': '-o-box-orient', + 'box-direction': '-o-box-direction', + 'flex-wrap': '-o-box-lines' + }) + + methods.decl('flex-flow', 'nowrap') + + o(sink.buffer).deepEquals([ + ['decl', '-o-box-lines', 'single'] + ]) + + o(fixers.properties).deepEquals({ + 'box-orient': '-o-box-orient', + 'box-direction': '-o-box-direction', + 'flex-wrap': '-o-box-lines' + }) + }) + o('with flexbox 2009, non-flexbox properties work as well', function() { + mocks(global) + initBrowser() + + fixers.prefix = '-o-' + fixers.properties['foo'] = '-o-foo' + fixers.hasKeywords = true + fixers.flexbox2009 = true + + var plugin = createPrefixPlugin() + plugin.set.setPrefixDb(fixers) + var sink = makeSink() + var methods = plugin.filter(sink) + + o(fixers.properties).deepEquals({ + 'foo': '-o-foo' + }) + + methods.decl('foo', '5') + methods.decl('bar', '6') + o(sink.buffer).deepEquals([ + ['decl', '-o-foo', '5'], + ['decl', 'bar', '6'] + ]) + + o(fixers.properties).deepEquals({ + 'foo': '-o-foo', + 'bar': 'bar' + }) + }) + o('the properties fixer can be specified manually', function(){ + fixers.fixProperty = function() {return 'replaced'} + + var plugin = createPrefixPlugin() + plugin.set.setPrefixDb(fixers) + var sink = makeSink() + var methods = plugin.filter(sink) + + o(fixers.properties).deepEquals({}) + + methods.decl('foo', 'bar') + methods.decl('baz', 'qux') + + o(sink.buffer).deepEquals([ + ['decl', 'replaced', 'bar'], + ['decl', 'replaced', 'qux'] + ]) + + o(fixers.properties).deepEquals({}) + + }) +}) \ No newline at end of file diff --git a/plugins/prefix-browser/tests/test-plugin-selectors.js b/plugins/prefix-browser/tests/test-plugin-selectors.js new file mode 100644 index 00000000..0c20cd2a --- /dev/null +++ b/plugins/prefix-browser/tests/test-plugin-selectors.js @@ -0,0 +1,63 @@ +var o = require('../test-utils/ospec-instance') + +var cleanupIfNeeded = require('../test-utils/misc').cleanupIfNeeded +var exposed = require('../test-utils/exposed') +var makeSink = require('../test-utils/misc').makeSink + +var blankFixers = exposed.blankFixers +var createPrefixPlugin = exposed.createPrefixPlugin +var hasCleanState = exposed.hasCleanState + +var referenceFixers = Object.keys(blankFixers()) + + + +o.spec('plugin.rule', function() { + var fixers + + o.beforeEach(function() { + o(hasCleanState()).equals(true)('detector utils state isn\'t clean') + fixers = blankFixers() + }) + o.afterEach(function() { + cleanupIfNeeded(exposed) + o(Object.keys(fixers)).deepEquals(referenceFixers) + fixers = null + }) + + o('it leaves unrelated selector as is', function() { + fixers.prefix = '-o-' + + var plugin = createPrefixPlugin() + plugin.set.setPrefixDb(fixers) + var sink = makeSink() + var methods = plugin.filter(sink) + + methods.rule('.foo:active #baz[foo]') + + o(sink.buffer).deepEquals([['rule', '.foo:active #baz[foo]']]) + }) + o('it prefixes known selectors appropriately', function() { + fixers.selectorList = [':any-link', '::selection', '::placeholder'] + fixers.selectorMap = { + ':any-link': ':-o-any-link', + '::selection': '::-o-selection', + '::placeholder': ':-o-placeholder' + } + fixers.hasSelectors = true + fixers.prefix = '-o-' + + var plugin = createPrefixPlugin() + plugin.set.setPrefixDb(fixers) + var sink = makeSink() + var methods = plugin.filter(sink) + + methods.rule('.foo:active #baz[foo]') + methods.rule('::placeholder .foo:any-link::selection:selection #baz[qux=":any-link"]/*::selection*/[quux=\'::selection\']') + + o(sink.buffer).deepEquals([ + ['rule', '.foo:active #baz[foo]'], + ['rule', ':-o-placeholder .foo:-o-any-link::-o-selection:selection #baz[qux=":any-link"]/*::selection*/[quux=\'::selection\']'] + ]) + }) +}) \ No newline at end of file diff --git a/plugins/prefix-browser/tests/test-plugin-values-complex.js b/plugins/prefix-browser/tests/test-plugin-values-complex.js new file mode 100644 index 00000000..fd605821 --- /dev/null +++ b/plugins/prefix-browser/tests/test-plugin-values-complex.js @@ -0,0 +1,46 @@ +var o = require('../test-utils/ospec-instance') + +var cleanupIfNeeded = require('../test-utils/misc').cleanupIfNeeded +var exposed = require('../test-utils/exposed') +var makeSink = require('../test-utils/misc').makeSink +var mocks = require('../test-utils/mocks') + +var blankFixers = exposed.blankFixers +var createPrefixPlugin = exposed.createPrefixPlugin +var hasCleanState = exposed.hasCleanState +var initBrowser = exposed.initBrowser + +var referenceFixers = Object.keys(blankFixers()) + +o.spec('plugin.decl for complex values', function() { + var fixers + + o.beforeEach(function() { + o(hasCleanState()).equals(true)('detector utils state isn\'t clean') + fixers = blankFixers() + }) + o.afterEach(function() { + cleanupIfNeeded(exposed) + o(Object.keys(fixers)).deepEquals(referenceFixers) + fixers = null + }) + + o('transition with a prefixed property and a prefixed function', function() { + mocks(global, {properties: {'-o-foo': 'bar'}}) + initBrowser() + + fixers.functions = ['linear-gradient', 'repeating-linear-gradient', 'calc', 'element', 'cross-fade'] + fixers.prefix = '-o-' + + var plugin = createPrefixPlugin() + plugin.set.setPrefixDb(fixers) + var sink = makeSink() + var methods = plugin.filter(sink) + + methods.decl('transition', 'bar 1s, foo 2s step(calc(3 * var(--foo)), foo)') + + o(sink.buffer).deepEquals([ + ['decl', 'transition', 'bar 1s,-o-foo 2s step(-o-calc(3 * var(--foo)), foo)'] + ]) + }) +}) diff --git a/plugins/prefix-browser/tests/test-plugin-values-functions.js b/plugins/prefix-browser/tests/test-plugin-values-functions.js new file mode 100644 index 00000000..2c57b53d --- /dev/null +++ b/plugins/prefix-browser/tests/test-plugin-values-functions.js @@ -0,0 +1,86 @@ +var o = require('../test-utils/ospec-instance') + +var cleanupIfNeeded = require('../test-utils/misc').cleanupIfNeeded +var exposed = require('../test-utils/exposed') +var makeSink = require('../test-utils/misc').makeSink +var mocks = require('../test-utils/mocks') + +var blankFixers = exposed.blankFixers +var createPrefixPlugin = exposed.createPrefixPlugin +var hasCleanState = exposed.hasCleanState +var initBrowser = exposed.initBrowser + +var referenceFixers = Object.keys(blankFixers()) + +o.spec('plugin.decl for values that have functions', function() { + var fixers + + o.beforeEach(function() { + o(hasCleanState()).equals(true)('detector utils state isn\'t clean') + fixers = blankFixers() + }) + o.afterEach(function() { + cleanupIfNeeded(exposed) + o(Object.keys(fixers)).deepEquals(referenceFixers) + fixers = null + }) + + o('leaves unknown functions alone', function() { + mocks(global) + initBrowser() + + fixers.functions = ['linear-gradient', 'repeating-linear-gradient', 'calc', 'element', 'cross-fade'] + fixers.prefix = '-o-' + + var plugin = createPrefixPlugin() + plugin.set.setPrefixDb(fixers) + var sink = makeSink() + var methods = plugin.filter(sink) + + methods.decl('foo', 'color(red a(0))') + + o(sink.buffer).deepEquals([['decl', 'foo', 'color(red a(0))']]) + }) + o('fixes known functions', function() { + mocks(global) + initBrowser() + + fixers.functions = ['linear-gradient', 'repeating-linear-gradient', 'calc', 'element', 'cross-fade'] + fixers.prefix = '-o-' + + var plugin = createPrefixPlugin() + plugin.set.setPrefixDb(fixers) + var sink = makeSink() + var methods = plugin.filter(sink) + + methods.decl('foo', 'cross-fade(linear-gradient(89deg red, green),repeating-linear-gradient(-20deg rgb( calc(2 * var(--foo)), 0, 0), green))') + methods.decl('foo', 'linear-gradient(100deg red, green)') + + o(sink.buffer).deepEquals([ + ['decl', 'foo', '-o-cross-fade(-o-linear-gradient(1deg red, green),-o-repeating-linear-gradient(110deg rgb( -o-calc(2 * var(--foo)), 0, 0), green))'], + ['decl', 'foo', '-o-linear-gradient(-10deg red, green)'] + ]) + }) + o('skips the gradient fixer if none are present (see coverage)', function() { + mocks(global) + initBrowser() + + fixers.functions = ['calc', 'element', 'cross-fade'] + fixers.prefix = '-o-' + + var plugin = createPrefixPlugin() + plugin.set.setPrefixDb(fixers) + var sink = makeSink() + var methods = plugin.filter(sink) + + methods.decl('foo', 'cross-fade(linear-gradient(89deg red, green),repeating-linear-gradient(-20deg rgb( calc(2 * var(--foo)), 0, 0), green))') + methods.decl('foo', 'linear-gradient(100deg red, green)') + + o(sink.buffer).deepEquals([ + ['decl', 'foo', '-o-cross-fade(linear-gradient(89deg red, green),repeating-linear-gradient(-20deg rgb( -o-calc(2 * var(--foo)), 0, 0), green))'], + ['decl', 'foo', 'linear-gradient(100deg red, green)'] + ]) + }) +}) + + diff --git a/plugins/prefix-browser/tests/test-plugin-values-keywords.js b/plugins/prefix-browser/tests/test-plugin-values-keywords.js new file mode 100644 index 00000000..d0b54eb6 --- /dev/null +++ b/plugins/prefix-browser/tests/test-plugin-values-keywords.js @@ -0,0 +1,144 @@ +var o = require('../test-utils/ospec-instance') + +var cleanupIfNeeded = require('../test-utils/misc').cleanupIfNeeded +var exposed = require('../test-utils/exposed') +var makeSink = require('../test-utils/misc').makeSink +var mocks = require('../test-utils/mocks') + +var blankFixers = exposed.blankFixers +var createPrefixPlugin = exposed.createPrefixPlugin +var hasCleanState = exposed.hasCleanState +var initBrowser = exposed.initBrowser + +var referenceFixers = Object.keys(blankFixers()) + +var prefixedKeywords = {} +var unPrefixedKeywords = {} +exposed.keywords.forEach(function(k) { + var valueMap = k.values.reduce(function(acc,v){ + acc[v] = '-o-' + v + return acc + },{}) + k.props.forEach(function(p){ + unPrefixedKeywords[p] = {} + prefixedKeywords[p] = valueMap + }) +}) + +o.spec('plugin.decl for keywords', function() { + var fixers + + o.beforeEach(function() { + o(hasCleanState()).equals(true)('detector utils state isn\'t clean') + fixers = blankFixers() + }) + o.afterEach(function() { + cleanupIfNeeded(exposed) + o(Object.keys(fixers)).deepEquals(referenceFixers) + fixers = null + }) + + o('leaves unknown values as is (hasKeywords set to false)', function() { + mocks(global) + initBrowser() + + fixers.prefix = '-o-' + + var plugin = createPrefixPlugin() + plugin.set.setPrefixDb(fixers) + var sink = makeSink() + var methods = plugin.filter(sink) + + methods.decl('display', 'inline') + methods.decl('color', 'red') + + o(sink.buffer).deepEquals([ + ['decl', 'display', 'inline'], + ['decl', 'color', 'red'] + ]) + }) + o('leaves unknown values as is (hasKeywords set to true)', function() { + mocks(global) + initBrowser() + + fixers.prefix = '-o-' + fixers.hasKeywords = true + fixers.keywords = unPrefixedKeywords + + var plugin = createPrefixPlugin() + plugin.set.setPrefixDb(fixers) + var sink = makeSink() + var methods = plugin.filter(sink) + + methods.decl('display', 'inline') + methods.decl('color', 'red') + + o(sink.buffer).deepEquals([ + ['decl', 'display', 'inline'], + ['decl', 'color', 'red'] + ]) + }) + o('leaves prefixable values as is when they don\'t need a prefix', function() { + mocks(global) + initBrowser() + + fixers.prefix = '-o-' + fixers.hasKeywords = true + fixers.keywords = unPrefixedKeywords + + var plugin = createPrefixPlugin() + plugin.set.setPrefixDb(fixers) + var sink = makeSink() + var methods = plugin.filter(sink) + + methods.decl('display', 'grid') + + o(sink.buffer).deepEquals([ + ['decl', 'display', 'grid'] + ]) + }) + o('adds prefixes', function() { + mocks(global) + initBrowser() + + fixers.prefix = '-o-' + fixers.hasKeywords = true + fixers.keywords = prefixedKeywords + fixers.initial = '-o-initial' + + var plugin = createPrefixPlugin() + plugin.set.setPrefixDb(fixers) + var sink = makeSink() + var methods = plugin.filter(sink) + + methods.decl('display', 'box') + methods.decl('display', 'inline-box') + methods.decl('display', 'inline') + methods.decl('cursor', 'grab') + methods.decl('cursor', 'inherit') + methods.decl('position', 'sticky') + methods.decl('position', 'absolute') + methods.decl('max-width', 'fit-content') + methods.decl('max-width', '10em') + methods.decl('color', 'initial') + methods.decl('color', 'red') + methods.decl('font', 'initial') + methods.decl('font', 'inherit') + + o(sink.buffer).deepEquals([ + ['decl', 'display', '-o-box'], + ['decl', 'display', '-o-inline-box'], + ['decl', 'display', 'inline'], + ['decl', 'cursor', '-o-grab'], + ['decl', 'cursor', 'inherit'], + ['decl', 'position', '-o-sticky'], + ['decl', 'position', 'absolute'], + ['decl', 'max-width', '-o-fit-content'], + ['decl', 'max-width', '10em'], + ['decl', 'color', '-o-initial'], + ['decl', 'color', 'red'], + ['decl', 'font', '-o-initial'], + ['decl', 'font', 'inherit'] + ]) + }) +}) \ No newline at end of file diff --git a/plugins/prefix-browser/tests/test-plugin-values-properties.js b/plugins/prefix-browser/tests/test-plugin-values-properties.js new file mode 100644 index 00000000..c07693ee --- /dev/null +++ b/plugins/prefix-browser/tests/test-plugin-values-properties.js @@ -0,0 +1,152 @@ +var o = require('../test-utils/ospec-instance') + +var cleanupIfNeeded = require('../test-utils/misc').cleanupIfNeeded +var exposed = require('../test-utils/exposed') +var makeSink = require('../test-utils/misc').makeSink +var mocks = require('../test-utils/mocks') + +var blankFixers = exposed.blankFixers +var createPrefixPlugin = exposed.createPrefixPlugin +var hasCleanState = exposed.hasCleanState +var initBrowser = exposed.initBrowser + +var referenceFixers = Object.keys(blankFixers()) + +o.spec('plugin.decl for properties whose values are properties', function() { + var fixers + + o.beforeEach(function() { + o(hasCleanState()).equals(true)('detector utils state isn\'t clean') + fixers = blankFixers() + }) + o.afterEach(function() { + cleanupIfNeeded(exposed) + o(Object.keys(fixers)).deepEquals(referenceFixers) + fixers = null + }) + + o('it leaves unknowned properties as is', function() { + mocks(global, {properties: {'-o-foo': null, 'foo': null}}) + initBrowser() + fixers.prefix = '-o-' + + var plugin = createPrefixPlugin() + plugin.set.setPrefixDb(fixers) + var sink = makeSink() + var methods = plugin.filter(sink) + + o(fixers.properties).deepEquals({}) + + methods.decl('transition', 'bar 1s step(0.5s, foo)') + methods.decl('transition', 'bar 1s, baz 2s') + + o(sink.buffer).deepEquals([ + ['decl', 'transition', 'bar 1s step(0.5s, foo)'], + ['decl', 'transition', 'bar 1s,baz 2s'] + ]) + + o(fixers.properties).deepEquals({ + 'transition': 'transition', + 'bar': 'bar', + 'baz': 'baz' + }) + }) + o('adds prefixes when necessary', function() { + mocks(global, {properties: {'-o-foo': 'bar'}}) + initBrowser() + + fixers.prefix = '-o-' + + var plugin = createPrefixPlugin() + plugin.set.setPrefixDb(fixers) + var sink = makeSink() + var methods = plugin.filter(sink) + + o(fixers.properties).deepEquals({}) + + methods.decl('transition', 'foo 1s') + methods.decl('transition', 'bar 1s, foo 2s step(0.5s, foo)') + + o(sink.buffer).deepEquals([ + ['decl', 'transition', '-o-foo 1s'], + ['decl', 'transition', 'bar 1s,-o-foo 2s step(0.5s, foo)'] + ]) + + o(fixers.properties).deepEquals({ + 'transition': 'transition', + 'foo': '-o-foo', + 'bar': 'bar' + }) + }) + o('doesn\'t prefix when both prefix an unprefixed are supported', function() { + mocks(global, {properties: {'-o-foo': 'bar', 'foo': 'bar'}}) + initBrowser() + + fixers.prefix = '-o-' + + var plugin = createPrefixPlugin() + plugin.set.setPrefixDb(fixers) + var sink = makeSink() + var methods = plugin.filter(sink) + + o(fixers.properties).deepEquals({}) + + methods.decl('transition', 'foo 1s') + methods.decl('transition', 'bar 1s, foo 2s step(0.5s, foo)') + + o(sink.buffer).deepEquals([ + ['decl', 'transition', 'foo 1s'], + ['decl', 'transition', 'bar 1s,foo 2s step(0.5s, foo)'] + ]) + + o(fixers.properties).deepEquals({ + 'transition': 'transition', + 'foo': 'foo', + 'bar': 'bar' + }) + }) + o('the properties fixer can be specified manually', function(){ + fixers.fixProperty = function() {return 'replaced'} + var plugin = createPrefixPlugin() + plugin.set.setPrefixDb(fixers) + var sink = makeSink() + var methods = plugin.filter(sink) + + o(fixers.properties).deepEquals({}) + + methods.decl('transition', 'foo 1s') + methods.decl('transition', 'bar 1s, foo 2s step(0.5s, foo)') + + o(sink.buffer).deepEquals([ + ['decl', 'replaced', 'replaced 1s'], + ['decl', 'replaced', 'replaced 1s,replaced 2s step(0.5s, foo)'] + ]) + + o(fixers.properties).deepEquals({}) + }) + o('selectively prefix background-clip when the value is `text`', function(){ + mocks(global, {properties: {}}) + initBrowser() + + fixers.prefix = '' + + fixers.WkBCTxt = true + var plugin = createPrefixPlugin() + plugin.set.setPrefixDb(fixers) + var sink = makeSink() + var methods = plugin.filter(sink) + + o(fixers.properties).deepEquals({}) + + methods.decl('background-clip', 'text') + methods.decl('background-clip', 'border-box') + + o(sink.buffer).deepEquals([ + ['decl', '-webkit-background-clip', 'text'], + ['decl', 'background-clip', 'border-box'] + ]) + + o(fixers.properties).deepEquals({'background-clip': 'background-clip'}) + + }) +}) \ No newline at end of file diff --git a/plugins/prefix-browser/upstream/README.md b/plugins/prefix-browser/upstream/README.md new file mode 100644 index 00000000..d6f140b2 --- /dev/null +++ b/plugins/prefix-browser/upstream/README.md @@ -0,0 +1,3 @@ +Here come the `prefixfree` files cloned by `npm run upstream`. + +PrefixFree is not available on NPM and must be cloned from the Git repo. \ No newline at end of file diff --git a/plugins/prefix-browser/yarn.lock b/plugins/prefix-browser/yarn.lock new file mode 100644 index 00000000..3c355b4d --- /dev/null +++ b/plugins/prefix-browser/yarn.lock @@ -0,0 +1,74 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +balanced-match@^0.4.1: + version "0.4.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" + +brace-expansion@^1.0.0: + version "1.1.6" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.6.tgz#7197d7eaa9b87e648390ea61fc66c84427420df9" + dependencies: + balanced-match "^0.4.1" + concat-map "0.0.1" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + +glob@^7.0.5: + version "7.1.1" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.2" + once "^1.3.0" + path-is-absolute "^1.0.0" + +in-publish@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/in-publish/-/in-publish-2.0.0.tgz#e20ff5e3a2afc2690320b6dc552682a9c7fadf51" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + +minimatch@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.3.tgz#2a4e4090b96b2db06a9d7df01055a62a77c9b774" + dependencies: + brace-expansion "^1.0.0" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + dependencies: + wrappy "1" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + +rimraf@^2.5.0: + version "2.5.4" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.5.4.tgz#96800093cbf1a0c86bd95b4625467535c29dfa04" + dependencies: + glob "^7.0.5" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" diff --git a/scripts/build.js b/scripts/build.js index 34e3157e..1e259cc4 100755 --- a/scripts/build.js +++ b/scripts/build.js @@ -11,7 +11,7 @@ var fs = require('fs'), { rollupOptions:{ format: 'iife', - moduleName: 'j2c' + moduleName: 'J2c' }, name: 'global', minify: {saveGzip:true} @@ -25,7 +25,7 @@ var fs = require('fs'), }, { // not sure this one is necessary by now... rollupOptions:{ - format: 'es6' + format: 'es' }, name: 'es6', minify: false // ATM, uglify chokes on the export statement. @@ -38,17 +38,20 @@ var fs = require('fs'), name: 'amd', minify: {save:true} } - ] - -var parsed = rollup.rollup({ - entry: 'src/main.js' -}) + ], + commonRollupOptions = { + sourceMap: true, + banner: '' + }, + parsed = rollup.rollup({ + entry: 'src/main.js' + }) outputs.forEach(function (output) { parsed.then(function(bundle){ - var result = bundle.generate(output.rollupOptions) - fs.writeFileSync('dist/j2c.' + output.name + '.js', result.code) + var result = bundle.generate(Object.assign({}, output.rollupOptions, commonRollupOptions)) + fs.writeFileSync('dist/j2c.' + output.name + '.js', result.code) //+'\n//'+result.map.toUrl() if (output.minify) { var minified = uglify.minify(result.code, { fromString: true, diff --git a/scripts/docs.js b/scripts/docs.js new file mode 100644 index 00000000..71552b8e --- /dev/null +++ b/scripts/docs.js @@ -0,0 +1,29 @@ +// var fs = require('fs') +// var commonmark = require('commonmark') +// var jsdom = require('jsdom') + +// var branch = fs.readFileSync('.git/HEAD').toString().split(/\r|\n|\r\n/)[0] +// branch = branch.slice(branch.lastIndexOf('/') + 1) + +// var destination = './gh-pages/' + branch + +// var mustRebuildAll = false + +// try { +// if (!fs.fstatSync(fs.openSync(destination)).isDirectory()){ +// console.error('Destination ' + JSON.stringify(destination)+ ' is not a directory.') +// } +// } catch (e) { +// fs.mkdirSync(destination) +// mustRebuildAll = true +// } + +// if (!fs.existsSync(destination + '/partials')) {fs.mkdirSync(destination + '/partials')} + + +// var sources = fs.readDirSync('./docs') +// sources.forEach(function(source) { + +// }) + +// // \ No newline at end of file diff --git a/scripts/install-plugins.js b/scripts/install-plugins.js new file mode 100644 index 00000000..39803a1a --- /dev/null +++ b/scripts/install-plugins.js @@ -0,0 +1,56 @@ +/*eslint no-console: 0*/ +var childProcess = require('child_process') + +var fs = require('fs') +var path = require('path') + +var cwd = process.cwd() +var env = process.env +var isWin = /^win/.test(process.platform) + +function runCmd(cmd, args = [], options = {}, result = {output: '', status: null, stderr: '', stdout: ''}) { + options = Object.assign({}, options, {cwd: options.cwd || path.join(cwd, options.path), env, shell: isWin}) + return new Promise(function(fulfil, reject) { + const proc = childProcess.spawn(cmd, args, options) + const interval = setInterval(function(){ + if(result.output !== '') { + console.log(result.output) + result.output = '' + } + }, 10000) + proc.on('exit', function(status){ + clearInterval(interval) + result.status = status + if(status === 0) fulfil(result) + else reject(result) + }) + proc.stdout.on('data', function(data) { + data = String(data) + result.stdout += data + result.output += data + }) + proc.stderr.on('data', function(data) { + data = String(data) + result.stderr += data + result.output += data + }) + }) +} + +Promise.all(fs.readdirSync('plugins').map(function(dir){ + return runCmd('yarn', [], {path: 'plugins/'+dir}).then( + result => runCmd('yarn', ['run build'], {path: 'plugins/'+dir}, result).catch(()=>({output:'nothing to build\n'})) + ) +})).then( + function ok(results) { + results.forEach(res => console.log(res && res.output)) + // process.exit(results[0] ? results[0].status : 0) + }, + function caught(res){ + console.error('error',res.output) + process.exit(res.status) + } +).catch(function(e){ + console.error(e && e.stack) + process.exit(1) +}) diff --git a/scripts/regexps.js b/scripts/regexps.js index d29ec6cb..a3fa3a95 100644 --- a/scripts/regexps.js +++ b/scripts/regexps.js @@ -1,67 +1,111 @@ /*eslint-env node*/ /*eslint no-console: 0*/ -/*eslint no-undef: 0*/ - +/* global capture: false, either:false, flags: false, greedy:false, sequence: false*/ global.__proto__ = require('compose-regexp') -var maybe = greedy.bind(null, '?') -maybe() +// var maybe = greedy.bind(null, '?') -var animation = sequence( - capture(), - either( - sequence( - ':global(', - /\s*/, - capture(/[-\w]+/), - /\s*/, - ')' - ), - sequence( - capture(), - capture(/[-\w]+/) - ) +var animation = either( + capture( + 'var(', + /[^)]+/, + ')' + ), + sequence( + /:?/, + 'global(', + /\s*/, + capture(/[_A-Za-z][-\w]*/), + /\s*/, + ')' + ), + sequence( + capture(), + capture(/-?[_A-Za-z][-\w]*/) ) ) -console.log('anumation / animation-name\n', animation) +console.log('animation / animation-name / @keyframes\n', animation) -var keyframes = sequence( - capture(' '), +var selector = flags('g', either( + capture( + either( + sequence( + '"', + greedy('*', + either( + /\\./, + /[^"\n]/ + ) + ), + '"' + ), + sequence( + "'", + greedy('*', + either( + /\\./, + /[^'\n]/ + ) + ), + "'" + ), + // This is both correct and faster than the W3C regexp. + // https://jsperf.com/regexpcomment + sequence( + '/*', + /[\s\S]*?/, + '*/' + ) + ) + ), sequence( ':global(', /\s*/, - capture(/[-\w]+/), + capture( + '.', + /-?[_A-Za-z][-\w]*/ + ), /\s*/, ')' ), sequence( - capture(), - capture(/[-\w]+/) + capture('.'), + capture(/-?[_A-Za-z][-\w]*/) ) ) ) -console.log('@keyframes\n', keyframes) +console.log('selector / @global\n', selector) -var selector = flags('g', sequence( - capture(''), +var selectorTokenizer = flags('g', either( + /[(),]/, sequence( - ':global(', - /\s*/, - capture( - '.', - /[-\w]+/ + '"', + greedy('*', + either( + /\\./, + /[^"\n]/ + ) ), - /\s*/, - ')' + '"' ), sequence( - capture('.'), - capture(/[-\w]+/) + "'", + greedy('*', + either( + /\\./, + /[^'\n]/ + ) + ), + "'" + ), + sequence( + '/*', + /[\s\S]*?/, + '*/' ) ) -)) - -console.log('selector / @global\n', selector) +) +console.log('selectorTokenizer = ', selectorTokenizer) diff --git a/src/README.md b/src/README.md new file mode 100644 index 00000000..bb0036b6 --- /dev/null +++ b/src/README.md @@ -0,0 +1,39 @@ +# ./src + +`j2c` is written in a possibly odd JS style. The goals are to provide a JS-backed SASS clone that has a small size, broad compatibilty and reasonable speed. + +As a consequence, the lib is written in shimable ES5 strict mode, with ES6 modules that are eliminated at build time with [rollup](), so that `j2c` works in old IE (for those who are forced to develop for it) and is as small as possible. + +The code is also written with maximal gzippability in mind. It sometimes means favouring code duplication over abstraction, since exact duplicates compress better. I also implies sometimes large conditional expressions, and a heavy use of regexps. + +Variables are created only to avoid duplicate computation, never for readability. Variables are recycled in independent parts of the code, and don't always have a descriptive name. + +These constrains can lead to code that's difficult to scale and bug prone. + +Scalability is not a concern, since I intend `j2c` to remain small. + +Regarding bugs, there's at this point a 2.5/1 test/code ratio (after .min.gzippification of both), and 100% test coverage, which allows to catch a lot of possible mistakes. The RegExps are used wisely, mostly for pattern matching or for lexing. The most complex ones are built separately using a library that emulates multi-line regexps. + +But most of all, behind the raw for loops and repetitions lies a clean, functional architecture. + +Excluding helpers, `j2c` is built around three almost pure, pattern matching (conceptually) functions that recursively walk down JS objects. + +The side effects are well defined: + +- populate the buffer (an array of strings to be joined to form the final style sheet) +- populate or query the local namespace (for local class and animation names, and `@composes`) + +Each of these are implemented through state holding objects (`buf` and `ns`) that are passed as parameters to the pure functions. Side effects are thus isolated and easy to spot. + +Feature isolation makes it trivial to track down and pinpoint the source of bugs when they occur. The hardest bugs I've had to track were, unsurprisingly, while refactoring the impure parts of `j2c`, and especially the class and animation name localization. However, problems usually become evident with a few well placed `console.*` calls. + +## Navigating the source: + +`main.js` and `extras.js` contain the public API (`j2c` and `j2c.*` functions, respectively) + +`sheet.js` handles the selectors part of the tree and dispatch to `at-rules` and `declarations` when needed. + +`declarations.js`, `at-rules.js` and `helpers.js` are self-explaining. `declarations.js` is also at the heart of `j2c.inline`. + +--- + diff --git a/src/at-rules.js b/src/at-rules.js index fa4a82ce..dcaee9fa 100644 --- a/src/at-rules.js +++ b/src/at-rules.js @@ -1,89 +1,134 @@ -import {type, ARRAY, emptyArray} from './helpers' -import {sheet} from './sheet' - -var findClass = /()(?::global\(\s*(\.[-\w]+)\s*\)|(\.)([-\w]+))/g +import {flatIter, randIdentifier} from './helpers' +import {rules} from './rules' +import {declarations} from './declarations' /** - * Hanldes at-rules + * Handle at-rules * - * @param {string} k - The at-rule name, and, if takes both parameters and a - * block, the parameters. - * @param {string[]} buf - the buffer in which the final style sheet is built - * @param {string[]} v - Either parameters for block-less rules or their block - * for the others. - * @param {string} prefix - the current selector or a prefix in case of nested rules - * @param {string} rawPrefix - as above, but without localization transformations - * @param {string} vendors - a list of vendor prefixes - * @Param {boolean} local - are we in @local or in @global scope? - * @param {object} ns - helper functions to populate or create the @local namespace - * and to @extend classes - * @param {function} ns.e - @extend helper - * @param {function} ns.l - @local helper + * @param {object} frontend - holds the localizer- and walker-related methods + * and state + * @param {object} emit - the contextual emitters to the final buffer + * @param {array} k - The parsed at-rule, including the parameters, + * if takes both parameters and a block. + * k == [match, fullAtRule, atRuleType, params?] + * So in `@-webkit-keyframes foo`, we have + * - match = "@-webkit-keyframes foo" + * - fullAtRule = "@-webkit-keyframes" + * - atRuleType = "keyframes" + * - params = "foo" + * @param {string|string[]|object|object[]} v - Either parameters for + * block-less rules or + * their block + * for the others. + * @param {string} prefix - the current selector or the selector prefix + * in case of nested rules + * @param {boolean} local - are we in @local or in @global scope? + * @param {string} nestingDepth - are we nested in an at-rule or a selector? */ -export function at(k, v, buf, prefix, rawPrefix, vendors, local, ns){ - var kk - if (/^@(?:namespace|import|charset)$/.test(k)) { - if(type.call(v) == ARRAY){ - for (kk = 0; kk < v.length; kk++) { - buf.push(k, ' ', v[kk], ';\n') + +export function modulesAtRules(next) { + return function (frontend, emit, k, v, prefix, local, nestingDepth) { + if (k[2] === 'global' && !k[3]) { + + rules(frontend, emit, prefix, v, 0, nestingDepth) + + + } else if (k[2] === 'local' && !k[3]) { + + rules(frontend, emit, prefix, v, 1, nestingDepth) + + + } else if (k[2] === 'adopt' && k[3]) { + + if (!local || nestingDepth) return emit.err('@adopt global or nested: ' + k[0]) + + if (!/^\.?[_A-Za-z][-\w]*$/.test(k[3])) return emit.err('bad adopter ' + JSON.stringify(k[3]) + ' in ' + k[0]) + + var classes = [] + flatIter(function(adoptee, asString) { + + if(adoptee == null || !/^\.?[_A-Za-z][-\w]*(?:\s+\.?[_A-Za-z][-\w]*)*$/.test(asString = adoptee + '')) emit.err('bad adoptee '+ JSON.stringify(adoptee) + ' in ' + k[0]) + + else classes.push(asString.replace(/\./g, '')) + + })(v) + + // we may end up with duplicate classes but AFAIK it has no consequences on specificity. + if (classes.length) { + frontend.localize(k[3] = k[3].replace(/\./g, '')) + frontend.names[k[3]] += (' ' + classes.join(' ')) } + } else { - buf.push(k, ' ', v, ';\n') - } - } else if (/^@keyframes /.test(k)) { - k = local ? k.replace( - // generated by script/regexps.js - /( )(?::global\(\s*([-\w]+)\s*\)|()([-\w]+))/, - ns.l - ) : k - // add a @-webkit-keyframes block too. - - buf.push('@-webkit-', k.slice(1), ' {\n') - sheet(v, buf, '', '', ['webkit']) - buf.push('}\n') - - buf.push(k, ' {\n') - sheet(v, buf, '', '', vendors, local, ns) - buf.push('}\n') - - } else if (/^@extends?$/.test(k)) { - - /*eslint-disable no-cond-assign*/ - // pick the last class to be extended - while (kk = findClass.exec(rawPrefix)) k = kk[4] - /*eslint-enable no-cond-assign*/ - if (k == null || !local) { - // we're in a @global{} block - buf.push('@-error-cannot-extend-in-global-context ', JSON.stringify(rawPrefix), ';\n') - return - } else if (/^@extends?$/.test(k)) { - // no class in the selector - buf.push('@-error-no-class-to-extend-in ', JSON.stringify(rawPrefix), ';\n') - return + if (local && k[2] === 'keyframes' && k[3]) { + + k[3] = k[3].replace( + // generated by script/regexps.js + /(var\([^)]+\))|:?global\(\s*([_A-Za-z][-\w]*)\s*\)|()(-?[_A-Za-z][-\w]*)/, + frontend.localizeReplacer + ) + + } + next(frontend, emit, k, v, prefix, local, nestingDepth) } - ns.e( - type.call(v) == ARRAY ? v.map(function (parent) { - return parent.replace(/()(?::global\(\s*(\.[-\w]+)\s*\)|()\.([-\w]+))/, ns.l) - }).join(' ') : v.replace(/()(?::global\(\s*(\.[-\w]+)\s*\)|()\.([-\w]+))/, ns.l), - k - ) + } +} +export function standardAtRules(next) { + return function(frontend, emit, k, v, prefix, local, nestingDepth) { + if ((k[2] === 'namespace' || k[2] === 'import' || k[2] === 'charset') && !k[3]) { + flatIter(function(v) { + + emit.atrule(k[1], k[2], v) + + })(v) + - } else if (/^@(?:font-face$|viewport$|page )/.test(k)) { - sheet(v, buf, k, k, emptyArray) + } else if (k[2] === 'font-face' || k[2] === 'page' || k[2] === 'viewport') { + flatIter(function(v) { - } else if (/^@global$/.test(k)) { - sheet(v, buf, prefix, rawPrefix, vendors, 0, ns) + emit.atrule(k[1], k[2], k[3], 'decl') - } else if (/^@local$/.test(k)) { - sheet(v, buf, prefix, rawPrefix, vendors, 1, ns) + declarations(frontend, emit, '', v, local) + + emit._atrule() + + })(v) + + } else if (( k[2] === 'media'|| k[2] === 'supports') && k[3] || k[2] === 'keyframes') { + + if (k[2] === 'keyframes' && k[3] === '') { + if(prefix !== '') { + k[3] = randIdentifier(8) + // this is necessary if the rule hasn't been opened yet + // it is ignored if the rule is already opened. + emit.rule(prefix) + emit.decl('animation-name', k[3]) + } else { + emit.err('Unexpected anonymous @keyframes out of selector') + return + } + } - } else if (/^@(?:media |supports |document )./.test(k)) { - buf.push(k, ' {\n') - sheet(v, buf, prefix, rawPrefix, vendors, local, ns) - buf.push('}\n') - } else { - buf.push('@-error-unsupported-at-rule ', JSON.stringify(k), ';\n') + emit.atrule(k[1], k[2], k[3], 'rule') + + rules( + frontend, emit, + 'keyframes' == k[2] ? '' : prefix, + v, local, nestingDepth + 1 + ) + + emit._atrule() + + } else { + + next(frontend, emit, k, v, prefix, local, nestingDepth) + + } } } + +export function unsupportedAtRule(frontend, emit, k){ + emit.err('Unsupported at-rule: ' + k[0]) +} diff --git a/src/declarations.js b/src/declarations.js index c2b826ed..493f982e 100644 --- a/src/declarations.js +++ b/src/declarations.js @@ -7,73 +7,69 @@ function decamelize(match) { /** * Handles the property:value; pairs. * - * @param {array|object|string} o - the declarations. - * @param {string[]} buf - the buffer in which the final style sheet is built. + * @param {object} frontend - holds the localizer- and walker-related methods + * and state + * @param {object} emit - the contextual emitters to the final buffer * @param {string} prefix - the current property or a prefix in case of nested * sub-properties. - * @param {string} vendors - a list of vendor prefixes. - * @Param {boolean} local - are we in @local or in @global scope. - * @param {object} ns - helper functions to populate or create the @local namespace - * and to @extend classes. - * @param {function} ns.e - @extend helper. - * @param {function} ns.l - @local helper. + * @param {array|object|string} o - the declarations. + * @param {boolean} local - are we in @local or in @global scope. */ -export function declarations(o, buf, prefix, vendors, local, ns, /*var*/ k, v, kk) { +export function declarations(frontend, emit, prefix, o, local) { + var k, v, kk if (o==null) return - if (/\$/.test(prefix)) { - for (kk in (prefix = prefix.split('$'))) if (own.call(prefix, kk)) { - declarations(o, buf, prefix[kk], vendors, local, ns) - } - return - } + switch ( type.call(o = o.valueOf()) ) { case ARRAY: for (k = 0; k < o.length; k++) - declarations(o[k], buf, prefix, vendors, local, ns) + + declarations(frontend, emit, prefix, o[k], local) + break case OBJECT: // prefix is falsy iif it is the empty string, which means we're at the root // of the declarations list. - prefix = (prefix && prefix + '-') + for (k in o) if (own.call(o, k)){ v = o[k] - if (/\$/.test(k)) { - for (kk in (k = k.split('$'))) if (own.call(k, kk)) - declarations(v, buf, prefix + k[kk], vendors, local, ns) + if (k.indexOf('$') !== -1) { + for (kk in (k = k.split('$'))) if (own.call(k, kk)) { + + declarations(frontend, emit, prefix + k[kk], v, local) + + } } else { - declarations(v, buf, prefix + k, vendors, local, ns) + + declarations(frontend, emit, prefix + k, v, local) + } } break default: // prefix is falsy when it is "", which means that we're // at the top level. - // `o` is then treated as a `property:value` pair. - // otherwise, `prefix` is the property name, and + // `o` is then treated as a `property:value` pair, or a + // semi-colon-separated list thereof. + if (!prefix) return emit.raw(o) + + // Otherwise, `prefix` is the property name, and // `o` is the value. - k = prefix.replace(/_/g, '-').replace(/[A-Z]/g, decamelize) - if (local && (k == 'animation-name' || k == 'animation')) { - o = o.split(',').map(function (o) { - return o.replace(/()(?::global\(\s*([-\w]+)\s*\)|()([-\w]+))/, ns.l) - }).join(',') - } - if (/^animation|^transition/.test(k)) vendors = ['webkit'] - // '@' in properties also triggers the *ielte7 hack - // Since plugins dispatch on the /^@/ for at-rules - // we swap the at for an asterisk - // http://browserhacks.com/#hack-6d49e92634f26ae6d6e46b3ebc10019a + // restore the dashes + k = prefix.replace(/[A-Z]/g, decamelize) - k = k.replace(/^@/, '*') + if (local && (k == 'animation-name' || k == 'animation')) { + // No need to tokenize here. We split in comas not followed by a keyword + // legal in `step()` functions. + // We may 'localize' a comment, but it's not a big deal. + o = o.split(/,(?!\s*(?:start|end)\b)/).map(function (o) { -/*/-statements-/*/ - // vendorify - for (kk = 0; kk < vendors.length; kk++) - buf.push('-', vendors[kk], '-', k, k ? ':': '', o, ';\n') -/*/-statements-/*/ + return o.replace(/^\s*(?:(var\([^),]+(?:\)|$))|(?:var\([^,]+,\s*)??:?global\(\s*([_A-Za-z][-\w]*)\s*\)|(?:var\([^,]+,\s*)??()(-?[_A-Za-z][-\w]*))/, frontend.localizeReplacer) - buf.push(k, k ? ':': '', o, ';\n') + }).join(',') + } + emit.decl(k, o) } } diff --git a/src/extras.js b/src/extras.js new file mode 100644 index 00000000..7a0eabd3 --- /dev/null +++ b/src/extras.js @@ -0,0 +1,22 @@ +export function global(x) { + return ':global(' + x + ')' +} + +export function kv (k, v, o) { + o = {} + o[k] = v + return o +} + +export function at (rule, params, block) { + if ( + arguments.length < 3 + ) { + // inner curry! + var _at = at.bind.apply(at, Array.prototype.concat.apply([this], arguments)) + // So that it can be used as a key in an ES6 object literal. + _at.toString = function(){return '@' + rule + ' ' + params} + return _at + } + return kv('@' + rule +' ' + params, block) +} \ No newline at end of file diff --git a/src/helpers.js b/src/helpers.js index 53368d94..5498b629 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -1,40 +1,106 @@ var emptyObject = {}, - emptyArray = [], type = emptyObject.toString, - own = emptyObject.hasOwnProperty, + ARRAY = type.call([]), + NUMBER = type.call(0), OBJECT = type.call(emptyObject), - ARRAY = type.call(emptyArray), - STRING = type.call('') - -/*/-inline-/*/ -// function cartesian(a, b, res, i, j) { -// res = []; -// for (j in b) if (own.call(b, j)) -// for (i in a) if (own.call(a, i)) -// res.push(a[i] + b[j]); -// return res; -// } -/*/-inline-/*/ - -/* /-statements-/*/ -function cartesian(a,b, selectorP, res, i, j) { - res = [] + STRING = type.call(''), + FUNCTION = type.call(type), + own = emptyObject.hasOwnProperty, + freeze = Object.freeze || function(o) {return o} + + +function defaults(target, source) { + for (var k in source) if (own.call(source, k)) { + if (k.indexOf('$') && !(k in target)) target[k] = source[k] + } + return target +} + +function cartesian(a,b) { + var res = [], i, j for (j in b) if(own.call(b, j)) for (i in a) if(own.call(a, i)) - res.push(concat(a[i], b[j], selectorP)) + res.push(a[i] + b[j]) + return res +} + +// "Tokenizes" the selectors into parts relevant for the next function. +// Strings and comments are matched, but ignored afterwards. +// This is not a full tokenizers. It only recognizes comas, parentheses, +// strings and comments. +// regexp generated by scripts/regexps.js then trimmed by hand +var selectorTokenizer = /[(),]|"(?:\\.|[^"\n])*"|'(?:\\.|[^'\n])*'|\/\*[\s\S]*?\*\//g + + +/** + * This will split a coma-separated selector list into individual selectors, + * ignoring comas in strings, comments and in :pseudo-selectors(parameter, lists). + * + * @param {string} selector + * @return {string[]} + */ + +function splitSelector(selector) { + var indices = [], res = [], inParen = 0, o + /*eslint-disable no-cond-assign*/ + while (o = selectorTokenizer.exec(selector)) { + /*eslint-enable no-cond-assign*/ + switch (o[0]) { + case '(': inParen++; break + case ')': inParen--; break + case ',': if (inParen) break; indices.push(o.index) + } + } + for (o = indices.length; o--;){ + res.unshift(selector.slice(indices[o] + 1)) + selector = selector.slice(0, indices[o]) + } + res.unshift(selector) return res } -function concat(a, b, selectorP) { - // `b.replace(/&/g, a)` is never falsy, since the - // 'a' of cartesian can't be the empty string - // in selector mode. - return selectorP && ( - /^[-\w$]+$/.test(b) && ':-error-bad-sub-selector-' + b || - /&/.test(b) && /* never falsy */ b.replace(/&/g, a) - ) || a + b +// Like the `selectorTokenizer`, but for the `&` operator +var ampersandTokenizer = /&|"(?:\\.|[^"\n])*"|'(?:\\.|[^'\n])*'|\/\*[\s\S]*?\*\//g + +function ampersand (selector, parents) { + var indices = [], split = [], res, o + /*eslint-disable no-cond-assign*/ + while (o = ampersandTokenizer.exec(selector)) { + /*eslint-enable no-cond-assign*/ + if (o[0] == '&') indices.push(o.index) + } + for (o = indices.length; o--;){ + split.unshift(selector.slice(indices[o] + 1)) + selector = selector.slice(0, indices[o]) + } + split.unshift(selector) + if (split.length === 1) split.unshift('') + res = [split[0]] + for (o = 1; o < split.length; o++) { + res = cartesian(res, cartesian(parents, [split[o]])) + } + return res.join(',') +} + +function flatIter (f) { + return function iter(arg) { + if (type.call(arg) === ARRAY) for (var i= 0 ; i < arg.length; i ++) iter(arg[i]) + else f(arg) + } +} + +var chars = '' +function randIdentifier(n) { + while(chars.length < n) chars += Math.floor(Math.random() * 0x100000000).toString(36) + var res = '_' + chars.slice(0, n) + chars = chars.slice(n) + return res } -/* /-statements-/*/ -export {emptyObject, emptyArray, type, own, OBJECT, ARRAY, STRING, cartesian, concat} \ No newline at end of file +export { + ARRAY, FUNCTION, NUMBER, OBJECT, STRING, + ampersand, cartesian, defaults, + flatIter, freeze, own, + randIdentifier, splitSelector, type +} \ No newline at end of file diff --git a/src/main.js b/src/main.js index 9b1c2ee0..7cb322fb 100644 --- a/src/main.js +++ b/src/main.js @@ -1,120 +1,155 @@ -import {own, cartesian, emptyArray} from './helpers' -import {sheet} from './sheet' +import {defaults, flatIter, freeze, randIdentifier, own, type, ARRAY, FUNCTION, NUMBER, OBJECT, STRING} from './helpers' +import {closeSelectors, rules} from './rules' import {declarations} from './declarations' +import {modulesAtRules, standardAtRules, unsupportedAtRule} from './at-rules' -var scope_root = '_j2c_' + - Math.floor(Math.random() * 0x100000000).toString(36) + '_' + - Math.floor(Math.random() * 0x100000000).toString(36) + '_' + - Math.floor(Math.random() * 0x100000000).toString(36) + '_' + - Math.floor(Math.random() * 0x100000000).toString(36) + '_', - counter = 0 +var baselineAtRules = modulesAtRules(standardAtRules(unsupportedAtRule)) -export default function j2c(res) { - res = res || {} - var extensions = [] +function invoke(fn, tree, state, backend) { + backend.init() + try{ + fn( + state, + backend, + '', // selector/property prefix + tree, + 1, // local, by default + 0 // nesting depth, only for sheet + ) + } catch(e) {backend.err(e instanceof Error ? e.stack : '' + e)} + return backend.done() +} - function finalize(buf, i) { - for (i = 0; i< extensions.length; i++) buf = extensions[i](buf) || buf - return buf.join('') - } +function bakePrefix(name, length) { + return ( + name + ? name.replace(/\W+/g, '_') + '__' + : randIdentifier(length) + '__' + ) +} - res.use = function() { - var args = arguments - for (var i = 0; i < args.length; i++){ - extensions.push(args[i]) - } - return res - } -/*/-statements-/*/ - res.sheet = function(ns, statements) { - if (arguments.length === 1) { - statements = ns; ns = {} - } - var - suffix = scope_root + counter++, - locals = {}, - k, buf = [] - // pick only non-numeric keys since `(NaN != NaN) === true` - for (k in ns) if (k-0 != k-0 && own.call(ns, k)) { - locals[k] = ns[k] - } - sheet( - statements, buf, '', '', emptyArray /*vendors*/, - 1, // local - { - e: function extend(parent, child) { - var nameList = locals[child] - locals[child] = - nameList.slice(0, nameList.lastIndexOf(' ') + 1) + - parent + ' ' + - nameList.slice(nameList.lastIndexOf(' ') + 1) - }, - l: function localize(match, space, global, dot, name) { - if (global) { - return space + global - } - if (!locals[name]) locals[name] = name + suffix - return space + dot + locals[name].match(/\S+$/) - } - } - ) - /*jshint -W053 */ - buf = new String(finalize(buf)) - /*jshint +W053 */ - for (k in locals) if (own.call(locals, k)) buf[k] = locals[k] - return buf +function makeInstance(prefix, prefixLength, atrules, nsCache, backend, setPropList) { + var names = {} + function localize(name) { + if (!own.call(names, name)) names[name] = prefix + name + return names[name].match(/^\S+/) } -/*/-statements-/*/ - res.inline = function (locals, decl, buf) { - if (arguments.length === 1) { - decl = locals; locals = {} + var state = { + atrules: atrules, + names: names, + /** + * Returns a localized version of a given name. + * Registers the pair in `instnace.name` if needed. + * + * @param {string} name - the name to localize + * @return {string} - the localized version + */ + localize: localize, + /** + * Used as second argument for str.replace(localizeRegex, replacer) + * `ignore`, `global` and `(dot, name)` are mutually exclusive + * + * @param {string} match - the whole match (ignored) + * @param {string|null} ignore - a comment or a string literal + * @param {string|null} global - a global name + * @param {string|null} dot - either '.' for a local class name or the empty string otherwise + * @param {string|null} name - the name to localize + * @return {string} + */ + localizeReplacer: function (match, ignore, global, dot, name) { + return ignore || global || dot + localize(name) } - declarations( - decl, - buf = [], - '', // prefix - emptyArray, // vendors - 1, - { - l: function localize(match, space, global, dot, name) { - if (global) return space + global - if (!locals[name]) return name - return space + dot + locals[name] - } - }) - return finalize(buf) } - res.prefix = function(val, vendors) { - return cartesian( - vendors.map(function(p){return '-' + p + '-'}).concat(['']), - [val] - ) + var instance = { + ns: function(name) { + var prefix = bakePrefix(name, prefixLength) + if (!own.call(nsCache, prefix)) { + nsCache[prefix] = makeInstance(prefix, prefixLength, atrules, nsCache, backend, setPropList) + } + return nsCache[prefix] + }, + names: names, + prefix: prefix, + sheet: function(tree) {return invoke(rules, tree, state, backend[0])}, + inline: function (tree) {return invoke(declarations, tree, state, backend[1])} } - return res + for (var i = setPropList.length; i--;) defaults(instance, setPropList[i]) + return instance } -j2c.global = function(x) { - return ':global(' + x + ')' -} +export default function J2c(options) { + options = options || {} + // the buffer that accumulates the output. Initialized in `$sink.init()` + var buf, err -j2c.kv = kv -function kv (k, v, o) { - o = {} - o[k] = v - return o -} + // the default sink. + var _backend = [{ + init: function () {buf=[], err=[]}, + done: function (raw) { + if (err.length != 0) throw new Error('j2c error(s): ' + JSON.stringify(err,null,2) + ' in context:\n' + buf.join('')) + return raw ? buf : buf.join('') + }, + err: function (msg) { + err.push(msg) + buf.push('/* +++ ERROR +++ ' + msg + ' */\n') + }, + raw: function (str) {buf.push(str, '\n')}, + atrule: function (rule, kind, param, takesBlock) { + buf.push(rule, param && ' ', param, takesBlock ? ' {\n' : ';\n') + }, + // close atrule + _atrule: function () {buf.push('}\n')}, + rule: function (selector) {buf.push(selector, ' {\n')}, + // close rule + _rule: function () {buf.push('}\n')}, + decl: function (prop, value) {buf.push(prop, ':', value, ';\n')} + }] + + // holds the `_filter` and `atrule` handlers + var _filters = [closeSelectors] + var _atrulePlugins = [] + var _setPropList = [] + var _prefixLength = 7, _prefix + var _nsCache = {} + var _atrules = baselineAtRules + // the public API (see the main docs) -j2c.at = function at (rule, params, block) { - if ( - arguments.length < 3 - ) { - var _at = at.bind.apply(at, [null].concat([].slice.call(arguments,0))) - _at.toString = function(){return '@' + rule + ' ' + params} - return _at + + // handler options + if (type.call(options.plugins) === ARRAY) { + flatIter(function(plugin) { + if (type.call(plugin) !== OBJECT) throw new Error('bad plugin, object expected, got '+ type.call(plugin)) + + if (type.call(plugin.filter) === FUNCTION) _filters.push(plugin.filter) + if (type.call(plugin.atrule) === FUNCTION) _atrulePlugins.push(plugin.atrule) + if (type.call(plugin.sink) === FUNCTION) _backend = plugin.sink() + if (type.call(plugin.set) === OBJECT) _setPropList.push(plugin.set) + })(options.plugins) } - else return kv('@' + rule + ' ' + params, block) -} + if (type.call(options.prefix) === NUMBER) _prefixLength = options.prefix + _prefix = bakePrefix((type.call(options.prefix) === STRING) ? options.prefix: null, _prefixLength) -j2c(j2c) -delete j2c.use + for (var i = _atrulePlugins.length; i--;) _atrules = _atrulePlugins[i](_atrules) + + _backend[1] = _backend[1] || { + init: _backend[0].init, + done: _backend[0].done, + raw: _backend[0].raw, + err: _backend[0].err, + decl: _backend[0].decl + } + + // finalize the backend by merging in the filters + for(i = 0; i < 2; i++){ // 0 for j2c.sheet, 1 for j2c.inline + for (var j = _filters.length; j--;) { + _backend[i] = freeze( + defaults( + _filters[j](_backend[i], !!i), + _backend[i] + ) + ) + } + } + return freeze(makeInstance(_prefix, _prefixLength, _atrules, _nsCache, _backend, _setPropList)) +} diff --git a/src/rules.js b/src/rules.js new file mode 100644 index 00000000..4a2a1595 --- /dev/null +++ b/src/rules.js @@ -0,0 +1,170 @@ +import {type, ARRAY, OBJECT, STRING, ampersand, own, splitSelector} from './helpers' +import {declarations} from './declarations' + +/** + * Add rulesets and other CSS tree to the sheet. + * + * @param {object} frontend - holds the localizer- and walker-related methods + * and state + * @param {object} emit - the contextual emitters to the final buffer + * @param {string} prefix - the current selector or a prefix in case of nested rules + * @param {array|string|object} tree - a source object or sub-object. + * @param {string} nestingDepth - are we nested in an at-rule? + * @param {boolean} local - are we in @local or in @global scope? + */ +export function rules(frontend, emit, prefix, tree, local, nestingDepth) { + var k, v, inDeclaration, kk + + switch (type.call(tree)) { + + case OBJECT: + for (k in tree) if (own.call(tree, k)) { + v = tree[k] + + if (prefix.length > 0 && /^\*?[-\w$]+$/.test(k)) { + if (!inDeclaration) { + inDeclaration = 1 + + emit.rule(prefix) + + } + if (k.indexOf('$') !== -1) { + for (kk in (k = k.split('$'))) if (own.call(k, kk)) { + + declarations(frontend, emit, k[kk], v, local) + + } + } else { + + declarations(frontend, emit, k, v, local) + + } + + } else if (k.charAt(0) === '@') { + // Handle At-rules + inDeclaration = 0 + + frontend.atrules(frontend, emit, + /^(.(?:-[\w]+-)?([_A-Za-z][-\w]*))\b\s*([\s\S]*?)\s*$/.exec(k) || [k,'@','',''], + v, prefix, local, nestingDepth + ) + + } else { + // selector or nested sub-selectors + inDeclaration = 0 + + if (k === '') { + emit._rule() + emit.err("Invalid selector ''") + continue + } + + rules( + frontend, emit, + // build the selector `prefix` for the next iteration. + // ugly and full of redundant bits but so far the fastest/shortest.gz + /*0 if*/(prefix.length > 0 && (prefix.indexOf(',') + k.indexOf(',') !== -2)) ? + + /*0 then*/ (kk = splitSelector(prefix), splitSelector( + local ? + + k.replace( + /("(?:\\.|[^"\n])*"|'(?:\\.|[^'\n])*'|\/\*[\s\S]*?\*\/)|:global\(\s*(\.-?[_A-Za-z][-\w]*)\s*\)|(\.)(-?[_A-Za-z][-\w]*)/g, + frontend.localizeReplacer + ) : + + k + ).map(function (k) { + return (k.indexOf('&') !== -1) ? ampersand(k, kk) : kk.map(function(kk) { + return kk + k + }).join(',') + }).join(',')) : + + /*0 else*/ /*1 if*/ (k.indexOf('&') !== -1) ? + + /*1 then*/ ampersand( + local ? + + k.replace( + /("(?:\\.|[^"\n])*"|'(?:\\.|[^'\n])*'|\/\*[\s\S]*?\*\/)|:global\(\s*(\.-?[_A-Za-z][-\w]*)\s*\)|(\.)(-?[_A-Za-z][-\w]*)/g, + frontend.localizeReplacer + ) : + + k, + [prefix] + ) : + + /*1 else*/ prefix + ( + local ? + + k.replace( + /("(?:\\.|[^"\n])*"|'(?:\\.|[^'\n])*'|\/\*[\s\S]*?\*\/)|:global\(\s*(\.-?[_A-Za-z][-\w]*)\s*\)|(\.)(-?[_A-Za-z][-\w]*)/g, + frontend.localizeReplacer + ) : + + k + ), + v, local, nestingDepth + 1 + ) + + } + } + + break + + case ARRAY: + for (k = 0; k < tree.length; k++){ + + rules(frontend, emit, prefix, tree[k], local, nestingDepth) + + } + break + + case STRING: + // CSS hacks or ouptut of `j2c.inline`. Even raw rulesets if in top position. + + if (prefix.length) emit.rule(prefix) + + emit.raw(tree) + + } +} + +// Technically the outermost part of the backend, it is +// conceptually part of the fronted that would otherwise +// emit invalid CSS. It inserts closing braces to close +// normal (non at-) rules (those that start with a selector). +// Doing it earlier is impossible without passing frontend +// around in unrelated code or ending up with duplicated +// selectors when the source tree contains arrays. +export function closeSelectors(next, inline) { + var lastSelector + return inline ? next : { + init: function(){ + lastSelector = '' + next.init() + }, + done: function () { + if (lastSelector !== '') {next._rule(); lastSelector = ''} + return next.done() + }, + atrule: function (rule, kind, param, takesBlock) { + if (lastSelector !== '') {next._rule(); lastSelector = ''} + next.atrule(rule, kind, param, takesBlock) + }, + _atrule: function (rule) { + if (lastSelector !== '') {next._rule(); lastSelector = ''} + next._atrule(rule) + }, + rule: function (selector) { + if (selector !== lastSelector){ + if (lastSelector !== '') next._rule() + next.rule(selector) + lastSelector = selector + } + }, + _rule: function(){ + if (lastSelector) {next._rule(); lastSelector = ''} + } + } +} diff --git a/src/sheet.js b/src/sheet.js deleted file mode 100644 index 1875e462..00000000 --- a/src/sheet.js +++ /dev/null @@ -1,78 +0,0 @@ -import {type, ARRAY, OBJECT, STRING, cartesian, concat} from './helpers' -import {declarations} from './declarations' -import {at} from './at-rules' - -/** - * Add rulesets and other CSS statements to the sheet. - * - * @param {array|string|object} statements - a source object or sub-object. - * @param {string[]} buf - the buffer in which the final style sheet is built - * @param {string} prefix - the current selector or a prefix in case of nested rules - * @param {string} rawPrefix - as above, but without localization transformations - * @param {string} vendors - a list of vendor prefixes - * @Param {boolean} local - are we in @local or in @global scope? - * @param {object} ns - helper functions to populate or create the @local namespace - * and to @extend classes - * @param {function} ns.e - @extend helper - * @param {function} ns.l - @local helper - */ -export function sheet(statements, buf, prefix, rawPrefix, vendors, local, ns) { - var k, kk, v, inDeclaration - - switch (type.call(statements)) { - - case ARRAY: - for (k = 0; k < statements.length; k++) - sheet(statements[k], buf, prefix, rawPrefix, vendors, local, ns) - break - - case OBJECT: - for (k in statements) { - v = statements[k] - if (prefix && /^[-\w$]+$/.test(k)) { - if (!inDeclaration) { - inDeclaration = 1 - buf.push(( prefix || '*' ), ' {\n') - } - declarations(v, buf, k, vendors, local, ns) - } else if (/^@/.test(k)) { - // Handle At-rules - inDeclaration = (inDeclaration && buf.push('}\n') && 0) - - at(k, v, buf, prefix, rawPrefix, vendors, local, ns) - - } else { - // selector or nested sub-selectors - - inDeclaration = (inDeclaration && buf.push('}\n') && 0) - - sheet(v, buf, - (kk = /,/.test(prefix) || prefix && /,/.test(k)) ? - cartesian(prefix.split(','), ( local ? - k.replace( - /()(?::global\(\s*(\.[-\w]+)\s*\)|(\.)([-\w]+))/g, ns.l - ) : k - ).split(','), prefix).join(',') : - concat(prefix, ( local ? - k.replace( - /()(?::global\(\s*(\.[-\w]+)\s*\)|(\.)([-\w]+))/g, ns.l - ) : k - ), prefix), - kk ? - cartesian(rawPrefix.split(','), k.split(','), rawPrefix).join(',') : - concat(rawPrefix, k, rawPrefix), - vendors, - local, ns - ) - } - } - if (inDeclaration) buf.push('}\n') - break - case STRING: - buf.push( - ( prefix || ':-error-no-selector' ) , ' {\n' - ) - declarations(statements, buf, '', vendors, local, ns) - buf.push('}\n') - } -} diff --git a/test-utils/ospec-instance.js b/test-utils/ospec-instance.js new file mode 100644 index 00000000..5541bd7a --- /dev/null +++ b/test-utils/ospec-instance.js @@ -0,0 +1,4 @@ +var ospec = require('ospec') + +var o = module.exports = ospec['new']('j2c') +ospec('j2c', o.run) \ No newline at end of file diff --git a/test-utils/sinks.js b/test-utils/sinks.js new file mode 100644 index 00000000..e062f96f --- /dev/null +++ b/test-utils/sinks.js @@ -0,0 +1,58 @@ + +// This simple backend pushes the arguments in arrays in a buffer and return it as is. +// So, for example, +// j2c.sheet({'@global': { +// '@keyframes foo': { +// 'from, to': {width: 0} +// }, +// '.bar' :{ +// animation: 'baz 1sec', +// } +// }}) +// +// becomes +// +// [ +// ['atrule', '@keyframes', 'keyframes', 'foo', 'rule'], +// ['rule', 'from, to'], +// ['decl', 'width', 0], +// ['_rule'], +// ['_atrule'], +// ['rule', '.bar'], +// ['decl', 'animation', 'baz 1sec'], +// ['_rule'] +// ] + +module.exports.simple = {sink: function() { + var buffer + return [{ + init: function() { + buffer = [] + }, + done: function() { + return buffer + }, + atrule : function(rule, kind, params, hasblock) { + buffer.push(['atrule', rule, kind, params, hasblock]) + }, + _atrule : function() { + buffer.push(['_atrule']) + }, + decl: function(prop, value) { + buffer.push(['decl', prop, value]) + }, + rule : function(selector) { + buffer.push(['rule', selector]) + }, + _rule : function() { + buffer.push(['_rule']) + }, + err: function(message) { + buffer.push(['err', message]) + }, + raw: function(str) { + buffer.push(['raw', str]) + }, + buffer: buffer + }] +}} \ No newline at end of file diff --git a/test/test.js b/test/test.js deleted file mode 100644 index f8a60206..00000000 --- a/test/test.js +++ /dev/null @@ -1,1080 +0,0 @@ -/*eslint-env node, mocha */ - -// used to normalize styles for reliable comparison. -var expect = require('expect.js'), - minifySelectors = require('postcss-minify-selectors'), - minifyParams = require('postcss-minify-params'), - perfectionist = require('perfectionist'), - postcss = require('postcss')([perfectionist({format:'compressed'}), minifySelectors(), minifyParams()]) - - - -function normalize(s) { return postcss.process(s).css } - -function check(result, expected){ - result = normalize(result) - - // since you can't rely on the order of JS object keys, sometimes, several "expected" - // values must be provided. - // expected = (expected instanceof Array ? expected : [expected]).map(function(s){ - // return normalize(s) - // }) - expect(normalize(expected)).to.contain(result) -} - -function randStr() { - return Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 5) -} -function randInt() { - return Math.random().toString().substr(2, 3) -} - -function webkitify(decl) {return '-webkit-' + decl + '\n' + decl} - -[ - '../dist/j2c.commonjs', - '../dist/j2c.commonjs.min' - // , - // '../dist/inline/j2c.commonjs', - // '../dist/inline/j2c.commonjs.min' -].forEach(function(lib){ - var j2c = require(lib) - - function checkinline(result, expected){ - result = 'p{' + j2c.inline(result) + '}' - expected = (expected instanceof Array ? expected : [expected]).map(function(s){ - return 'p{' + s + '}' - }) - check(result, expected) - } - - - - ///////////////////////////// - /**/ suite('Inline: ') /**/ - ///////////////////////////// - - - test('a single property', function() { - checkinline( - {foo: 'bar'}, - 'foo:bar;' - ) - }) - - test('two properties', function() { - checkinline( - {foo: 'bar', baz: 'qux'}, - 'foo:bar;baz:qux;' - ) - }) - - test('two properties, ensure order', function() { - check(j2c.inline({foo: 'bar', baz: 'qux'}), 'foo:bar;\nbaz:qux;') - }) - - - test('array of values', function() { - checkinline( - {foo:['bar', 'baz']}, - 'foo:bar;foo:baz;' - ) - }) - - test('sub-properties', function(){ - checkinline( - {foo: {bar: 'baz'}}, - 'foo-bar:baz;' - ) - }) - - test('multiple sub-properties', function(){ - checkinline( - {foo: {bar$qux: 'baz'}}, - 'foo-bar:baz;foo-qux:baz;' - ) - }) - - test('multiple sub-properties, ensure order', function() { - check(j2c.inline({foo$baz: 'qux'}), 'foo:qux;\nbaz:qux;') - }) - - - - test('multiple sub-properties with a sub-sub-property', function(){ - checkinline( - {foo: {bar$baz: {qux: 'quux'}}}, - 'foo-bar-qux:quux;foo-baz-qux:quux;' - ) - }) - - test('multiple sub-properties with two sub-sub-properties', function(){ - checkinline( - {foo: {bar$baz: {qux$quux: 'fred'}}}, - 'foo-bar-qux:fred;foo-bar-quux:fred;foo-baz-qux:fred;foo-baz-quux:fred;' - ) - }) - - test('convert underscores', function() { - checkinline( - {'f_o_o': 'bar'}, - 'f-o-o:bar;' - ) - }) - - test('convert CamelCase', function() { - checkinline( - {'FoO': 'bar'}, - '-fo-o:bar;' - ) - }) - - test('String value', function() { - checkinline( - 'foo:bar', - 'foo:bar;' - ) - }) - - test('Array of Strings values', function() { - checkinline( - ['foo:bar', 'foo:baz'], - 'foo:bar;foo:baz' - ) - }) - - test('Array of mixed values at the root', function() { - checkinline( - ['foo:bar', {foo: 'baz'}], - 'foo:bar;foo:baz' - ) - }) - - test('Array of mixed value and sub-property', function() { - checkinline( - {foo:['bar', {baz: 'qux'}]}, - 'foo:bar;foo-baz:qux' - ) - }) - - test('Prefixes by hand', function() { - checkinline( - {_o$_p$: {foo: 'bar'}}, - '-o-foo:bar;-p-foo:bar;foo:bar;' - ) - }) - - test('CSS *Hack', function() { - // tested manually because the crass normalization - // outputs an empty string. - check(j2c.inline({'*foo': 'bar'}), '*foo:bar;') - }) - - test('CSS _Hack', function() { - checkinline( - ['_foo:bar', {_baz: 'qux'}], - '_foo:bar;-baz:qux;' - ) - }) - - test('custom obj.valueOf', function() { - var bar = {valueOf:function(){return 'bar'}} - checkinline( - {foo:bar}, - 'foo:bar;' - ) - }) - - - - //////////////////////////////////// - /**/ suite('Inline, nulls: ') /**/ - //////////////////////////////////// - - test('null value', function() { - checkinline( - null, - '' - ) - }) - - test('null leafs', function() { - checkinline( - {foo:null}, - '' - ) - }) - - test('undefined leafs', function() { - checkinline( - {foo:void 8}, - '' - ) - }) - - test('null value', function() { - checkinline( - null, - '' - ) - }) - - test('undefined value', function() { - checkinline( - void 8, - '' - ) - }) - test('null in Array', function() { - checkinline( - [null], - '' - ) - }) - - test('undefined in Array', function() { - checkinline( - [void 8], - '' - ) - }) - - //////////////////////////////////////// - /**/ suite('Inline namespaces: ') /**/ - //////////////////////////////////////// - - test('namespaced animation', function() { - var result = j2c.inline({foo:'theFoo'}, {animation:'foo 1sec'}) - check(result, webkitify('animation:theFoo 1sec;')) - }) - - test('namespaced animation-name', function() { - var result = j2c.inline({foo:'theFoo'}, {animation_name:'foo'}) - check(result, webkitify('animation-name:theFoo;')) - }) - - test('namespaced and non-namespaced animation-name', function() { - var result = j2c.inline({foo:'theFoo'}, {animation_name:'foo, bar'}) - check(result, webkitify('animation-name:theFoo, bar;')) - }) - - test('two namespaced animations', function() { - var result = j2c.inline({foo:'theFoo', bar:'theBar'}, {animation:'foo 1sec, bar 2sec'}) - check(result, webkitify('animation:theFoo 1sec, theBar 2sec;')) - }) - - - - ///////////////////////////////////// - /**/ suite('Inline plugins: ') /**/ - ///////////////////////////////////// - - test('one plugin that does nothing', function() { - check(''+j2c().use(function(){}).inline( - {foo: 'bar'} - ), 'foo:bar;') - }) - - test('one plugin that mutates the buffer', function() { - check(''+j2c().use( - function(buf){ - buf[0] = buf[0].replace('f','k') - } - ).inline( - {foo: 'bar'} - ), 'koo:bar;') - }) - - test('one plugin that returns a new buffer', function() { - expect(''+j2c().use( - function(){ - return ['hello:world;'] - } - ).inline( - {foo: 'bar'} - )).to.be('hello:world;') - }) - - test('two plugins that mutate the buffer', function() { - check(''+j2c().use( - function(buf){ - buf[0]=buf[0].replace('f', 'a') - }, - function(buf){ - buf[0]=buf[0].replace('a', 'm') - } - ).inline( - {foo: 'bar'} - ), 'moo:bar;') - }) - - - - ///////////////////////////////// - /**/ suite('j2c.prefix: ') /**/ - ///////////////////////////////// - - - test('1 x 1', function() { - var prod = j2c.prefix('foo', ['o']) - expect(prod[0]).to.be('-o-foo') - expect(prod[1]).to.be('foo') - }) - - test('2 x 1', function() { - var prod = j2c.prefix('foo', ['o', 'p']) - expect(prod[0]).to.be('-o-foo') - expect(prod[1]).to.be('-p-foo') - expect(prod[2]).to.be('foo') - }) - -}); - - - - -['../dist/j2c.commonjs', '../dist/j2c.commonjs.min'].forEach(function(lib){ - var j2c = require(lib) - - - - //////////////////////////////// - /**/ suite('j2c.sheet: ') /**/ - //////////////////////////////// - - - test('direct sheet call', function(){ - check( - j2c.sheet({p: {foo:5}}), - 'p{foo:5}' - ) - }) - - - - ////////////////////////////////// - /**/ suite('Definitions: ') /**/ - ////////////////////////////////// - - - test('basic', function() { - check( - j2c.sheet({p: { - foo: 'bar' - }}), - 'p{foo:bar}' - ) - }) - - test('convert underscores', function() { - check( - j2c.sheet({p: { - foo_foo: 'bar' - }}), - 'p{foo-foo:bar}' - ) - }) - - test('number values', function() { - check( - j2c.sheet({p: { - foo:5 - }}), - 'p{foo:5}' - ) - }) - - test('composed property name', function() { - check( - j2c.sheet({p: { - foo: {bar: 'baz'} - }}), - - 'p{foo-bar:baz}' - ) - }) - - test('composed selector : child with a given class', function() { - check( - j2c.sheet({'@global': {p: { - ' .foo': {bar: 'baz'} - }}}), - - 'p .foo{bar:baz}' - ) - }) - - test('composed selector: add a class to the root', function() { - check( - j2c.sheet({'@global': {p: { - '.foo': {bar: 'baz'} - }}}), - - 'p.foo{bar:baz}' - ) - }) - - test('manual vendor prefixes', function() { - check( - j2c.sheet({p: { - _o$_ms$_moz$_webkit$: {foo: 'bar'} - }}), - - 'p {-o-foo:bar;-ms-foo:bar;-moz-foo:bar;-webkit-foo:bar;foo:bar}' - ) - }) - - test('mixing definitions and sub-selectors', function() { - check( - j2c.sheet({'@global': {p: { - foo: 'bar', - ' .foo': {bar: 'baz'} - }}}), - - ['p .foo{bar:baz} p {foo:bar}', 'p {foo:bar} p .foo{bar:baz}'] - ) - }) - - - - ////////////////////////////////////////////////// - /**/ suite('Selector Cartesian product: ') /**/ - ////////////////////////////////////////////////// - - - test('1 x 2', function() { - check( - j2c.sheet({'@global': {p: { - ' .foo': { - ':before,:after': { - foo: 'bar' - } - } - }}}), - - 'p .foo:before, p .foo:after {foo:bar}' - ) - }) - - test('2 x 1', function() { - check( - j2c.sheet({'@global': {p: { - ' .foo, .bar': { - ':before': { - foo: 'bar' - } - } - }}}), - - 'p .foo:before, p .bar:before {foo:bar}' - ) - }) - - test('2 x 2', function() { - check( - j2c.sheet({'@global': {p: { - ' .foo, .bar': { - ':before,:after': { - foo: 'bar' - } - } - }}}), - - 'p .foo:before, p .bar:before, p .foo:after, p .bar:after {foo:bar}' - ) - }) - - - test('2 x 3 one of which is empty', function() { - check( - j2c.sheet({'@global': {p: { - ' .foo, .bar': { - ',:before,:after': { - foo: 'bar' - } - } - }}}), - 'p .foo, p .bar, p .foo:before, p .bar:before, p .foo:after, p .bar:after {foo:bar}' - ) - }) - - - - ///////////////////////////////// - /**/ suite('Ampersand: ') /**/ - ////////////////////////////////// - - - test('composed selector: add a class to the root', function() { - check( - j2c.sheet({p: { - ':global(.foo) &': {bar: 'baz'} - }}), - '.foo p{bar:baz}' - ) - }) - - test('& &', function() { - check( - j2c.sheet({':global(.foo)': { - '& &': { - bar: 'baz' - } - }}), - '.foo .foo{bar:baz}' - ) - }) - - test('2 x 2', function() { - check( - j2c.sheet({p: { - ' :global(.foo), :global(.bar)': { - ' :global(.baz) &, :global(.qux)': { - foo: 'bar' - } - } - }}), - '.baz p .foo,.baz p .bar,p .foo .qux ,p .bar .qux {foo:bar}' - ) - }) - - ////////////////////////////////////////// - /**/ suite('Strings and Arrays: ') /**/ - ////////////////////////////////////////// - - - test('String literal', function() { - check( - j2c.sheet({p: 'foo:bar'}), - 'p{foo:bar}' - ) - }) - - test('String literal with two declarations', function() { - check( - j2c.sheet({p: 'foo:bar;baz:qux'}), - 'p {foo:bar;baz:qux}' - ) - }) - - test('String literal starting with an underscore', function() { - check( - j2c.sheet({p: '_foo:bar'}), - 'p {_foo:bar}' - ) - }) - - test('Array of String literals', function() { - check( - j2c.sheet({p: ['foo:bar', 'foo:baz']}), - 'p{foo:bar}p{foo:baz}' - ) - }) - - - test('overloaded properties', function() { - check( - j2c.sheet({p: { - foo:['bar', 'baz'] - }}), - 'p{foo:bar;foo:baz}' - ) - }) - - test('overloaded sub-properties', function() { - check( - j2c.sheet({p: { - foo:[{bar: 'baz'}, {bar: 'qux'}] - }}), - 'p{foo-bar:baz;foo-bar:qux}' - ) - }) - - test('nested Arrays', function(){ - check( - j2c.sheet({p: [ - [ - {bar: 'baz'}, - {bar: 'qux'} - ], - 'bar:quux;' - ]}), - 'p{bar:baz}p{bar:qux}p{bar:quux}' - ) - }) - - - - // /////////////////////////////////////////// - // /**/ suite("Sheet auto prefixes: "); /**/ - // /////////////////////////////////////////// - - // test("String literal", function() { - // check( - // j2c.sheet({" p": "foo:bar"}, {vendors: ["o", "p"]}), - // "p{-o-foo:bar;-p-foo:bar;foo:bar}" - // ); - // }); - - // test("Array of Strings", function() { - // check( - // j2c.sheet({" p": ["foo:bar", "_baz:qux"]}, {vendors: ["o", "p"]}), - // "p{-o-foo:bar;-p-foo:bar;foo:bar;-o-_baz:qux;-p-_baz:qux;_baz:qux}" - // ); - // }); - - - - //////////////////////////////// - /**/ suite('At-rules: ') /**/ - //////////////////////////////// - - - test('standard at-rule with text value', function() { - check( - j2c.sheet({p: { - '@import': "'bar'" - }}), - - "@import 'bar';" - ) - }) - - test('standard at-rule with object value', function() { - check( - j2c.sheet({p: { - '@media foo': {bar: 'baz'} - }}), - - '@media foo {p{bar:baz}}' - ) - }) - - test('several at-rules with object value', function() { - check( - j2c.sheet({p: { - '@media foo': {bar: 'baz'}, - '@media foo2': {bar2: 'baz2'} - }}), - [ - '@media foo {p{bar:baz}} @media foo2 {p{bar2:baz2}}' - ] - ) - }) - - test('Array of at-rules with text values', function() { - check( - j2c.sheet({p: [ - {'@import': "'bar'"}, - {'@import': "'baz'"} - ]}), - "@import 'bar'; @import 'baz';" - ) - }) - - test('nested at-rules', function() { - check( - j2c.sheet({p: {'@media screen': {width:1000, '@media (max-width: 12cm)': {size:5}}}}), - [ - '@media screen{p{width:1000}@media (max-width:12cm){p{size:5}}}' - ] - ) - }) - - test('@font-face', function(){ - check( - j2c.sheet({p: {'@font-face': {foo: 'bar'}}}), - '@font-face{foo:bar}' - ) - }) - - test('@keyframes', function(){ - check( - j2c.sheet({p: {'@keyframes :global(qux)': { - ' from': {foo: 'bar'}, - ' to': {foo: 'baz'} - }}}), - '@-webkit-keyframes qux{from{-webkit-foo:bar;foo:bar}to{-webkit-foo:baz;foo:baz}}' + - '@keyframes qux{from{foo:bar}to{foo:baz}}' - ) - }) - - test('invalid @foo becomes at-foo property', function(){ - check( - j2c.sheet({'@foo': 'bar'}), - '@-error-unsupported-at-rule "@foo";' - ) - - }) - - ////////////////////////////////////////////////// - /**/ suite('At-rules with array values: ') /**/ - ////////////////////////////////////////////////// - - - test('@font-face with a 1-element array', function(){ - check( - j2c.sheet({p: {'@font-face':[{foo: 'bar'}]}}), - '@font-face{foo:bar}' - ) - }) - - test('@font-face with a 2-elements array', function(){ - check( - j2c.sheet({p: {'@font-face':[{foo: 'bar'}, {foo: 'baz'}]}}), - '@font-face{foo:bar}@font-face{foo:baz}' - ) - }) - - test('@namespace with a 1-element array', function(){ - check( - j2c.sheet({'@namespace': ["'http://foo.example.com'"]}), - "@namespace 'http://foo.example.com';" - ) - }) - - test('@namespace with a 2-elements array', function(){ - check( - j2c.sheet({'@namespace': ["'http://foo.example.com'", "bar 'http://bar.example.com'"]}), - "@namespace 'http://foo.example.com';@namespace bar 'http://bar.example.com';" - ) - }) - - ///////////////////////////////////// - /**/ suite('Locals, Globals ') /**/ - ///////////////////////////////////// - - - test('a local class', function(){ - var css = j2c.sheet({'.bit': {foo:5}}) - expect(css.bit.slice(0, 8)).to.be('bit_j2c_') - expect(css + '').to.contain('.' + css.bit + ' {\nfoo:5;\n}') - }) - - test('two local classes', function(){ - var css = j2c.sheet({'.bit': {foo:5}, '.bat': {bar:6}}) - expect(css.bit.slice(0, 8)).to.be('bit_j2c_') - expect(css.bit.slice(4)).to.be(css.bat.slice(4)) - expect(css + '').to.contain('.' + css.bit + ' {\nfoo:5;\n}') - expect(css + '').to.contain('.' + css.bat + ' {\nbar:6;\n}') - }) - - test('a local and a global class', function(){ - var css = j2c.sheet({'.bit': {foo:5}, ':global(.bat)': {bar:6}}) - expect(css.bit.slice(0, 8)).to.be('bit_j2c_') - expect(css.bat).to.be(undefined) - expect(css + '').to.contain('.' + css.bit + ' {\nfoo:5;\n}') - expect(css + '').to.contain('.bat {\nbar:6;\n}') - }) - - test('a local wrapping a global block', function(){ - var css = j2c.sheet({'.bit': {'@global': {'.bat': {foo:5}}}}) - expect(css.bit.slice(0, 8)).to.be('bit_j2c_') - expect(css.bat).to.be(undefined) - expect(css + '').to.contain('.' + css.bit + '.bat {\nfoo:5;\n}') - }) - - test('two local classes, nested', function(){ - var css = j2c.sheet({'.bit': {foo:5, '.bat': {bar:6}}}) - expect(css.bit.slice(0, 8)).to.be('bit_j2c_') - expect(css.bit.slice(4)).to.be(css.bat.slice(4)) - expect(css + '').to.contain('.' + css.bit + ' {\nfoo:5;\n}') - expect(css + '').to.contain('.' + css.bit +'.' + css.bat + ' {\nbar:6;\n}') - }) - - test('@keyframes', function(){ - var css = j2c.sheet({'@keyframes bit': {}}) - expect(css.bit.slice(0, 8)).to.be('bit_j2c_') - expect(css + '').to.contain('@keyframes ' + css.bit +' {') - }) - - test('a global @keyframes', function() { - var css = j2c.sheet({'@keyframes :global(bit)': {}}) - expect(css.bit).to.be(undefined) - expect(css + '').to.contain('@keyframes bit {') - }) - - test('a @keyframe nested in a @global at-rule', function() { - var css = j2c.sheet({'@global': {'@keyframes bat': {'from':{foo:6}}}}) - expect(css.bat).to.be(undefined) - expect(css + '').to.contain('@keyframes bat {') - }) - - test('one animation', function(){ - var css = j2c.sheet({p: {animation: 'bit 1sec'}}) - expect(css.bit.slice(0, 8)).to.be('bit_j2c_') - expect(css + '').to.contain('animation:' + css.bit +' ') - }) - - test('a global animation', function() { - var css = j2c.sheet({p: {animation: ':global(bit) 1sec'}}) - expect(css.bit).to.be(undefined) - expect(css + '').to.contain('animation:bit ') - }) - - test('an animation nested in a @global at-rule', function() { - var css = j2c.sheet({'@global': {p: {animation: 'bit 1sec'}}}) - expect(css.bit).to.be(undefined) - expect(css + '').to.contain('animation:bit ') - }) - - test('one animation-name', function() { - var css = j2c.sheet({p: {animation_name: 'bit'}}) - expect(css.bit.slice(0, 8)).to.be('bit_j2c_') - expect(css + '').to.contain('animation-name:' + css.bit +';') - }) - - test('two animation-name', function() { - var css = j2c.sheet({p: {animation_name: 'bit, bat'}}) - expect(css.bit.slice(0, 8)).to.be('bit_j2c_') - expect(css.bit.slice(4)).to.be(css.bat.slice(4)) - expect(css + '').to.contain('animation-name:' + css.bit +', ' + css.bat) - }) - - test('two animation-name, one global', function() { - var css = j2c.sheet({p: {animation_name: 'bit, :global(bat)'}}) - expect(css.bit.slice(0, 8)).to.be('bit_j2c_') - expect(css.bat).to.be(undefined) - expect(css + '').to.contain('animation-name:' + css.bit +', bat;') - }) - - test('a nested @global at-rule', function() { - var css = j2c.sheet({'.bit': {'@global': {'.bat': {'foo':6}}}}) - expect(css.bit.slice(0, 8)).to.be('bit_j2c_') - expect(css.bat).to.be(undefined) - expect(css + '').to.contain( css.bit +'.bat {') - }) - - - /////////////////////////////////// - /**/ suite('Foolproofing: ') /**/ - /////////////////////////////////// - - - - test('property-like sub-selector', function() { - var sheet = j2c.sheet({'.foo': {'g,p': {animation_name: 'bit, bat'}}}) - - expect(''+sheet).to.contain(':-error-bad-sub-selector-g,:-error-bad-sub-selector-p') - }) - - - //////////////////////////// - /**/ suite('Order: ') /**/ - //////////////////////////// - - test('two properties', function() { - check(j2c.sheet({p: {foo: 'bar', baz: 'qux'}}), 'p {\nfoo:bar;\nbaz:qux;\n}') - }) - - test('$ combiner', function() { - check(j2c.sheet({p: {foo$baz: 'qux'}}), 'p {\nfoo:qux;\nbaz:qux;\n}') - }) - - - // This was built with the assumption that - // objects are totally unordered which isn't true in JS - // As long as no properties are deleted and created again, - // the insertion order is the iteration order in all - // browsers and in node too. - test('subselector > declaration > @media', function(){ - var prop = randStr() - var klass = randStr() - var width = randInt() - - var permutations = [ - [0, 1, 2], - [1, 2, 0], - [2, 0, 1], - [2, 1, 0], - [0, 2, 1], - [1, 0, 2] - ] - - var jsKeys = [ - prop, - '.' + klass, - '@media (min-width: ' + width + 'em)' - ] - - var o = {} - o[prop] = 5 - o['.' + klass] = {foo:6} - o['@media (min-width: ' + width + 'em)'] = {bar:7} - - var rules = [ - 'p{' + prop + ':5;}', - 'p.' + klass + '{foo:6;}', - '@media (min-width: ' + width + 'em){p{bar:7;}}' - ] - - permutations.forEach(function(indices) { - var source = {p:{}} - var CSS = [] - indices.forEach(function(i){ - source.p[jsKeys[i]] = o[jsKeys[i]] - CSS.push(rules[i]) - }) - expect(normalize(j2c.sheet({'@global': source}))).to.be(normalize(CSS.join(''))) - }) - - - }) - - test('@namespace then selector', function() { - check(j2c.sheet({ - '@namespace': "'foo'", - p: {foo: 'bar'} - }), "@namespace 'foo';p{foo:bar;}") - }) - - ///////////////////////////////// - /**/ suite('Namespaces: ') /**/ - ///////////////////////////////// - - test('namespaced class', function() { - var css = j2c.sheet( - {foo: 'FOO'}, - {'.foo': {foo: 'bar', baz: 'qux'}} - ) - check('' + css, '.FOO{foo:bar;baz:qux;}') - expect(css.foo).to.be('FOO') - }) - - test('namespaced class wrapping a global block', function() { - var css = j2c.sheet( - {foo: 'FOOO'}, - {'.foo': {'@global': {'.foo': {foo: 'bar', baz: 'qux'}}}} - ) - check('' + css, '.FOOO.foo{foo:bar;baz:qux;}') - expect(css.foo).to.be('FOOO') - }) - - test('namespaced @keyframes', function(){ - var css = j2c.sheet( - {bit: 'BOT'}, - {'@keyframes bit': {}} - ) - expect(css.bit).to.be('BOT') - expect(css + '').to.contain('@keyframes BOT {') - }) - - test('namespaced animation', function(){ - var css = j2c.sheet( - {bit: 'BOT'}, - {p: {animation: 'bit 1sec'}} - ) - expect(css.bit).to.be('BOT') - check(css + '', 'p{' + webkitify('animation:BOT 1sec;') + '}') - }) - - test('namespaced animation-name', function() { - var css = j2c.sheet( - {bit: 'BOT'}, - {p: {animation_name: 'bit'}} - ) - expect(css.bit).to.be('BOT') - check(css + '', 'p{' + webkitify('animation-name:BOT;') + '}') - }) - - ///////////////////////////// - /**/ suite('extend: ') /**/ - ///////////////////////////// - - test('local extend', function() { - var css = j2c.sheet({'.bit': {'@extend':'.bat'}}) - expect(css.bit).to.contain('bit_j2c_') - expect(css.bat).to.contain('bat_j2c_') - expect(css.bat).not.to.contain('bit_j2c_') - expect(css.bit).to.contain(css.bat + ' ') - }) - - test('global extend', function() { - var css = j2c.sheet({'.bit': {'@extend':':global(.bat)'}}) - expect(css.bit).to.contain('bit_j2c_') - expect(css.bit).to.contain('bat ') - }) - - test('two local extends', function() { - var css = j2c.sheet({'.bit': {'@extends':['.bat', '.bot']}}) - expect(css.bit).to.contain('bit_j2c_') - expect(css.bat).to.contain('bat_j2c_') - expect(css.bot).to.contain('bot_j2c_') - expect(css.bat).not.to.contain('bit_j2c_') - expect(css.bot).not.to.contain('bit_j2c_') - expect(css.bit).to.contain(css.bat + ' ' + css.bot + ' ') - }) - - test('extend applies only to the last class in the selector', function() { - var css = j2c.sheet({'.bot p .bit': {'@extend':'.bat'}}) - expect(css.bit).to.contain('bit_j2c_') - expect(css.bat).to.contain('bat_j2c_') - expect(css.bot).to.contain('bot_j2c_') - expect(css.bat).not.to.contain('bit_j2c_') - expect(css.bit).to.contain(css.bat + ' ') - expect(css.bot).not.to.contain(css.bat + ' ') - }) - - test("if we can't find a class to extend, pass @extend as a property", function() { - var css = j2c.sheet({'p > a': {'@extend':'.bat'}}) - expect(css.bat).to.be(undefined) - expect('' + css).to.contain('@-error-no-class-to-extend-in "p > a";') - }) - - test("extend doesn't extend into global selectors", function() { - // @extend thus extends the last local class in the stream - var css = j2c.sheet({'.bit :global(.bot)': {'@extend':'.bat'}}) - expect(css.bit).to.contain('bit_j2c_') - expect(css.bat).to.be(undefined) - expect(css.bot).to.be(undefined) - expect(css.bit).not.to.contain(css.bat + ' ') - expect('' + css).to.contain('@-error-cannot-extend-in-global-context ".bit :global(.bot)";') - }) - - ////////////////////////////// - /**/ suite('Plugins: ') /**/ - ////////////////////////////// - - test('one plugin that does nothing', function() { - check(''+j2c().use(function(){}).sheet( - {p: {foo: 'bar'}} - ), 'p{foo:bar;}') - }) - - test('one plugin that mutates the buffer', function() { - check(''+j2c().use( - function(buf){ - buf[0] = 'li' - } - ).sheet( - {p: {foo: 'bar'}} - ), 'li{foo:bar;}') - }) - - test('one plugin that returns a new buffer', function() { - check(''+j2c().use( - function(){ - return ['li{foo:bar;}'] - } - ).sheet( - {p: {foo: 'bar'}} - ), 'li{foo:bar;}') - }) - - test('two plugins that mutate the buffer', function() { - check(''+j2c().use( - function(buf){ - buf[0]=buf[0].replace('p', 'a') - }, - function(buf){ - buf[0]=buf[0].replace('a', 'i') - } - ).sheet( - {p: {fop: 'bar'}} - ), 'i{fop:bar;}') - }) -}) - - - - diff --git a/tests/at-adopt.js b/tests/at-adopt.js new file mode 100644 index 00000000..3101111e --- /dev/null +++ b/tests/at-adopt.js @@ -0,0 +1,203 @@ +var o = require('../test-utils/ospec-instance') + +var J2c = require('../dist/j2c.commonjs') +var sink = require('../test-utils/sinks').simple + +o.spec('@adopt: ', function() { + var j2c + o.beforeEach(function() { + j2c = J2c() + }) + o('basic usage', function() { + o(j2c.sheet({ + '@adopt foo': 'bar' + })).equals('') + o(j2c.names.hasOwnProperty('foo')).equals(true) + o(j2c.names.foo).equals(j2c.prefix + 'foo' + ' bar') + }) + + o('basic usage (with dots)', function() { + o(j2c.sheet({ + '@adopt .foo': '.bar' + })).equals('') + o(j2c.names.hasOwnProperty('foo')).equals(true) + o(j2c.names.foo).equals(j2c.prefix + 'foo' + ' bar') + }) + + o('array of adoptees', function() { + o(j2c.sheet({ + '@adopt foo': ['.bar', 'baz'] + })).equals( '') + o(j2c.names.hasOwnProperty('foo')).equals(true) + o(j2c.names.foo).equals(j2c.prefix + 'foo' + ' bar baz') + }) + + o('bad target name', function() { + var err + + try { + j2c.sheet({ + '@adopt /foo': '.bar' + }) + } catch (e) { + err = e + } + + o(err).notEquals(void 8) + o(err.message.indexOf('/* +++ ERROR +++ bad adopter "/foo" in @adopt /foo */')).notEquals(-1) + o(j2c.names.hasOwnProperty('/foo')).equals(false) + o(j2c.names.hasOwnProperty('foo')).equals(false) + }) + + o('bad parameter name', function() { + var err + + try { + j2c.sheet({ + '@adopt foo': '/bar' + }) + } catch (e) { + err = e + } + + o(err).notEquals(void 8) + o(err.message.indexOf('/* +++ ERROR +++ bad adoptee "/bar" in @adopt foo */')).notEquals(-1) + o(j2c.names.hasOwnProperty('foo')).equals(false) + }) + + o('bad parameter: null', function() { + var err + + try { + j2c.sheet({ + '@adopt foo': null + }) + } catch (e) { + err = e + } + + o(err).notEquals(void 8) + o(err.message.indexOf('/* +++ ERROR +++ bad adoptee null in @adopt foo */')).notEquals(-1) + o(j2c.names.hasOwnProperty('foo')).equals(false) + }) + + o('bad parameter: [null]', function() { + var err + + try { + j2c.sheet({ + '@adopt foo': [null] + }) + } catch (e) { + err = e + } + + o(err).notEquals(void 8) + o(err.message.indexOf('/* +++ ERROR +++ bad adoptee null in @adopt foo */')).notEquals(-1) + o(j2c.names.hasOwnProperty('foo')).equals(false) + }) + + o('bad parameter: undefined', function() { + var err + + try { + j2c.sheet({ + '@adopt foo': void 8 + }) + } catch (e) { + err = e + } + + o(err).notEquals(void 8) + o(err.message.indexOf('/* +++ ERROR +++ bad adoptee undefined in @adopt foo */')).notEquals(-1) + o(j2c.names.hasOwnProperty('foo')).equals(false) + }) + + o('bad parameter: [undefined, null]', function() { + var err + + try { + j2c.sheet({ + '@adopt foo': [void 8, null] + }) + } catch (e) { + err = e + } + + o(err).notEquals(void 8) + o(err.message.indexOf('/* +++ ERROR +++ bad adoptee undefined in @adopt foo */')).notEquals(-1) + o(err.message.indexOf('/* +++ ERROR +++ bad adoptee null in @adopt foo */')).notEquals(-1) + o(j2c.names.hasOwnProperty('foo')).equals(false) + }) + + o('forbidden in global scope', function() { + var err + + try { + j2c.sheet({ + '@global': { + '@adopt foo': 'bar' + } + }) + } catch (e) { + err = e + } + + o(err).notEquals(void 8) + o(err.message.indexOf('/* +++ ERROR +++ @adopt global or nested: @adopt foo */')).notEquals(-1) + o(j2c.names.hasOwnProperty('foo')).equals(false) + }) + + o('forbidden in conditional scope', function() { + var err + + try { + j2c.sheet({ + '@media screen': { + '@adopt foo': 'bar' + } + }) + } catch (e) { + err = e + } + + o(err).notEquals(void 8) + o(err.message.indexOf('/* +++ ERROR +++ @adopt global or nested: @adopt foo */')).notEquals(-1) + o(j2c.names.hasOwnProperty('foo')).equals(false) + }) + + o('forbidden in selector', function() { + var err + + try { + j2c.sheet({ + 'p': { + '@adopt foo': 'bar' + } + }) + } catch (e) { + err = e + } + + o(err).notEquals(void 8) + o(err.message.indexOf('/* +++ ERROR +++ @adopt global or nested: @adopt foo */')).notEquals(-1) + o(j2c.names.hasOwnProperty('foo')).equals(false) + }) + o('defining a local after @adopting doesn\'t erase the adopted name', function() { + j2c = J2c({plugins:[sink]}) + o(j2c.sheet({ + '@adopt foo': 'bar' + })).deepEquals([]) + o(j2c.names.hasOwnProperty('foo')).equals(true) + o(j2c.names.foo).equals(j2c.prefix + 'foo' + ' bar') + + var css = j2c.sheet({'.foo': {color: 'red'}}) + + o(j2c.names.foo).equals(j2c.prefix + 'foo' + ' bar') + o(css).deepEquals([ + ['rule', '.' + j2c.prefix + 'foo'], + ['decl', 'color', 'red'], + ['_rule'] + ]) + }) +}) diff --git a/tests/ns.js b/tests/ns.js new file mode 100644 index 00000000..e1ccc05a --- /dev/null +++ b/tests/ns.js @@ -0,0 +1,61 @@ +var o = require('../test-utils/ospec-instance') + +var J2c = require('../dist/j2c.commonjs') +var sink = require('../test-utils/sinks').simple + +o.spec('namespaces', function (){ + o('named namespace', function(){ + var j2c = J2c({plugins: [sink]}) + var other = j2c.ns('other') + var otherRes = other.inline({animationName: 'foo'}) + + o(other.prefix).equals('other__') + + o(other.names.foo).equals('other__foo') + + o(otherRes).deepEquals([['decl', 'animation-name', other.names.foo]]) + }) + o('anonymous namespace of default length', function(){ + var j2c = J2c({plugins: [sink]}) + var other = j2c.ns() + var otherRes = other.inline({animationName: 'foo'}) + + o(other.prefix.length).equals(10) + + o(other.names.foo).equals(other.prefix + 'foo') + + o(otherRes).deepEquals([['decl', 'animation-name', other.names.foo]]) + }) + o('anonymous namespace of custom length', function(){ + var j2c = J2c({prefix: 3, plugins: [sink]}) + var other = j2c.ns() + var otherRes = other.inline({animationName: 'foo'}) + + o(other.prefix.length).equals(6) + + o(other.names.foo).equals(other.prefix + 'foo') + + o(otherRes).deepEquals([['decl', 'animation-name', other.names.foo]]) + }) + o('namespaced instances inherit plugin.set properties', function() { + var plugin = {set: {foo: 'bar'}} + + var j2c = J2c({plugins: [plugin, sink]}) + var other = j2c.ns('other') + + o(j2c.hasOwnProperty('foo')).equals(true) + o(other.hasOwnProperty('foo')).equals(true) + + o(j2c.foo).equals('bar') + o(other.foo).equals('bar') + }) + o('namespaces are cached', function() { + var j2c = J2c({plugins: [sink]}) + var other = j2c.ns('other') + var other2 = j2c.ns('other') + + o(other).equals(other2) + o(other.names).equals(other2.names) + + }) +}) \ No newline at end of file diff --git a/tests/old_suite.js b/tests/old_suite.js new file mode 100644 index 00000000..234953d5 --- /dev/null +++ b/tests/old_suite.js @@ -0,0 +1,1518 @@ +/*eslint-env node*/ + +// No new tests should be added here. + +var o = require('../test-utils/ospec-instance') + +// used to normalize styles for reliable comparison. +var minifySelectors = require('postcss-minify-selectors'), + minifyParams = require('postcss-minify-params'), + perfectionist = require('perfectionist'), + postcss = require('postcss')([perfectionist({ + format: 'compressed' + }), minifySelectors(), minifyParams()]) + +function normalize(s) { + return postcss.process(s).css +} + +function check(result, expected) { + result = normalize(result) + o(normalize(expected)).equals(result) +} + +function randStr() { + return Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 5) +} + +function randInt() { + return Math.random().toString().substr(2, 3) +} + +[ + // '../dist/j2c.commonjs.min', + '../dist/j2c.commonjs' + // , + // '../dist/inline/j2c.commonjs', + // '../dist/inline/j2c.commonjs.min' +].forEach(function(lib) { + var J2c = require(lib) + + o.spec(lib, function() { + + function checkinline(result, expected) { + result = 'p{' + J2c().inline(result) + '}' + expected = (expected instanceof Array ? expected : [expected]).map(function(s) { + return 'p{' + s + '}' + }) + check(result, expected) + } + + + o.spec('Inline', function() { + o.beforeEach(function(){ + // ensure that we are not sensitive to additions to Object.prototype. + Object.prototype.BADBAD = 5 + }) + o.afterEach(function() { + delete Object.prototype.BADBAD + }) + + o('ensure BADBAD is set to 5', function() { + o(({}).BADBAD).equals(5) + }) + + o.spec('Basic', function() { + o('a single property', function() { + checkinline( + { + foo: 'bar' + }, + 'foo:bar;' + ) + }) + + o('two properties, ensure order', function() { + checkinline( + { + foo: 'bar', + baz: 'qux' + }, + 'foo:bar; baz:qux;' + ) + }) + + o('array of values', function() { + checkinline( + { + foo: ['bar', 'baz'] + }, + 'foo:bar; foo:baz;' + ) + }) + + o('one sub-property', function() { + checkinline( + { + foo: { + Bar: 'baz' + } + }, + 'foo-bar: baz;' + ) + }) + + o('two declrations at the top level', function() { + checkinline( + { + foo: 'qux', + baz: 'qux' + }, + 'foo:qux;baz:qux;' + ) + }) + + o('two sub-properties', function() { + checkinline( + { + foo: { + Bar: 'baz', + Qux: 'baz' + } + }, + 'foo-bar:baz; foo-qux:baz;' + ) + }) + + o('two sub-properties with a sub-sub-property', function() { + checkinline( + { + foo: { + Bar: { + Qux: 'quux' + }, + Baz: { + Qix: 'qiix' + } + } + }, + 'foo-bar-qux:quux; foo-baz-qix:qiix;' + ) + }) + + o('two sub-properties with two sub-sub-property', function() { + checkinline( + { + foo: { + Bar: { + Qux: 'fred', + Quux: 'frod' + }, + Baz: { + Qix: 'frad', + Qiix: 'frud' + } + } + }, + 'foo-bar-qux:fred; foo-bar-quux:frod; foo-baz-qix:frad; foo-baz-qiix:frud;' + ) + }) + + o('$ operator at the top level', function() { + checkinline( + { + foo$baz: 'qux' + }, + 'foo:qux;baz:qux;' + ) + }) + + o('$ operator in sub-properties', function() { + checkinline( + { + foo: { + Bar$Qux: 'baz' + } + }, + 'foo-bar:baz; foo-qux:baz;' + ) + }) + + o('$ operator in a sub-property with a sub-sub-property', function() { + checkinline( + { + foo: { + Bar$Baz: { + Qux: 'quux' + } + } + }, + 'foo-bar-qux:quux; foo-baz-qux:quux;' + ) + }) + + o('$ operator in sub-property and sub-sub-property', function() { + checkinline( + { + foo: { + Bar$Baz: { + Qux$Quux: 'fred' + } + } + }, + 'foo-bar-qux:fred; foo-bar-quux:fred; foo-baz-qux:fred; foo-baz-quux:fred;' + ) + }) + + o('don\'t convert underscores', function() { + checkinline( + { + 'f_o_o': 'bar' + }, + 'f_o_o:bar;' + ) + }) + + o('convert CamelCase', function() { + checkinline( + { + 'FoO': 'bar' + }, + '-fo-o:bar;' + ) + }) + + o('String value', function() { + checkinline( + 'foo:bar;', + 'foo:bar;' + ) + }) + + o('Array of Strings values', function() { + checkinline( + ['foo:bar;', 'foo:baz;'], + 'foo:bar;foo:baz;' + ) + }) + + o('Array of mixed values at the root', function() { + checkinline( + ['foo:bar;', { + foo: 'baz' + }], + 'foo:bar;foo:baz;' + ) + }) + + o('Array of mixed value and sub-property', function() { + checkinline( + { + foo: ['bar', { + Baz: 'qux' + }] + }, + 'foo:bar;foo-baz:qux' + ) + }) + + o('Prefixes by hand', function() { + checkinline( + { + '-o-$-p-$': { + foo: 'bar' + } + }, + '-o-foo:bar;-p-foo:bar;foo:bar;' + ) + }) + + o('CSS *Hack', function() { + // tested manually because the crass normalization + // outputs an empty string. + checkinline( + { + '*foo': 'bar' + }, + '*foo:bar;' + ) + }) + + o('CSS _Hack', function() { + checkinline( + ['_foo:bar;', { + _baz: 'qux', + '*star':'hack' + }], + '_foo:bar;_baz:qux;*star:hack;' + ) + }) + + o('custom obj.valueOf', function() { + var bar = { + valueOf: function() { + return 'theBAR' + } + } + checkinline( + { + foo: bar + }, + 'foo:theBAR;' + ) + }) + }) + + o.spec('Nulls', function() { + o('null value', function() { + checkinline( + null, + '' + ) + }) + + o('null leafs', function() { + checkinline( + { + foo: null + }, + '' + ) + }) + + o('undefined leafs', function() { + checkinline( + { + foo: void 8 + }, + '' + ) + }) + + o('null leafs in array', function() { + checkinline( + { + foo: [null] + }, + '' + ) + }) + + o('undefined leafs in array', function() { + checkinline( + { + foo: [void 8] + }, + '' + ) + }) + + o('null leafs in array, preceded by value', function() { + checkinline( + { + foo: ['bar', null] + }, + 'foo:bar;' + ) + }) + + o('undefined leafs in array, preceded by value', function() { + checkinline( + { + foo: ['bar', void 8] + }, + 'foo:bar;' + ) + }) + + o('null leafs in arry, followed by a value', function() { + checkinline( + { + foo: [null, 'bar'] + }, + 'foo:bar;' + ) + }) + + o('undefined leafs in arry, followed by a value', function() { + checkinline( + { + foo: [void 8, 'bar'] + }, + 'foo:bar;' + ) + }) + + o('undefined value', function() { + checkinline( + void 8, + '' + ) + }) + + o('null in Array', function() { + checkinline( + [null], + '' + ) + }) + + o('undefined in Array', function() { + checkinline( + [void 8], + '' + ) + }) + + o('null in Array followed by object', function() { + checkinline( + [null, { + foo: 'bar' + }], + 'foo:bar;' + ) + }) + + o('undefined in Array followed by object', function() { + checkinline( + [void 8, { + foo: 'bar' + }], + 'foo:bar;' + ) + }) + + o('object followed by null in Array', function() { + checkinline( + [{ + foo: 'bar' + }, null], + 'foo:bar;' + ) + }) + + o('object followed by undefined in Array', function() { + checkinline( + [{ + foo: 'bar' + }, void 8], + 'foo:bar;' + ) + }) + }) + }) + + o.spec('sheet', function() { + o.beforeEach(function(){ + // ensure that we are not sensitive to additions to Object.prototype. + Object.prototype.BADBAD = 5 + }) + o.afterEach(function() { + delete Object.prototype.BADBAD + }) + + o.spec('J2c().sheet: ', function() { + o('direct sheet call', function() { + check( + J2c().sheet({ + p: { + foo: 5 + } + }), + 'p{foo:5}' + ) + }) + }) + + o.spec('Definitions: ', function() { + o('basic', function() { + check( + J2c().sheet({ + p: { + foo: 'bar' + } + }), + 'p{foo:bar}' + ) + }) + + o('raw', function() { + o(J2c().sheet('p{color:red}')).equals('p{color:red}\n') + }) + + o('don\'t convert underscores', function() { + check( + J2c().sheet({ + p: { + foo_foo: 'bar' + } + }), + 'p{foo_foo:bar}' + ) + }) + + o('convert CamelCase', function() { + check( + J2c().sheet({ + p: { + fooFoo: 'bar' + } + }), + 'p{foo-foo:bar}' + ) + }) + + o('number values', function() { + check( + J2c().sheet({ + p: { + foo: 5 + } + }), + 'p{foo:5}' + ) + }) + + o('composed property name', function() { + check( + J2c().sheet({ + p: { + foo: { + Bar: 'baz' + } + } + }), + + 'p{foo-bar:baz}' + ) + }) + + o('composed selector : child with a given class', function() { + check( + J2c().sheet({ + '@global': { + p: { + ' .foo': { + bar: 'baz' + } + } + } + }), + + 'p .foo{bar:baz}' + ) + }) + + o('composed selector: add a class to the root', function() { + check( + J2c().sheet({ + '@global': { + p: { + '.foo': { + bar: 'baz' + } + } + } + }), + + 'p.foo{bar:baz}' + ) + }) + + o('manual vendor prefixes', function() { + check( + J2c().sheet({ + p: { + '-o-$-ms-$': { + foo: 'bar' + } + } + }), + + 'p {-o-foo:bar;-ms-foo:bar;foo:bar}' + ) + }) + + o('mixing definitions and sub-selectors', function() { + check( + J2c().sheet({ + '@global': { + p: { + foo: 'bar', + ' .foo': { + bar: 'baz' + } + } + } + }), + 'p {foo:bar} p .foo{bar:baz}' + ) + }) + }) + + o.spec('Selector Cartesian product: ', function() { + o('1 x 2', function() { + check( + J2c().sheet({ + '@global': { + p: { + ' .foo': { + ':before,:after': { + foo: 'bar' + } + } + } + } + }), + + 'p .foo:before, p .foo:after {foo:bar}' + ) + }) + + o('2 x 1', function() { + check( + J2c().sheet({ + '@global': { + p: { + ' .foo, .bar': { + ':before': { + foo: 'bar' + } + } + } + } + }), + + 'p .foo:before, p .bar:before {foo:bar}' + ) + }) + + o('2 x 2', function() { + check( + J2c().sheet({ + '@global': { + p: { + ' .foo, .bar': { + ':before,:after': { + foo: 'bar' + } + } + } + } + }), + + 'p .foo:before, p .bar:before, p .foo:after, p .bar:after {foo:bar}' + ) + }) + + + o('2 x 3 one of which is empty', function() { + check( + J2c().sheet({ + '@global': { + p: { + ' .foo, .bar': { + ',:before,:after': { + foo: 'bar' + } + } + } + } + }), + 'p .foo, p .bar, p .foo:before, p .bar:before, p .foo:after, p .bar:after {foo:bar}' + ) + }) + + o("don't split on comas inside double quoted strings ...", function() { + check(J2c().sheet({ + 'a[foo="bar,baz"]': { + ' p': { + qux: 5 + } + } + }), 'a[foo="bar,baz"] p {qux: 5}') + }) + + o('... nor split on comas inside single quoted strings ...', function() { + check(J2c().sheet({ + "a[foo='bar,baz']": { + ' p': { + qux: 5 + } + } + }), "a[foo='bar,baz'] p {qux: 5}") + }) + + o('... nor split on comas inside comments ...', function() { + check(J2c().sheet({ + 'a/*bar,baz*/': { + ' p': { + qux: 5 + } + } + }), 'a/*bar,baz*/ p {qux: 5}') + }) + + o('... nor split on comas inside parentheses ...', function() { + check(J2c().sheet({ + 'p:any(a, ul)': { + ' li': { + qux: 5 + } + } + }), 'p:any(a, ul) li {qux: 5}') + }) + + o('... but split in between', function() { + check(J2c().sheet({ + 'a[foo="bar,baz"], a:any(p, ul), a/*bar,baz*/': { + ' p': { + qux: 5 + } + } + }), 'a[foo="bar,baz"] p, a:any(p, ul) p, a/*bar,baz*/ p {qux: 5}') + }) + }) + + o.spec('Ampersand: ', function() { + o('.foo &', function() { + check( + J2c().sheet({ + p: { + ':global(.foo) &': { + bar: 'baz' + } + } + }), + '.foo p{bar:baz}' + ) + }) + + o('& &', function() { + check( + J2c().sheet({ + ':global(.foo)': { + '& &': { + bar: 'baz' + } + } + }), + '.foo .foo{bar:baz}' + ) + }) + + o('& [bar="&"].baz', function() { + check( + J2c().sheet({ + ':global(.foo)': { + '& [bar="&"]:global(.baz)': { + qux: 'quux' + } + } + }), + '.foo [bar="&"].baz{qux:quux}' + ) + }) + + o("& [bar='&'].baz", function() { + check( + J2c().sheet({ + ':global(.foo)': { + '& [bar="&"]:global(.baz)': { + qux: 'quux' + } + } + }), + '.foo [bar="&"].baz{qux:quux}' + ) + }) + + o('& /*&*/.baz', function() { + check( + J2c().sheet({ + ':global(.foo)': { + '& /*&*/:global(.baz)': { + qux: 'quux' + } + } + }), + '.foo /*&*/.baz{qux:quux}' + ) + }) + + o(' /*&*/.baz', function() { + check( + J2c().sheet({ + ':global(.foo)': { + ' /*&*/:global(.baz)': { + qux: 'quux' + } + } + }), + '.foo /*&*/.baz{qux:quux}' + ) + }) + + o('& &, cartesian product', function() { + check( + J2c().sheet({ + 'p,a': { + '& &': { + bar: 'baz' + } + } + }), + 'p p,p a,a p,a a {bar:baz}' + ) + }) + + o(' :global(.baz) &, :global(.qux)', function() { + check( + J2c().sheet({ + p: { + ' :global(.foo), :global(.bar)': { + ' :global(.baz) &, :global(.qux)': { + foo: 'bar' + } + } + } + }), + '.baz p .foo,.baz p .bar,p .foo .qux ,p .bar .qux {foo:bar}' + ) + }) + + o('& in @global context', function() { + check( + J2c().sheet({ + '@global': { + '.foo': { + '& &': { + bar: 'baz' + } + } + } + }), + '.foo .foo{bar:baz}' + ) + }) + }) + + o.spec('Strings and Arrays: ', function() { + o('String literal', function() { + check( + J2c().sheet({ + p: 'foo:bar' + }), + 'p{foo:bar}' + ) + }) + + o('String literal with two declarations', function() { + check( + J2c().sheet({ + p: 'foo:bar;baz:qux' + }), + 'p {foo:bar;baz:qux}' + ) + }) + + o('String literal starting with an underscore', function() { + check( + J2c().sheet({ + p: '_foo:bar' + }), + 'p {_foo:bar}' + ) + }) + + o('Array of String literals', function() { + check( + J2c().sheet({ + p: ['foo:bar;', 'foo:baz;'] + }), + 'p{foo:bar;foo:baz}' + ) + }) + + + o('overloaded properties', function() { + check( + J2c().sheet({ + p: { + foo: ['bar', 'baz'] + } + }), + 'p{foo:bar;foo:baz}' + ) + }) + + o('overloaded sub-properties', function() { + check( + J2c().sheet({ + p: { + foo: [{ + Bar: 'baz' + }, { + Bar: 'qux' + }] + } + }), + 'p{foo-bar:baz;foo-bar:qux}' + ) + }) + + o('nested Arrays', function() { + check( + J2c().sheet({ + p: [ + [{ + bar: 'baz' + }, { + bar: 'qux' + }], + 'bar:quux;' + ] + }), + 'p{bar:baz;bar:qux;bar:quux}' + ) + }) + }) + + o.spec('At-rules: ', function() { + o('@charset', function() { + check( + J2c().sheet({ + '@charset': '"UTF-8"' + }), + + '@charset "UTF-8";' + ) + }) + + o('@import', function() { + check( + J2c().sheet({ + '@import': 'url("bluish.css") projection, tv' + }), + + '@import url("bluish.css") projection, tv;' + ) + }) + + o('@namespace', function() { + check( + J2c().sheet({ + '@namespace': 'prefix url(XML-namespace-URL)' + }), + + '@namespace prefix url(XML-namespace-URL);' + ) + }) + + o('@media', function() { + check( + J2c().sheet({ + p: { + '@media foo': { + bar: 'baz' + } + } + }), + + '@media foo {p{bar:baz}}' + ) + }) + + o('@supports', function() { + check( + J2c().sheet({ + '@supports not (text-align-last:justify)': { + 'p': { + textAlignLast: 'justify' + } + } + }), + + '@supports not (text-align-last:justify) {p {text-align-last:justify}}' + ) + }) + + o('@page', function() { + check( + J2c().sheet({ + '@page :first': { + margin: '2in 3in' + } + }), + + '@page :first {margin: 2in 3in;}' + ) + }) + + o('several @media with object value', function() { + check( + J2c().sheet({ + p: { + '@media foo': { + bar: 'baz' + }, + '@media foo2': { + bar2: 'baz2' + } + } + }), [ + '@media foo {p{bar:baz}} @media foo2 {p{bar2:baz2}}' + ] + ) + }) + + o('Array of @import with text values', function() { + check( + J2c().sheet([{ + '@import': "'bar'" + }, { + '@import': "'baz'" + }]), + "@import 'bar'; @import 'baz';" + ) + }) + + o('nested @media', function() { + check( + J2c().sheet({ + p: { + '@media screen': { + width: 1000, + '@media (max-width: 12cm)': { + size: 5 + } + } + } + }), [ + '@media screen{p{width:1000}@media (max-width:12cm){p{size:5}}}' + ] + ) + }) + + o('@font-face', function() { + check( + J2c().sheet({ + '@font-face': { + foo: 'bar' + } + }), + '@font-face{foo:bar}' + ) + }) + + o('invalid @foo becomes @-error-unsupported-at-rule "@foo"', function() { + var err + try { + J2c().sheet({ + '@foo': 'bar' + }) + } catch (e) { + err = e + } + + o(err).notEquals(void 8) + o(err.message.indexOf('/* +++ ERROR +++ Unsupported at-rule: @foo */')).notEquals(-1) + o(err.message.indexOf('"Unsupported at-rule: @foo"')).notEquals(-1) + }) + + o('invalid @ errors out', function() { + var err + try { + J2c().sheet({ + '@': 'bar' + }) + } catch (e) { + err = e + } + + o(err).notEquals(void 8) + o(err.message.indexOf('/* +++ ERROR +++ Unsupported at-rule: @ */')).notEquals(-1) + o(err.message.indexOf('"Unsupported at-rule: @"')).notEquals(-1) + }) + }) + + o.spec('At-rules with prefixes: ', function() { + o('@-webkit-keyframes', function() { + check( + J2c().sheet({ + '@-webkit-keyframes global(qux)': { + from: { + foo: 'bar' + }, + to: { + foo: 'baz' + } + } + }), + '@-webkit-keyframes qux{from{foo:bar}to{foo:baz}}' + ) + }) + }) + + o.spec('At-rules with array values: ', function() { + o('@font-face with a 1-element array', function() { + check( + J2c().sheet({ + '@font-face': [{ + foo: 'bar' + }] + }), + '@font-face{foo:bar}' + ) + }) + + o('@font-face with a 2-elements array', function() { + check( + J2c().sheet({ + '@font-face': [{ + foo: 'bar' + }, { + foo: 'baz' + }] + }), + '@font-face{foo:bar}@font-face{foo:baz}' + ) + }) + + o('@namespace with a 1-element array', function() { + check( + J2c().sheet({ + '@namespace': ["'http://foo.example.com'"] + }), + "@namespace 'http://foo.example.com';" + ) + }) + + o('@namespace with a 2-elements array', function() { + check( + J2c().sheet({ + '@namespace': ["'http://foo.example.com'", "bar 'http://bar.example.com'"] + }), + "@namespace 'http://foo.example.com';@namespace bar 'http://bar.example.com';" + ) + }) + }) + }) + + o.spec('Order: ', function() { + o.beforeEach(function(){ + // ensure that we are not sensitive to additions to Object.prototype. + Object.prototype.BADBAD = 5 + }) + o.afterEach(function() { + delete Object.prototype.BADBAD + }) + + + o('two properties', function() { + check(J2c().sheet({ + p: { + foo: 'bar', + baz: 'qux' + } + }), 'p {\nfoo:bar;\nbaz:qux;\n}') + }) + + o('$ combiner', function() { + check(J2c().sheet({ + p: { + foo$baz: 'qux' + } + }), 'p {\nfoo:qux;\nbaz:qux;\n}') + }) + + o('source order is respected when mixing declarations, subselectors and at rules', function() { + var prop = randStr() + var klass = randStr() + var width = randInt() + + var permutations = [ + [0, 1, 2], + [1, 2, 0], + [2, 0, 1], + [2, 1, 0], + [0, 2, 1], + [1, 0, 2] + ] + + var jsKeys = [ + prop, + '.' + klass, + '@media (min-width: ' + width + 'em)' + ] + + var x = {} + x[prop] = 5 + x['.' + klass] = { + foo: 6 + } + x['@media (min-width: ' + width + 'em)'] = { + bar: 7 + } + + var rules = [ + 'p{' + prop + ':5;}', + 'p.' + klass + '{foo:6;}', + '@media (min-width: ' + width + 'em){p{bar:7;}}' + ] + + var J2c_1 = J2c() + + // This second instance ensures that we don't rely on the falsy return value + // of the default buffer methods. + // This happens in this test because it vists most if not all of the code paths + // where this may be relevant, especially in `src/sheet.js`. + var J2c_2 = J2c({ + filter: function(next) { + return { + done: next.done, + init: function() { + next.init.apply(next, arguments) + return true + }, + atrule: function() { + next.atrule.apply(next, arguments) + return true + }, + _atrule: function() { + next._atrule.apply(next, arguments) + return true + }, + decl: function() { + next.decl.apply(next, arguments) + return true + }, + rule: function() { + next.rule.apply(next, arguments) + return true + }, + _rule: function() { + next._rule.apply(next, arguments) + return true + } + } + } + }) + + + permutations.forEach(function(indices) { + var source = { + p: {} + } + var CSS = [] + indices.forEach(function(i) { + source.p[jsKeys[i]] = x[jsKeys[i]] + CSS.push(rules[i]) + }) + o(normalize(J2c_1.sheet({ + '@global': source + }))).equals(normalize(CSS.join(''))) + o(normalize(J2c_2.sheet({ + '@global': source + }))).equals(normalize(CSS.join(''))) + }) + }) + + o('@namespace then selector', function() { + check(J2c().sheet({ + '@namespace': "'foo'", + p: { + foo: 'bar' + } + }), "@namespace 'foo';p{foo:bar;}") + }) + }) + + o.spec('Plugins: ', function() { + o.beforeEach(function(){ + // ensure that we are not sensitive to additions to Object.prototype. + Object.prototype.BADBAD = 5 + }) + o.afterEach(function() { + delete Object.prototype.BADBAD + }) + + o.spec('Misc: ', function() { + o('an empty array as plugin', function() { + check( + J2c([]).sheet({ + p: { + color: 'red' + } + }), + 'p{color:red;}' + ) + }) + + o('an null plugin', function() { + check( + J2c(null).sheet({ + p: { + color: 'red' + } + }), + 'p{color:red;}' + ) + }) + + o('an invalid plugin name', function() { + var _J2c = J2c({ + invalid: 'foo' + }) + check( + _J2c.sheet({ + p: { + color: 'red' + } + }), + 'p{color:red;}' + ) + o(_J2c.hasOwnProperty('invalid')).equals(false) + }) + + o('a plugin that sets a property on the instance that already exists', function() { + var _J2c = J2c({ + sheet: 'foo' + }) + o(_J2c.sheet).notEquals('foo') + check( + _J2c.sheet({ + p: { + color: 'red' + } + }), + 'p{color:red;}' + ) + + o(_J2c.sheet).notEquals('foo') + }) + }) + + o.spec('filter plugins', function() { + o('a sheet filter', function() { + var tested = {} + var filter = { + filter: function(next, inline) { + o(next instanceof Object).equals(true)('value should have been a Object') + o(next.init instanceof Function).equals(true)('value should have been a Function') + o(next.done instanceof Function).equals(true)('value should have been a Function') + o(next.decl instanceof Function).equals(true)('value should have been a Function') + o(next.err instanceof Function).equals(true)('value should have been a Function') + if (!inline) { + o(next.rule instanceof Function).equals(true)('value should have been a Function') + o(next._rule instanceof Function).equals(true)('value should have been a Function') + o(next.atrule instanceof Function).equals(true)('value should have been a Function') + o(next._atrule instanceof Function).equals(true)('value should have been a Function') + } + + return { + init: function() { + next.init() + o(!!inline).equals(false) + }, + done: function() { + var buf = next.done(1) + + o(buf instanceof Array).equals(true)('value should have been a Array') + + o(buf.length).notEquals(0) + var txt = next.done() + + o(typeof txt).equals('string')('value should have been a string') + + return txt + }, + atrule: function(name, kind, arg, hasBlock) { + if (kind === 'import' && (tested.atImport = true)) o(!!hasBlock).equals(false) + if (kind === 'keyframes' && (tested.atKeyframes = true)) o(hasBlock).equals('rule') + next.atrule(name + 'o', kind, 'a' + arg, hasBlock) + }, + _atrule: function(name, arg) { + next._atrule(name + 'o', 'a' + arg) + }, + decl: function(prop, value) { + next.decl('p' + prop, 'v' + value) + }, + rule: function(selector) { + next.rule('h1, ' + selector) + }, + _rule: function() { + next._rule() + } + } + } + } + + check( + J2c({plugins: [filter]}).sheet({ + '@global': { + '@import': 'foo', + p: { + foo: 'bar' + }, + '@keyframes foo': { + from: { + foo: 'baz' + } + } + } + }), + '@importo afoo;' + + 'h1, p {pfoo:vbar}' + + '@keyframeso afoo{h1, from{pfoo:vbaz}}' + ) + o(tested.atImport).equals(true) + o(tested.atKeyframes).equals(true) + + }) + + o('a declaration filter', function() { + var plugin = { + filter: function(next, inline) { + o(next instanceof Object).equals(true)('value should have been a Object') + o(next.init instanceof Function).equals(true)('value should have been a Function') + o(next.done instanceof Function).equals(true)('value should have been a Function') + o(next.decl instanceof Function).equals(true)('value should have been a Function') + if (!inline) { + o(next.rule instanceof Function).equals(true)('value should have been a Function') + o(next._rule instanceof Function).equals(true)('value should have been a Function') + o(next.atrule instanceof Function).equals(true)('value should have been a Function') + o(next._atrule instanceof Function).equals(true)('value should have been a Function') + } + + return { + init: function() { + next.init() + o(!!inline).equals(true) + }, + done: function() { + var buf = next.done(true) + o(buf instanceof Array).equals(true)('value should have been a Array') + o(buf.length).notEquals(0) + var txt = next.done() + o(typeof txt).equals('string')('value should have been a string') + return txt + }, + decl: function(prop, value) { + next.decl('p' + prop, 'v' + value) + } + } + } + } + check( + J2c({plugins: [plugin]}).inline({ + foo: 'bar' + }), + 'pfoo:vbar;' + ) + }) + + o('filter order', function() { + var acc = [] + + function filter(x) { + return { + filter: function(next) { + return { + rule: function() { + acc.push(x) + return next.rule.apply(next, arguments) + } + } + } + } + } + J2c({plugins: [filter(1), filter(2)]}).sheet({ + '.foo': 'bar:baz;' + }) + o(acc.length).equals(2) + o(acc[0]).equals(1) + o(acc[1]).equals(2) + }) + + o('filter default', function() { + var acc = [] + check( + J2c({plugins: [{ + filter: function(next) { + return { + rule: function(selector) { + acc.push(selector) + return next.rule(selector + 're') + } + } + } + }]}).sheet({ + 'p': 'bar:baz;' + }), + 'pre{bar:baz}' + ) + o(acc.length).equals(1) + o(acc[0]).equals('p') + }) + }) + }) + }) +}) // DONE! + + + + + +// TODO +// +// - spy on String.prototype.replace and RegExp.prototype.* +// to generate coverage reports for the branches hidden +// in these native functions. +// +// - verify that all at-rules behave properly in filters +// (wrt selectors and definitions) +// +// - test `inAtRule` from atrule plugins (is it set appropriately?) +// +// - test @keyframes nested in selector scope \ No newline at end of file diff --git a/tests/plugins.js b/tests/plugins.js new file mode 100644 index 00000000..a86e2ee3 --- /dev/null +++ b/tests/plugins.js @@ -0,0 +1,274 @@ +var o = require('../test-utils/ospec-instance') + +var J2c = require('../dist/j2c.commonjs') +var sink = require('../test-utils/sinks').simple + +o.spec('Options and plugins', function(){ + o('string prefix', function(){ + var j2c = new J2c({prefix: 'pref', plugins:[sink]}) + + o(j2c.prefix).equals('pref__') + + var css = j2c.sheet({ + '@keyframes foo': { + 'from, to': {width: 0} + }, + '.bar' :{ + animation: 'baz 1sec', + animationName: 'qux' + } + }) + + o(css).deepEquals([ + ['atrule', '@keyframes', 'keyframes', 'pref__foo', 'rule'], + ['rule', 'from, to'], + ['decl', 'width', 0], + ['_rule'], + ['_atrule'], + ['rule', '.pref__bar'], + ['decl', 'animation', 'pref__baz 1sec'], + ['decl', 'animation-name', 'pref__qux'], + ['_rule'] + ]) + o(j2c.names.foo).equals('pref__foo') + o(j2c.names.bar).equals('pref__bar') + o(j2c.names.baz).equals('pref__baz') + o(j2c.names.qux).equals('pref__qux') + }) + + o('number prefix', function(){ + var j2c = new J2c({prefix: 4, plugins:[sink]}) + var pref = j2c.prefix + + o(pref.length).equals(7) + + var css = j2c.sheet({ + '@keyframes foo': { + 'from, to': {width: 0} + }, + '.bar' :{ + animation: 'baz 1sec', + animationName: 'qux' + } + }) + + o(css).deepEquals([ + ['atrule', '@keyframes', 'keyframes', pref + 'foo', 'rule'], + ['rule', 'from, to'], + ['decl', 'width', 0], + ['_rule'], + ['_atrule'], + ['rule', '.' + pref + 'bar'], + ['decl', 'animation', pref + 'baz 1sec'], + ['decl', 'animation-name', pref + 'qux'], + ['_rule'] + ]) + o(j2c.names).deepEquals({ + foo: pref + 'foo', + bar: pref + 'bar', + baz: pref + 'baz', + qux: pref + 'qux' + }) + }) + + o('honours the options.plugins field', function(){ + var buf = [] + function plugin(name) { + return {filter: function (next) { + return {init: function(){ + buf.push(name) + return next.init() + }} + }} + } + var j2c = new J2c({plugins: [plugin('foo'), plugin('bar')]}) + + o(j2c.sheet('')).equals('\n') + o(buf).deepEquals(['foo', 'bar']) + }) + + o('plugin.set adds fields to the instance', function(){ + var plugin = {set: {method: function(){return 5}}} + + var j2c = new J2c({plugins: [plugin]}) + + o(typeof j2c.method).equals('function') + o(j2c.method()).equals(5) + }) + + o('plugin.set the last plugin wins, but default methods and properties are preserved', function(){ + var plugin1 = {set: {foo: 5}} + var plugin2 = {set: {foo: 6, names :7, sheet: 8}} + + var j2c = new J2c({plugins: [plugin1, plugin2]}) + + o(j2c.foo).equals(6) + o(j2c.names).notEquals(7) + o(j2c.sheet).notEquals(8) + }) + + o('non-object plugins throw', function(){ + var threw = false + try{ + J2c({plugins: [null]}) + } catch (e){ + threw = true + } + o(threw).equals(true) + }) + + o('empty plugins are tolerated', function(){ + var threw = false + try{ + new J2c({plugins: []}) + new J2c({plugins: [[]]}) + } catch (e){ + threw = true + } + o(threw).equals(false) + }) + + o('catches errors while parsing (throw a String)', function(){ + var plugin = {filter: function(){ + return {decl: function(){ + throw 'foo' + }} + }} + var j2c = J2c({plugins:[plugin, sink]}) + var res = j2c.sheet({p:{color: 'red'}}) + o(res).deepEquals([ + ['rule', 'p'], + ['err', 'foo'], + ['_rule'] + ]) + }) + + o('catches errors while parsing (throw an Error)', function(){ + var plugin = {filter: function(){ + return {decl: function(){ + throw new Error('foo') + }} + }} + var j2c = J2c({plugins:[plugin, sink]}) + var res = j2c.sheet({p:{color: 'red'}}) + o(res[0]).deepEquals(['rule', 'p']) + o(res[1][0]).equals('err') + o(res[1][1].indexOf('Error')).notEquals(-1) + o(res[1][1].indexOf('foo')).notEquals(-1) + o(res[2]).deepEquals(['_rule']) + }) + + o.spec('atrule plugins', function() { + o('one atrule plugin', function() { + var plugin = { + atrule: function(next) { + o(typeof next).equals('function') + return function(walker, emit, match, v, prefix, local, depth) { + o(match instanceof Array).equals(true)('value should have been a Array') + o(walker instanceof Object).equals(true)('value should have been a Object') + o(walker.hasOwnProperty('atrules')).equals(true) + o(walker.hasOwnProperty('localize')).equals(true) + o(walker.hasOwnProperty('localizeReplacer')).equals(true) + o(walker.hasOwnProperty('names')).equals(true) + + o(emit instanceof Object).equals(true)('value should have been a Object') + o(emit.hasOwnProperty('atrule')).equals(true) + + o(typeof prefix).equals('string')('value should have been a string') + // `local` can be many things, the only things that matters is its truthiness + o(!!local).equals(true) + // `inAtRule` can be many things, the only things that matters is its truthiness + o(depth).equals(0) + + if (match[2] === 'foo') emit.atrule(match[1], match[2], v, false) + else next(walker, emit, match, v, prefix, local, depth) + } + } + } + + var _j2c = J2c({plugins: [plugin, sink]}) + o(_j2c.sheet({'@foo': 'param'})).deepEquals([ + ['atrule', '@foo', 'foo', 'param', false] + ]) + + o(_j2c.sheet({ + '@foo': 'param2', + '@bar': 'param', + '@baz': 'param' + })).deepEquals([ + ['atrule', '@foo', 'foo', 'param2', false], + ['err', 'Unsupported at-rule: @bar'], + ['err', 'Unsupported at-rule: @baz'] + ]) + + }) + + o('two atrule plugins', function() { + function plugin(name) { + return { + atrule: function(next) { + o(typeof next).equals('function') + return function(walker, emit, match, v, prefix, local, depth) { + if (match[2] === name) return emit.atrule(match[1], match[2], v, false) + else next(walker, emit, match, v, prefix, local, depth) + } + } + } + } + var _j2c = J2c({plugins: [plugin('foo'), plugin('bar'), sink]}) + o(_j2c.sheet({ + '@foo': 'param', + '@bar': 'param' + })).deepEquals([ + ['atrule', '@foo', 'foo', 'param', false], + ['atrule', '@bar', 'bar', 'param', false] + ]) + o(_j2c.sheet({ + '@foo': 'param2', + '@bar': 'param3', + '@baz': 'param' + })).deepEquals([ + ['atrule', '@foo', 'foo', 'param2', false], + ['atrule', '@bar', 'bar', 'param3', false], + ['err', 'Unsupported at-rule: @baz'] + ]) + }) + + o('atrule plugin has precedence over default at-rules', function() { + var plugin = { + atrule: function(next) { + o(typeof next).equals('function') + return function(walker, emit, match, v, prefix, local, depth) { + if (match[2] === 'import') emit.atrule('@intercepted', 'intercepted', v, false) + else next(walker, emit, match, v, prefix, local, depth) + } + } + } + + o(J2c({plugins: [plugin, sink]}).sheet({'@import': 'foo'})).deepEquals([ + ['atrule', '@intercepted', 'intercepted', 'foo', false] + ]) + }) + + o('atrule plugin that verifies malformed rules are properly passed unparsed', function() { + var plugin = { + atrule: function (next) { + o(typeof next).equals('function') + return function(walker, emit, match, v) { + + o(match[0]).equals('@; hey') + o(match[1]).equals('@') + o(match[2]).equals('') + o(match[3]).equals('') + o(v).equals('foo') + // no need to call next + } + } + } + + o(J2c({plugins: [plugin, sink]}).sheet({ + '@; hey': 'foo' + })).deepEquals([]) + }) + }) +}) diff --git a/tests/sheet.js b/tests/sheet.js new file mode 100644 index 00000000..cfada906 --- /dev/null +++ b/tests/sheet.js @@ -0,0 +1,656 @@ +var o = require('../test-utils/ospec-instance') + +var J2c = require('../dist/j2c.commonjs') +var sink = require('../test-utils/sinks').simple + +o.spec('sheets (new suite, WIP)', function() { + var j2c + o.beforeEach(function(){ + j2c = J2c({plugins: [sink]}) + }) + o.spec('selectors', function() { + o('empty root selectors are rejected', function() { + + var css = j2c.sheet({p:{color:'red'},'':{color:'blue'}}) + + o(css).deepEquals([ + ['rule', 'p'], + ['decl', 'color', 'red'], + ['_rule'], + ['err', "Invalid selector ''"] + ]) + }) + o('empty sub-selectors are rejected', function() { + + var css = j2c.sheet({p:{'':{color: 'red'}}}) + + o(css).deepEquals([['err', "Invalid selector ''"]]) + }) + }) + o.spec('at-rules', function() { + o('@keyframes', function() { + var css = j2c.sheet({ + '@keyframes global(qux)': { + from: { + foo: 'bar' + }, + to: { + foo: 'baz' + } + } + }) + o(css).deepEquals([ + ['atrule', '@keyframes', 'keyframes', 'qux', 'rule'], + ['rule', 'from'], + ['decl', 'foo', 'bar'], + ['_rule'], + ['rule', 'to'], + ['decl', 'foo', 'baz'], + ['_rule'], + ['_atrule'] + ]) + }) + }) + o('anonymous @keyframes alone in a block', function() { + var css = j2c.sheet({ + 'p': { + '@keyframes': { + from: { + foo: 'bar' + }, + to: { + foo: 'baz' + } + } + } + }) + var name = css[1][2] + + o(typeof name).equals('string') + o(name.length).equals(9) + o(name.charAt(0)).equals('_') + + o(css).deepEquals([ + ['rule', 'p'], + ['decl', 'animation-name', name], + ['_rule'], + ['atrule', '@keyframes', 'keyframes', name, 'rule'], + ['rule', 'from'], + ['decl', 'foo', 'bar'], + ['_rule'], + ['rule', 'to'], + ['decl', 'foo', 'baz'], + ['_rule'], + ['_atrule'] + ]) + }) + o('anonymous @keyframes after another declaration', function() { + var css = j2c.sheet({ + 'p': { + animationDuration: '1s', + '@keyframes ': { + from: { + foo: 'bar' + }, + to: { + foo: 'baz' + } + } + } + }) + var name = css[2][2] + + o(typeof name).equals('string') + o(name.length).equals(9) + o(name.charAt(0)).equals('_') + + o(css).deepEquals([ + ['rule', 'p'], + ['decl', 'animation-duration', '1s'], + ['decl', 'animation-name', name], + ['_rule'], + ['atrule', '@keyframes', 'keyframes', name, 'rule'], + ['rule', 'from'], + ['decl', 'foo', 'bar'], + ['_rule'], + ['rule', 'to'], + ['decl', 'foo', 'baz'], + ['_rule'], + ['_atrule'] + ]) + }) + o('anonymous @keyframes at the root errors out', function() { + var css = j2c.sheet({ + '@keyframes': { + from: { + foo: 'bar' + }, + to: { + foo: 'baz' + } + } + }) + + o(css).deepEquals([ + ['err', 'Unexpected anonymous @keyframes out of selector'] + ]) + }) + o.spec('Locals, Globals ', function() { + o('a local class', function() { + var names = j2c.names + var css = j2c.sheet({ + '.bit': { + foo: 5 + } + }) + + o(names.bit).equals(j2c.prefix + 'bit') + o(css).deepEquals([ + ['rule', '.' + names.bit], + ['decl', 'foo', 5], + ['_rule'] + ]) + }) + + o("a local class in a doubly quoted string shouldn't be localized", function() { + var names = j2c.names + var css = j2c.sheet({ + '[foo=".bit"]': { + foo: 5 + } + }) + + o(names.bit).equals(undefined) + o(css).deepEquals([ + ['rule', '[foo=".bit"]'], + ['decl', 'foo', 5], + ['_rule'] + ]) + }) + + o("a local class in a singly quoted string shouldn't be localized", function() { + var names = j2c.names + var css = j2c.sheet({ + "[foo='.bit']": { + foo: 5 + } + }) + + o(names.bit).equals(undefined) + o(css).deepEquals([ + ['rule', "[foo='.bit']"], + ['decl', 'foo', 5], + ['_rule'] + ]) + }) + + o("a local class in a comment shouldn't be localized", function() { + var names = j2c.names + var css = j2c.sheet({ + 'p/*.bit*/': { + foo: 5 + } + }) + + o(names.bit).equals(undefined) + o(css).deepEquals([ + ['rule', 'p/*.bit*/'], + ['decl', 'foo', 5], + ['_rule'] + ]) + }) + + o('Mixing strings and comments (regexp validation)', function() { + var names = j2c.names + var css = j2c.sheet({ + "/*'*/.bit/*'*/": { + foo: 5 + } + }) + + o(names.bit).equals(j2c.prefix + 'bit') + o(css).deepEquals([ + ['rule', "/*'*/." + names.bit + "/*'*/"], + ['decl', 'foo', 5], + ['_rule'] + ]) + }) + + o('two local classes', function() { + var names = j2c.names + var css = j2c.sheet({ + '.bit': { + foo: 5 + }, + '.bat': { + bar: 6 + } + }) + + o(names.bit).equals(j2c.prefix + 'bit') + o(names.bat).equals(j2c.prefix + 'bat') + o(css).deepEquals([ + ['rule', '.' + names.bit], + ['decl', 'foo', 5], + ['_rule'], + ['rule', '.' + names.bat], + ['decl', 'bar', 6], + ['_rule'] + ]) + }) + + o('a local and a global class', function() { + var names = j2c.names + var css = j2c.sheet({ + '.bit': { + foo: 5 + }, + ':global(.bat)': { + bar: 6 + } + }) + o(names.bit).equals(j2c.prefix + 'bit') + o(names.bat).equals(undefined) + + o(css).deepEquals([ + ['rule', '.' + names.bit], + ['decl', 'foo', 5], + ['_rule'], + ['rule', '.bat'], + ['decl', 'bar', 6], + ['_rule'] + ]) + }) + + o('a local wrapping a global block', function() { + var names = j2c.names + var css = j2c.sheet({ + '.bit': { + '@global': { + '.bat': { + foo: 5 + } + } + } + }) + + o(names.bit).equals(j2c.prefix + 'bit') + o(names.bat).equals(undefined) + o(css).deepEquals([ + ['rule', '.' + names.bit + '.bat'], + ['decl', 'foo', 5], + ['_rule'] + ]) + }) + + o('two local classes, nested', function() { + var names = j2c.names + var css = j2c.sheet({ + '.bit': { + foo: 5, + '.bat': { + bar: 6 + } + } + }) + + o(names.bit).equals(j2c.prefix + 'bit') + o(names.bat).equals(j2c.prefix + 'bat') + o(css).deepEquals([ + ['rule', '.' + names.bit], + ['decl', 'foo', 5], + ['_rule'], + ['rule', '.' + names.bit + '.' + names.bat], + ['decl', 'bar', 6], + ['_rule'] + ]) + }) + + o('@keyframes', function() { + var names = j2c.names + var css = j2c.sheet({ + '@keyframes bit': {} + }) + + o(names.bit).equals(j2c.prefix + 'bit') + o(css).deepEquals([ + ['atrule', '@keyframes', 'keyframes', names.bit, 'rule'], + ['_atrule'] + ]) + }) + + o('@keyframes with a CSS variable as name', function() { + var names = j2c.names + var css = j2c.sheet({ + '@keyframes var(--foo)': {} + }) + + o(names.hasOwnProperty('var')).equals(false) + o(css).deepEquals([ + ['atrule', '@keyframes', 'keyframes', 'var(--foo)', 'rule'], + ['_atrule'] + ]) + }) + + o('a global @keyframes', function() { + var names = j2c.names + var css = j2c.sheet({ + '@keyframes global(bit)': {} + }) + + o(names.bit).equals(undefined) + o(css).deepEquals([ + ['atrule', '@keyframes', 'keyframes', 'bit', 'rule'], + ['_atrule'] + ]) + }) + + o('a @keyframes nested in a @global at-rule', function() { + var names = j2c.names + var css = j2c.sheet({ + '@global': { + '@keyframes bat': { + 'from': { + foo: 6 + } + } + } + }) + + o(names.bat).equals(undefined) + o(css).deepEquals([ + ['atrule', '@keyframes', 'keyframes', 'bat', 'rule'], + ['rule', 'from'], + ['decl', 'foo', 6], + ['_rule'], + ['_atrule'] + ]) + }) + + o('one animation', function() { + var names = j2c.names + var css = j2c.sheet({ + p: { + animation: 'bit 1sec' + } + }) + + o(names.bit).equals(j2c.prefix + 'bit') + o(css).deepEquals([ + ['rule', 'p'], + ['decl', 'animation', names.bit + ' 1sec'], + ['_rule'] + ]) + }) + + o('a global animation', function() { + var names = j2c.names + var css = j2c.sheet({ + p: { + animation: 'global(bit) 1sec' + } + }) + + o(names.bit).equals(undefined) + o(css).deepEquals([ + ['rule', 'p'], + ['decl', 'animation', 'bit' + ' 1sec'], + ['_rule'] + ]) + }) + + o('an animation nested in a @global at-rule', function() { + var names = j2c.names + var css = j2c.sheet({ + '@global': { + p: { + animation: 'bit 1sec' + } + } + }) + o(names.bit).equals(undefined) + o(css).deepEquals([ + ['rule', 'p'], + ['decl', 'animation', 'bit 1sec'], + ['_rule'] + ]) + }) + + o('one animation with a CSS variable', function() { + var names = j2c.names + var css = j2c.sheet({ + p: { + animation: 'var(--foo) 1sec' + } + }) + + o(names.hasOwnProperty('var')).equals(false) + o(names.hasOwnProperty('foo')).equals(false) + o(names.hasOwnProperty('sec')).equals(false) + o(css).deepEquals([ + ['rule', 'p'], + ['decl', 'animation', 'var(--foo) 1sec'], + ['_rule'] + ]) + }) + + o('a complex animation list ', function() { + var names = j2c.names + var css = j2c.sheet({ + p: { + animation: 'var(--foo, but) 1sec, bit 2sec, global(bat) 3sec' + } + }) + + o(names.hasOwnProperty('var')).equals(false) + o(names.hasOwnProperty('foo')).equals(false) + o(names.hasOwnProperty('sec')).equals(false) + o(names.bit).equals(j2c.prefix + 'bit') + o(names.but).equals(j2c.prefix + 'but') + o(names.hasOwnProperty('bat')).equals(false) + o(css).deepEquals([ + ['rule', 'p'], + ['decl', 'animation', 'var(--foo,' + names.but + ') 1sec,' + names.bit + ' 2sec,bat 3sec'], + ['_rule'] + ]) + }) + + o('one animation without a name', function() { + var names = j2c.names + var css = j2c.sheet({ + p: { + animation: '1sec step(4, end), 2sec step(3, start)' + } + }) + o(names.hasOwnProperty('ease')).equals(false) + o(names.hasOwnProperty('step')).equals(false) + o(names.hasOwnProperty('end')).equals(false) + o(css).deepEquals([ + ['rule', 'p'], + ['decl', 'animation', '1sec step(4, end), 2sec step(3, start)'], + ['_rule'] + ]) + }) + + o('one animation-name', function() { + var names = j2c.names + var css = j2c.sheet({ + p: { + animationName: 'bit' + } + }) + + o(names.bit).equals(j2c.prefix + 'bit') + o(css).deepEquals([ + ['rule', 'p'], + ['decl', 'animation-name', names.bit], + ['_rule'] + ]) + }) + + o('two animation-name', function() { + var names = j2c.names + var css = j2c.sheet({ + p: { + animationName: 'bit, bat' + } + }) + + o(names.bit).equals(j2c.prefix + 'bit') + o(names.bat).equals(j2c.prefix + 'bat') + o(css).deepEquals([ + ['rule', 'p'], + ['decl', 'animation-name', names.bit + ',' + names.bat], + ['_rule'] + ]) + }) + + o('two animation-name, one global', function() { + var names = j2c.names + var css = j2c.sheet({ + p: { + animationName: 'bit, global(bat)' + } + }) + o(names.bit).equals(j2c.prefix + 'bit') + o(names.bat).equals(undefined) + o(css).deepEquals([ + ['rule', 'p'], + ['decl', 'animation-name', names.bit + ',bat'], + ['_rule'] + ]) + }) + + o('one animation-name with a CSS variable', function() { + var names = j2c.names + var css = j2c.sheet({ + p: { + animationName: 'var(--foo)' + } + }) + o(names.hasOwnProperty('var')).equals(false) + o(css).deepEquals([ + ['rule', 'p'], + ['decl', 'animation-name', 'var(--foo)'], + ['_rule'] + ]) + }) + + o('one animation-name with a CSS variable that has a local fallback', function() { + var names = j2c.names + var css = j2c.sheet({ + p: { + animationName: 'var(--foo, bar)' + } + }) + o(names.hasOwnProperty('var')).equals(false) + o(names.hasOwnProperty('foo')).equals(false) + o(names.bar).equals(j2c.prefix + 'bar') + o(css).deepEquals([ + ['rule', 'p'], + ['decl', 'animation-name', 'var(--foo,' + names.bar + ')'], + ['_rule'] + ]) + }) + + o('one animation-name with nested CSS variables that have a local fallback', function() { + var names = j2c.names + var css = j2c.sheet({ + p: { + animationName: 'var(--foo, var(--bar, bar))' + } + }) + o(names.hasOwnProperty('var')).equals(false) + o(names.hasOwnProperty('foo')).equals(false) + o(names.bar).equals(j2c.prefix + 'bar') + o(css).deepEquals([ + ['rule', 'p'], + ['decl', 'animation-name', 'var(--foo,var(--bar,' + names.bar + '))'], + ['_rule'] + ]) + }) + + o('one animation-name with a CSS variable that has a global fallback', function() { + var names = j2c.names + var css = j2c.sheet({ + p: { + animationName: 'var(--foo, global(bar))' + } + }) + o(names.hasOwnProperty('var')).equals(false) + o(names.hasOwnProperty('foo')).equals(false) + o(names.hasOwnProperty('bar')).equals(false) + o(css).deepEquals([ + ['rule', 'p'], + ['decl', 'animation-name', 'var(--foo,bar)'], + ['_rule'] + ]) + }) + + o('one animation-name with nested CSS variables that have a global fallback', function() { + var names = j2c.names + var css = j2c.sheet({ + p: { + animationName: 'var(--foo, var(--bar, global(bar)))' + } + }) + o(names.hasOwnProperty('var')).equals(false) + o(names.hasOwnProperty('foo')).equals(false) + o(names.hasOwnProperty('bar')).equals(false) + o(css).deepEquals([ + ['rule', 'p'], + ['decl', 'animation-name', 'var(--foo,var(--bar,bar))'], + ['_rule'] + ]) + }) + o('a nested @global at-rule', function() { + var names = j2c.names + var css = j2c.sheet({ + '.bit': { + '@global': { + '.bat': { + 'foo': 6 + } + } + } + }) + o(names.bit).equals(j2c.prefix + 'bit') + o(names.bat).equals(undefined) + o(css).deepEquals([ + ['rule', '.' + names.bit + '.bat'], + ['decl', 'foo', 6], + ['_rule'] + ]) + }) + + o('a @local rule nested in a @global block', function() { + var names = j2c.names + var css = j2c.sheet({ + '@global': { + '.bit': { + '@local': { + ':global(.bat)': { + foo: 6 + }, + '.but': { + bar: 7 + } + } + } + } + }) + + o(names.hasOwnProperty('bit')).equals(false) + o(names.hasOwnProperty('bat')).equals(false) + o(names.but).equals(j2c.prefix + 'but') + o(css).deepEquals([ + ['rule', '.bit.bat'], + ['decl', 'foo', 6], + ['_rule'], + ['rule', '.bit.' + names.but], + ['decl', 'bar', 7], + ['_rule'] + ]) + }) + }) +}) diff --git a/tests/sinks.js b/tests/sinks.js new file mode 100644 index 00000000..47033337 --- /dev/null +++ b/tests/sinks.js @@ -0,0 +1,68 @@ +var o = require('../test-utils/ospec-instance') + +var J2c = require('../dist/j2c.commonjs') + +var own = ({}).hasOwnProperty + +var ref = { + inline: ['init', 'done', 'err', 'raw', 'decl'], + sheet: ['init', 'done', 'err', 'raw', 'decl', 'rule', '_rule', 'atrule', '_atrule'] +} + + +o.spec('sinks', function() { + o.spec('default sinks', function(){ + var j2c, sheetSink, inlineSink + o.beforeEach(function(){ + // get the Sinks out + j2c = new J2c({plugins: [{filter: function(next) { + if ('rule' in next) sheetSink = next + else inlineSink = next + return {} + }}]}) + j2c.sheet({}) + j2c.inline({}) + }) + o('the sheet sink exposes appropriate methods', function() { + var methods = [] + for (var k in sheetSink) if (own.call(sheetSink, k)) methods.push(k) + o(methods.sort()).deepEquals(ref.sheet.sort()) + }) + o('the inline sink exposes appropriate methods', function() { + var methods = [] + for (var k in inlineSink) if (own.call(sheetSink, k)) methods.push(k) + o(methods.sort()).deepEquals(ref.inline.sort()) + }) + // Do we want more individual tests for the default sink? + }) + o.spec('sink plugins', function() { + o('sheet sinks work', function() { + var methods = [ + 'init', 'done', 'err', 'raw', 'decl', 'rule', '_rule', 'atrule', '_atrule' + ] + var sink = methods.reduce(function(acc, method){ + acc[method] = o.spy() + return acc + }, {}) + var j2c = J2c({plugins: [{sink:function(){return [sink]}}]}) + j2c.sheet([ + {'@media foo': { + 'p': { + color:'red' + }, + '@adopt foo': ['bar'] + }}, + 'raw' + ]) + o(sink.init.callCount).equals(1) + o(sink.done.callCount).equals(1) + o(sink.err.callCount).equals(1) + o(sink.raw.callCount).equals(1) + o(sink.decl.callCount).equals(1) + o(sink.rule.callCount).equals(1) + o(sink._rule.callCount).equals(1) + o(sink.atrule.callCount).equals(1) + o(sink._atrule.callCount).equals(1) + }) + }) +}) \ No newline at end of file diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 00000000..ff0bc02c --- /dev/null +++ b/yarn.lock @@ -0,0 +1,2118 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +abab@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.3.tgz#b81de5f7274ec4e756d797cd834f303642724e5d" + +abbrev@1, abbrev@1.0.x: + version "1.0.9" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135" + +acorn-globals@^1.0.4: + version "1.0.9" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-1.0.9.tgz#55bb5e98691507b74579d0513413217c380c54cf" + dependencies: + acorn "^2.1.0" + +acorn-jsx@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b" + dependencies: + acorn "^3.0.4" + +acorn@^2.1.0, acorn@^2.4.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-2.7.0.tgz#ab6e7d9d886aaca8b085bc3312b79a198433f0e7" + +acorn@^3.0.4: + version "3.3.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" + +acorn@^4.0.1: + version "4.0.4" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.4.tgz#17a8d6a7a6c4ef538b814ec9abac2779293bf30a" + +ajv-keywords@^1.0.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c" + +ajv@^4.7.0: + version "4.11.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.2.tgz#f166c3c11cbc6cb9dcc102a5bcfe5b72c95287e6" + dependencies: + co "^4.6.0" + json-stable-stringify "^1.0.1" + +align-text@^0.1.1, align-text@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" + dependencies: + kind-of "^3.0.2" + longest "^1.0.1" + repeat-string "^1.5.2" + +alphanum-sort@^1.0.1, alphanum-sort@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" + +amdefine@>=0.0.4: + version "1.0.1" + resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" + +ansi-escapes@^1.0.0, ansi-escapes@^1.1.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + +ansi-styles@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + +aproba@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.0.4.tgz#2713680775e7614c8ba186c065d4e2e52d1072c0" + +are-we-there-yet@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.2.tgz#80e470e95a084794fe1899262c5667c6e88de1b3" + dependencies: + delegates "^1.0.0" + readable-stream "^2.0.0 || ^1.1.13" + +argparse@^1.0.7: + version "1.0.9" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86" + dependencies: + sprintf-js "~1.0.2" + +argv@>=0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/argv/-/argv-0.0.2.tgz#ecbd16f8949b157183711b1bda334f37840185ab" + +array-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" + +array-find-index@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" + +array-union@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" + dependencies: + array-uniq "^1.0.1" + +array-uniq@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" + +arrify@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" + +asn1@~0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" + +assert-plus@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" + +assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + +async@1.x, async@^1.4.0: + version "1.5.2" + resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" + +async@~0.2.6: + version "0.2.10" + resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1" + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + +aws-sign2@~0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" + +aws4@^1.2.1: + version "1.5.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.5.0.tgz#0a29ffb79c31c9e712eeb087e8e7a64b4a56d755" + +babel-code-frame@^6.16.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.22.0.tgz#027620bee567a88c32561574e7fd0801d33118e4" + dependencies: + chalk "^1.1.0" + esutils "^2.0.2" + js-tokens "^3.0.0" + +babel-polyfill@^6.3.14: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.22.0.tgz#1ac99ebdcc6ba4db1e2618c387b2084a82154a3b" + dependencies: + babel-runtime "^6.22.0" + core-js "^2.4.0" + regenerator-runtime "^0.10.0" + +babel-runtime@^6.0.0, babel-runtime@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.22.0.tgz#1cf8b4ac67c77a4ddb0db2ae1f74de52ac4ca611" + dependencies: + core-js "^2.4.0" + regenerator-runtime "^0.10.0" + +balanced-match@^0.4.1: + version "0.4.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" + +bcrypt-pbkdf@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.0.tgz#3ca76b85241c7170bf7d9703e7b9aa74630040d4" + dependencies: + tweetnacl "^0.14.3" + +bl@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.0.tgz#1397e7ec42c5f5dc387470c500e34a9f6be9ea98" + dependencies: + readable-stream "^2.0.5" + +block-stream@*: + version "0.0.9" + resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" + dependencies: + inherits "~2.0.0" + +boom@2.x.x: + version "2.10.1" + resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" + dependencies: + hoek "2.x.x" + +brace-expansion@^1.0.0: + version "1.1.6" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.6.tgz#7197d7eaa9b87e648390ea61fc66c84427420df9" + dependencies: + balanced-match "^0.4.1" + concat-map "0.0.1" + +buffer-shims@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51" + +builtin-modules@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" + +bytes@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.4.0.tgz#7d97196f9d5baf7f6935e25985549edd2a6c2339" + +caller-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f" + dependencies: + callsites "^0.2.0" + +callsites@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca" + +camelcase@^1.0.2: + version "1.2.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" + +camelcase@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" + +caseless@~0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7" + +cash-true@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/cash-true/-/cash-true-0.0.2.tgz#dd092e78878a170b6888405b602860cfceaa0f6e" + dependencies: + vorpal "^1.10.8" + +center-align@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad" + dependencies: + align-text "^0.1.3" + lazy-cache "^1.0.3" + +chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + dependencies: + ansi-styles "^2.2.1" + escape-string-regexp "^1.0.2" + has-ansi "^2.0.0" + strip-ansi "^3.0.0" + supports-color "^2.0.0" + +ci-info@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.0.0.tgz#dc5285f2b4e251821683681c381c3388f46ec534" + +circular-json@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.1.tgz#be8b36aefccde8b3ca7aa2d6afc07a37242c0d2d" + +cli-cursor@^1.0.1, cli-cursor@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987" + dependencies: + restore-cursor "^1.0.1" + +cli-width@^1.0.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-1.1.1.tgz#a4d293ef67ebb7b88d4a4d42c0ccf00c4d1e366d" + +cli-width@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.1.0.tgz#b234ca209b29ef66fc518d9b98d5847b00edf00a" + +cliui@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" + dependencies: + center-align "^0.1.1" + right-align "^0.1.1" + wordwrap "0.0.2" + +clone@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.2.tgz#260b7a99ebb1edfe247538175f783243cb19d149" + +cmd-shim@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-2.0.2.tgz#6fcbda99483a8fd15d7d30a196ca69d688a2efdb" + dependencies: + graceful-fs "^4.1.2" + mkdirp "~0.5.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + +codecov@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/codecov/-/codecov-1.0.1.tgz#97260ceac0e96b8eda8d562006558a53a139dffd" + dependencies: + argv ">=0.0.2" + execSync "1.0.2" + request ">=2.42.0" + urlgrey ">=0.4.0" + +combined-stream@^1.0.5, combined-stream@~1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" + dependencies: + delayed-stream "~1.0.0" + +commander@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" + dependencies: + graceful-readlink ">= 1.0.0" + +comment-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/comment-regex/-/comment-regex-1.0.0.tgz#7dd70268c83648a9c4cc19bf472d52e64f63918d" + +commonmark@^0.27.0: + version "0.27.0" + resolved "https://registry.yarnpkg.com/commonmark/-/commonmark-0.27.0.tgz#d86c262b962821e9483c69c547bc58840c047b34" + dependencies: + entities "~ 1.1.1" + mdurl "~ 1.0.1" + minimist "~ 1.2.0" + string.prototype.repeat "^0.2.0" + +compose-regexp@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/compose-regexp/-/compose-regexp-0.1.8.tgz#b213c63566d215e8a6568f601257113f6b7ed8ec" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + +concat-stream@^1.4.6, concat-stream@^1.4.7: + version "1.6.0" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" + dependencies: + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + +console-control-strings@^1.0.0, console-control-strings@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + +content-type-parser@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/content-type-parser/-/content-type-parser-1.0.1.tgz#c3e56988c53c65127fb46d4032a3a900246fdc94" + +core-js@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.4.1.tgz#4de911e667b0eae9124e34254b53aea6fc618d3e" + +core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + +cryptiles@2.x.x: + version "2.0.5" + resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" + dependencies: + boom "2.x.x" + +cssom@0.3.x, "cssom@>= 0.3.0 < 0.4.0": + version "0.3.2" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.2.tgz#b8036170c79f07a90ff2f16e22284027a243848b" + +"cssstyle@>= 0.2.36 < 0.3.0": + version "0.2.37" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-0.2.37.tgz#541097234cb2513c83ceed3acddc27ff27987d54" + dependencies: + cssom "0.3.x" + +currently-unhandled@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" + dependencies: + array-find-index "^1.0.1" + +d@^0.1.1, d@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/d/-/d-0.1.1.tgz#da184c535d18d8ee7ba2aa229b914009fae11309" + dependencies: + es5-ext "~0.10.2" + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + dependencies: + assert-plus "^1.0.0" + +death@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/death/-/death-1.1.0.tgz#01aa9c401edd92750514470b8266390c66c67318" + +debug@^2.1.1, debug@^2.2.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.0.tgz#bc596bcabe7617f11d9fa15361eded5608b8499b" + dependencies: + ms "0.7.2" + +decamelize@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + +deep-is@~0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + +defaults@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" + dependencies: + clone "^1.0.2" + +defined@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" + +del@^2.0.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8" + dependencies: + globby "^5.0.0" + is-path-cwd "^1.0.0" + is-path-in-cwd "^1.0.0" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + rimraf "^2.2.8" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + +detect-indent@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" + dependencies: + repeating "^2.0.0" + +diff@^2.2.1: + version "2.2.3" + resolved "https://registry.yarnpkg.com/diff/-/diff-2.2.3.tgz#60eafd0d28ee906e4e8ff0a52c1229521033bf99" + +doctrine@^1.2.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" + dependencies: + esutils "^2.0.2" + isarray "^1.0.0" + +ecc-jsbn@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" + dependencies: + jsbn "~0.1.0" + +end-of-stream@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.1.0.tgz#e9353258baa9108965efc41cb0ef8ade2f3cfb07" + dependencies: + once "~1.3.0" + +"entities@~ 1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" + +err-code@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/err-code/-/err-code-1.1.1.tgz#739d71b6851f24d050ea18c79a5b722420771d59" + +es5-ext@^0.10.7, es5-ext@^0.10.8, es5-ext@~0.10.11, es5-ext@~0.10.2, es5-ext@~0.10.7: + version "0.10.12" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.12.tgz#aa84641d4db76b62abba5e45fd805ecbab140047" + dependencies: + es6-iterator "2" + es6-symbol "~3.1" + +es6-iterator@2: + version "2.0.0" + resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.0.tgz#bd968567d61635e33c0b80727613c9cb4b096bac" + dependencies: + d "^0.1.1" + es5-ext "^0.10.7" + es6-symbol "3" + +es6-map@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/es6-map/-/es6-map-0.1.4.tgz#a34b147be224773a4d7da8072794cefa3632b897" + dependencies: + d "~0.1.1" + es5-ext "~0.10.11" + es6-iterator "2" + es6-set "~0.1.3" + es6-symbol "~3.1.0" + event-emitter "~0.3.4" + +es6-set@~0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.4.tgz#9516b6761c2964b92ff479456233a247dc707ce8" + dependencies: + d "~0.1.1" + es5-ext "~0.10.11" + es6-iterator "2" + es6-symbol "3" + event-emitter "~0.3.4" + +es6-symbol@3, es6-symbol@~3.1, es6-symbol@~3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.0.tgz#94481c655e7a7cad82eba832d97d5433496d7ffa" + dependencies: + d "~0.1.1" + es5-ext "~0.10.11" + +es6-weak-map@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.1.tgz#0d2bbd8827eb5fb4ba8f97fbfea50d43db21ea81" + dependencies: + d "^0.1.1" + es5-ext "^0.10.8" + es6-iterator "2" + es6-symbol "3" + +escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + +escodegen@1.8.x, escodegen@^1.6.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.8.1.tgz#5a5b53af4693110bebb0867aa3430dd3b70a1018" + dependencies: + esprima "^2.7.1" + estraverse "^1.9.1" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.2.0" + +escope@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/escope/-/escope-3.6.0.tgz#e01975e812781a163a6dadfdd80398dc64c889c3" + dependencies: + es6-map "^0.1.3" + es6-weak-map "^2.0.1" + esrecurse "^4.1.0" + estraverse "^4.1.1" + +eslint@^3.12.0: + version "3.14.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-3.14.1.tgz#8a62175f2255109494747a1b25128d97b8eb3d97" + dependencies: + babel-code-frame "^6.16.0" + chalk "^1.1.3" + concat-stream "^1.4.6" + debug "^2.1.1" + doctrine "^1.2.2" + escope "^3.6.0" + espree "^3.3.1" + estraverse "^4.2.0" + esutils "^2.0.2" + file-entry-cache "^2.0.0" + glob "^7.0.3" + globals "^9.14.0" + ignore "^3.2.0" + imurmurhash "^0.1.4" + inquirer "^0.12.0" + is-my-json-valid "^2.10.0" + is-resolvable "^1.0.0" + js-yaml "^3.5.1" + json-stable-stringify "^1.0.0" + levn "^0.3.0" + lodash "^4.0.0" + mkdirp "^0.5.0" + natural-compare "^1.4.0" + optionator "^0.8.2" + path-is-inside "^1.0.1" + pluralize "^1.2.1" + progress "^1.1.8" + require-uncached "^1.0.2" + shelljs "^0.7.5" + strip-bom "^3.0.0" + strip-json-comments "~2.0.1" + table "^3.7.8" + text-table "~0.2.0" + user-home "^2.0.0" + +espree@^3.3.1: + version "3.3.2" + resolved "https://registry.yarnpkg.com/espree/-/espree-3.3.2.tgz#dbf3fadeb4ecb4d4778303e50103b3d36c88b89c" + dependencies: + acorn "^4.0.1" + acorn-jsx "^3.0.0" + +esprima@2.7.x, esprima@^2.6.0, esprima@^2.7.1: + version "2.7.3" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" + +esrecurse@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.1.0.tgz#4713b6536adf7f2ac4f327d559e7756bff648220" + dependencies: + estraverse "~4.1.0" + object-assign "^4.0.1" + +estraverse@^1.9.1: + version "1.9.3" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-1.9.3.tgz#af67f2dc922582415950926091a4005d29c9bb44" + +estraverse@^4.1.1, estraverse@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" + +estraverse@~4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.1.1.tgz#f6caca728933a850ef90661d0e17982ba47111a2" + +esutils@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" + +event-emitter@~0.3.4: + version "0.3.4" + resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.4.tgz#8d63ddfb4cfe1fae3b32ca265c4c720222080bb5" + dependencies: + d "~0.1.1" + es5-ext "~0.10.7" + +execSync@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/execSync/-/execSync-1.0.2.tgz#1f42eda582225180053224ecdd3fd1960fdb3139" + dependencies: + temp "~0.5.1" + +exit-hook@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" + +extend@^3.0.0, extend@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.0.tgz#5a474353b9f3353ddd8176dfd37b91c83a46f1d4" + +external-editor@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-1.1.1.tgz#12d7b0db850f7ff7e7081baf4005700060c4600b" + dependencies: + extend "^3.0.0" + spawn-sync "^1.0.15" + tmp "^0.0.29" + +extsprintf@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.0.2.tgz#e1080e0658e300b06294990cc70e1502235fd550" + +fast-levenshtein@~2.0.4: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + +figures@^1.3.5: + version "1.7.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" + dependencies: + escape-string-regexp "^1.0.5" + object-assign "^4.1.0" + +file-entry-cache@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361" + dependencies: + flat-cache "^1.2.1" + object-assign "^4.0.1" + +flat-cache@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.2.2.tgz#fa86714e72c21db88601761ecf2f555d1abc6b96" + dependencies: + circular-json "^0.3.1" + del "^2.0.2" + graceful-fs "^4.1.2" + write "^0.2.1" + +flatten@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + +form-data@~2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.2.tgz#89c3534008b97eada4cbb157d58f6f5df025eae4" + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.5" + mime-types "^2.1.12" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + +fstream@^1.0.0, fstream@^1.0.2: + version "1.0.10" + resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.10.tgz#604e8a92fe26ffd9f6fae30399d4984e1ab22822" + dependencies: + graceful-fs "^4.1.2" + inherits "~2.0.0" + mkdirp ">=0.5 0" + rimraf "2" + +function-bind@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.0.tgz#16176714c801798e4e8f2cf7f7529467bb4a5771" + +gather-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/gather-stream/-/gather-stream-1.0.0.tgz#b33994af457a8115700d410f317733cbe7a0904b" + +gauge@~2.7.1: + version "2.7.2" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.2.tgz#15cecc31b02d05345a5d6b0e171cdb3ad2307774" + dependencies: + aproba "^1.0.3" + console-control-strings "^1.0.0" + has-unicode "^2.0.0" + object-assign "^4.1.0" + signal-exit "^3.0.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + supports-color "^0.2.0" + wide-align "^1.1.0" + +generate-function@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.0.0.tgz#6858fe7c0969b7d4e9093337647ac79f60dfbe74" + +generate-object-property@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0" + dependencies: + is-property "^1.0.0" + +getpass@^0.1.1: + version "0.1.6" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.6.tgz#283ffd9fc1256840875311c1b60e8c40187110e6" + dependencies: + assert-plus "^1.0.0" + +glob@^5.0.15: + version "5.0.15" + resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" + dependencies: + inflight "^1.0.4" + inherits "2" + minimatch "2 || 3" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^7.0.0, glob@^7.0.3, glob@^7.0.5: + version "7.1.1" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.2" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^9.14.0: + version "9.14.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-9.14.0.tgz#8859936af0038741263053b39d0e76ca241e4034" + +globby@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d" + dependencies: + array-union "^1.0.1" + arrify "^1.0.0" + glob "^7.0.3" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + +graceful-fs@^4.1.2: + version "4.1.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" + +graceful-fs@~1: + version "1.2.3" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-1.2.3.tgz#15a4806a57547cb2d2dbf27f42e89a8c3451b364" + +"graceful-readlink@>= 1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" + +handlebars@^4.0.1: + version "4.0.6" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.6.tgz#2ce4484850537f9c97a8026d5399b935c4ed4ed7" + dependencies: + async "^1.4.0" + optimist "^0.6.1" + source-map "^0.4.4" + optionalDependencies: + uglify-js "^2.6" + +har-validator@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-2.0.6.tgz#cdcbc08188265ad119b6a5a7c8ab70eecfb5d27d" + dependencies: + chalk "^1.1.1" + commander "^2.9.0" + is-my-json-valid "^2.12.4" + pinkie-promise "^2.0.0" + +has-ansi@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" + dependencies: + ansi-regex "^2.0.0" + +has-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" + +has-unicode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + +has@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28" + dependencies: + function-bind "^1.0.2" + +hawk@~3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" + dependencies: + boom "2.x.x" + cryptiles "2.x.x" + hoek "2.x.x" + sntp "1.x.x" + +hoek@2.x.x: + version "2.16.3" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" + +html-encoding-sniffer@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.1.tgz#79bf7a785ea495fe66165e734153f363ff5437da" + dependencies: + whatwg-encoding "^1.0.1" + +http-signature@~1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" + dependencies: + assert-plus "^0.2.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +iconv-lite@0.4.13: + version "0.4.13" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2" + +iconv-lite@^0.4.13: + version "0.4.15" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb" + +ignore@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.2.2.tgz#1c51e1ef53bab6ddc15db4d9ac4ec139eceb3410" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + +in-publish@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/in-publish/-/in-publish-2.0.0.tgz#e20ff5e3a2afc2690320b6dc552682a9c7fadf51" + +indexes-of@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + +ini@^1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e" + +inquirer@0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.11.0.tgz#7448bfa924092af311d47173bbab990cae2bb027" + dependencies: + ansi-escapes "^1.1.0" + ansi-regex "^2.0.0" + chalk "^1.0.0" + cli-cursor "^1.0.1" + cli-width "^1.0.1" + figures "^1.3.5" + lodash "^3.3.1" + readline2 "^1.0.1" + run-async "^0.1.0" + rx-lite "^3.1.2" + strip-ansi "^3.0.0" + through "^2.3.6" + +inquirer@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.12.0.tgz#1ef2bfd63504df0bc75785fff8c2c41df12f077e" + dependencies: + ansi-escapes "^1.1.0" + ansi-regex "^2.0.0" + chalk "^1.0.0" + cli-cursor "^1.0.1" + cli-width "^2.0.0" + figures "^1.3.5" + lodash "^4.3.0" + readline2 "^1.0.1" + run-async "^0.1.0" + rx-lite "^3.1.2" + string-width "^1.0.1" + strip-ansi "^3.0.0" + through "^2.3.6" + +inquirer@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-1.2.3.tgz#4dec6f32f37ef7bb0b2ed3f1d1a5c3f545074918" + dependencies: + ansi-escapes "^1.1.0" + chalk "^1.0.0" + cli-cursor "^1.0.1" + cli-width "^2.0.0" + external-editor "^1.1.0" + figures "^1.3.5" + lodash "^4.3.0" + mute-stream "0.0.6" + pinkie-promise "^2.0.0" + run-async "^2.2.0" + rx "^4.1.0" + string-width "^1.0.1" + strip-ansi "^3.0.0" + through "^2.3.6" + +interpret@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.1.tgz#d579fb7f693b858004947af39fa0db49f795602c" + +invariant@^2.2.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360" + dependencies: + loose-envify "^1.0.0" + +is-buffer@^1.0.2: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.4.tgz#cfc86ccd5dc5a52fa80489111c6920c457e2d98b" + +is-builtin-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe" + dependencies: + builtin-modules "^1.0.0" + +is-ci@^1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.0.10.tgz#f739336b2632365061a9d48270cd56ae3369318e" + dependencies: + ci-info "^1.0.0" + +is-finite@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + +is-my-json-valid@^2.10.0, is-my-json-valid@^2.12.4: + version "2.15.0" + resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.15.0.tgz#936edda3ca3c211fd98f3b2d3e08da43f7b2915b" + dependencies: + generate-function "^2.0.0" + generate-object-property "^1.1.0" + jsonpointer "^4.0.0" + xtend "^4.0.0" + +is-path-cwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" + +is-path-in-cwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz#6477582b8214d602346094567003be8a9eac04dc" + dependencies: + is-path-inside "^1.0.0" + +is-path-inside@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.0.tgz#fc06e5a1683fbda13de667aff717bbc10a48f37f" + dependencies: + path-is-inside "^1.0.1" + +is-promise@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" + +is-property@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" + +is-resolvable@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.0.0.tgz#8df57c61ea2e3c501408d100fb013cf8d6e0cc62" + dependencies: + tryit "^1.0.1" + +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + +is-utf8@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" + +isarray@^1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + +isexe@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-1.1.2.tgz#36f3e22e60750920f5e7241a476a8c6a42275ad0" + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + +istanbul@^0.4.2: + version "0.4.5" + resolved "https://registry.yarnpkg.com/istanbul/-/istanbul-0.4.5.tgz#65c7d73d4c4da84d4f3ac310b918fb0b8033733b" + dependencies: + abbrev "1.0.x" + async "1.x" + escodegen "1.8.x" + esprima "2.7.x" + glob "^5.0.15" + handlebars "^4.0.1" + js-yaml "3.x" + mkdirp "0.5.x" + nopt "3.x" + once "1.x" + resolve "1.1.x" + supports-color "^3.1.0" + which "^1.1.1" + wordwrap "^1.0.0" + +jodid25519@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/jodid25519/-/jodid25519-1.0.2.tgz#06d4912255093419477d425633606e0e90782967" + dependencies: + jsbn "~0.1.0" + +js-base64@^2.1.9: + version "2.1.9" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.1.9.tgz#f0e80ae039a4bd654b5f281fc93f04a914a7fcce" + +js-tokens@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7" + +js-yaml@3.x, js-yaml@^3.5.1: + version "3.7.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80" + dependencies: + argparse "^1.0.7" + esprima "^2.6.0" + +jsbn@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.0.tgz#650987da0dd74f4ebf5a11377a2aa2d273e97dfd" + +jsdom@^9.8.3: + version "9.9.1" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-9.9.1.tgz#84f3972ad394ab963233af8725211bce4d01bfd5" + dependencies: + abab "^1.0.0" + acorn "^2.4.0" + acorn-globals "^1.0.4" + array-equal "^1.0.0" + content-type-parser "^1.0.1" + cssom ">= 0.3.0 < 0.4.0" + cssstyle ">= 0.2.36 < 0.3.0" + escodegen "^1.6.1" + html-encoding-sniffer "^1.0.1" + iconv-lite "^0.4.13" + nwmatcher ">= 1.3.9 < 2.0.0" + parse5 "^1.5.1" + request "^2.55.0" + sax "^1.1.4" + symbol-tree ">= 3.1.0 < 4.0.0" + tough-cookie "^2.3.1" + webidl-conversions "^3.0.1" + whatwg-encoding "^1.0.1" + whatwg-url "^4.1.0" + xml-name-validator ">= 2.0.1 < 3.0.0" + +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + +json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" + dependencies: + jsonify "~0.0.0" + +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + +jsonify@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" + +jsonpointer@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" + +jsprim@^1.2.2: + version "1.3.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.3.1.tgz#2a7256f70412a29ee3670aaca625994c4dcff252" + dependencies: + extsprintf "1.0.2" + json-schema "0.2.3" + verror "1.3.6" + +kind-of@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.1.0.tgz#475d698a5e49ff5e53d14e3e732429dc8bf4cf47" + dependencies: + is-buffer "^1.0.2" + +lazy-cache@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" + +leven@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-2.0.0.tgz#74c45744439550da185801912829f61d22071bc1" + +levn@^0.3.0, levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +lodash@^3.3.1: + version "3.10.1" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" + +lodash@^4.0.0, lodash@^4.3.0, lodash@^4.5.1: + version "4.17.4" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" + +log-update@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/log-update/-/log-update-1.0.2.tgz#19929f64c4093d2d2e7075a1dad8af59c296b8d1" + dependencies: + ansi-escapes "^1.0.0" + cli-cursor "^1.0.2" + +longest@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" + +loose-envify@^1.0.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" + dependencies: + js-tokens "^3.0.0" + +loud-rejection@^1.2.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" + dependencies: + currently-unhandled "^0.4.1" + signal-exit "^3.0.0" + +"mdurl@~ 1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" + +mime-db@~1.26.0: + version "1.26.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.26.0.tgz#eaffcd0e4fc6935cf8134da246e2e6c35305adff" + +mime-types@^2.1.12, mime-types@~2.1.7: + version "2.1.14" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.14.tgz#f7ef7d97583fcaf3b7d282b6f8b5679dab1e94ee" + dependencies: + mime-db "~1.26.0" + +"minimatch@2 || 3", minimatch@^3.0.2, minimatch@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.3.tgz#2a4e4090b96b2db06a9d7df01055a62a77c9b774" + dependencies: + brace-expansion "^1.0.0" + +minimist@0.0.8, minimist@~0.0.1: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + +minimist@^1.2.0, "minimist@~ 1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + +mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + dependencies: + minimist "0.0.8" + +ms@0.7.2: + version "0.7.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765" + +mute-stream@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0" + +mute-stream@0.0.6, mute-stream@~0.0.4: + version "0.0.6" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.6.tgz#48962b19e169fd1dfc240b3f1e7317627bbc47db" + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + +node-emoji@^1.0.4: + version "1.5.1" + resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.5.1.tgz#fd918e412769bf8c448051238233840b2aff16a1" + dependencies: + string.prototype.codepointat "^0.2.0" + +node-gyp@^3.2.1: + version "3.5.0" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.5.0.tgz#a8fe5e611d079ec16348a3eb960e78e11c85274a" + dependencies: + fstream "^1.0.0" + glob "^7.0.3" + graceful-fs "^4.1.2" + minimatch "^3.0.2" + mkdirp "^0.5.0" + nopt "2 || 3" + npmlog "0 || 1 || 2 || 3 || 4" + osenv "0" + request "2" + rimraf "2" + semver "2.x || 3.x || 4 || 5" + tar "^2.0.0" + which "1" + +node-localstorage@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/node-localstorage/-/node-localstorage-0.6.0.tgz#45a0601c6932dfde6644a23361f1be173c75d3af" + +"nopt@2 || 3", nopt@3.x: + version "3.0.6" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" + dependencies: + abbrev "1" + +"npmlog@0 || 1 || 2 || 3 || 4": + version "4.0.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.0.2.tgz#d03950e0e78ce1527ba26d2a7592e9348ac3e75f" + dependencies: + are-we-there-yet "~1.1.2" + console-control-strings "~1.1.0" + gauge "~2.7.1" + set-blocking "~2.0.0" + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + +"nwmatcher@>= 1.3.9 < 2.0.0": + version "1.3.9" + resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.3.9.tgz#8bab486ff7fa3dfd086656bbe8b17116d3692d2a" + +oauth-sign@~0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" + +object-assign@^4.0.1, object-assign@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + +object-path@^0.11.2: + version "0.11.3" + resolved "https://registry.yarnpkg.com/object-path/-/object-path-0.11.3.tgz#3e21a42ad07234d815429ae9e15c1c5f38050554" + +once@1.x, once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + dependencies: + wrappy "1" + +once@~1.3.0: + version "1.3.3" + resolved "https://registry.yarnpkg.com/once/-/once-1.3.3.tgz#b2e261557ce4c314ec8304f3fa82663e4297ca20" + dependencies: + wrappy "1" + +onetime@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789" + +optimist@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" + dependencies: + minimist "~0.0.1" + wordwrap "~0.0.2" + +optionator@^0.8.1, optionator@^0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.4" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + wordwrap "~1.0.0" + +os-homedir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + +os-shim@^0.1.2: + version "0.1.3" + resolved "https://registry.yarnpkg.com/os-shim/-/os-shim-0.1.3.tgz#6b62c3791cf7909ea35ed46e17658bb417cb3917" + +os-tmpdir@^1.0.0, os-tmpdir@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + +osenv@0: + version "0.1.4" + resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.4.tgz#42fe6d5953df06c8064be6f176c3d05aaaa34644" + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.0" + +ospec@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/ospec/-/ospec-1.2.3.tgz#92e2a458d00ff8547ba03bb0fe859c69103b9f70" + +parse5@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-1.5.1.tgz#9b7f3b0de32be78dc2401b17573ccaf0f6f59d94" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + +path-is-inside@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" + +perfectionist@^2.1.2: + version "2.4.0" + resolved "https://registry.yarnpkg.com/perfectionist/-/perfectionist-2.4.0.tgz#c147ad3714e126467f1764129ee72df861d47ea0" + dependencies: + comment-regex "^1.0.0" + defined "^1.0.0" + minimist "^1.2.0" + postcss "^5.0.8" + postcss-scss "^0.3.0" + postcss-value-parser "^3.3.0" + read-file-stdin "^0.2.0" + string.prototype.repeat "^0.2.0" + vendors "^1.0.0" + write-file-stdout "0.0.2" + +pify@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + +pinkie-promise@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" + dependencies: + pinkie "^2.0.0" + +pinkie@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" + +pluralize@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45" + +postcss-minify-params@^1.0.4: + version "1.2.2" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-1.2.2.tgz#ad2ce071373b943b3d930a3fa59a358c28d6f1f3" + dependencies: + alphanum-sort "^1.0.1" + postcss "^5.0.2" + postcss-value-parser "^3.0.2" + uniqs "^2.0.0" + +postcss-minify-selectors@^2.0.2: + version "2.1.1" + resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-2.1.1.tgz#b2c6a98c0072cf91b932d1a496508114311735bf" + dependencies: + alphanum-sort "^1.0.2" + has "^1.0.1" + postcss "^5.0.14" + postcss-selector-parser "^2.0.0" + +postcss-scss@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/postcss-scss/-/postcss-scss-0.3.1.tgz#65c610d8e2a7ee0e62b1835b71b8870734816e4b" + dependencies: + postcss "^5.2.4" + +postcss-selector-parser@^2.0.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-2.2.2.tgz#3d70f5adda130da51c7c0c2fc023f56b1374fe08" + dependencies: + flatten "^1.0.2" + indexes-of "^1.0.1" + uniq "^1.0.1" + +postcss-value-parser@^3.0.2, postcss-value-parser@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz#87f38f9f18f774a4ab4c8a232f5c5ce8872a9d15" + +postcss@^5.0.14, postcss@^5.0.2, postcss@^5.0.8, postcss@^5.2.4: + version "5.2.11" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.11.tgz#ff29bcd6d2efb98bfe08a022055ec599bbe7b761" + dependencies: + chalk "^1.1.3" + js-base64 "^2.1.9" + source-map "^0.5.6" + supports-color "^3.2.3" + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + +process-nextick-args@~1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" + +progress@^1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" + +proper-lockfile@^1.1.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-1.2.0.tgz#ceff5dd89d3e5f10fb75e1e8e76bc75801a59c34" + dependencies: + err-code "^1.0.0" + extend "^3.0.0" + graceful-fs "^4.1.2" + retry "^0.10.0" + +punycode@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + +qs@~6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.0.tgz#f403b264f23bc01228c74131b407f18d5ea5d442" + +read-file-stdin@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/read-file-stdin/-/read-file-stdin-0.2.1.tgz#25eccff3a153b6809afacb23ee15387db9e0ee61" + dependencies: + gather-stream "^1.0.0" + +read@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4" + dependencies: + mute-stream "~0.0.4" + +readable-stream@^2.0.0, "readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.5, readable-stream@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.2.tgz#a9e6fec3c7dda85f8bb1b3ba7028604556fc825e" + dependencies: + buffer-shims "^1.0.0" + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "~1.0.0" + process-nextick-args "~1.0.6" + string_decoder "~0.10.x" + util-deprecate "~1.0.1" + +readline2@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/readline2/-/readline2-1.0.1.tgz#41059608ffc154757b715d9989d199ffbf372e35" + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + mute-stream "0.0.5" + +rechoir@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" + dependencies: + resolve "^1.1.6" + +regenerator-runtime@^0.10.0: + version "0.10.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.1.tgz#257f41961ce44558b18f7814af48c17559f9faeb" + +repeat-string@^1.5.2: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + +repeating@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" + dependencies: + is-finite "^1.0.0" + +request-capture-har@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/request-capture-har/-/request-capture-har-1.1.4.tgz#e6ad76eb8e7a1714553fdbeef32cd4518e4e2013" + +request@2, request@>=2.42.0, request@^2.55.0, request@^2.75.0: + version "2.79.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de" + dependencies: + aws-sign2 "~0.6.0" + aws4 "^1.2.1" + caseless "~0.11.0" + combined-stream "~1.0.5" + extend "~3.0.0" + forever-agent "~0.6.1" + form-data "~2.1.1" + har-validator "~2.0.6" + hawk "~3.1.3" + http-signature "~1.1.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.7" + oauth-sign "~0.8.1" + qs "~6.3.0" + stringstream "~0.0.4" + tough-cookie "~2.3.0" + tunnel-agent "~0.4.1" + uuid "^3.0.0" + +require-uncached@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3" + dependencies: + caller-path "^0.1.0" + resolve-from "^1.0.0" + +resolve-from@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" + +resolve@1.1.x, resolve@^1.1.6: + version "1.1.7" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" + +restore-cursor@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541" + dependencies: + exit-hook "^1.0.0" + onetime "^1.0.0" + +retry@^0.10.0: + version "0.10.1" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4" + +right-align@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" + dependencies: + align-text "^0.1.1" + +rimraf@2, rimraf@^2.2.8, rimraf@^2.5.0: + version "2.5.4" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.5.4.tgz#96800093cbf1a0c86bd95b4625467535c29dfa04" + dependencies: + glob "^7.0.5" + +rimraf@~2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.1.4.tgz#5a6eb62eeda068f51ede50f29b3e5cd22f3d9bb2" + optionalDependencies: + graceful-fs "~1" + +roadrunner@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/roadrunner/-/roadrunner-1.1.0.tgz#1180a30d64e1970d8f55dd8cb0da8ffccecad71e" + +rollup@^0.36.4: + version "0.36.4" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-0.36.4.tgz#a224494c5386c1d73d38f7bb86f69f5eb011a3d2" + dependencies: + source-map-support "^0.4.0" + +run-async@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-0.1.0.tgz#c8ad4a5e110661e402a7d21b530e009f25f8e389" + dependencies: + once "^1.3.0" + +run-async@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" + dependencies: + is-promise "^2.1.0" + +rx-lite@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102" + +rx@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782" + +sax@^1.1.4: + version "1.2.1" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a" + +"semver@2.x || 3.x || 4 || 5", semver@^5.1.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" + +set-blocking@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + +shelljs@^0.7.5: + version "0.7.6" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.6.tgz#379cccfb56b91c8601e4793356eb5382924de9ad" + dependencies: + glob "^7.0.0" + interpret "^1.0.0" + rechoir "^0.6.2" + +signal-exit@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + +slice-ansi@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" + +sntp@1.x.x: + version "1.0.9" + resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" + dependencies: + hoek "2.x.x" + +source-map-support@^0.4.0: + version "0.4.11" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.11.tgz#647f939978b38535909530885303daf23279f322" + dependencies: + source-map "^0.5.3" + +source-map@^0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" + dependencies: + amdefine ">=0.0.4" + +source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1: + version "0.5.6" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" + +source-map@~0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.2.0.tgz#dab73fbcfc2ba819b4de03bd6f6eaa48164b3f9d" + dependencies: + amdefine ">=0.0.4" + +spawn-sync@^1.0.15: + version "1.0.15" + resolved "https://registry.yarnpkg.com/spawn-sync/-/spawn-sync-1.0.15.tgz#b00799557eb7fb0c8376c29d44e8a1ea67e57476" + dependencies: + concat-stream "^1.4.7" + os-shim "^0.1.2" + +spdx-correct@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40" + dependencies: + spdx-license-ids "^1.0.2" + +spdx-expression-parse@~1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz#9bdf2f20e1f40ed447fbe273266191fced51626c" + +spdx-license-ids@^1.0.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + +sshpk@^1.7.0: + version "1.10.2" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.10.2.tgz#d5a804ce22695515638e798dbe23273de070a5fa" + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + dashdash "^1.12.0" + getpass "^0.1.1" + optionalDependencies: + bcrypt-pbkdf "^1.0.0" + ecc-jsbn "~0.1.1" + jodid25519 "^1.0.0" + jsbn "~0.1.0" + tweetnacl "~0.14.0" + +string-width@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + +string-width@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.0.0.tgz#635c5436cc72a6e0c387ceca278d4e2eec52687e" + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^3.0.0" + +string.prototype.codepointat@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/string.prototype.codepointat/-/string.prototype.codepointat-0.2.0.tgz#6b26e9bd3afcaa7be3b4269b526de1b82000ac78" + +string.prototype.repeat@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/string.prototype.repeat/-/string.prototype.repeat-0.2.0.tgz#aba36de08dcee6a5a337d49b2ea1da1b28fc0ecf" + +string_decoder@~0.10.x: + version "0.10.31" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + +stringstream@~0.0.4: + version "0.0.5" + resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" + +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + dependencies: + ansi-regex "^2.0.0" + +strip-bom@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" + dependencies: + is-utf8 "^0.2.0" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + +supports-color@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-0.2.0.tgz#d92de2694eb3f67323973d7ae3d8b55b4c22190a" + +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + +supports-color@^3.1.0, supports-color@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" + dependencies: + has-flag "^1.0.0" + +"symbol-tree@>= 3.1.0 < 4.0.0": + version "3.2.1" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.1.tgz#8549dd1d01fa9f893c18cc9ab0b106b4d9b168cb" + +table@^3.7.8: + version "3.8.3" + resolved "https://registry.yarnpkg.com/table/-/table-3.8.3.tgz#2bbc542f0fda9861a755d3947fefd8b3f513855f" + dependencies: + ajv "^4.7.0" + ajv-keywords "^1.0.0" + chalk "^1.1.1" + lodash "^4.0.0" + slice-ansi "0.0.4" + string-width "^2.0.0" + +tar-stream@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.5.2.tgz#fbc6c6e83c1a19d4cb48c7d96171fc248effc7bf" + dependencies: + bl "^1.0.0" + end-of-stream "^1.0.0" + readable-stream "^2.0.0" + xtend "^4.0.0" + +tar@^2.0.0, tar@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1" + dependencies: + block-stream "*" + fstream "^1.0.2" + inherits "2" + +temp@~0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/temp/-/temp-0.5.1.tgz#77ab19c79aa7b593cbe4fac2441768cad987b8df" + dependencies: + rimraf "~2.1.4" + +text-table@~0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + +through@^2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + +tmp@^0.0.29: + version "0.0.29" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.29.tgz#f25125ff0dd9da3ccb0c2dd371ee1288bb9128c0" + dependencies: + os-tmpdir "~1.0.1" + +tough-cookie@^2.3.1, tough-cookie@~2.3.0: + version "2.3.2" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.2.tgz#f081f76e4c85720e6c37a5faced737150d84072a" + dependencies: + punycode "^1.4.1" + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + +tryit@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tryit/-/tryit-1.0.3.tgz#393be730a9446fd1ead6da59a014308f36c289cb" + +tunnel-agent@~0.4.1: + version "0.4.3" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + dependencies: + prelude-ls "~1.1.2" + +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + +uglify-js@^2.6, uglify-js@^2.6.0: + version "2.7.5" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.7.5.tgz#4612c0c7baaee2ba7c487de4904ae122079f2ca8" + dependencies: + async "~0.2.6" + source-map "~0.5.1" + uglify-to-browserify "~1.0.0" + yargs "~3.10.0" + +uglify-to-browserify@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" + +uniq@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" + +uniqs@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" + +urlgrey@>=0.4.0: + version "0.4.4" + resolved "https://registry.yarnpkg.com/urlgrey/-/urlgrey-0.4.4.tgz#892fe95960805e85519f1cd4389f2cb4cbb7652f" + +user-home@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/user-home/-/user-home-2.0.0.tgz#9c70bfd8169bc1dcbf48604e0f04b8b49cde9e9f" + dependencies: + os-homedir "^1.0.0" + +util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + +uuid@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1" + +validate-npm-package-license@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc" + dependencies: + spdx-correct "~1.0.0" + spdx-expression-parse "~1.0.0" + +vendors@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.1.tgz#37ad73c8ee417fb3d580e785312307d274847f22" + +verror@1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.3.6.tgz#cff5df12946d297d2baaefaa2689e25be01c005c" + dependencies: + extsprintf "1.0.2" + +vorpal@^1.10.8: + version "1.11.4" + resolved "https://registry.yarnpkg.com/vorpal/-/vorpal-1.11.4.tgz#fc43989a52e616b61d156e4b8079376c5ca8f226" + dependencies: + babel-polyfill "^6.3.14" + chalk "^1.1.0" + in-publish "^2.0.0" + inquirer "0.11.0" + lodash "^4.5.1" + log-update "^1.0.2" + minimist "^1.2.0" + node-localstorage "^0.6.0" + strip-ansi "^3.0.0" + wrap-ansi "^2.0.0" + +webidl-conversions@^3.0.0, webidl-conversions@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + +whatwg-encoding@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.1.tgz#3c6c451a198ee7aec55b1ec61d0920c67801a5f4" + dependencies: + iconv-lite "0.4.13" + +whatwg-url@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-4.3.0.tgz#92aaee21f4f2a642074357d70ef8500a7cbb171a" + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +which@1, which@^1.1.1: + version "1.2.12" + resolved "https://registry.yarnpkg.com/which/-/which-1.2.12.tgz#de67b5e450269f194909ef23ece4ebe416fa1192" + dependencies: + isexe "^1.1.1" + +wide-align@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.0.tgz#40edde802a71fea1f070da3e62dcda2e7add96ad" + dependencies: + string-width "^1.0.1" + +window-size@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" + +wordwrap@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" + +wordwrap@^1.0.0, wordwrap@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + +wordwrap@~0.0.2: + version "0.0.3" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" + +wrap-ansi@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + +write-file-stdout@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/write-file-stdout/-/write-file-stdout-0.0.2.tgz#c252d7c7c5b1b402897630e3453c7bfe690d9ca1" + +write@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757" + dependencies: + mkdirp "^0.5.1" + +"xml-name-validator@>= 2.0.1 < 3.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635" + +xtend@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" + +yargs@~3.10.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1" + dependencies: + camelcase "^1.0.2" + cliui "^2.1.0" + decamelize "^1.0.0" + window-size "0.1.0" + +yarn@^0.18.1: + version "0.18.1" + resolved "https://registry.yarnpkg.com/yarn/-/yarn-0.18.1.tgz#02e2efee30255a60b6df4b5b2c7da9c1fd4c8e83" + dependencies: + babel-runtime "^6.0.0" + bytes "^2.4.0" + camelcase "^3.0.0" + chalk "^1.1.1" + cmd-shim "^2.0.1" + commander "^2.9.0" + death "^1.0.0" + debug "^2.2.0" + defaults "^1.0.3" + detect-indent "^4.0.0" + diff "^2.2.1" + ini "^1.3.4" + inquirer "^1.2.2" + invariant "^2.2.0" + is-builtin-module "^1.0.0" + is-ci "^1.0.10" + leven "^2.0.0" + loud-rejection "^1.2.0" + minimatch "^3.0.3" + mkdirp "^0.5.1" + node-emoji "^1.0.4" + node-gyp "^3.2.1" + object-path "^0.11.2" + proper-lockfile "^1.1.3" + read "^1.0.7" + repeating "^2.0.0" + request "^2.75.0" + request-capture-har "^1.1.4" + rimraf "^2.5.0" + roadrunner "^1.1.0" + semver "^5.1.0" + strip-bom "^2.0.0" + tar "^2.2.1" + tar-stream "^1.5.2" + user-home "^2.0.0" + validate-npm-package-license "^3.0.1"