Extracts 'reduce non adjacent' optimization into a module.
authorJakub Pawlowicz <contact@jakubpawlowicz.com>
Sun, 21 Jun 2015 11:09:06 +0000 (12:09 +0100)
committerJakub Pawlowicz <contact@jakubpawlowicz.com>
Sun, 21 Jun 2015 11:09:06 +0000 (12:09 +0100)
lib/selectors/advanced.js
lib/selectors/reduce-non-adjacent.js [new file with mode: 0644]
test/integration-test.js
test/selectors/advanced-test.js
test/selectors/reduce-non-adjacent-test.js [new file with mode: 0644]

index ef6c4e7..cdfa961 100644 (file)
@@ -10,6 +10,7 @@ var stringifySelectors = require('../stringifier/one-time').selectors;
 
 var removeDuplicates = require('./remove-duplicates');
 var mergeAdjacent = require('./merge-adjacent');
+var reduceNonAdjacent = require('./reduce-non-adjacent');
 
 function AdvancedOptimizer(options, context) {
   this.options = options;
@@ -28,159 +29,6 @@ AdvancedOptimizer.prototype.isSpecial = function (selector) {
   return this.options.compatibility.selectors.special.test(selector);
 };
 
-AdvancedOptimizer.prototype.reduceNonAdjacent = function (tokens) {
-  var candidates = {};
-  var repeated = [];
-
-  for (var i = tokens.length - 1; i >= 0; i--) {
-    var token = tokens[i];
-
-    if (token[0] != 'selector')
-      continue;
-    if (token[2].length === 0)
-      continue;
-
-    var selectorAsString = stringifySelectors(token[1]);
-    var isComplexAndNotSpecial = token[1].length > 1 && !this.isSpecial(selectorAsString);
-    var selectors = isComplexAndNotSpecial ?
-      [selectorAsString].concat(token[1]) :
-      [selectorAsString];
-
-    for (var j = 0, m = selectors.length; j < m; j++) {
-      var selector = selectors[j];
-
-      if (!candidates[selector])
-        candidates[selector] = [];
-      else
-        repeated.push(selector);
-
-      candidates[selector].push({
-        where: i,
-        list: token[1],
-        isPartial: isComplexAndNotSpecial && j > 0,
-        isComplex: isComplexAndNotSpecial && j === 0
-      });
-    }
-  }
-
-  this.reduceSimpleNonAdjacentCases(tokens, repeated, candidates);
-  this.reduceComplexNonAdjacentCases(tokens, candidates);
-};
-
-AdvancedOptimizer.prototype.reduceSimpleNonAdjacentCases = function (tokens, repeated, candidates) {
-  function filterOut(idx, bodies) {
-    return data[idx].isPartial && bodies.length === 0;
-  }
-
-  function reduceBody(token, newBody, processedCount, tokenIdx) {
-    if (!data[processedCount - tokenIdx - 1].isPartial)
-      token[2] = newBody;
-  }
-
-  for (var i = 0, l = repeated.length; i < l; i++) {
-    var selector = repeated[i];
-    var data = candidates[selector];
-
-    this.reduceSelector(tokens, selector, data, {
-      filterOut: filterOut,
-      callback: reduceBody
-    });
-  }
-};
-
-AdvancedOptimizer.prototype.reduceComplexNonAdjacentCases = function (tokens, candidates) {
-  var localContext = {};
-
-  function filterOut(idx) {
-    return localContext.data[idx].where < localContext.intoPosition;
-  }
-
-  function collectReducedBodies(token, newBody, processedCount, tokenIdx) {
-    if (tokenIdx === 0)
-      localContext.reducedBodies.push(newBody);
-  }
-
-  allSelectors:
-  for (var complexSelector in candidates) {
-    var into = candidates[complexSelector];
-    if (!into[0].isComplex)
-      continue;
-
-    var intoPosition = into[into.length - 1].where;
-    var intoToken = tokens[intoPosition];
-    var reducedBodies = [];
-
-    var selectors = this.isSpecial(complexSelector) ?
-      [complexSelector] :
-      into[0].list;
-
-    localContext.intoPosition = intoPosition;
-    localContext.reducedBodies = reducedBodies;
-
-    for (var j = 0, m = selectors.length; j < m; j++) {
-      var selector = selectors[j];
-      var data = candidates[selector];
-
-      if (data.length < 2)
-        continue allSelectors;
-
-      localContext.data = data;
-
-      this.reduceSelector(tokens, selector, data, {
-        filterOut: filterOut,
-        callback: collectReducedBodies
-      });
-
-      if (stringifyBody(reducedBodies[reducedBodies.length - 1]) != stringifyBody(reducedBodies[0]))
-        continue allSelectors;
-    }
-
-    intoToken[2] = reducedBodies[0];
-  }
-};
-
-AdvancedOptimizer.prototype.reduceSelector = function (tokens, selector, data, options) {
-  var bodies = [];
-  var bodiesAsList = [];
-  var joinsAt = [];
-  var processedTokens = [];
-
-  for (var j = data.length - 1, m = 0; j >= 0; j--) {
-    if (options.filterOut(j, bodies))
-      continue;
-
-    var where = data[j].where;
-    var token = tokens[where];
-
-    bodies = bodies.concat(token[2]);
-    bodiesAsList.push(token[2]);
-    processedTokens.push(where);
-  }
-
-  for (j = 0, m = bodiesAsList.length; j < m; j++) {
-    if (bodiesAsList[j].length > 0)
-      joinsAt.push((joinsAt[j - 1] || 0) + bodiesAsList[j].length);
-  }
-
-  optimizeProperties(selector, bodies, joinsAt, false, this.options, this.validator);
-
-  var processedCount = processedTokens.length;
-  var propertyIdx = bodies.length - 1;
-  var tokenIdx = processedCount - 1;
-
-  while (tokenIdx >= 0) {
-     if ((tokenIdx === 0 || (bodies[propertyIdx] && bodiesAsList[tokenIdx].indexOf(bodies[propertyIdx]) > -1)) && propertyIdx > -1) {
-      propertyIdx--;
-      continue;
-    }
-
-    var newBody = bodies.splice(propertyIdx + 1);
-    options.callback(tokens[processedTokens[tokenIdx]], newBody, processedCount, tokenIdx);
-
-    tokenIdx--;
-  }
-};
-
 AdvancedOptimizer.prototype.mergeNonAdjacentBySelector = function (tokens) {
   var allSelectors = {};
   var repeatedSelectors = [];
@@ -754,7 +602,7 @@ AdvancedOptimizer.prototype.optimize = function (tokens) {
 
     removeDuplicates(tokens);
     mergeAdjacent(tokens, self.options, self.validator);
-    self.reduceNonAdjacent(tokens);
+    reduceNonAdjacent(tokens, self.options, self.validator);
 
     self.mergeNonAdjacentBySelector(tokens);
     self.mergeNonAdjacentByBody(tokens);
diff --git a/lib/selectors/reduce-non-adjacent.js b/lib/selectors/reduce-non-adjacent.js
new file mode 100644 (file)
index 0000000..c3d7efb
--- /dev/null
@@ -0,0 +1,159 @@
+var optimizeProperties = require('../properties/optimizer');
+var stringifyBody = require('../stringifier/one-time').body;
+var stringifySelectors = require('../stringifier/one-time').selectors;
+var isSpecial = require('./is-special');
+
+function reduceNonAdjacent(tokens, options, validator) {
+  var candidates = {};
+  var repeated = [];
+
+  for (var i = tokens.length - 1; i >= 0; i--) {
+    var token = tokens[i];
+
+    if (token[0] != 'selector')
+      continue;
+    if (token[2].length === 0)
+      continue;
+
+    var selectorAsString = stringifySelectors(token[1]);
+    var isComplexAndNotSpecial = token[1].length > 1 && !isSpecial(options, selectorAsString);
+    var selectors = isComplexAndNotSpecial ?
+      [selectorAsString].concat(token[1]) :
+      [selectorAsString];
+
+    for (var j = 0, m = selectors.length; j < m; j++) {
+      var selector = selectors[j];
+
+      if (!candidates[selector])
+        candidates[selector] = [];
+      else
+        repeated.push(selector);
+
+      candidates[selector].push({
+        where: i,
+        list: token[1],
+        isPartial: isComplexAndNotSpecial && j > 0,
+        isComplex: isComplexAndNotSpecial && j === 0
+      });
+    }
+  }
+
+  reduceSimpleNonAdjacentCases(tokens, repeated, candidates, options, validator);
+  reduceComplexNonAdjacentCases(tokens, candidates, options, validator);
+}
+
+function reduceSimpleNonAdjacentCases(tokens, repeated, candidates, options, validator) {
+  function filterOut(idx, bodies) {
+    return data[idx].isPartial && bodies.length === 0;
+  }
+
+  function reduceBody(token, newBody, processedCount, tokenIdx) {
+    if (!data[processedCount - tokenIdx - 1].isPartial)
+      token[2] = newBody;
+  }
+
+  for (var i = 0, l = repeated.length; i < l; i++) {
+    var selector = repeated[i];
+    var data = candidates[selector];
+
+    reduceSelector(tokens, selector, data, {
+      filterOut: filterOut,
+      callback: reduceBody
+    }, options, validator);
+  }
+}
+
+function reduceComplexNonAdjacentCases(tokens, candidates, options, validator) {
+  var localContext = {};
+
+  function filterOut(idx) {
+    return localContext.data[idx].where < localContext.intoPosition;
+  }
+
+  function collectReducedBodies(token, newBody, processedCount, tokenIdx) {
+    if (tokenIdx === 0)
+      localContext.reducedBodies.push(newBody);
+  }
+
+  allSelectors:
+  for (var complexSelector in candidates) {
+    var into = candidates[complexSelector];
+    if (!into[0].isComplex)
+      continue;
+
+    var intoPosition = into[into.length - 1].where;
+    var intoToken = tokens[intoPosition];
+    var reducedBodies = [];
+
+    var selectors = isSpecial(options, complexSelector) ?
+      [complexSelector] :
+      into[0].list;
+
+    localContext.intoPosition = intoPosition;
+    localContext.reducedBodies = reducedBodies;
+
+    for (var j = 0, m = selectors.length; j < m; j++) {
+      var selector = selectors[j];
+      var data = candidates[selector];
+
+      if (data.length < 2)
+        continue allSelectors;
+
+      localContext.data = data;
+
+      reduceSelector(tokens, selector, data, {
+        filterOut: filterOut,
+        callback: collectReducedBodies
+      }, options, validator);
+
+      if (stringifyBody(reducedBodies[reducedBodies.length - 1]) != stringifyBody(reducedBodies[0]))
+        continue allSelectors;
+    }
+
+    intoToken[2] = reducedBodies[0];
+  }
+}
+
+function reduceSelector(tokens, selector, data, context, options, validator) {
+  var bodies = [];
+  var bodiesAsList = [];
+  var joinsAt = [];
+  var processedTokens = [];
+
+  for (var j = data.length - 1, m = 0; j >= 0; j--) {
+    if (context.filterOut(j, bodies))
+      continue;
+
+    var where = data[j].where;
+    var token = tokens[where];
+
+    bodies = bodies.concat(token[2]);
+    bodiesAsList.push(token[2]);
+    processedTokens.push(where);
+  }
+
+  for (j = 0, m = bodiesAsList.length; j < m; j++) {
+    if (bodiesAsList[j].length > 0)
+      joinsAt.push((joinsAt[j - 1] || 0) + bodiesAsList[j].length);
+  }
+
+  optimizeProperties(selector, bodies, joinsAt, false, options, validator);
+
+  var processedCount = processedTokens.length;
+  var propertyIdx = bodies.length - 1;
+  var tokenIdx = processedCount - 1;
+
+  while (tokenIdx >= 0) {
+     if ((tokenIdx === 0 || (bodies[propertyIdx] && bodiesAsList[tokenIdx].indexOf(bodies[propertyIdx]) > -1)) && propertyIdx > -1) {
+      propertyIdx--;
+      continue;
+    }
+
+    var newBody = bodies.splice(propertyIdx + 1);
+    context.callback(tokens[processedTokens[tokenIdx]], newBody, processedCount, tokenIdx);
+
+    tokenIdx--;
+  }
+}
+
+module.exports = reduceNonAdjacent;
index eb8487d..1a12fed 100644 (file)
@@ -2158,98 +2158,6 @@ vows.describe('integration tests')
       ]
     }, { aggressiveMerging: false })
   )
-  .addBatch(
-    optimizerContext('same non-adjacent selectors', {
-      'with one redefined property': [
-        'a{color:red;display:block}.one{color:red}a{color:#fff;margin:2px}',
-        '.one{color:red}a{display:block;color:#fff;margin:2px}'
-      ],
-      'with intentionally redefined properties on joins': [
-        'a{display:inline-block;display:-moz-inline-box;color:red}.one{margin:12px}a{color:#fff;margin:2px}',
-        '.one{margin:12px}a{display:inline-block;display:-moz-inline-box;color:#fff;margin:2px}'
-      ],
-      'with intentionally redefined properties on multiple joins': [
-        'a{color:red}.one{font-size:12px}a{color:#fff;margin:2px}.two{margin:10px}a{margin:0}',
-        '.one{font-size:12px}.two{margin:10px}a{color:#fff;margin:0}'
-      ],
-      'with all redefined properties': [
-        'a{color:red;display:block}.one{font-size:12px}a{color:#fff;display:inline-block;margin:2px}',
-        '.one{font-size:12px}a{color:#fff;display:inline-block;margin:2px}'
-      ],
-      'many with all redefined properties': [
-        'a{padding:10px}.zero{color:transparent}a{color:red;display:block}.one{font-size:12px}a{color:#fff;display:inline-block;margin:2px}',
-        '.zero{color:transparent}.one{font-size:12px}a{padding:10px;color:#fff;display:inline-block;margin:2px}'
-      ],
-      'when overriden by an empty selector': [
-        'a{padding:10px}.one{color:red}a{}',
-        'a{padding:10px}.one{color:red}'
-      ],
-      'when overriden by a complex selector': [
-        'a{padding:10px;margin:0;color:red}.one{color:red}a,p{color:red;padding:0}',
-        '.one,a,p{color:red}a{margin:0}a,p{padding:0}'
-      ],
-      'when overriden by complex selectors': [
-        'a{padding:10px;margin:0;color:red}.one{color:red}a,p{color:red;padding:0}.one,a{color:#fff}',
-        'a{margin:0}a,p{color:red;padding:0}.one,a{color:#fff}'
-      ],
-      'when complex selector overriden by simple selectors': [
-        'a,p{margin:0;color:red}a{color:#fff}',
-        'a,p{margin:0;color:red}a{color:#fff}'
-      ],
-      'when complex selector overriden by complex and simple selectors': [
-        'a,p{margin:0;color:red}a{color:#fff}a,p{color:#00f}p{color:#0f0}',
-        'a,p{margin:0;color:#00f}p{color:#0f0}'
-      ],
-      'when complex selector overriden by complex selectors': [
-        '.one>.two,.three{color:red;line-height:1rem}#zero,.one>.two,.three,.www{color:#fff;margin:0}a{color:red}.one>.two,.three{line-height:2rem;font-size:1.5rem}',
-        '#zero,.one>.two,.three,.www{color:#fff;margin:0}a{color:red}.one>.two,.three{line-height:2rem;font-size:1.5rem}'
-      ],
-      'when undefined is used as a value': [
-        '.one{text-shadow:undefined}p{font-size:14px}.one{font-size:12px}',
-        'p{font-size:14px}.one{text-shadow:undefined;font-size:12px}'
-      ],
-      'when undefined is used as a value with reduction': [
-        '.one{text-shadow:undefined}p{color:red}.one{font-size:12px;text-shadow:none}',
-        'p{color:red}.one{font-size:12px;text-shadow:none}'
-      ],
-      'when overriden with a browser specific selector': [
-        'a{color:red}p{display:block}::-moz-selection,a{color:#fff}',
-        'a{color:red}p{display:block}::-moz-selection,a{color:#fff}'
-      ],
-      'when same browser specific selector more than once': [
-        'a,::-moz-selection{color:red}p{display:block}a,::-moz-selection{color:#fff}',
-        'p{display:block}::-moz-selection,a{color:#fff}'
-      ],
-      'with full property comparison': [
-        '.one{height:7rem}.two{color:#fff}.one{line-height:7rem;color:red}',
-        '.two{color:#fff}.one{height:7rem;line-height:7rem;color:red}'
-      ],
-      'with two intermediate, non-overriding selectors': [
-        '.one{color:red;margin:0}.two{color:#fff}.one{font-size:12px}',
-        '.one{color:red;margin:0;font-size:12px}.two{color:#fff}'
-      ],
-      'with two intermediate, overriding more specific selectors': [
-        '.one{color:red;margin:0}.two{font:12px serif}.one{font-size:12px}',
-        '.two{font:12px serif}.one{color:red;margin:0;font-size:12px}'
-      ],
-      'with granular selectors from the same shorthand': [
-        '.one{color:red;margin:0}.two{font-weight:700}.one{font-size:12px}',
-        '.one{color:red;margin:0;font-size:12px}.two{font-weight:700}'
-      ],
-      'with three intermediate, non-overriding selectors': [
-        '.one{color:red;margin:0}.two{color:#fff}.one{font-size:12px}.three{color:#000}.one{padding:0}',
-        '.one{color:red;margin:0;font-size:12px;padding:0}.two{color:#fff}.three{color:#000}'
-      ],
-      'successive selectors': [
-        'footer,header{top:1.25em;bottom:1.25em}header{top:2.5em}footer{bottom:2.5em}',
-        'footer,header{top:1.25em;bottom:1.25em}header{top:2.5em}footer{bottom:2.5em}'
-      ],
-      'over a @media block': [
-        '.one{color:red;margin:0}@media{.two{font-weight:700}}.one{font-size:12px}',
-        '.one{color:red;margin:0;font-size:12px}@media{.two{font-weight:700}}'
-      ]
-    })
-  )
   .addBatch(
     optimizerContext('rerun optimizers', {
       'selectors reducible once': [
index f47b11c..0ad9960 100644 (file)
@@ -208,14 +208,6 @@ vows.describe('advanced optimizer')
       'repeated' : [
         'a{color:red;color:red}',
         'a{color:red}'
-      ],
-      'non-adjacent': [
-        'a{color:red;display:block}.one{margin:12px}a{color:#fff;margin:2px}',
-        '.one{margin:12px}a{display:block;color:#fff;margin:2px}'
-      ],
-      'non-adjacent with multi selectors': [
-        'a{padding:10px;margin:0;color:red}.one{color:red}a,p{color:red;padding:0}',
-        '.one,a,p{color:red}a{margin:0}a,p{padding:0}'
       ]
     }, { advanced: true, aggressiveMerging: true })
   )
@@ -232,10 +224,6 @@ vows.describe('advanced optimizer')
       'repeated' : [
         'a{color:red;color:red}',
         'a{color:red}'
-      ],
-      'non-adjacent with multi selectors': [
-        'a{padding:10px;margin:0;color:red}.one{color:red}a,p{color:red;padding:0}',
-        '.one,a,p{color:red}a{padding:10px;margin:0}a,p{padding:0}'
       ]
     }, { advanced: true, aggressiveMerging: false })
   )
@@ -244,10 +232,6 @@ vows.describe('advanced optimizer')
       'repeated' : [
         'a{color:red;color:red}',
         'a{color:red;color:red}'
-      ],
-      'non-adjacent': [
-        'a{color:red;display:block}.one{font-size:12px}a{color:#fff;margin:2px}',
-        'a{color:red;display:block}.one{font-size:12px}a{color:#fff;margin:2px}'
       ]
     }, { advanced: false })
   )
diff --git a/test/selectors/reduce-non-adjacent-test.js b/test/selectors/reduce-non-adjacent-test.js
new file mode 100644 (file)
index 0000000..848e343
--- /dev/null
@@ -0,0 +1,121 @@
+var vows = require('vows');
+var optimizerContext = require('../test-helper').optimizerContext;
+
+vows.describe('remove duplicates')
+  .addBatch(
+    optimizerContext('advanced on', {
+      'single selectors': [
+        'a{color:red;display:block}.one{margin:12px}a{color:#fff;margin:2px}',
+        '.one{margin:12px}a{display:block;color:#fff;margin:2px}'
+      ],
+      'multiple selectors': [
+        'a{padding:10px;margin:0;color:red}.one{color:red}a,p{color:red;padding:0}',
+        '.one,a,p{color:red}a{margin:0}a,p{padding:0}'
+      ],
+      'with one redefined property': [
+        'a{color:red;display:block}.one{color:red}a{color:#fff;margin:2px}',
+        '.one{color:red}a{display:block;color:#fff;margin:2px}'
+      ],
+      'with intentionally redefined properties on joins': [
+        'a{display:inline-block;display:-moz-inline-box;color:red}.one{margin:12px}a{color:#fff;margin:2px}',
+        '.one{margin:12px}a{display:inline-block;display:-moz-inline-box;color:#fff;margin:2px}'
+      ],
+      'with intentionally redefined properties on multiple joins': [
+        'a{color:red}.one{font-size:12px}a{color:#fff;margin:2px}.two{margin:10px}a{margin:0}',
+        '.one{font-size:12px}.two{margin:10px}a{color:#fff;margin:0}'
+      ],
+      'with all redefined properties': [
+        'a{color:red;display:block}.one{font-size:12px}a{color:#fff;display:inline-block;margin:2px}',
+        '.one{font-size:12px}a{color:#fff;display:inline-block;margin:2px}'
+      ],
+      'many with all redefined properties': [
+        'a{padding:10px}.zero{color:transparent}a{color:red;display:block}.one{font-size:12px}a{color:#fff;display:inline-block;margin:2px}',
+        '.zero{color:transparent}.one{font-size:12px}a{padding:10px;color:#fff;display:inline-block;margin:2px}'
+      ],
+      'when overriden by an empty selector': [
+        'a{padding:10px}.one{color:red}a{}',
+        'a{padding:10px}.one{color:red}'
+      ],
+      'when overriden by a complex selector': [
+        'a{padding:10px;margin:0;color:red}.one{color:red}a,p{color:red;padding:0}',
+        '.one,a,p{color:red}a{margin:0}a,p{padding:0}'
+      ],
+      'when overriden by complex selectors': [
+        'a{padding:10px;margin:0;color:red}.one{color:red}a,p{color:red;padding:0}.one,a{color:#fff}',
+        'a{margin:0}a,p{color:red;padding:0}.one,a{color:#fff}'
+      ],
+      'when complex selector overriden by simple selectors': [
+        'a,p{margin:0;color:red}a{color:#fff}',
+        'a,p{margin:0;color:red}a{color:#fff}'
+      ],
+      'when complex selector overriden by complex and simple selectors': [
+        'a,p{margin:0;color:red}a{color:#fff}a,p{color:#00f}p{color:#0f0}',
+        'a,p{margin:0;color:#00f}p{color:#0f0}'
+      ],
+      'when complex selector overriden by complex selectors': [
+        '.one>.two,.three{color:red;line-height:1rem}#zero,.one>.two,.three,.www{color:#fff;margin:0}a{color:red}.one>.two,.three{line-height:2rem;font-size:1.5rem}',
+        '#zero,.one>.two,.three,.www{color:#fff;margin:0}a{color:red}.one>.two,.three{line-height:2rem;font-size:1.5rem}'
+      ],
+      'when undefined is used as a value': [
+        '.one{text-shadow:undefined}p{font-size:14px}.one{font-size:12px}',
+        'p{font-size:14px}.one{text-shadow:undefined;font-size:12px}'
+      ],
+      'when undefined is used as a value with reduction': [
+        '.one{text-shadow:undefined}p{color:red}.one{font-size:12px;text-shadow:none}',
+        'p{color:red}.one{font-size:12px;text-shadow:none}'
+      ],
+      'when overriden with a browser specific selector': [
+        'a{color:red}p{display:block}::-moz-selection,a{color:#fff}',
+        'a{color:red}p{display:block}::-moz-selection,a{color:#fff}'
+      ],
+      'when same browser specific selector more than once': [
+        'a,::-moz-selection{color:red}p{display:block}a,::-moz-selection{color:#fff}',
+        'p{display:block}::-moz-selection,a{color:#fff}'
+      ],
+      'with full property comparison': [
+        '.one{height:7rem}.two{color:#fff}.one{line-height:7rem;color:red}',
+        '.two{color:#fff}.one{height:7rem;line-height:7rem;color:red}'
+      ],
+      'with two intermediate, non-overriding selectors': [
+        '.one{color:red;margin:0}.two{color:#fff}.one{font-size:12px}',
+        '.one{color:red;margin:0;font-size:12px}.two{color:#fff}'
+      ],
+      'with two intermediate, overriding more specific selectors': [
+        '.one{color:red;margin:0}.two{font:12px serif}.one{font-size:12px}',
+        '.two{font:12px serif}.one{color:red;margin:0;font-size:12px}'
+      ],
+      'with granular selectors from the same shorthand': [
+        '.one{color:red;margin:0}.two{font-weight:700}.one{font-size:12px}',
+        '.one{color:red;margin:0;font-size:12px}.two{font-weight:700}'
+      ],
+      'with three intermediate, non-overriding selectors': [
+        '.one{color:red;margin:0}.two{color:#fff}.one{font-size:12px}.three{color:#000}.one{padding:0}',
+        '.one{color:red;margin:0;font-size:12px;padding:0}.two{color:#fff}.three{color:#000}'
+      ],
+      'successive selectors': [
+        'footer,header{top:1.25em;bottom:1.25em}header{top:2.5em}footer{bottom:2.5em}',
+        'footer,header{top:1.25em;bottom:1.25em}header{top:2.5em}footer{bottom:2.5em}'
+      ],
+      'over a @media block': [
+        '.one{color:red;margin:0}@media{.two{font-weight:700}}.one{font-size:12px}',
+        '.one{color:red;margin:0;font-size:12px}@media{.two{font-weight:700}}'
+      ]
+    })
+  )
+  .addBatch(
+    optimizerContext('advanced on and aggressive merging off', {
+      'non-adjacent with multi selectors': [
+        'a{padding:10px;margin:0;color:red}.one{color:red}a,p{color:red;padding:0}',
+        '.one,a,p{color:red}a{padding:10px;margin:0}a,p{padding:0}'
+      ]
+    }, { aggressiveMerging: false })
+  )
+  .addBatch(
+    optimizerContext('advanced off', {
+      'non-adjacent': [
+        'a{color:red;display:block}.one{font-size:12px}a{color:#fff;margin:2px}',
+        'a{color:red;display:block}.one{font-size:12px}a{color:#fff;margin:2px}'
+      ]
+    }, { advanced: false })
+  )
+  .export(module);