diff --git a/History.md b/History.md index a51e4eafe..42b91da9b 100644 --- a/History.md +++ b/History.md @@ -2,6 +2,7 @@ ================== * Adds `process` method for compatibility with optimize-css-assets-webpack-plugin. +* Fixed issue [#861](https://github.com/jakubpawlowicz/clean-css/issues/861) - new `transition` property optimizer. [4.1.4 / 2017-06-14](https://github.com/jakubpawlowicz/clean-css/compare/v4.1.3...v4.1.4) ================== diff --git a/README.md b/README.md index ce7f6ac58..802f82c58 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,7 @@ clean-css 4.1 introduces the following changes / features: clean-css 4.2 will introduce the following changes / features: * Adds `process` method for compatibility with optimize-css-assets-webpack-plugin; +* new `transition` property optimizer; ## Constructor options diff --git a/lib/optimizer/level-2/break-up.js b/lib/optimizer/level-2/break-up.js index b48ccf23d..40b0c7620 100644 --- a/lib/optimizer/level-2/break-up.js +++ b/lib/optimizer/level-2/break-up.js @@ -105,7 +105,7 @@ function animation(property, compactable, validator) { } else if (validator.isTime(value[1]) && !delaySet) { delay.value = [value]; delaySet = true; - } else if ((validator.isGlobal(value[1]) || validator.isAnimationTimingFunction(value[1])) && !timingSet) { + } else if ((validator.isGlobal(value[1]) || validator.isTimingFunction(value[1])) && !timingSet) { timing.value = [value]; timingSet = true; } else if ((validator.isAnimationIterationCountKeyword(value[1]) || validator.isPositiveNumber(value[1])) && !iterationSet) { @@ -489,6 +489,53 @@ function listStyle(property, compactable, validator) { return components; } +function transition(property, compactable, validator) { + var prop = _wrapDefault(property.name + '-property', property, compactable); + var duration = _wrapDefault(property.name + '-duration', property, compactable); + var timing = _wrapDefault(property.name + '-timing-function', property, compactable); + var delay = _wrapDefault(property.name + '-delay', property, compactable); + var components = [prop, duration, timing, delay]; + var values = property.value; + var value; + var durationSet = false; + var delaySet = false; + var propSet = false; + var timingSet = false; + var i; + var l; + + if (property.value.length == 1 && property.value[0][1] == 'inherit') { + prop.value = duration.value = timing.value = delay.value = property.value; + return components; + } + + if (values.length > 1 && _anyIsInherit(values)) { + throw new InvalidPropertyError('Invalid animation values at ' + formatPosition(values[0][2][0]) + '. Ignoring.'); + } + + for (i = 0, l = values.length; i < l; i++) { + value = values[i]; + + if (validator.isTime(value[1]) && !durationSet) { + duration.value = [value]; + durationSet = true; + } else if (validator.isTime(value[1]) && !delaySet) { + delay.value = [value]; + delaySet = true; + } else if ((validator.isGlobal(value[1]) || validator.isTimingFunction(value[1])) && !timingSet) { + timing.value = [value]; + timingSet = true; + } else if (validator.isIdentifier(value[1]) && !propSet) { + prop.value = [value]; + propSet = true; + } else { + throw new InvalidPropertyError('Invalid animation value at ' + formatPosition(value[2][0]) + '. Ignoring.'); + } + } + + return components; +} + function widthStyleColor(property, compactable, validator) { var descriptor = compactable[property.name]; var components = [ @@ -558,5 +605,6 @@ module.exports = { fourValues: fourValues, listStyle: listStyle, multiplex: multiplex, - outline: widthStyleColor + outline: widthStyleColor, + transition: transition }; diff --git a/lib/optimizer/level-2/can-override.js b/lib/optimizer/level-2/can-override.js index 10d3bba17..ceac48217 100644 --- a/lib/optimizer/level-2/can-override.js +++ b/lib/optimizer/level-2/can-override.js @@ -20,16 +20,6 @@ function animationName(validator, value1, value2) { return validator.isAnimationNameKeyword(value2) || validator.isIdentifier(value2); } -function animationTimingFunction(validator, value1, value2) { - if (!understandable(validator, value1, value2, 0, true) && !(validator.isAnimationTimingFunction(value2) || validator.isGlobal(value2))) { - return false; - } else if (validator.isVariable(value1) && validator.isVariable(value2)) { - return true; - } - - return validator.isAnimationTimingFunction(value2) || validator.isGlobal(value2); -} - function areSameFunction(validator, value1, value2) { if (!validator.isFunction(value1) || !validator.isFunction(value2)) { return false; @@ -129,14 +119,22 @@ function keywordWithGlobal(propertyName) { }; } +function propertyName(validator, value1, value2) { + if (!understandable(validator, value1, value2, 0, true) && !validator.isIdentifier(value2)) { + return false; + } else if (validator.isVariable(value1) && validator.isVariable(value2)) { + return true; + } + + return validator.isIdentifier(value2); +} + function sameFunctionOrValue(validator, value1, value2) { return areSameFunction(validator, value1, value2) ? true : value1 === value2; } - - function textShadow(validator, value1, value2) { if (!understandable(validator, value1, value2, 0, true) && !(validator.isUnit(value2) || validator.isColor(value2) || validator.isGlobal(value2))) { return false; @@ -165,6 +163,16 @@ function time(validator, value1, value2) { return sameFunctionOrValue(validator, value1, value2); } +function timingFunction(validator, value1, value2) { + if (!understandable(validator, value1, value2, 0, true) && !(validator.isTimingFunction(value2) || validator.isGlobal(value2))) { + return false; + } else if (validator.isVariable(value1) && validator.isVariable(value2)) { + return true; + } + + return validator.isTimingFunction(value2) || validator.isGlobal(value2); +} + function unit(validator, value1, value2) { if (!understandable(validator, value1, value2, 0, true) && !validator.isUnit(value2)) { return false; @@ -206,7 +214,9 @@ module.exports = { color: color, components: components, image: image, + propertyName: propertyName, time: time, + timingFunction: timingFunction, unit: unit }, property: { @@ -215,7 +225,6 @@ module.exports = { animationIterationCount: animationIterationCount, animationName: animationName, animationPlayState: keywordWithGlobal('animation-play-state'), - animationTimingFunction: animationTimingFunction, backgroundAttachment: keyword('background-attachment'), backgroundClip: keywordWithGlobal('background-clip'), backgroundOrigin: keyword('background-origin'), diff --git a/lib/optimizer/level-2/compactable.js b/lib/optimizer/level-2/compactable.js index 97e7e2aca..81c3f17e5 100644 --- a/lib/optimizer/level-2/compactable.js +++ b/lib/optimizer/level-2/compactable.js @@ -38,7 +38,7 @@ var compactable = { 'animation': { canOverride: canOverride.generic.components([ canOverride.generic.time, - canOverride.property.animationTimingFunction, + canOverride.generic.timingFunction, canOverride.generic.time, canOverride.property.animationIterationCount, canOverride.property.animationDirection, @@ -158,7 +158,7 @@ var compactable = { ] }, 'animation-timing-function': { - canOverride: canOverride.property.animationTimingFunction, + canOverride: canOverride.generic.timingFunction, componentOf: [ 'animation' ], @@ -917,6 +917,82 @@ var compactable = { '-webkit-' ] }, + 'transition': { + breakUp: breakUp.multiplex(breakUp.transition), + canOverride: canOverride.generic.components([ + canOverride.property.transitionProperty, + canOverride.generic.time, + canOverride.generic.timingFunction, + canOverride.generic.time + ]), + components: [ + 'transition-property', + 'transition-duration', + 'transition-timing-function', + 'transition-delay' + ], + defaultValue: 'none', + restore: restore.multiplex(restore.withoutDefaults), + shorthand: true, + vendorPrefixes: [ + '-moz-', + '-o-', + '-webkit-' + ] + }, + 'transition-delay': { + canOverride: canOverride.generic.time, + componentOf: [ + 'transition' + ], + defaultValue: '0s', + intoMultiplexMode: 'real', + vendorPrefixes: [ + '-moz-', + '-o-', + '-webkit-' + ] + }, + 'transition-duration': { + canOverride: canOverride.generic.time, + componentOf: [ + 'transition' + ], + defaultValue: '0s', + intoMultiplexMode: 'real', + vendorPrefixes: [ + '-moz-', + '-o-', + '-webkit-' + ] + }, + 'transition-property': { + canOverride: canOverride.generic.propertyName, + componentOf: [ + 'transition' + ], + defaultValue: 'all', + intoMultiplexMode: 'placeholder', + placeholderValue: '_', // it's a short value that won't match any property and still be a valid `transition-property` + vendorPrefixes: [ + '-moz-', + '-o-', + '-webkit-' + ] + }, + 'transition-timing-function': { + canOverride: canOverride.generic.timingFunction, + componentOf: [ + 'transition' + ], + defaultValue: 'ease', + intoMultiplexMode: 'real', + vendorPrefixes: [ + '-moz-', + '-o-', + '-webkit-' + ] + }, 'vertical-align': { canOverride: canOverride.property.verticalAlign, defaultValue: 'baseline' diff --git a/lib/optimizer/level-2/properties/override-properties.js b/lib/optimizer/level-2/properties/override-properties.js index 3749720c9..2c014ea0f 100644 --- a/lib/optimizer/level-2/properties/override-properties.js +++ b/lib/optimizer/level-2/properties/override-properties.js @@ -95,10 +95,11 @@ function turnShorthandValueIntoMultiplex(property, size) { } function turnLonghandValueIntoMultiplex(property, size) { - var withRealValue = compactable[property.name].intoMultiplexMode == 'real'; - var withValue = withRealValue ? + var descriptor = compactable[property.name]; + var withRealValue = descriptor.intoMultiplexMode == 'real'; + var withValue = descriptor.intoMultiplexMode == 'real' ? property.value.slice(0) : - compactable[property.name].defaultValue; + (descriptor.intoMultiplexMode == 'placeholder' ? descriptor.placeholderValue : descriptor.defaultValue); var i = multiplexSize(property); var j; var m = withValue.length; diff --git a/lib/optimizer/validator.js b/lib/optimizer/validator.js index cfccd0f9d..b3937075c 100644 --- a/lib/optimizer/validator.js +++ b/lib/optimizer/validator.js @@ -3,7 +3,6 @@ var functionVendorRegexStr = '\\-(\\-|[A-Z]|[0-9])+\\(.*?\\)'; var variableRegexStr = 'var\\(\\-\\-[^\\)]+\\)'; var functionAnyRegexStr = '(' + variableRegexStr + '|' + functionNoVendorRegexStr + '|' + functionVendorRegexStr + ')'; -var animationTimingFunctionRegex = /^(cubic\-bezier|steps)\([^\)]+\)$/; var calcRegex = new RegExp('^(\\-moz\\-|\\-webkit\\-)?calc\\([^\\)]+\\)$', 'i'); var functionAnyRegex = new RegExp('^' + functionAnyRegexStr + '$', 'i'); var hslColorRegex = /^hsl\(\s*[\-\.\d]+\s*,\s*[\.\d]+%\s*,\s*[\.\d]+%\s*\)|hsla\(\s*[\-\.\d]+\s*,\s*[\.\d]+%\s*,\s*[\.\d]+%\s*,\s*[\.\d]+\s*\)$/; @@ -14,6 +13,7 @@ var prefixRegex = /^-([a-z0-9]|-)*$/i; var rgbColorRegex = /^rgb\(\s*[\d]{1,3}\s*,\s*[\d]{1,3}\s*,\s*[\d]{1,3}\s*\)|rgba\(\s*[\d]{1,3}\s*,\s*[\d]{1,3}\s*,\s*[\d]{1,3}\s*,\s*[\.\d]+\s*\)$/; var shortHexColorRegex = /^#[0-9a-f]{3}$/i; var timeRegex = new RegExp('^(\\-?\\+?\\.?\\d+\\.?\\d*(s|ms))$'); +var timingFunctionRegex = /^(cubic\-bezier|steps)\([^\)]+\)$/; var urlRegex = /^url\([\s\S]+\)$/i; var variableRegex = new RegExp('^' + variableRegexStr + '$', 'i'); @@ -36,6 +36,15 @@ var Keywords = { 'ridge', 'solid' ], + '*-timing-function': [ + 'ease', + 'ease-in', + 'ease-in-out', + 'ease-out', + 'linear', + 'step-end', + 'step-start' + ], 'animation-direction': [ 'alternate', 'alternate-reverse', @@ -58,15 +67,6 @@ var Keywords = { 'paused', 'running' ], - 'animation-timing-function': [ - 'ease', - 'ease-in', - 'ease-in-out', - 'ease-out', - 'linear', - 'step-end', - 'step-start' - ], 'background-attachment': [ 'fixed', 'inherit', @@ -337,14 +337,6 @@ var Units = [ 'vw' ]; -function isAnimationTimingFunction() { - var isTimingFunctionKeyword = isKeyword('animation-timing-function'); - - return function (value) { - return isTimingFunctionKeyword(value) || animationTimingFunctionRegex.test(value); - }; -} - function isColor(value) { return value != 'auto' && ( @@ -418,6 +410,14 @@ function isTime(value) { return timeRegex.test(value); } +function isTimingFunction() { + var isTimingFunctionKeyword = isKeyword('*-timing-function'); + + return function (value) { + return isTimingFunctionKeyword(value) || timingFunctionRegex.test(value); + }; +} + function isUnit(compatibleCssUnitRegex, value) { return compatibleCssUnitRegex.test(value); } @@ -446,7 +446,7 @@ function validator(compatibility) { isAnimationIterationCountKeyword: isKeyword('animation-iteration-count'), isAnimationNameKeyword: isKeyword('animation-name'), isAnimationPlayStateKeyword: isKeyword('animation-play-state'), - isAnimationTimingFunction: isAnimationTimingFunction(), + isTimingFunction: isTimingFunction(), isBackgroundAttachmentKeyword: isKeyword('background-attachment'), isBackgroundClipKeyword: isKeyword('background-clip'), isBackgroundOriginKeyword: isKeyword('background-origin'), diff --git a/test/fixtures/bootstrap-min.css b/test/fixtures/bootstrap-min.css index 40d233a4b..c23e26764 100644 --- a/test/fixtures/bootstrap-min.css +++ b/test/fixtures/bootstrap-min.css @@ -330,7 +330,7 @@ figure{margin:0} .carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto} .img-thumbnail,.list-inline>li,label{display:inline-block} .img-rounded{border-radius:6px} -.img-thumbnail{max-width:100%;height:auto;padding:4px;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out} +.img-thumbnail{max-width:100%;height:auto;padding:4px;border:1px solid #ddd;border-radius:4px;-webkit-transition:.2s ease-in-out;-o-transition:.2s ease-in-out;transition:.2s ease-in-out} .img-circle{border-radius:50%} hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee} .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;clip:rect(0,0,0,0);border:0} @@ -686,7 +686,7 @@ input[type=range]{display:block;width:100%} select[multiple],select[size]{height:auto} input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:dotted thin;outline:-webkit-focus-ring-color auto 5px;outline-offset:-2px} output{padding-top:7px} -.form-control{width:100%;height:34px;padding:6px 12px;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s} +.form-control{width:100%;height:34px;padding:6px 12px;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color .15s ease-in-out,-webkit-box-shadow .15s ease-in-out;-o-transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out} .form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)} .form-control::-moz-placeholder{color:#999;opacity:1} .form-control:-ms-input-placeholder{color:#999} @@ -1145,7 +1145,7 @@ from{background-position:40px 0} to{background-position:0 0} } .progress{height:20px;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)} -.progress-bar{float:left;width:0;height:100%;line-height:20px;color:#fff;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease} +.progress-bar{float:left;width:0;height:100%;line-height:20px;color:#fff;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s;-o-transition:width .6s;transition:width .6s} .progress-bar-striped,.progress-striped .progress-bar{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px} .progress-bar.active,.progress.active .progress-bar{-webkit-animation:2s linear infinite progress-bar-stripes;-o-animation:2s linear infinite progress-bar-stripes;animation:2s linear infinite progress-bar-stripes} .progress-bar-success{background-color:#5cb85c} @@ -1340,7 +1340,7 @@ button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;bor .popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:rgba(0,0,0,.25)} .popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff} .carousel-inner{width:100%;overflow:hidden} -.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left} +.carousel-inner>.item{position:relative;display:none;-webkit-transition:left .6s ease-in-out;-o-transition:left .6s ease-in-out;transition:left .6s ease-in-out} .carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev,.center-block{display:block} @media all and (transform-3d),(-webkit-transform-3d){ .carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000;perspective:1000} diff --git a/test/optimizer/level-2/break-up-test.js b/test/optimizer/level-2/break-up-test.js index ef867252d..69a6757aa 100644 --- a/test/optimizer/level-2/break-up-test.js +++ b/test/optimizer/level-2/break-up-test.js @@ -71,7 +71,7 @@ vows.describe(breakUp) assert.deepEqual(components[7].value, [['property-value', 'slidein']]); } }, - 'all with inverted order': { + 'all with reversed order': { 'topic': function () { return _breakUp([ [ @@ -2177,4 +2177,350 @@ vows.describe(breakUp) } } }) + .addBatch({ + 'transition': { + 'all': { + 'topic': function () { + return _breakUp([ + [ + 'property', + ['property-name', 'transition'], + ['property-value', 'all'], + ['property-value', '1s'], + ['property-value', 'ease-in'], + ['property-value', '2s'] + ] + ]); + }, + 'has 4 components': function (components) { + assert.lengthOf(components, 4); + }, + 'has transition-property': function (components) { + assert.deepEqual(components[0].name, 'transition-property'); + assert.deepEqual(components[0].value, [['property-value', 'all']]); + }, + 'has transition-duration': function (components) { + assert.deepEqual(components[1].name, 'transition-duration'); + assert.deepEqual(components[1].value, [['property-value', '1s']]); + }, + 'has transition-timing-function': function (components) { + assert.deepEqual(components[2].name, 'transition-timing-function'); + assert.deepEqual(components[2].value, [['property-value', 'ease-in']]); + }, + 'has transition-delay': function (components) { + assert.deepEqual(components[3].name, 'transition-delay'); + assert.deepEqual(components[3].value, [['property-value', '2s']]); + } + }, + 'all vendor prefixed': { + 'topic': function () { + return _breakUp([ + [ + 'property', + ['property-name', '-moz-transition'], + ['property-value', 'all'], + ['property-value', '1s'], + ['property-value', 'ease-in'], + ['property-value', '2s'] + ] + ]); + }, + 'has 4 components': function (components) { + assert.lengthOf(components, 4); + }, + 'has -moz-transition-property': function (components) { + assert.deepEqual(components[0].name, '-moz-transition-property'); + assert.deepEqual(components[0].value, [['property-value', 'all']]); + }, + 'has -moz-transition-duration': function (components) { + assert.deepEqual(components[1].name, '-moz-transition-duration'); + assert.deepEqual(components[1].value, [['property-value', '1s']]); + }, + 'has -moz-transition-timing-function': function (components) { + assert.deepEqual(components[2].name, '-moz-transition-timing-function'); + assert.deepEqual(components[2].value, [['property-value', 'ease-in']]); + }, + 'has -moz-transition-delay': function (components) { + assert.deepEqual(components[3].name, '-moz-transition-delay'); + assert.deepEqual(components[3].value, [['property-value', '2s']]); + } + }, + 'all with reversed order': { + 'topic': function () { + return _breakUp([ + [ + 'property', + ['property-name', 'transition'], + ['property-value', '2s'], + ['property-value', 'ease-in'], + ['property-value', '1s'], + ['property-value', 'all'] + ] + ]); + }, + 'has transition-property': function (components) { + assert.deepEqual(components[0].name, 'transition-property'); + assert.deepEqual(components[0].value, [['property-value', 'all']]); + }, + 'has transition-duration': function (components) { + assert.deepEqual(components[1].name, 'transition-duration'); + assert.deepEqual(components[1].value, [['property-value', '2s']]); + }, + 'has transition-timing-function': function (components) { + assert.deepEqual(components[2].name, 'transition-timing-function'); + assert.deepEqual(components[2].value, [['property-value', 'ease-in']]); + }, + 'has transition-delay': function (components) { + assert.deepEqual(components[3].name, 'transition-delay'); + assert.deepEqual(components[3].value, [['property-value', '1s']]); + } + }, + 'some': { + 'topic': function () { + return _breakUp([ + [ + 'property', + ['property-name', 'transition'], + ['property-value', 'margin'], + ['property-value', '1s'] + ] + ]); + }, + 'has transition-property': function (components) { + assert.deepEqual(components[0].name, 'transition-property'); + assert.deepEqual(components[0].value, [['property-value', 'margin']]); + }, + 'has transition-duration': function (components) { + assert.deepEqual(components[1].name, 'transition-duration'); + assert.deepEqual(components[1].value, [['property-value', '1s']]); + }, + 'has transition-timing-function': function (components) { + assert.deepEqual(components[2].name, 'transition-timing-function'); + assert.deepEqual(components[2].value, [['property-value', 'ease']]); + }, + 'has transition-delay': function (components) { + assert.deepEqual(components[3].name, 'transition-delay'); + assert.deepEqual(components[3].value, [['property-value', '0s']]); + } + }, + 'only property': { + 'topic': function () { + return _breakUp([ + [ + 'property', + ['property-name', 'transition'], + ['property-value', 'margin'] + ] + ]); + }, + 'has transition-property': function (components) { + assert.deepEqual(components[0].name, 'transition-property'); + assert.deepEqual(components[0].value, [['property-value', 'margin']]); + }, + 'has transition-duration': function (components) { + assert.deepEqual(components[1].name, 'transition-duration'); + assert.deepEqual(components[1].value, [['property-value', '0s']]); + }, + 'has transition-timing-function': function (components) { + assert.deepEqual(components[2].name, 'transition-timing-function'); + assert.deepEqual(components[2].value, [['property-value', 'ease']]); + }, + 'has transition-delay': function (components) { + assert.deepEqual(components[3].name, 'transition-delay'); + assert.deepEqual(components[3].value, [['property-value', '0s']]); + } + }, + 'only one `time`': { + 'topic': function () { + return _breakUp([ + [ + 'property', + ['property-name', 'transition'], + ['property-value', '1s'] + ] + ]); + }, + 'has transition-property': function (components) { + assert.deepEqual(components[0].name, 'transition-property'); + assert.deepEqual(components[0].value, [['property-value', 'all']]); + }, + 'has transition-duration': function (components) { + assert.deepEqual(components[1].name, 'transition-duration'); + assert.deepEqual(components[1].value, [['property-value', '1s']]); + }, + 'has transition-timing-function': function (components) { + assert.deepEqual(components[2].name, 'transition-timing-function'); + assert.deepEqual(components[2].value, [['property-value', 'ease']]); + }, + 'has transition-delay': function (components) { + assert.deepEqual(components[3].name, 'transition-delay'); + assert.deepEqual(components[3].value, [['property-value', '0s']]); + } + }, + 'only two `time`s': { + 'topic': function () { + return _breakUp([ + [ + 'property', + ['property-name', 'transition'], + ['property-value', '1s'], + ['property-value', '2s'] + ] + ]); + }, + 'has transition-property': function (components) { + assert.deepEqual(components[0].name, 'transition-property'); + assert.deepEqual(components[0].value, [['property-value', 'all']]); + }, + 'has transition-duration': function (components) { + assert.deepEqual(components[1].name, 'transition-duration'); + assert.deepEqual(components[1].value, [['property-value', '1s']]); + }, + 'has transition-timing-function': function (components) { + assert.deepEqual(components[2].name, 'transition-timing-function'); + assert.deepEqual(components[2].value, [['property-value', 'ease']]); + }, + 'has transition-delay': function (components) { + assert.deepEqual(components[3].name, 'transition-delay'); + assert.deepEqual(components[3].value, [['property-value', '2s']]); + } + }, + 'only timing function': { + 'topic': function () { + return _breakUp([ + [ + 'property', + ['property-name', 'transition'], + ['property-value', 'ease-out'] + ] + ]); + }, + 'has transition-property': function (components) { + assert.deepEqual(components[0].name, 'transition-property'); + assert.deepEqual(components[0].value, [['property-value', 'all']]); + }, + 'has transition-duration': function (components) { + assert.deepEqual(components[1].name, 'transition-duration'); + assert.deepEqual(components[1].value, [['property-value', '0s']]); + }, + 'has transition-timing-function': function (components) { + assert.deepEqual(components[2].name, 'transition-timing-function'); + assert.deepEqual(components[2].value, [['property-value', 'ease-out']]); + }, + 'has transition-delay': function (components) { + assert.deepEqual(components[3].name, 'transition-delay'); + assert.deepEqual(components[3].value, [['property-value', '0s']]); + } + }, + '`inherit`': { + 'topic': function () { + return _breakUp([ + [ + 'property', + ['property-name', 'transition'], + ['property-value', 'inherit'] + ] + ]); + }, + 'has transition-property': function (components) { + assert.deepEqual(components[0].name, 'transition-property'); + assert.deepEqual(components[0].value, [['property-value', 'inherit']]); + }, + 'has transition-duration': function (components) { + assert.deepEqual(components[1].name, 'transition-duration'); + assert.deepEqual(components[1].value, [['property-value', 'inherit']]); + }, + 'has transition-timing-function': function (components) { + assert.deepEqual(components[2].name, 'transition-timing-function'); + assert.deepEqual(components[2].value, [['property-value', 'inherit']]); + }, + 'has transition-delay': function (components) { + assert.deepEqual(components[3].name, 'transition-delay'); + assert.deepEqual(components[3].value, [['property-value', 'inherit']]); + } + }, + 'multiplex': { + 'topic': function () { + return _breakUp([ + [ + 'property', + ['property-name', 'transition'], + ['property-value', 'background-color'], + ['property-value', '1s'], + ['property-value', 'ease-in'], + ['property-value', '1s'], + ['property-value', ','], + ['property-value', 'opacity'], + ['property-value', '2s'] + ] + ]); + }, + 'has transition-property': function (components) { + assert.deepEqual(components[0].name, 'transition-property'); + assert.deepEqual(components[0].value, [['property-value', 'background-color'], ['property-value', ','], ['property-value', 'opacity']]); + }, + 'has transition-duration': function (components) { + assert.deepEqual(components[1].name, 'transition-duration'); + assert.deepEqual(components[1].value, [['property-value', '1s'], ['property-value', ','], ['property-value', '2s']]); + }, + 'has transition-timing-function': function (components) { + assert.deepEqual(components[2].name, 'transition-timing-function'); + assert.deepEqual(components[2].value, [['property-value', 'ease-in'], ['property-value', ','], ['property-value', 'ease']]); + }, + 'has transition-delay': function (components) { + assert.deepEqual(components[3].name, 'transition-delay'); + assert.deepEqual(components[3].value, [['property-value', '1s'], ['property-value', ','], ['property-value', '0s']]); + } + }, + 'three `time`s': { + 'topic': function () { + return _breakUp([ + [ + 'property', + ['property-name', 'transition'], + ['property-value', '1s'], + ['property-value', '2s'], + ['property-value', '3s', [[1, 30, undefined]]] + ] + ]); + }, + 'has no components': function (components) { + assert.lengthOf(components, 0); + } + }, + 'extra value': { + 'topic': function () { + return _breakUp([ + [ + 'property', + ['property-name', 'transition'], + ['property-value', 'all'], + ['property-value', '1s'], + ['property-value', 'ease-in'], + ['property-value', '3s'], + ['property-value', 'extra', [[1, 30, undefined]]] + ] + ]); + }, + 'has no components': function (components) { + assert.lengthOf(components, 0); + } + }, + 'mixed-in inherit': { + 'topic': function () { + return _breakUp([ + [ + 'property', + ['property-name', 'transition'], + ['property-value', 'all', [[1, 30, undefined]]], + ['property-value', 'inherit'] + ] + ]); + }, + 'has no components': function (components) { + assert.lengthOf(components, 0); + } + } + } + }) .export(module); diff --git a/test/optimizer/level-2/properties/merge-into-shorthands-test.js b/test/optimizer/level-2/properties/merge-into-shorthands-test.js index 2efa17ff6..3c085f9e6 100644 --- a/test/optimizer/level-2/properties/merge-into-shorthands-test.js +++ b/test/optimizer/level-2/properties/merge-into-shorthands-test.js @@ -814,6 +814,30 @@ vows.describe(optimizeProperties) ] ]); } - } + } + }) + .addBatch({ + 'transition': { + 'topic': function () { + return _optimize('.block{transition-property:width;transition-duration:5s;transition-timing-function:ease-in;transition-delay:2s}'); + }, + 'into': function (properties) { + assert.deepEqual(properties, [ + [ + 'property', + ['property-name', 'transition', [ + [1, 7, undefined], + [1, 33, undefined], + [1, 56, undefined], + [1, 91, undefined] + ]], + ['property-value', 'width', [[1, 27, undefined]]], + ['property-value', '5s', [[1, 53, undefined]]], + ['property-value', 'ease-in', [[1, 83, undefined]]], + ['property-value', '2s', [[1, 108, undefined]]] + ] + ]); + } + } }) .export(module); diff --git a/test/optimizer/level-2/properties/override-properties-test.js b/test/optimizer/level-2/properties/override-properties-test.js index 62f062cd9..2ff59a925 100644 --- a/test/optimizer/level-2/properties/override-properties-test.js +++ b/test/optimizer/level-2/properties/override-properties-test.js @@ -36,7 +36,7 @@ function _optimize(source, compat) { } vows.describe(optimizeProperties) - .addBatch({ + .addBatch({ 'animation shorthand and longhand': { 'topic': function () { return _optimize('.block{animation:1s ease-in;animation-name:slidein}'); @@ -2429,6 +2429,151 @@ vows.describe(optimizeProperties) } } }) + .addBatch({ + 'transition shorthand and longhand': { + 'topic': function () { + return _optimize('.block{transition:1s ease-in;transition-property:opacity}'); + }, + 'into': function (properties) { + assert.deepEqual(properties, [ + [ + 'property', + ['property-name', 'transition', [[1, 7, undefined]]], + ['property-value', 'opacity', [[1, 49, undefined]]], + ['property-value', '1s', [[1, 18, undefined]]], + ['property-value', 'ease-in', [[1, 21, undefined]]] + ] + ]); + } + }, + 'transition longhand and shorthand': { + 'topic': function () { + return _optimize('.block{transition-duration:2s;transition:ease-in}'); + }, + 'into': function (properties) { + assert.deepEqual(properties, [ + [ + 'property', + ['property-name', 'transition', [[1, 30, undefined]]], + ['property-value', 'ease-in', [[1, 41, undefined]]], + ] + ]); + } + }, + 'transition shorthand with overriddable shorthand': { + 'topic': function () { + return _optimize('.block{transition:opacity 1s;transition:margin 1s ease-in}'); + }, + 'into': function (properties) { + assert.deepEqual(properties, [ + [ + 'property', + ['property-name', 'transition', [[1, 7, undefined]]], + ['property-value', 'margin', [[1, 40, undefined]]], + ['property-value', '1s', [[1, 47, undefined]]], + ['property-value', 'ease-in', [[1, 50, undefined]]] + ] + ]); + } + }, + 'transition shorthand and multiplex longhand': { + 'topic': function () { + return _optimize('.block{transition:margin 1s;transition-timing-function:ease-in,ease-out}'); + }, + 'into': function (properties) { + assert.deepEqual(properties, [ + [ + 'property', + ['property-name', 'transition', [[1, 7, undefined]]], + ['property-value', 'margin', [[1, 18, undefined]]], + ['property-value', '1s', [[1, 25, undefined]]], + ['property-value', 'ease-in', [[1, 55, undefined]]], + ['property-value', ','], + ['property-value', '_'], + ['property-value', '1s', [[1, 25, undefined]]], + ['property-value', 'ease-out', [[1, 63, undefined]]] + ] + ]); + } + }, + 'transition multiplex shorthand and longhand': { + 'topic': function () { + return _optimize('.block{transition:ease-in,ease-out;transition-duration:1s}'); + }, + 'into': function (properties) { + assert.deepEqual(properties, [ + [ + 'property', + ['property-name', 'transition', [[1, 7, undefined]]], + ['property-value', '1s', [[1, 55, undefined]]], + ['property-value', 'ease-in', [[1, 18, undefined]]], + ['property-value', ','], + ['property-value', '1s', [[1, 55, undefined]]], + ['property-value', 'ease-out', [[1, 26, undefined]]] + ] + ]); + } + }, + 'transition shorthand and multiplex longhand - too long to merge': { + 'topic': function () { + return _optimize('.block{transition:2s ease-in 1s;transition-property:margin,opacity,padding}'); + }, + 'into': function (properties) { + assert.deepEqual(properties, [ + [ + 'property', + ['property-name', 'transition', [[1, 7, undefined]]], + ['property-value', '2s', [[1, 18, undefined]]], + ['property-value', 'ease-in', [[1, 21, undefined]]], + ['property-value', '1s', [[1, 29, undefined]]] + ], + [ + 'property', + ['property-name', 'transition-property', [[1, 32, undefined]]], + ['property-value', 'margin', [[1, 52, undefined]]], + ['property-value', ',', [[1, 58, undefined]]], + ['property-value', 'opacity', [[1, 59, undefined]]], + ['property-value', ',', [[1, 66, undefined]]], + ['property-value', 'padding', [[1, 67, undefined]]] + ] + ]); + } + }, + 'transition shorthand and inherit longhand': { + 'topic': function () { + return _optimize('.block{transition:1s;transition-timing-function:inherit}'); + }, + 'into': function (properties) { + assert.deepEqual(properties, [ + [ + 'property', + ['property-name', 'transition', [[1, 7, undefined]]], + ['property-value', '1s', [[1, 18, undefined]]], + ], + [ + 'property', + ['property-name', 'transition-timing-function', [[1, 21, undefined]]], + ['property-value', 'inherit', [[1, 48, undefined]]] + ] + ]); + } + }, + 'vendor prefixed transition shorthand and longhand': { + 'topic': function () { + return _optimize('.block{-webkit-transition:1s;-webkit-transition-timing-function:ease-in}'); + }, + 'into': function (properties) { + assert.deepEqual(properties, [ + [ + 'property', + ['property-name', '-webkit-transition', [[1, 7, undefined]]], + ['property-value', '1s', [[1, 26, undefined]]], + ['property-value', 'ease-in', [[1, 64, undefined]]] + ] + ]); + } + } + }) .addBatch({ 'overriding !important by a star hack': { 'topic': function () {