Readds shorthand compacting.
authorJakub Pawlowicz <contact@jakubpawlowicz.com>
Sun, 12 Apr 2015 09:49:26 +0000 (10:49 +0100)
committerJakub Pawlowicz <contact@jakubpawlowicz.com>
Sun, 12 Apr 2015 16:58:06 +0000 (17:58 +0100)
Basically tries to compact longhands into shorthands if possible,
caring about number of components and their understandability.

It DOES NOT re-add `!important`-based compacting which was previously
available because:

* it's logic is flawed in global context, when introduced important
  property may override properties appearing later on;
* it's very uncommon to occur.

lib/properties/compactable.js
lib/properties/optimizer.js
lib/properties/shorthand-compactor.js
test/fixtures/big-min.css
test/properties/shorthand-compacting-test.js

index 05f5dec..09b1477 100644 (file)
@@ -272,4 +272,13 @@ addFourValueShorthand('margin', [
   'margin-left'
 ]);
 
+// Adds `componentOf` field to all longhands
+for (var property in compactable) {
+  if (compactable[property].shorthand) {
+    for (var i = 0, l = compactable[property].components.length; i < l; i++) {
+      compactable[compactable[property].components[i]].componentOf = property;
+    }
+  }
+}
+
 module.exports = compactable;
index e55c568..11f6a3e 100644 (file)
@@ -2,7 +2,7 @@ var compactable = require('./compactable');
 var wrapForOptimizing = require('./wrap-for-optimizing').all;
 var populateComponents = require('./populate-components');
 var compactOverrides = require('./override-compactor');
-// var compactShorthands = require('./shorthand-compactor');
+var compactShorthands = require('./shorthand-compactor');
 var removeUnused = require('./remove-unused');
 var restoreShorthands = require('./restore-shorthands');
 var stringifyProperty = require('../stringifier/one-time').property;
@@ -187,8 +187,7 @@ function optimize(selector, properties, mergeAdjacent, withCompacting, options,
 
   if (withCompacting && options.shorthandCompacting && !options.sourceMap) {
     compactOverrides(_properties, options.compatibility, validator);
-    // compactShorthands(_properties, false, options.compatibility);
-    // compactShorthands(_properties, true, options.compatibility);
+    compactShorthands(_properties, options.compatibility, validator);
   }
 
   restoreShorthands(_properties);
index 0c94f31..2fbe09e 100644 (file)
+var compactable = require('./compactable');
+var deepClone = require('./clone').deep;
+var populateComponents = require('./populate-components');
+var wrapSingle = require('./wrap-for-optimizing').single;
 
-// // Compacts the tokens by transforming properties into their shorthand notations when possible
-
-// module.exports = (function () {
-//   var isHackValue = function (t) { return t.value === '__hack'; };
-
-//   var compactShorthands = function(tokens, isImportant, processable, Token) {
-//     // Contains the components found so far, grouped by shorthand name
-//     var componentsSoFar = { };
-
-//     // Initializes a prop in componentsSoFar
-//     var initSoFar = function (shprop, last, clearAll) {
-//       var found = {};
-//       var shorthandPosition;
-
-//       if (!clearAll && componentsSoFar[shprop]) {
-//         for (var i = 0; i < processable[shprop].components.length; i++) {
-//           var prop = processable[shprop].components[i];
-//           found[prop] = [];
-
-//           if (!(componentsSoFar[shprop].found[prop]))
-//             continue;
-
-//           for (var ii = 0; ii < componentsSoFar[shprop].found[prop].length; ii++) {
-//             var comp = componentsSoFar[shprop].found[prop][ii];
-
-//             if (comp.trashed)
-//               continue;
-
-//             found[prop].push(comp);
-
-//             if (comp.position && (!shorthandPosition || comp.position < shorthandPosition))
-//               shorthandPosition = comp.position;
-//           }
-//         }
-//       }
-//       componentsSoFar[shprop] = {
-//         lastShorthand: last,
-//         found: found,
-//         shorthandPosition: shorthandPosition
-//       };
-//     };
-
-//     // Adds a component to componentsSoFar
-//     var addComponentSoFar = function (token, index) {
-//       var shprop = processable[token.name].componentOf;
-//       if (!componentsSoFar[shprop])
-//         initSoFar(shprop);
-//       if (!componentsSoFar[shprop].found[token.name])
-//         componentsSoFar[shprop].found[token.name] = [];
-
-//       // Add the newfound component to componentsSoFar
-//       componentsSoFar[shprop].found[token.name].push(token);
-
-//       if (!componentsSoFar[shprop].shorthandPosition && index) {
-//         // If the haven't decided on where the shorthand should go, put it in the place of this component
-//         componentsSoFar[shprop].shorthandPosition = index;
-//       }
-//     };
-
-//     // Tries to compact a prop in componentsSoFar
-//     var compactSoFar = function (prop) {
-//       var i;
-//       var componentsCount = processable[prop].components.length;
-
-//       // Check basics
-//       if (!componentsSoFar[prop] || !componentsSoFar[prop].found)
-//         return false;
-
-//       // Find components for the shorthand
-//       var components = [];
-//       var realComponents = [];
-//       for (i = 0 ; i < componentsCount; i++) {
-//         // Get property name
-//         var pp = processable[prop].components[i];
-
-//         if (componentsSoFar[prop].found[pp] && componentsSoFar[prop].found[pp].length) {
-//           // We really found it
-//           var foundRealComp = componentsSoFar[prop].found[pp][0];
-//           components.push(foundRealComp);
-//           if (foundRealComp.real !== false) {
-//             realComponents.push(foundRealComp);
-//           }
-//         } else if (componentsSoFar[prop].lastShorthand) {
-//           // It's defined in the previous shorthand
-//           var c = componentsSoFar[prop].lastShorthand.components[i].clone(isImportant);
-//           components.push(c);
-//         } else {
-//           // Couldn't find this component at all
-//           return false;
-//         }
-//       }
-
-//       if (realComponents.length === 0) {
-//         // Couldn't find enough components, sorry
-//         return false;
-//       }
-
-//       if (realComponents.length === componentsCount) {
-//         // When all the components are from real values, only allow shorthanding if their understandability allows it
-//         // This is the case when every component can override their default values, or when all of them use the same function
-
-//         var canOverrideDefault = true;
-//         var functionNameMatches = true;
-//         var functionName;
-
-//         for (var ci = 0; ci < realComponents.length; ci++) {
-//           var rc = realComponents[ci];
-
-//           if (!processable[rc.name].canOverride(processable[rc.name].defaultValue, rc.value)) {
-//             canOverrideDefault = false;
-//           }
-//           var iop = rc.value.indexOf('(');
-//           if (iop >= 0) {
-//             var otherFunctionName = rc.value.substring(0, iop);
-//             if (functionName)
-//               functionNameMatches = functionNameMatches && otherFunctionName === functionName;
-//             else
-//               functionName = otherFunctionName;
-//           }
-//         }
-
-//         if (!canOverrideDefault || !functionNameMatches)
-//           return false;
-//       }
-
-//       // Compact the components into a shorthand
-//       var compacted = processable[prop].putTogether(prop, components, isImportant);
-//       if (!(compacted instanceof Array)) {
-//         compacted = [compacted];
-//       }
-
-//       var compactedLength = Token.getDetokenizedLength(compacted);
-//       var authenticLength = Token.getDetokenizedLength(realComponents);
-
-//       if (realComponents.length === componentsCount || compactedLength < authenticLength || components.some(isHackValue)) {
-//         compacted[0].isShorthand = true;
-//         compacted[0].components = processable[prop].breakUp(compacted[0]);
-
-//         // Mark the granular components for deletion
-//         for (i = 0; i < realComponents.length; i++) {
-//           realComponents[i].isMarkedForDeletion = true;
-//         }
-
-//         // Mark the position of the new shorthand
-//         tokens[componentsSoFar[prop].shorthandPosition].replaceWith = compacted;
-
-//         // Reinitialize the thing for further compacting
-//         initSoFar(prop, compacted[0]);
-//         for (i = 1; i < compacted.length; i++) {
-//           addComponentSoFar(compacted[i]);
-//         }
-
-//         // Yes, we can keep the new shorthand!
-//         return true;
-//       }
-
-//       return false;
-//     };
-
-//     // Tries to compact all properties currently in componentsSoFar
-//     var compactAllSoFar = function () {
-//       for (var i in componentsSoFar) {
-//         if (componentsSoFar.hasOwnProperty(i)) {
-//           while (compactSoFar(i)) { }
-//         }
-//       }
-//     };
-
-//     var i, token;
-
-//     // Go through each token and collect components for each shorthand as we go on
-//     for (i = 0; i < tokens.length; i++) {
-//       token = tokens[i];
-//       if (token.trashed) {
-//         continue;
-//       }
-//       if (!processable[token.name]) {
-//         // We don't know what it is, move on
-//         continue;
-//       }
-//       if (processable[token.name].isShorthand) {
-//         // Found an instance of a full shorthand
-//         // NOTE: we should NOT mix together tokens that come before and after the shorthands
-
-//         if (token.important === isImportant || (token.important && !isImportant)) {
-//           // Try to compact what we've found so far
-//           while (compactSoFar(token.name)) { }
-//           // Reset
-//           initSoFar(token.name, token, true);
-//         }
-
-//         // TODO: when the old optimizer is removed, take care of this corner case:
-//         //   div{background-color:#111;background-image:url(aaa);background:linear-gradient(aaa);background-repeat:no-repeat;background-position:1px 2px;background-attachment:scroll}
-//         //   -> should not be shorthanded / minified at all because the result wouldn't be equivalent to the original in any browser
-//       } else if (processable[token.name].componentOf) {
-//         // Found a component of a shorthand
-//         if (token.important === isImportant) {
-//           // Same importantness
-//           token.position = i;
-//           addComponentSoFar(token, i);
-//         } else if (!isImportant && token.important) {
-//           // Use importants for optimalization opportunities
-//           // https://github.com/jakubpawlowicz/clean-css/issues/184
-//           var importantTrickComp = new Token(token.name, token.value, isImportant);
-//           importantTrickComp.irrelevant = true;
-//           importantTrickComp.real = false;
-//           addComponentSoFar(importantTrickComp);
-//         }
-//       } else {
-//         // This is not a shorthand and not a component, don't care about it
-//         continue;
-//       }
-//     }
-
-//     // Perform all possible compactions
-//     compactAllSoFar();
-
-//     // Process the results - throw away stuff marked for deletion, insert compacted things, etc.
-//     var result = [];
-//     for (i = 0; i < tokens.length; i++) {
-//       token = tokens[i];
-
-//       if (token.replaceWith) {
-//         for (var ii = 0; ii < token.replaceWith.length; ii++) {
-//           result.push(token.replaceWith[ii]);
-//         }
-//       }
-//       if (!token.trashed) {
-//         result.push(token);
-//       }
-
-//       token.trashed = false;
-//       token.replaceWith = null;
-//     }
-
-//     return result;
-//   };
-
-//   return {
-//     compactShorthands: compactShorthands
-//   };
-
-// })();
+function mixedImportance(components) {
+  var important;
+
+  for (var name in components) {
+    if (undefined !== important && components[name].important != important)
+      return true;
+
+    important = components[name].important;
+  }
+
+  return false;
+}
+
+function replaceWithShorthand(properties, candidateComponents, name, validator) {
+  var descriptor = compactable[name];
+  var newValuePlaceholder = [[name, false, false], [descriptor.defaultValue]];
+  var all;
+
+  var newProperty = wrapSingle(newValuePlaceholder);
+  newProperty.shorthand = true;
+  newProperty.dirty = true;
+
+  populateComponents([newProperty], validator);
+
+  for (var i = 0, l = descriptor.components.length; i < l; i++) {
+    var component = candidateComponents[descriptor.components[i]];
+    var canOverride = compactable[component.name].canOverride;
+
+    if (!canOverride(newProperty.components[i], component, validator))
+      return;
+
+    newProperty.components[i] = deepClone(component);
+    newProperty.important = component.important;
+
+    all = component.all;
+  }
+
+  for (var componentName in candidateComponents) {
+    candidateComponents[componentName].unused = true;
+  }
+
+  newProperty.position = all.length;
+  newProperty.all = all;
+  newProperty.all.push(newValuePlaceholder);
+  newValuePlaceholder[0][1] = newProperty.important;
+
+  properties.push(newProperty);
+}
+
+function invalidateOrCompact(properties, position, candidates, validator) {
+  var property = properties[position];
+
+  for (var name in candidates) {
+    if (undefined !== property && name == property.name)
+      continue;
+
+    var descriptor = compactable[name];
+    var candidateComponents = candidates[name];
+    if (descriptor.components.length > Object.keys(candidateComponents).length) {
+      delete candidates[name];
+      continue;
+    }
+
+    if (mixedImportance(candidateComponents))
+      continue;
+
+    replaceWithShorthand(properties, candidateComponents, name, validator);
+  }
+}
+
+function compactShortands(properties, compatibility, validator) {
+  var candidates = {};
+
+  for (var i = 0, l = properties.length; i < l; i++) {
+    var property = properties[i];
+    if (property.unused)
+      continue;
+
+    var descriptor = compactable[property.name];
+    if (!descriptor || !descriptor.componentOf)
+      continue;
+
+    if (property.shorthand) {
+      invalidateOrCompact(properties, i, candidates, validator);
+    } else {
+      var componentOf = descriptor.componentOf;
+      candidates[componentOf] = candidates[componentOf] || {};
+      candidates[componentOf][property.name] = property;
+    }
+  }
+
+  invalidateOrCompact(properties, i, candidates, validator);
+}
+
+module.exports = compactShortands;
index 8577b36..67a96a7 100644 (file)
@@ -1527,7 +1527,7 @@ label.comparer input{margin-right:8px}
 .lien_img314x64.explorer_discours_2012{background:url(/medias/web/img/textes/elections/widget_explorer_discours_2012.png);text-indent:-9999px}
 *{margin:0;padding:0}
 form,img{border:0}
-ul{list-style-image:none;list-style-position:inside;list-style-type:none}
+ul{list-style:none inside}
 #mainContent{background:#fff;font-size:12px;color:#222}
 body>img{position:absolute}
 .megaban{margin-right:auto;margin-left:auto}
@@ -2125,6 +2125,7 @@ a.god:hover{background:#3c3c3c;color:#fff}
 #core-liberation .block-search-head form input[type=submit]:hover{background-image:url(http://s0.libe.com/libe/img/common/bg-search-formsubmit-on.png?21388ca68b89)}
 #core-liberation .block-search-results form input[type=submit]:active{background-image:url(http://s0.libe.com/libe/img/common/bg-search-formsubmit-active.png?16bd2d8fbc96)}
 #core-liberation .block-search-head .pagination{border-top:0}
+#core-liberation .block-search-head .advanced,#core-liberation .pagination{border-top-color:#b7b7b7;border-top:1px dotted}
 #core-liberation .block-search-head .pagination a,#core-liberation .block-search-results .pagination a{background-color:#fff}
 #core-liberation .block-search-head .pagination .prev,#core-liberation .block-search-results .pagination .prev{margin-left:0;padding-left:17px}
 #core-liberation .block-search-head .pagination .next,#core-liberation .block-search-results .pagination .next{margin-right:0;padding-right:17px}
@@ -2173,7 +2174,7 @@ a.god:hover{background:#3c3c3c;color:#fff}
 #core-liberation .block-search-results .block-content .object-picture img{display:block;width:87px}
 #core-liberation .block-search-results .block-content .article .object-picture,#core-liberation .block-search-results .block-content .article .object-picture img{height:116px}
 #core-liberation .block-search-results .block-content .object-picture .np{position:relative}
-#core-liberation .block-search-results .block-content .object-picture .np .p1,#core-liberation .block-search-results .block-content .object-picture .np .p2{position:absolute;width:79px;height:102px;border-style:solid;border-top-width:7px;border-bottom-width:7px;border-left-width:4px;border-right-width:4px}
+#core-liberation .block-search-results .block-content .object-picture .np .p1,#core-liberation .block-search-results .block-content .object-picture .np .p2{position:absolute;width:79px;height:102px;border-style:solid;border-width:7px 4px}
 #core-liberation .block-search-results .block-content .object-picture .np .p1{z-index:2000}
 #core-liberation .block-search-results .block-content .object-picture .np .p2{z-index:1000}
 #core-liberation .block-search-results .block-content .object-picture .np a.date{position:absolute;display:block;width:80px;top:10px;padding:2px 2px 3px 4px;z-index:500}
@@ -2783,7 +2784,6 @@ body.slideshow .ad-top .megaban{background:#333}
 #core-liberation .block-search-head p.opinion span{color:#707070}
 #core-liberation .block-search-head p.opinion a{color:#e20000}
 #core-liberation .block-search-head .advanced .note .links,#core-liberation .block-search-head .advanced .note .links a,#core-liberation .block-search-head .advanced .note a.displayer,#core-liberation .block-search-head .results p{color:#87888a}
-#core-liberation .block-search-head .advanced{border-top-color:#b7b7b7}
 #core-liberation .block-search-head .advanced .searchform .between,#core-liberation .block-search-head .advanced .searchform .category,#core-liberation .block-search-head .advanced .searchform .period,#core-liberation .block-search-head .advanced .searchform .source{border-bottom-color:#fff}
 #core-liberation .block-search-head .results{margin-bottom:15px}
 #core-liberation .block-search-head .results p.filters strong{color:#2e2e2e}
@@ -2826,7 +2826,7 @@ body.slideshow .ad-top .megaban{background:#333}
 .site-liberation .btn-read-digitalpaper{border-color:#878787}
 .site-liberation .toolbox,.site-liberation .toolbox li.fold-options ul,.site-liberation .toolbox li.fold-options+li.fold-options{border-color:#d7d7d7}
 .site-liberation .btn-read-digitalpaper a,.site-liberation .btn-read-digitalpaper span{color:#2e2e2e}
-#core-liberation .pagination{background-color:#e7e7e7;border-top-color:#b7b7b7;border-bottom-color:#b7b7b7}
+#core-liberation .pagination{background-color:#e7e7e7;border-bottom-color:#b7b7b7}
 #bar-liberation,#bar-liberation #login-box-content,#bar-liberation .content ul.list li{border-bottom-color:#dadada}
 #core-liberation .pagination .disabled{background-color:transparent;color:#c8c8c8}
 #core-liberation .pagination .current{background-color:transparent;color:#e20000}
index ed1b694..245bcb8 100644 (file)
-// 'shorthand properties': cssContext({
-//   'shorthand background #1' : [
-//     'div{background-color:#111;background-image:url(aaa);background-repeat:repeat;background-position:0 0;background-attachment:scroll;background-size:auto;background-origin:padding-box;background-clip:border-box}',
-//     'div{background:url(aaa)#111}'
-//   ],
-//   'shorthand background #2' : [
-//     'div{background-color:#111;background-image:url(aaa);background-repeat:no-repeat;background-position:0 0;background-attachment:scroll;background-size:auto;background-origin:padding-box;background-clip:border-box}',
-//     'div{background:url(aaa)no-repeat #111}'
-//   ],
-//   'shorthand important background' : [
-//     'div{background-color:#111!important;background-image:url(aaa)!important;background-repeat:repeat!important;background-position:0 0!important;background-attachment:scroll!important;background-size:auto!important;background-origin:padding-box!important;background-clip:border-box!important}',
-//     'div{background:url(aaa)#111!important}'
-//   ],
-//   'shorthand important background overriding': [
-//     'a{background:url(a.jpg) !important; background-color:#fff !important}',
-//     'a{background:url(a.jpg)#fff!important}'
-//   ],
-//   'shorthand important background overriding by non-mergeable property': [
-//     'a{background:url(a.jpg) !important; background-color:#fff !important; background-size:10px 10px !important}',
-//     'a{background:url(a.jpg)#fff!important;background-size:10px 10px!important}'
-//   ],
-//   'shorthand background-repeat correctly': [
-//     'a{background:url(/image/path.png) no-repeat repeat}',
-//     'a{background:url(/image/path.png)no-repeat repeat}'
-//   ],
-//   'shorthand border-width': [
-//     '.t{border-top-width:7px;border-bottom-width:7px;border-left-width:4px;border-right-width:4px}',
-//     '.t{border-width:7px 4px}'
-//   ],
-//   'shorthand border-color #1': [
-//     '.t{border-top-color:#9fce00;border-bottom-color:#9fce00;border-left-color:#9fce00;border-right-color:#9fce00}',
-//     '.t{border-color:#9fce00}'
-//   ],
-//   'shorthand border-color #2': [
-//     '.t{border-right-color:#002;border-bottom-color:#003;border-top-color:#001;border-left-color:#004}',
-//     '.t{border-color:#001 #002 #003 #004}'
-//   ],
-//   'shorthand border-radius': [
-//     '.t{border-top-left-radius:7px;border-bottom-right-radius:6px;border-bottom-left-radius:5px;border-top-right-radius:3px}',
-//     '.t{border-radius:7px 3px 6px 5px}'
-//   ],
-//   'shorthand border-radius none': 'li{border-radius:none}',
-//   'shorthand list-style #1': [
-//     '.t{list-style-type:circle;list-style-position:outside;list-style-image:url(aaa)}',
-//     '.t{list-style:circle url(aaa)}'
-//   ],
-//   'shorthand list-style #2': [
-//     '.t{list-style-image:url(aaa);list-style-type:circle;list-style-position:inside}',
-//     '.t{list-style:circle inside url(aaa)}'
-//   ]
-// }),
-// 'cares about understandability of shorthand components': cssContext({
-//   'linear-gradient should NOT clear out background with color only' : [
-//     'div{background:#fff;background:linear-gradient(whatever)}',
-//     'div{background:#fff;background:linear-gradient(whatever)}'
-//   ],
-//   'linear-gradient should NOT clear out background with color only, even if it has a color' : [
-//     'div{background:#fff;background:linear-gradient(whatever) #222}',
-//     'div{background:#fff;background:linear-gradient(whatever)#222}'
-//   ],
-//   'a background-image with just a linear-gradient should not be compacted to a shorthand' : [
-//     'div{background-color:#111;background-image:linear-gradient(aaa);background-repeat:no-repeat;background-position:0 0;background-attachment:scroll}',
-//     'div{background-color:#111;background-image:linear-gradient(aaa);background-repeat:no-repeat;background-position:0 0;background-attachment:scroll}'
-//   ],
-//   'a background-image with a none and a linear-gradient should result in two shorthands' : [
-//     'div{background-color:#111;background-image:none;background-image:linear-gradient(aaa);background-repeat:repeat;background-position:0 0;background-attachment:scroll;background-size:auto;background-origin:padding-box;background-clip:border-box}',
-//     'div{background:#111;background:linear-gradient(aaa)#111}'
-//   ]
-// }),
-// 'cares about understandability of border components': cssContext({
-//   'border(none) with border(rgba)': 'a{border:none;border:1px solid rgba(1,0,0,.5)}',
-//   'border(rgba) with border(none)': 'a{border:1px solid rgba(1,0,0,.5);border:none}',
-//   'border(hex) with border(rgba)': 'a{border:1px solid #fff;border:1px solid rgba(1,0,0,.5)}'
-// }),
-// 'merge same properties sensibly': cssContext({
-//   'should merge color values with same understandability #1': [
-//     'p{color:red;color:#fff;color:blue}',
-//     'p{color:#00f}'
-//   ],
-//   'should merge color values with same understandability #2': [
-//     'p{color:red;color:#fff;color:blue;color:transparent}',
-//     'p{color:transparent}'
-//   ],
-//   'should NOT destroy less understandable values': [
-//     'p{color:red;color:#fff;color:blue;color:rgba(1,2,3,.4)}',
-//     'p{color:#00f;color:rgba(1,2,3,.4)}'
-//   ],
-//   'should destroy even less understandable values if a more understandable one comes after them': [
-//     'p{color:red;color:#fff;color:blue;color:rgba(1,2,3,.4);color:#9fce00}',
-//     'p{color:#9fce00}'
-//   ],
-//   'should merge functions with the same name but keep different functions intact': [
-//     'p{background:-webkit-linear-gradient(aaa);background:-webkit-linear-gradient(bbb);background:linear-gradient(aaa);}',
-//     'p{background:-webkit-linear-gradient(bbb);background:linear-gradient(aaa)}'
-//   ],
-//   'should merge nonimportant + important into one important': [
-//     'a{color:#aaa;color:#bbb!important}',
-//     'a{color:#bbb!important}'
-//   ],
-//   'should merge important + nonimportant into one important': [
-//     'a{color:#aaa!important;color:#bbb}',
-//     'a{color:#aaa!important}'
-//   ],
-//   'should merge importants just like nonimportants while also overriding them': [
-//     'p{color:red!important;color:#fff!important;color:blue!important;color:rgba(1,2,3,.4)}',
-//     'p{color:#00f!important}'
-//   ]
-// }),
-// 'shorthand granular properties when other granular properties are already covered by the shorthand': cssContext({
-//   'should consider the already existing margin to shorthand margin-top and margin-bottom': [
-//     'p{margin:5px;margin-top:foo(1);margin-left:foo(2)}',
-//     'p{margin:5px;margin:foo(1)5px 5px foo(2)}'
-//   ],
-//   'should merge margin-top and margin-left with shorthand if their understandability is the same': [
-//     'p{margin:5px;margin-top:1px;margin-left:2px}',
-//     'p{margin:1px 5px 5px 2px}'
-//   ],
-//   'should NOT shorthand to margin-top if the result would be longer than the input': [
-//     'p{margin:5px;margin-top:foo(1)}',
-//     'p{margin:5px;margin-top:foo(1)}'
-//   ],
-//   'should consider the already existing background to shorthand background-color': [
-//     'p{background:#9fce00;background-color:rgba(1,2,3,.4)}',
-//     'p{background:#9fce00;background:rgba(1,2,3,.4)}'
-//   ],
-//   'should NOT touch important outline-color but should minify default value of outline to 0': [
-//     'p{outline:medium;outline-color:#9fce00!important}',
-//     'p{outline:0;outline-color:#9fce00!important}'
-//   ]
-// }),
-// 'take advantage of importants for optimalization opportunities': cssContext({
-//   'should take into account important margin-left to shorthand non-important margin-top, margin-right and margin-bottom': [
-//     'p{margin-top:1px;margin-right:2px;margin-bottom:3px;margin-left:4px !important}',
-//     'p{margin:1px 2px 3px;margin-left:4px!important}'
-//   ],
-//   'should take into account important margin-bottom and margin-left to shorten shorthanded non-important margin-top and margin-bottom': [
-//     'p{margin-top:1px;margin-right:2px;margin-bottom:3px!important;margin-left:4px !important}',
-//     'p{margin:1px 2px;margin-bottom:3px!important;margin-left:4px!important}'
-//   ],
-//   'should take into account important margin-right and margin-left to shorten shorthanded non-important margin-top and margin-bottom': [
-//     'p{margin-top:1px;margin-bottom:3px;margin-right:2px!important;margin-left:4px !important}',
-//     'p{margin:1px 0 3px;margin-right:2px!important;margin-left:4px!important}'
-//   ],
-//   'should take into account important margin-right and margin-left to shorten shorthanded non-important margin-top and margin-bottom #2': [
-//     'p{margin-top:1px;margin-bottom:1px;margin-right:2px!important;margin-left:4px !important}',
-//     'p{margin:1px;margin-right:2px!important;margin-left:4px!important}'
-//   ],
-//   'should take into account important background-color and shorthand others into background': [
-//     'p{background-color:#9fce00!important;background-image:url(hello);background-attachment:scroll;background-position:1px 2px;background-repeat:repeat-y;background-size:auto;background-origin:padding-box;background-clip:border-box}',
-//     'p{background-color:#9fce00!important;background:url(hello)1px 2px repeat-y}'
-//   ],
-//   'should take into account important outline-color and default value of outline-width': [
-//     'p{outline:inset medium;outline-color:#9fce00!important;outline-style:inset!important}',
-//     'p{outline:0;outline-color:#9fce00!important;outline-style:inset!important}'
-//   ],
-//   'should take into account important background-position remove its irrelevant counterpart': [
-//     'p{background:#9fce00 url(hello) 4px 5px;background-position:5px 3px!important}',
-//     'p{background:url(hello)#9fce00;background-position:5px 3px!important}'
-//   ],
-//   'should take into account important background-position and assign the shortest possible value for its irrelevant counterpart': [
-//     'p{background:transparent;background-position:5px 3px!important}',
-//     'p{background:0;background-position:5px 3px!important}'
-//   ]
-// }),
-// 'properly care about inherit': cssContext({
-//   'merge multiple inherited margin granular properties into one inherited shorthand': [
-//     'p{margin-top:inherit;margin-right:inherit;margin-bottom:inherit;margin-left:inherit}',
-//     'p{margin:inherit}'
-//   ],
-//   'merge multiple inherited background granular properties into one inherited shorthand': [
-//     'p{background-color:inherit;background-image:inherit;background-attachment:inherit;background-position:inherit;background-repeat:inherit;;background-size:inherit;background-origin:inherit;background-clip:inherit}',
-//     'p{background:inherit}'
-//   ],
-//   'when shorter, optimize inherited/non-inherited background granular properties into an inherited shorthand and some non-inherited granular properties': [
-//     'p{background-color:inherit;background-image:inherit;background-attachment:inherit;background-position:inherit;background-repeat:repeat-y;background-size:inherit;background-origin:inherit;background-clip:inherit}',
-//     'p{background:inherit;background-repeat:repeat-y}'
-//   ],
-//   'when shorter, optimize inherited/non-inherited background granular properties into a non-inherited shorthand and some inherited granular properties': [
-//     'p{background-color:#9fce00;background-image:inherit;background-attachment:scroll;background-position:1px 2px;background-repeat:repeat-y;background-size:auto;background-clip:inherit;background-origin:padding-box;}',
-//     'p{background:1px 2px repeat-y #9fce00;background-image:inherit;background-clip:inherit}'
-//   ],
-//   'put inherit to the place where it consumes the least space': [
-//     'div{padding:0;padding-bottom:inherit;padding-right:inherit}',
-//     'div{padding:inherit;padding-top:0;padding-left:0}'
-//   ]
-// }),
-// 'complex granular properties': cssContext({
-//   'two granular properties': 'a{border-bottom:1px solid red;border-color:red}',
-//   'more understandable granular property should override less understandable': [
-//     'a{border-color:rgba(0,0,0,.5);border-color:red}',
-//     'a{border-color:red}'
-//   ],
-//   'less understandable granular property should NOT override more understandable': [
-//     'a{border-color:red;border-color:rgba(0,0,0,.5)}',
-//     'a{border-color:red;border-color:rgba(0,0,0,.5)}'
-//   ],
-//   'two same granular properties redefined': [
-//     'a{border-color:rgba(0,0,0,.5);border-color:red;border:0}',
-//     'a{border:0}'
-//   ],
-//   'important granular property redefined': 'a{border-color:red!important;border:0}',
-//   'important granular property redefined with important': [
-//     'a{border-color:red!important;border:0!important}',
-//     'a{border:0!important}'
-//   ],
-//   'mix of border properties': [
-//     'a{border-top:1px solid red;border-top-color:#0f0;color:red;border-top-width:2px;border-bottom-width:1px;border:0;border-left:1px solid red}',
-//     'a{color:red;border:0;border-left:1px solid red}'
-//   ]
-// }),
+var vows = require('vows');
+var assert = require('assert');
+
+var optimize = require('../../lib/properties/optimizer');
+
+var Tokenizer = require('../../lib/selectors/tokenizer');
+var SourceTracker = require('../../lib/utils/source-tracker');
+var Compatibility = require('../../lib/utils/compatibility');
+var Validator = require('../../lib/properties/validator');
+var addOptimizationMetadata = require('../../lib/selectors/optimization-metadata');
+
+function _optimize(source) {
+  var tokens = new Tokenizer({
+    options: {},
+    sourceTracker: new SourceTracker(),
+    warnings: []
+  }).toTokens(source);
+
+  var compatibility = new Compatibility(compatibility).toOptions();
+  var validator = new Validator(compatibility);
+  var options = {
+    aggressiveMerging: true,
+    compatibility: compatibility,
+    shorthandCompacting: true
+  };
+  addOptimizationMetadata(tokens);
+  optimize(tokens[0][1], tokens[0][2], false, true, options, validator);
+
+  return tokens[0][2];
+}
+
+vows.describe(optimize)
+  .addBatch({
+    'shorthand background #1': {
+      'topic': 'p{background-color:#111;background-image:__ESCAPED_URL_CLEAN_CSS0__;background-repeat:repeat;background-position:0 0;background-attachment:scroll;background-size:auto;background-origin:padding-box;background-clip:border-box}',
+      'into': function (topic) {
+        assert.deepEqual(_optimize(topic), [
+          [['background', false , false], ['__ESCAPED_URL_CLEAN_CSS0__'], ['#111']]
+        ]);
+      }
+    },
+    'shorthand background #2': {
+      'topic': 'p{background-color:#111;background-image:__ESCAPED_URL_CLEAN_CSS0__;background-repeat:no-repeat;background-position:0 0;background-attachment:scroll;background-size:auto;background-origin:padding-box;background-clip:border-box}',
+      'into': function (topic) {
+        assert.deepEqual(_optimize(topic), [
+          [['background', false , false], ['__ESCAPED_URL_CLEAN_CSS0__'], ['no-repeat'], ['#111']]
+        ]);
+      }
+    },
+    'shorthand important background': {
+      'topic': 'p{background-color:#111!important;background-image:__ESCAPED_URL_CLEAN_CSS0__!important;background-repeat:repeat!important;background-position:0 0!important;background-attachment:scroll!important;background-size:auto!important;background-origin:padding-box!important;background-clip:border-box!important}',
+      'into': function (topic) {
+        assert.deepEqual(_optimize(topic), [
+          [['background', true , false], ['__ESCAPED_URL_CLEAN_CSS0__'], ['#111']]
+        ]);
+      }
+    },
+    'shorthand border-width': {
+      'topic': 'p{border-top-width:7px;border-bottom-width:7px;border-left-width:4px;border-right-width:4px}',
+      'into': function (topic) {
+        assert.deepEqual(_optimize(topic), [
+          [['border-width', false , false], ['7px'], ['4px']]
+        ]);
+      }
+    },
+    'shorthand border-color #1': {
+      'topic': 'p{border-top-color:#9fce00;border-bottom-color:#9fce00;border-left-color:#9fce00;border-right-color:#9fce00}',
+      'into': function (topic) {
+        assert.deepEqual(_optimize(topic), [
+          [['border-color', false , false], ['#9fce00']]
+        ]);
+      }
+    },
+    'shorthand border-color #2': {
+      'topic': 'p{border-right-color:#002;border-bottom-color:#003;border-top-color:#001;border-left-color:#004}',
+      'into': function (topic) {
+        assert.deepEqual(_optimize(topic), [
+          [['border-color', false , false], ['#001'], ['#002'], ['#003'], ['#004']]
+        ]);
+      }
+    },
+    'shorthand border-radius': {
+      'topic': 'p{border-top-left-radius:7px;border-bottom-right-radius:6px;border-bottom-left-radius:5px;border-top-right-radius:3px}',
+      'into': function (topic) {
+        assert.deepEqual(_optimize(topic), [
+          [['border-radius', false , false], ['7px'], ['3px'], ['6px'], ['5px']]
+        ]);
+      }
+    },
+    'shorthand list-style #1': {
+      'topic': 'a{list-style-type:circle;list-style-position:outside;list-style-image:__ESCAPED_URL_CLEAN_CSS0__}',
+      'into': function (topic) {
+        assert.deepEqual(_optimize(topic), [
+          [['list-style', false , false], ['circle'], ['__ESCAPED_URL_CLEAN_CSS0__']]
+        ]);
+      }
+    },
+    'shorthand list-style #2': {
+      'topic': 'a{list-style-image:__ESCAPED_URL_CLEAN_CSS0__;list-style-type:circle;list-style-position:inside}',
+      'into': function (topic) {
+        assert.deepEqual(_optimize(topic), [
+          [['list-style', false , false], ['circle'], ['inside'], ['__ESCAPED_URL_CLEAN_CSS0__']]
+        ]);
+      }
+    },
+    'shorthand margin': {
+      'topic': 'a{margin-top:10px;margin-right:5px;margin-bottom:3px;margin-left:2px}',
+      'into': function (topic) {
+        assert.deepEqual(_optimize(topic), [
+          [['margin', false , false], ['10px'], ['5px'], ['3px'], ['2px']]
+        ]);
+      }
+    },
+    'shorthand padding': {
+      'topic': 'a{padding-top:10px;padding-left:5px;padding-bottom:3px;padding-right:2px}',
+      'into': function (topic) {
+        assert.deepEqual(_optimize(topic), [
+          [['padding', false , false], ['10px'], ['2px'], ['3px'], ['5px']]
+        ]);
+      }
+    },
+    'mixed': {
+      'topic': 'a{padding-top:10px;margin-top:3px;padding-left:5px;margin-left:3px;padding-bottom:3px;margin-bottom:3px;padding-right:2px;margin-right:3px}',
+      'into': function (topic) {
+        assert.deepEqual(_optimize(topic), [
+          [['padding', false , false], ['10px'], ['2px'], ['3px'], ['5px']],
+          [['margin', false , false], ['3px']]
+        ]);
+      }
+    },
+    'with other properties': {
+      'topic': 'a{padding-top:10px;padding-left:5px;padding-bottom:3px;color:red;padding-right:2px;width:100px}',
+      'into': function (topic) {
+        assert.deepEqual(_optimize(topic), [
+          [['color', false , false], ['red']],
+          [['width', false , false], ['100px']],
+          [['padding', false , false], ['10px'], ['2px'], ['3px'], ['5px']]
+        ]);
+      }
+    }
+  })
+  .addBatch({
+    'not enough components': {
+      'topic': 'a{padding-top:10px;padding-left:5px;padding-bottom:3px}',
+      'into': function (topic) {
+        assert.deepEqual(_optimize(topic), [
+          [['padding-top', false , false], ['10px']],
+          [['padding-left', false , false], ['5px']],
+          [['padding-bottom', false , false], ['3px']]
+        ]);
+      }
+    },
+    'mixed importance': {
+      'topic': 'a{padding-top:10px;padding-left:5px;padding-bottom:3px;padding-right:2px!important}',
+      'into': function (topic) {
+        assert.deepEqual(_optimize(topic), [
+          [['padding-top', false , false], ['10px']],
+          [['padding-left', false , false], ['5px']],
+          [['padding-bottom', false , false], ['3px']],
+          [['padding-right', true , false], ['2px']]
+        ]);
+      }
+    },
+    'mixed understandability of units': {
+      'topic': 'a{padding-top:10px;padding-left:5px;padding-bottom:3px;padding-right:calc(100% - 20px)}',
+      'into': function (topic) {
+        assert.deepEqual(_optimize(topic), [
+          [['padding-top', false , false], ['10px']],
+          [['padding-left', false , false], ['5px']],
+          [['padding-bottom', false , false], ['3px']],
+          [['padding-right', false , false], ['calc(100% - 20px)']]
+        ]);
+      }
+    },
+    'mixed understandability of images': {
+      'topic': 'p{background-color:#111;background-image:linear-gradient(sth);background-repeat:repeat;background-position:0 0;background-attachment:scroll;background-size:auto;background-origin:padding-box;background-clip:border-box}',
+      'into': function (topic) {
+        assert.deepEqual(_optimize(topic), [
+          [['background-color', false , false], ['#111']],
+          [['background-image', false , false], ['linear-gradient(sth)']],
+          [['background-repeat', false , false], ['repeat']],
+          [['background-position', false , false], ['0'], ['0']],
+          [['background-attachment', false , false], ['scroll']],
+          [['background-size', false , false], ['auto']],
+          [['background-origin', false , false], ['padding-box']],
+          [['background-clip', false , false], ['border-box']]
+        ]);
+      }
+    }
+  })
+  .export(module);