From 0b4ea6bb6de933ee10876ba0fe8543c3e8bc60ce Mon Sep 17 00:00:00 2001 From: Jakub Pawlowicz Date: Thu, 16 Feb 2017 16:44:02 +0100 Subject: [PATCH] Fixes #886 - better multi pseudo class / element merging. Why: * 4.0 introduced a stricter mergeability rules, however in some cases merging is still possible but harder to determine; * tests suggested all multi pseudo class / element rules can be merged unless those contain more than one of the following: `:after`, `::after`, `:before`, `::before`, `:first-letter`, `::first-letter`, `:first-line`, `::first-line`, or `:lang`; * there's also a new compatibility switch, `{ compatibility: { selectors: { multiplePseudoMerging: false } } }` which, if set to false, disables merging for all rules with multiple pseudo classes / elements. --- History.md | 1 + README.md | 4 +- lib/optimizer/level-2/is-mergeable.js | 41 ++++++-- lib/optimizer/level-2/merge-adjacent.js | 5 +- .../level-2/merge-non-adjacent-by-body.js | 5 +- lib/optimizer/level-2/reduce-non-adjacent.js | 6 +- lib/optimizer/level-2/restructure.js | 3 +- lib/options/compatibility.js | 3 +- test/optimizer/level-2/is-mergeable-test.js | 94 +++++++++++++------ test/optimizer/level-2/merge-adjacent-test.js | 12 +++ test/options/compatibility-test.js | 7 ++ 11 files changed, 132 insertions(+), 49 deletions(-) diff --git a/History.md b/History.md index d6d3f330..138fd3f4 100644 --- a/History.md +++ b/History.md @@ -3,6 +3,7 @@ * Fixed issue [#893](https://github.com/jakubpawlowicz/clean-css/issues/893) - `inline: false` as alias to `inline: 'none'`. * Fixed issue [#890](https://github.com/jakubpawlowicz/clean-css/issues/890) - adds toggle to disable empty tokens removal. +* Fixed issue [#886](https://github.com/jakubpawlowicz/clean-css/issues/886) - better multi pseudo class / element merging. [4.0.7 / 2017-02-14](https://github.com/jakubpawlowicz/clean-css/compare/v4.0.6...v4.0.7) ================== diff --git a/README.md b/README.md index 4c23288d..d69d18de 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,7 @@ clean-css 4.0 introduces some breaking changes: Once released clean-css 4.1 will introduce the following changes / features: * `inline: false` as an alias to `inline: ['none']`; +* `multiplePseudoMerging` compatibility flag controlling merging of rules with multiple pseudo classes / elements; * `removeEmpty` flag in level 1 optimizations controlling removal of rules and nested blocks; * `removeEmpty` flag in level 2 optimizations controlling removal of rules and nested blocks; @@ -151,7 +152,8 @@ new CleanCSS({ adjacentSpace: false, // controls extra space before `nav` element ie7Hack: true, // controls removal of IE7 selector hacks, e.g. `*+html...` mergeablePseudoClasses: [':active', ...], // controls a whitelist of mergeable pseudo classes - mergeablePseudoElements: ['::after', ...] // controls a whitelist of mergeable pseudo elements + mergeablePseudoElements: ['::after', ...], // controls a whitelist of mergeable pseudo elements + multiplePseudoMerging: true // controls merging of rules with multiple pseudo classes / elements (since 4.1.0-pre) }, units: { ch: true, // controls treating `ch` as a supported unit diff --git a/lib/optimizer/level-2/is-mergeable.js b/lib/optimizer/level-2/is-mergeable.js index 4a91a833..29049302 100644 --- a/lib/optimizer/level-2/is-mergeable.js +++ b/lib/optimizer/level-2/is-mergeable.js @@ -14,6 +14,19 @@ var PSEUDO_CLASSES_WITH_ARGUMENTS = [ ':nth-of-type' ]; var RELATION_PATTERN = /[>\+~]/; +var UNMIXABLE_PSEUDO_CLASSES = [ + ':after', + ':before', + ':first-letter', + ':first-line', + ':lang' +]; +var UNMIXABLE_PSEUDO_ELEMENTS = [ + '::after', + '::before', + '::first-letter', + '::first-line' +]; var Level = { DOUBLE_QUOTE: 'double-quote', @@ -21,7 +34,7 @@ var Level = { ROOT: 'root' }; -function isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements) { +function isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements, multiplePseudoMerging) { var singleSelectors = split(selector, Marker.COMMA); var singleSelector; var i, l; @@ -31,7 +44,7 @@ function isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements) if (singleSelector.length === 0 || isDeepSelector(singleSelector) || - (singleSelector.indexOf(Marker.COLON) > -1 && !areMergeable(singleSelector, extractPseudoFrom(singleSelector), mergeablePseudoClasses, mergeablePseudoElements))) { + (singleSelector.indexOf(Marker.COLON) > -1 && !areMergeable(singleSelector, extractPseudoFrom(singleSelector), mergeablePseudoClasses, mergeablePseudoElements, multiplePseudoMerging))) { return false; } } @@ -122,11 +135,11 @@ function extractPseudoFrom(selector) { return list; } -function areMergeable(selector, matches, mergeablePseudoClasses, mergeablePseudoElements) { +function areMergeable(selector, matches, mergeablePseudoClasses, mergeablePseudoElements, multiplePseudoMerging) { return areAllowed(matches, mergeablePseudoClasses, mergeablePseudoElements) && needArguments(matches) && (matches.length < 2 || !someIncorrectlyChained(selector, matches)) && - (matches.length < 2 || !someMixed(matches)); + (matches.length < 2 || multiplePseudoMerging && allMixable(matches)); } function areAllowed(matches, mergeablePseudoClasses, mergeablePseudoElements) { @@ -217,20 +230,30 @@ function someIncorrectlyChained(selector, matches) { return false; } -function someMixed(matches) { - var firstIsPseudoElement = DOUBLE_COLON_PATTERN.test(matches[0]); +function allMixable(matches) { + var unmixableMatches = 0; var match; var i, l; for (i = 0, l = matches.length; i < l; i++) { match = matches[i]; - if (DOUBLE_COLON_PATTERN.test(match) != firstIsPseudoElement) { - return true; + if (isPseudoElement(match)) { + unmixableMatches += UNMIXABLE_PSEUDO_ELEMENTS.indexOf(match) > -1 ? 1 : 0; + } else { + unmixableMatches += UNMIXABLE_PSEUDO_CLASSES.indexOf(match) > -1 ? 1 : 0; + } + + if (unmixableMatches > 1) { + return false; } } - return false; + return true; +} + +function isPseudoElement(pseudo) { + return DOUBLE_COLON_PATTERN.test(pseudo); } module.exports = isMergeable; diff --git a/lib/optimizer/level-2/merge-adjacent.js b/lib/optimizer/level-2/merge-adjacent.js index a89a0ee3..70f92e9d 100644 --- a/lib/optimizer/level-2/merge-adjacent.js +++ b/lib/optimizer/level-2/merge-adjacent.js @@ -19,6 +19,7 @@ function mergeAdjacent(tokens, context) { var selectorsSortingMethod = options.level[OptimizationLevel.One].selectorsSortingMethod; var mergeablePseudoClasses = options.compatibility.selectors.mergeablePseudoClasses; var mergeablePseudoElements = options.compatibility.selectors.mergeablePseudoElements; + var multiplePseudoMerging = options.compatibility.selectors.multiplePseudoMerging; for (var i = 0, l = tokens.length; i < l; i++) { var token = tokens[i]; @@ -33,8 +34,8 @@ function mergeAdjacent(tokens, context) { optimizeProperties(lastToken[2], true, true, context); token[2] = []; } else if (lastToken[0] == Token.RULE && serializeBody(token[2]) == serializeBody(lastToken[2]) && - isMergeable(serializeRules(token[1]), mergeablePseudoClasses, mergeablePseudoElements) && - isMergeable(serializeRules(lastToken[1]), mergeablePseudoClasses, mergeablePseudoElements)) { + isMergeable(serializeRules(token[1]), mergeablePseudoClasses, mergeablePseudoElements, multiplePseudoMerging) && + isMergeable(serializeRules(lastToken[1]), mergeablePseudoClasses, mergeablePseudoElements, multiplePseudoMerging)) { lastToken[1] = tidyRules(lastToken[1].concat(token[1]), false, adjacentSpace, false, context.warnings); lastToken[1] = lastToken.length > 1 ? sortSelectors(lastToken[1], selectorsSortingMethod) : lastToken[1]; token[2] = []; diff --git a/lib/optimizer/level-2/merge-non-adjacent-by-body.js b/lib/optimizer/level-2/merge-non-adjacent-by-body.js index bdcf53ec..82db950f 100644 --- a/lib/optimizer/level-2/merge-non-adjacent-by-body.js +++ b/lib/optimizer/level-2/merge-non-adjacent-by-body.js @@ -42,6 +42,7 @@ function mergeNonAdjacentByBody(tokens, context) { var selectorsSortingMethod = options.level[OptimizationLevel.One].selectorsSortingMethod; var mergeablePseudoClasses = options.compatibility.selectors.mergeablePseudoClasses; var mergeablePseudoElements = options.compatibility.selectors.mergeablePseudoElements; + var multiplePseudoMerging = options.compatibility.selectors.multiplePseudoMerging; var candidates = {}; for (var i = tokens.length - 1; i >= 0; i--) { @@ -58,8 +59,8 @@ function mergeNonAdjacentByBody(tokens, context) { var candidateBody = serializeBody(token[2]); var oldToken = candidates[candidateBody]; if (oldToken && - isMergeable(serializeRules(token[1]), mergeablePseudoClasses, mergeablePseudoElements) && - isMergeable(serializeRules(oldToken[1]), mergeablePseudoClasses, mergeablePseudoElements)) { + isMergeable(serializeRules(token[1]), mergeablePseudoClasses, mergeablePseudoElements, multiplePseudoMerging) && + isMergeable(serializeRules(oldToken[1]), mergeablePseudoClasses, mergeablePseudoElements, multiplePseudoMerging)) { if (token[2].length > 0) { token[1] = tidyRules(oldToken[1].concat(token[1]), false, adjacentSpace, false, context.warnings); diff --git a/lib/optimizer/level-2/reduce-non-adjacent.js b/lib/optimizer/level-2/reduce-non-adjacent.js index 3461bb26..6ce0902b 100644 --- a/lib/optimizer/level-2/reduce-non-adjacent.js +++ b/lib/optimizer/level-2/reduce-non-adjacent.js @@ -13,6 +13,7 @@ function reduceNonAdjacent(tokens, context) { var options = context.options; var mergeablePseudoClasses = options.compatibility.selectors.mergeablePseudoClasses; var mergeablePseudoElements = options.compatibility.selectors.mergeablePseudoElements; + var multiplePseudoMerging = options.compatibility.selectors.multiplePseudoMerging; var candidates = {}; var repeated = []; @@ -27,7 +28,7 @@ function reduceNonAdjacent(tokens, context) { var selectorAsString = serializeRules(token[1]); var isComplexAndNotSpecial = token[1].length > 1 && - isMergeable(selectorAsString, mergeablePseudoClasses, mergeablePseudoElements); + isMergeable(selectorAsString, mergeablePseudoClasses, mergeablePseudoElements, multiplePseudoMerging); var wrappedSelectors = wrappedSelectorsFrom(token[1]); var selectors = isComplexAndNotSpecial ? [selectorAsString].concat(wrappedSelectors) : @@ -88,6 +89,7 @@ function reduceSimpleNonAdjacentCases(tokens, repeated, candidates, options, con function reduceComplexNonAdjacentCases(tokens, candidates, options, context) { var mergeablePseudoClasses = options.compatibility.selectors.mergeablePseudoClasses; var mergeablePseudoElements = options.compatibility.selectors.mergeablePseudoElements; + var multiplePseudoMerging = options.compatibility.selectors.multiplePseudoMerging; var localContext = {}; function filterOut(idx) { @@ -109,7 +111,7 @@ function reduceComplexNonAdjacentCases(tokens, candidates, options, context) { var intoToken = tokens[intoPosition]; var reducedBodies = []; - var selectors = isMergeable(complexSelector, mergeablePseudoClasses, mergeablePseudoElements) ? + var selectors = isMergeable(complexSelector, mergeablePseudoClasses, mergeablePseudoElements, multiplePseudoMerging) ? into[0].list : [complexSelector]; diff --git a/lib/optimizer/level-2/restructure.js b/lib/optimizer/level-2/restructure.js index 4c0c11e3..c85f7850 100644 --- a/lib/optimizer/level-2/restructure.js +++ b/lib/optimizer/level-2/restructure.js @@ -25,6 +25,7 @@ function restructure(tokens, context) { var options = context.options; var mergeablePseudoClasses = options.compatibility.selectors.mergeablePseudoClasses; var mergeablePseudoElements = options.compatibility.selectors.mergeablePseudoElements; + var multiplePseudoMerging = options.compatibility.selectors.multiplePseudoMerging; var specificityCache = context.cache.specificity; var movableTokens = {}; var movedProperties = []; @@ -85,7 +86,7 @@ function restructure(tokens, context) { var mergeableTokens = []; for (var i = sourceTokens.length - 1; i >= 0; i--) { - if (!isMergeable(serializeRules(sourceTokens[i][1]), mergeablePseudoClasses, mergeablePseudoElements)) { + if (!isMergeable(serializeRules(sourceTokens[i][1]), mergeablePseudoClasses, mergeablePseudoElements, multiplePseudoMerging)) { continue; } diff --git a/lib/options/compatibility.js b/lib/options/compatibility.js index ca881c62..3f365959 100644 --- a/lib/options/compatibility.js +++ b/lib/options/compatibility.js @@ -56,7 +56,8 @@ var DEFAULTS = { '::before', '::first-letter', '::first-line' - ] // selectors with these pseudo-elements can be merged as these are universally supported + ], // selectors with these pseudo-elements can be merged as these are universally supported + multiplePseudoMerging: true }, units: { ch: true, diff --git a/test/optimizer/level-2/is-mergeable-test.js b/test/optimizer/level-2/is-mergeable-test.js index cc04aa15..f6854c92 100644 --- a/test/optimizer/level-2/is-mergeable-test.js +++ b/test/optimizer/level-2/is-mergeable-test.js @@ -12,181 +12,213 @@ vows.describe(isMergeable) 'tag name selector': { 'topic': 'div', 'is mergeable': function (selector) { - assert.isTrue(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements)); + assert.isTrue(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements, true)); } }, 'class selector': { 'topic': '.class', 'is mergeable': function (selector) { - assert.isTrue(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements)); + assert.isTrue(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements, true)); } }, 'id selector': { 'topic': '#id', 'is mergeable': function (selector) { - assert.isTrue(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements)); + assert.isTrue(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements, true)); } }, 'complex selector': { 'topic': 'div ~ #id > .class', 'is mergeable': function (selector) { - assert.isTrue(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements)); + assert.isTrue(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements, true)); } }, 'vendor-prefixed pseudo-class': { 'topic': ':-moz-placeholder', 'is not mergeable': function (selector) { - assert.isFalse(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements)); + assert.isFalse(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements, true)); } }, 'vendor-prefixed pseudo-element': { 'topic': '::-moz-placeholder', 'is not mergeable': function (selector) { - assert.isFalse(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements)); + assert.isFalse(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements, true)); } }, - 'vendor-prefixed pseudo-class as descendant of attribute rule 123': { + 'vendor-prefixed pseudo-class as descendant of attribute rule': { 'topic': '[data-x="y"] :-moz-placeholder', 'is not mergeable': function (selector) { - assert.isFalse(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements)); + assert.isFalse(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements, true)); } }, 'vendor-prefixed pseudo-element as descendant of attribute rule': { 'topic': '[data-x="y"] ::-moz-placeholder', 'is not mergeable': function (selector) { - assert.isFalse(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements)); + assert.isFalse(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements, true)); } }, 'unsupported pseudo-class': { 'topic': ':first-child', 'is not mergeable': function (selector) { - assert.isFalse(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements)); + assert.isFalse(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements, true)); } }, 'unsupported pseudo-element': { 'topic': '::marker', 'is not mergeable': function (selector) { - assert.isFalse(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements)); + assert.isFalse(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements, true)); } }, 'supported pseudo-class': { 'topic': ':after', 'is mergeable': function (selector) { - assert.isTrue(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements)); + assert.isTrue(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements, true)); } }, 'supported pseudo-class with selector': { 'topic': 'div:after', 'is mergeable': function (selector) { - assert.isTrue(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements)); + assert.isTrue(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements, true)); } }, 'supported pseudo-class with arguments': { 'topic': 'div:lang(en)', 'is mergeable': function (selector) { - assert.isTrue(isMergeable(selector, [':lang'], mergeablePseudoElements)); + assert.isTrue(isMergeable(selector, [':lang'], mergeablePseudoElements, true)); } }, 'supported pseudo-class in the middle': { 'topic': 'div :first-child > span', 'is mergeable': function (selector) { - assert.isTrue(isMergeable(selector, [':first-child'], mergeablePseudoElements)); + assert.isTrue(isMergeable(selector, [':first-child'], mergeablePseudoElements, true)); } }, 'supported pseudo-classes in the middle': { 'topic': 'div :first-child > span:last-child > em', 'is mergeable': function (selector) { - assert.isTrue(isMergeable(selector, [':first-child', ':last-child'], mergeablePseudoElements)); + assert.isTrue(isMergeable(selector, [':first-child', ':last-child'], mergeablePseudoElements, true)); } }, 'supported pseudo-classes in the middle without spaces': { 'topic': 'div :first-child>span:last-child>em', 'is mergeable': function (selector) { - assert.isTrue(isMergeable(selector, [':first-child', ':last-child'], mergeablePseudoElements)); + assert.isTrue(isMergeable(selector, [':first-child', ':last-child'], mergeablePseudoElements, true)); } }, 'double :not pseudo-class': { 'topic': 'div:not(:first-child):not(.one)', 'is mergeable': function (selector) { - assert.isTrue(isMergeable(selector, [':first-child', ':not'], mergeablePseudoElements)); + assert.isTrue(isMergeable(selector, [':first-child', ':not'], mergeablePseudoElements, true)); } }, 'supported pseudo-class with unsupported arguments': { 'topic': 'div:after(test)', 'is not mergeable': function (selector) { - assert.isFalse(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements)); + assert.isFalse(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements, true)); } }, 'supported pseudo-class repeated': { 'topic': 'div:after:after', 'is not mergeable': function (selector) { - assert.isFalse(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements)); + assert.isFalse(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements, true)); } }, 'supported pseudo-element': { 'topic': '::before', 'is mergeable': function (selector) { - assert.isTrue(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements)); + assert.isTrue(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements, true)); } }, 'supported pseudo-element with selector': { 'topic': 'div::before', 'is mergeable': function (selector) { - assert.isTrue(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements)); + assert.isTrue(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements, true)); } }, 'supported pseudo-element with arguments': { 'topic': '::before(test)', 'is not mergeable': function (selector) { - assert.isFalse(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements)); + assert.isFalse(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements, true)); } }, 'supported pseudo-element repeated': { 'topic': '::before::before', 'is not mergeable': function (selector) { - assert.isFalse(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements)); + assert.isFalse(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements, true)); } }, 'supported pseudo-class and -element mixed': { 'topic': ':after::before', 'is not mergeable': function (selector) { - assert.isFalse(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements)); + assert.isFalse(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements, true)); } }, 'supported pseudo-element and -class mixed': { 'topic': '::before:after', 'is not mergeable': function (selector) { - assert.isFalse(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements)); + assert.isFalse(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements, true)); + } + }, + 'supported mixable pseudo classes / elements': { + 'topic': ':hover::after', + 'is mergeable': function (selector) { + assert.isTrue(isMergeable(selector, [':hover'], ['::after'], true)); + } + }, + 'supported unmixable pseudo classes': { + 'topic': ':first-line:before', + 'is not mergeable': function (selector) { + assert.isFalse(isMergeable(selector, [':before', ':first-line'], [], true)); + } + }, + 'supported unmixable pseudo elements': { + 'topic': '::first-line::before', + 'is not mergeable': function (selector) { + assert.isFalse(isMergeable(selector, [], ['::before', '::first-line'], true)); } }, '/deep/ selector': { 'topic': '.wrapper /deep/ a', 'is not mergeable': function (selector) { - assert.isFalse(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements)); + assert.isFalse(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements, true)); } }, 'empty selector': { 'topic': '', 'is not mergeable': function (selector) { - assert.isFalse(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements)); + assert.isFalse(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements, true)); } }, 'multi selector': { 'topic': 'h1,div', 'is mergeable': function (selector) { - assert.isTrue(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements)); + assert.isTrue(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements, true)); } }, 'multi selector with pseudo-class': { 'topic': 'h1:first-child,div', 'is not mergeable': function (selector) { - assert.isFalse(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements)); + assert.isFalse(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements, true)); } }, 'multi selector with empty': { 'topic': ',h1', 'is not mergeable': function (selector) { - assert.isFalse(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements)); + assert.isFalse(isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements, true)); + } + } + }) + .addBatch({ + 'pseudo-classes with disabled mixed merging': { + 'topic': ':first-child:before', + 'is not mergeable': function (selector) { + assert.isFalse(isMergeable(selector, [':before', ':first-child'], [], false)); + } + }, + 'pseudo-classes and -elements with disabled mixed merging': { + 'topic': ':hover::before', + 'is not mergeable': function (selector) { + assert.isFalse(isMergeable(selector, [':hover'], ['::before'], false)); } } }) diff --git a/test/optimizer/level-2/merge-adjacent-test.js b/test/optimizer/level-2/merge-adjacent-test.js index 9ed0df03..8d7f7c7d 100644 --- a/test/optimizer/level-2/merge-adjacent-test.js +++ b/test/optimizer/level-2/merge-adjacent-test.js @@ -114,4 +114,16 @@ vows.describe('remove duplicates') ], }, { level: 1 }) ) + .addBatch( + optimizerContext('with multiplePseudoMerging: false', { + 'single pseudo classes': [ + ':hover{color:red}:first-child{color:red}', + ':first-child,:hover{color:red}' + ], + 'multiple pseudo classes': [ + ':hover:before{color:red}.block{color:red}', + ':hover:before{color:red}.block{color:red}' + ] + }, { compatibility: { selectors: { multiplePseudoMerging: false } }, level: 2 }) + ) .export(module); diff --git a/test/options/compatibility-test.js b/test/options/compatibility-test.js index af35fdad..a6b9903e 100644 --- a/test/options/compatibility-test.js +++ b/test/options/compatibility-test.js @@ -25,6 +25,7 @@ vows.describe(compatibilityFrom) assert.isTrue(compat.properties.zeroUnits); assert.isFalse(compat.selectors.adjacentSpace); assert.isFalse(compat.selectors.ie7Hack); + assert.isTrue(compat.selectors.multiplePseudoMerging); assert.isTrue(compat.units.ch); assert.isTrue(compat.units.in); assert.isTrue(compat.units.pc); @@ -65,6 +66,7 @@ vows.describe(compatibilityFrom) assert.isTrue(compat.properties.zeroUnits); assert.isFalse(compat.selectors.adjacentSpace); assert.isFalse(compat.selectors.ie7Hack); + assert.isTrue(compat.selectors.multiplePseudoMerging); assert.isTrue(compat.units.ch); assert.isTrue(compat.units.in); assert.isTrue(compat.units.pc); @@ -100,6 +102,7 @@ vows.describe(compatibilityFrom) assert.isTrue(compat.properties.zeroUnits); assert.isFalse(compat.selectors.adjacentSpace); assert.isFalse(compat.selectors.ie7Hack); + assert.isTrue(compat.selectors.multiplePseudoMerging); assert.isTrue(compat.units.ch); assert.isTrue(compat.units.in); assert.isTrue(compat.units.pc); @@ -133,6 +136,7 @@ vows.describe(compatibilityFrom) assert.isTrue(compat.properties.zeroUnits); assert.isFalse(compat.selectors.adjacentSpace); assert.isFalse(compat.selectors.ie7Hack); + assert.isTrue(compat.selectors.multiplePseudoMerging); assert.isFalse(compat.units.ch); assert.isTrue(compat.units.in); assert.isTrue(compat.units.pc); @@ -209,6 +213,7 @@ vows.describe(compatibilityFrom) assert.isTrue(compat.properties.zeroUnits); assert.isFalse(compat.selectors.adjacentSpace); assert.isFalse(compat.selectors.ie7Hack); + assert.isTrue(compat.selectors.multiplePseudoMerging); assert.isFalse(compat.units.ch); assert.isTrue(compat.units.in); assert.isTrue(compat.units.pc); @@ -242,6 +247,7 @@ vows.describe(compatibilityFrom) assert.isTrue(compat.properties.zeroUnits); assert.isFalse(compat.selectors.adjacentSpace); assert.isFalse(compat.selectors.ie7Hack); + assert.isTrue(compat.selectors.multiplePseudoMerging); assert.isTrue(compat.units.ch); assert.isTrue(compat.units.in); assert.isTrue(compat.units.pc); @@ -275,6 +281,7 @@ vows.describe(compatibilityFrom) assert.isTrue(compat.properties.zeroUnits); assert.isFalse(compat.selectors.adjacentSpace); assert.isFalse(compat.selectors.ie7Hack); + assert.isTrue(compat.selectors.multiplePseudoMerging); assert.isTrue(compat.units.ch); assert.isTrue(compat.units.in); assert.isTrue(compat.units.pc); -- 2.34.1