From: Jakub Pawlowicz Date: Fri, 24 Mar 2017 14:42:10 +0000 (+0100) Subject: See #254 - implements `font` compacting. X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=77c25fe79442ea3045bccc88cc457c99664d5e4b;p=clean-css.git See #254 - implements `font` compacting. Why: * Just as for other shorthand properties, the `font` one allows collapsing components into it; * we will remove level 1 font optimizations to avoid logic duplication. --- diff --git a/README.md b/README.md index f4e04bc5..d130d653 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,7 @@ Once released clean-css 4.1 will introduce the following changes / features: * `minify` method improved signature accepting a list of hashes for a predictable traversal; * `selectorsSortingMethod` level 1 optimization allows `false` or `'none'` for disabling selector sorting; * `fetch` option controlling a function for handling remote requests; +* new `font` shorthand and `font-*` longhand optimizers; ## Constructor options diff --git a/lib/optimizer/level-2/break-up.js b/lib/optimizer/level-2/break-up.js index 9e44d5a1..905516b0 100644 --- a/lib/optimizer/level-2/break-up.js +++ b/lib/optimizer/level-2/break-up.js @@ -6,8 +6,21 @@ var Token = require('../../tokenizer/token'); var formatPosition = require('../../utils/format-position'); +var FORWARD_SLASH = '/'; var MULTIPLEX_SEPARATOR = ','; +function _anyIsInherit(values) { + var i, l; + + for (i = 0, l = values.length; i < l; i++) { + if (values[i][1] == 'inherit') { + return true; + } + } + + return false; +} + function _colorFilter(validator) { return function (value) { return value[1] == 'invert' || validator.isValidColor(value[1]) || validator.isValidVendorPrefixedValue(value[1]); @@ -180,6 +193,116 @@ function borderRadius(property, compactable) { return target.components; } +function font(property, compactable, validator) { + var style = _wrapDefault('font-style', property, compactable); + var variant = _wrapDefault('font-variant', property, compactable); + var weight = _wrapDefault('font-weight', property, compactable); + var stretch = _wrapDefault('font-stretch', property, compactable); + var size = _wrapDefault('font-size', property, compactable); + var height = _wrapDefault('line-height', property, compactable); + var family = _wrapDefault('font-family', property, compactable); + var components = [style, variant, weight, stretch, size, height, family]; + var values = property.value; + var fuzzyMatched = 4; // style, variant, weight, and stretch + var index = 0; + var isStretchSet = false; + var isStretchValid; + var isStyleSet = false; + var isStyleValid; + var isVariantSet = false; + var isVariantValid; + var isWeightSet = false; + var isWeightValid; + var isSizeSet = false; + var appendableFamilyName = false; + + if (!values[index]) { + throw new InvalidPropertyError('Missing font values at ' + formatPosition(property.all[property.position][1][2][0]) + '. Ignoring.'); + } + + if (values.length == 1 && values[0][1] == 'inherit') { + style.value = variant.value = weight.value = stretch.value = size.value = height.value = family.value = values; + return components; + } + + if (values.length > 1 && _anyIsInherit(values)) { + throw new InvalidPropertyError('Invalid font values at ' + formatPosition(values[0][2][0]) + '. Ignoring.'); + } + + // fuzzy match style, variant, weight, and stretch on first elements + while (index < fuzzyMatched) { + isStretchValid = validator.isValidKeywordValue('font-stretch', values[index][1], true); + isStyleValid = validator.isValidKeywordValue('font-style', values[index][1], true); + isVariantValid = validator.isValidKeywordValue('font-variant', values[index][1], true); + isWeightValid = validator.isValidKeywordValue('font-weight', values[index][1], true); + + if (isStyleValid && !isStyleSet) { + style.value = [values[index]]; + isStyleSet = true; + } else if (isVariantValid && !isVariantSet) { + variant.value = [values[index]]; + isVariantSet = true; + } else if (isWeightValid && !isWeightSet) { + weight.value = [values[index]]; + isWeightSet = true; + } else if (isStretchValid && !isStretchSet) { + stretch.value = [values[index]]; + isStretchSet = true; + } else if (isStyleValid && isStyleSet || isVariantValid && isVariantSet || isWeightValid && isWeightSet || isStretchValid && isStretchSet) { + throw new InvalidPropertyError('Invalid font style / variant / weight / stretch value at ' + formatPosition(values[0][2][0]) + '. Ignoring.'); + } else { + break; + } + + index++; + } + + // now comes font-size ... + if (validator.isValidFontSize(values[index][1])) { + size.value = [values[index]]; + isSizeSet = true; + index++; + } else { + throw new InvalidPropertyError('Missing font size at ' + formatPosition(values[0][2][0]) + '. Ignoring.'); + } + + if (!values[index]) { + throw new InvalidPropertyError('Missing font family at ' + formatPosition(values[0][2][0]) + '. Ignoring.'); + } + + // ... and perhaps line-height + if (isSizeSet && values[index] && values[index][1] == FORWARD_SLASH && values[index + 1] && validator.isValidLineHeight(values[index + 1][1])) { + height.value = [values[index + 1]]; + index++; + index++; + } + + // ... and whatever comes next is font-family + family.value = []; + + while (values[index]) { + if (values[index][1] == MULTIPLEX_SEPARATOR) { + appendableFamilyName = false; + } else { + if (appendableFamilyName) { + family.value[family.value.length - 1][1] += ' ' + values[index][1]; + } else { + family.value.push(values[index]); + } + + appendableFamilyName = true; + } + + index++; + } + + if (family.value.length === 0) { + throw new InvalidPropertyError('Missing font family at ' + formatPosition(values[0][2][0]) + '. Ignoring.'); + } + + return components; +} + function fourValues(property, compactable) { var componentNames = compactable[property.name].components; var components = []; @@ -355,6 +478,7 @@ module.exports = { background: background, border: widthStyleColor, borderRadius: borderRadius, + font: font, fourValues: fourValues, listStyle: listStyle, multiplex: multiplex, diff --git a/lib/optimizer/level-2/can-override.js b/lib/optimizer/level-2/can-override.js index 2617234c..a6a49e67 100644 --- a/lib/optimizer/level-2/can-override.js +++ b/lib/optimizer/level-2/can-override.js @@ -46,6 +46,10 @@ function components(overrideCheckers) { }; } +function fontFamily(validator, value1, value2) { + return understandable(validator, value1, value2, 0, true); +} + function image(validator, value1, value2) { if (!understandable(validator, value1, value2, 0, true) && !validator.isValidImage(value2)) { return false; @@ -157,8 +161,11 @@ module.exports = { cursor: keywordWithGlobal('cursor'), display: keywordWithGlobal('display'), float: keywordWithGlobal('float'), - fontStyle: keywordWithGlobal('font-style'), left: unitOrKeywordWithGlobal('left'), + fontFamily: fontFamily, + fontStretch: keywordWithGlobal('font-stretch'), + fontStyle: keywordWithGlobal('font-style'), + fontVariant: keywordWithGlobal('font-variant'), fontWeight: keywordWithGlobal('font-weight'), listStyleType: keywordWithGlobal('list-style-type'), listStylePosition: keywordWithGlobal('list-style-position'), diff --git a/lib/optimizer/level-2/compactable.js b/lib/optimizer/level-2/compactable.js index 9cf334d4..5858aeea 100644 --- a/lib/optimizer/level-2/compactable.js +++ b/lib/optimizer/level-2/compactable.js @@ -469,18 +469,53 @@ var compactable = { canOverride: canOverride.property.float, defaultValue: 'none' }, + 'font': { + breakUp: breakUp.font, + canOverride: canOverride.generic.components([ + canOverride.property.fontStyle, + canOverride.property.fontVariant, + canOverride.property.fontWeight, + canOverride.property.fontStretch, + canOverride.generic.unit, + canOverride.generic.unit, + canOverride.property.fontFamily + ]), + components: [ + 'font-style', + 'font-variant', + 'font-weight', + 'font-stretch', + 'font-size', + 'line-height', + 'font-family' + ], + restore: restore.font, + shorthand: true + }, + 'font-family': { + canOverride: canOverride.property.fontFamily, + defaultValue: 'user|agent|specific' + }, 'font-size': { canOverride: canOverride.generic.unit, defaultValue: 'medium', shortestValue: '0' }, + 'font-stretch': { + canOverride: canOverride.property.fontStretch, + defaultValue: 'normal' + }, 'font-style': { canOverride: canOverride.property.fontStyle, defaultValue: 'normal' }, + 'font-variant': { + canOverride: canOverride.property.fontVariant, + defaultValue: 'normal' + }, 'font-weight': { canOverride: canOverride.property.fontWeight, - defaultValue: '400', + defaultValue: 'normal', shortestValue: '400' }, 'height': { diff --git a/lib/optimizer/level-2/restore.js b/lib/optimizer/level-2/restore.js index 149688c5..2f27ef5d 100644 --- a/lib/optimizer/level-2/restore.js +++ b/lib/optimizer/level-2/restore.js @@ -135,6 +135,54 @@ function borderRadius(property, compactable) { } } +function font(property, compactable) { + var components = property.components; + var restored = []; + var component; + var componentIndex = 0; + var fontFamilyIndex = 0; + + // first four components are optional + while (componentIndex < 4) { + component = components[componentIndex]; + + if (component.value[0][1] != compactable[component.name].defaultValue) { + Array.prototype.push.apply(restored, component.value); + } + + componentIndex++; + } + + // then comes font-size + Array.prototype.push.apply(restored, components[componentIndex].value); + componentIndex++; + + // then may come line-height + if (components[componentIndex].value[0][1] != compactable[components[componentIndex].name].defaultValue) { + Array.prototype.push.apply(restored, [[Token.PROPERTY_VALUE, Marker.FORWARD_SLASH]]); + Array.prototype.push.apply(restored, components[componentIndex].value); + } + + componentIndex++; + + // then comes font-family + while (components[componentIndex].value[fontFamilyIndex]) { + restored.push(components[componentIndex].value[fontFamilyIndex]); + + if (components[componentIndex].value[fontFamilyIndex + 1]) { + restored.push([Token.PROPERTY_VALUE, Marker.COMMA]); + } + + fontFamilyIndex++; + } + + if (isInheritOnly(restored)) { + return [restored[0]]; + } + + return restored; +} + function fourValues(property) { var components = property.components; var value1 = components[0].value[0]; @@ -227,6 +275,7 @@ function withoutDefaults(property, compactable) { module.exports = { background: background, borderRadius: borderRadius, + font: font, fourValues: fourValues, multiplex: multiplex, withoutDefaults: withoutDefaults diff --git a/lib/optimizer/validator.js b/lib/optimizer/validator.js index 35f041ea..04b0b3b0 100644 --- a/lib/optimizer/validator.js +++ b/lib/optimizer/validator.js @@ -156,11 +156,37 @@ var Keywords = { 'left': [ 'auto' ], + 'font-size': [ + 'large', + 'larger', + 'medium', + 'small', + 'smaller', + 'x-large', + 'x-small', + 'xx-large', + 'xx-small' + ], + 'font-stretch': [ + 'condensed', + 'expanded', + 'extra-condensed', + 'extra-expanded', + 'normal', + 'semi-condensed', + 'semi-expanded', + 'ultra-condensed', + 'ultra-expanded' + ], 'font-style': [ 'italic', 'normal', 'oblique' ], + 'font-variant': [ + 'normal', + 'small-caps' + ], 'font-weight': [ '100', '200', @@ -176,6 +202,9 @@ var Keywords = { 'lighter', 'normal' ], + 'line-height': [ + 'normal' + ], 'list-style-position': [ 'inside', 'outside' @@ -336,6 +365,10 @@ function isValidColorValue(value) { isValidHslaColor(value); } +function isValidFontSize(compatibleCssUnitRegex, value) { + return isValidUnit(compatibleCssUnitRegex, value) || Keywords['font-size'].indexOf(value) > -1; +} + function isValidFunction(value) { return !urlRegex.test(value) && cssFunctionAnyRegex.test(value); } @@ -364,6 +397,10 @@ function isValidKeywordValue(propertyName, value, includeGlobal) { return Keywords[propertyName].indexOf(value) > -1 || includeGlobal && isValidGlobalValue(value); } +function isValidLineHeight(compatibleCssUnitRegex, value) { + return isValidUnit(compatibleCssUnitRegex, value) || isValidNumber(value) || Keywords['line-height'].indexOf(value) > -1; +} + function isValidListStyleType(value) { return Keywords['list-style-type'].indexOf(value) > -1; } @@ -377,6 +414,10 @@ function isValidNamedColor(value) { return value !== 'auto' && (value === 'transparent' || value === 'inherit' || /^[a-zA-Z]+$/.test(value)); } +function isValidNumber(value) { + return ('' + parseFloat(value)) === value; +} + function isValidRgbaColor(value) { return value.length > 0 && value.indexOf('rgba(') === 0 && value.indexOf(')') === value.length - 1; } @@ -444,6 +485,7 @@ function validator(compatibility) { isValidBackgroundSizePart: isValidBackgroundSizePart, isValidColor: isValidColor, isValidColorValue: isValidColorValue, + isValidFontSize: isValidFontSize.bind(null, compatibleCssUnitRegex), isValidFunction: isValidFunction, isValidFunctionWithoutVendorPrefix: isValidFunctionWithoutVendorPrefix, isValidGlobalValue: isValidGlobalValue, @@ -451,6 +493,7 @@ function validator(compatibility) { isValidHslaColor: isValidHslaColor, isValidImage: isValidImage, isValidKeywordValue: isValidKeywordValue, + isValidLineHeight: isValidLineHeight.bind(null, compatibleCssUnitRegex), isValidListStylePosition: isValidListStylePosition, isValidListStyleType: isValidListStyleType, isValidNamedColor: isValidNamedColor, diff --git a/test/fixtures/blueprint-min.css b/test/fixtures/blueprint-min.css index c2ca18a2..d4a4020f 100644 --- a/test/fixtures/blueprint-min.css +++ b/test/fixtures/blueprint-min.css @@ -37,7 +37,7 @@ dfn,dl dt,strong,th{font-weight:700} sub,sup{line-height:0} abbr,acronym{border-bottom:1px dotted #666} pre{margin:1.5em 0;white-space:pre} -code,pre,tt{font:1em 'andale mono','lucida console',monospace;line-height:1.5} +code,pre,tt{font:1em/1.5 'andale mono','lucida console',monospace} label,legend{font-weight:700} ol,ul{margin:0 1.5em 1.5em 0;padding-left:1.5em} ul{list-style-type:disc} @@ -235,4 +235,4 @@ input.span-24,textarea.span-24{width:938px} hr{background:#ddd;color:#ddd;float:none;height:1px;margin:0 0 1.45em;border:none} hr.space{color:#fff;visibility:hidden} .clearfix:after,.container:after{content:"\0020";display:block;height:0;clear:both;visibility:hidden;overflow:hidden} -.clearfix,.container{display:block} \ No newline at end of file +.clearfix,.container{display:block} diff --git a/test/fixtures/bootstrap-min.css b/test/fixtures/bootstrap-min.css index abc80787..b3e55376 100644 --- a/test/fixtures/bootstrap-min.css +++ b/test/fixtures/bootstrap-min.css @@ -22,7 +22,7 @@ sub{bottom:-.25em} img{border:0} hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box} *,:after,:before,input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box} -code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em} +code,kbd,pre,samp{font-size:1em} button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit} .glyphicon,address{font-style:normal} button{overflow:visible} @@ -1453,4 +1453,4 @@ td.visible-print,th.visible-print{display:table-cell!important} @media print{ .visible-print-inline-block{display:inline-block!important} .hidden-print{display:none!important} -} \ No newline at end of file +} diff --git a/test/optimizer/level-2/break-up-test.js b/test/optimizer/level-2/break-up-test.js index 3f00a69a..34ad49ac 100644 --- a/test/optimizer/level-2/break-up-test.js +++ b/test/optimizer/level-2/break-up-test.js @@ -633,6 +633,446 @@ vows.describe(breakUp) } } }, + 'font': { + 'all values': { + 'topic': function () { + return _breakUp([ + [ + 'property', + ['property-name', 'font'], + ['property-value', 'italic'], + ['property-value', 'small-caps'], + ['property-value', 'bold'], + ['property-value', 'normal'], + ['property-value', '18px'], + ['property-value', '/'], + ['property-value', '16px'], + ['property-value', 'sans-serif'] + ] + ]); + }, + 'has 7 components': function (components) { + assert.lengthOf(components, 7); + }, + 'has font-style': function (components) { + assert.equal(components[0].name, 'font-style'); + assert.deepEqual(components[0].value, [['property-value', 'italic']]); + }, + 'has font-variant': function (components) { + assert.equal(components[1].name, 'font-variant'); + assert.deepEqual(components[1].value, [['property-value', 'small-caps']]); + }, + 'has font-weight': function (components) { + assert.equal(components[2].name, 'font-weight'); + assert.deepEqual(components[2].value, [['property-value', 'bold']]); + }, + 'has font-stretch': function (components) { + assert.equal(components[3].name, 'font-stretch'); + assert.deepEqual(components[3].value, [['property-value', 'normal']]); + }, + 'has font-size': function (components) { + assert.equal(components[4].name, 'font-size'); + assert.deepEqual(components[4].value, [['property-value', '18px']]); + }, + 'has line-height': function (components) { + assert.equal(components[5].name, 'line-height'); + assert.deepEqual(components[5].value, [['property-value', '16px']]); + }, + 'has font-family': function (components) { + assert.equal(components[6].name, 'font-family'); + assert.deepEqual(components[6].value, [['property-value', 'sans-serif']]); + } + }, + 'multiple font-family': { + 'topic': function () { + return _breakUp([ + [ + 'property', + ['property-name', 'font'], + ['property-value', 'italic'], + ['property-value', 'small-caps'], + ['property-value', 'bold'], + ['property-value', 'normal'], + ['property-value', '18px'], + ['property-value', '/'], + ['property-value', '16px'], + ['property-value', 'Helvetica'], + ['property-value', ','], + ['property-value', 'Arial'], + ['property-value', ','], + ['property-value', 'sans-serif'] + ] + ]); + }, + 'has all font-family': function (components) { + assert.equal(components[6].name, 'font-family'); + assert.deepEqual(components[6].value, [['property-value', 'Helvetica'], ['property-value', 'Arial'], ['property-value', 'sans-serif']]); + } + }, + 'no line-height': { + 'topic': function () { + return _breakUp([ + [ + 'property', + ['property-name', 'font'], + ['property-value', 'italic'], + ['property-value', 'small-caps'], + ['property-value', 'bold'], + ['property-value', 'normal'], + ['property-value', '18px'], + ['property-value', 'sans-serif'] + ] + ]); + }, + 'has 7 components': function (components) { + assert.lengthOf(components, 7); + }, + 'has font-size': function (components) { + assert.equal(components[4].name, 'font-size'); + assert.deepEqual(components[4].value, [['property-value', '18px']]); + }, + 'has line-height': function (components) { + assert.equal(components[5].name, 'line-height'); + assert.deepEqual(components[5].value, [['property-value', 'normal']]); + }, + 'has font-family': function (components) { + assert.equal(components[6].name, 'font-family'); + assert.deepEqual(components[6].value, [['property-value', 'sans-serif']]); + } + }, + 'no line-height or fuzzy matched properties': { + 'topic': function () { + return _breakUp([ + [ + 'property', + ['property-name', 'font'], + ['property-value', '18px'], + ['property-value', 'sans-serif'] + ] + ]); + }, + 'has 7 components': function (components) { + assert.lengthOf(components, 7); + }, + 'has font-style': function (components) { + assert.equal(components[0].name, 'font-style'); + assert.deepEqual(components[0].value, [['property-value', 'normal']]); + }, + 'has font-variant': function (components) { + assert.equal(components[1].name, 'font-variant'); + assert.deepEqual(components[1].value, [['property-value', 'normal']]); + }, + 'has font-weight': function (components) { + assert.equal(components[2].name, 'font-weight'); + assert.deepEqual(components[2].value, [['property-value', 'normal']]); + }, + 'has font-stretch': function (components) { + assert.equal(components[3].name, 'font-stretch'); + assert.deepEqual(components[3].value, [['property-value', 'normal']]); + }, + 'has font-size': function (components) { + assert.equal(components[4].name, 'font-size'); + assert.deepEqual(components[4].value, [['property-value', '18px']]); + }, + 'has line-height': function (components) { + assert.equal(components[5].name, 'line-height'); + assert.deepEqual(components[5].value, [['property-value', 'normal']]); + }, + 'has font-family': function (components) { + assert.equal(components[6].name, 'font-family'); + assert.deepEqual(components[6].value, [['property-value', 'sans-serif']]); + } + }, + 'some fuzzy matched properties #1': { + 'topic': function () { + return _breakUp([ + [ + 'property', + ['property-name', 'font'], + ['property-value', 'bold'], + ['property-value', 'small-caps'], + ['property-value', '18px'], + ['property-value', 'sans-serif'] + ] + ]); + }, + 'has 7 components': function (components) { + assert.lengthOf(components, 7); + }, + 'has font-style': function (components) { + assert.equal(components[0].name, 'font-style'); + assert.deepEqual(components[0].value, [['property-value', 'normal']]); + }, + 'has font-variant': function (components) { + assert.equal(components[1].name, 'font-variant'); + assert.deepEqual(components[1].value, [['property-value', 'small-caps']]); + }, + 'has font-weight': function (components) { + assert.equal(components[2].name, 'font-weight'); + assert.deepEqual(components[2].value, [['property-value', 'bold']]); + }, + 'has font-stretch': function (components) { + assert.equal(components[3].name, 'font-stretch'); + assert.deepEqual(components[3].value, [['property-value', 'normal']]); + } + }, + 'some fuzzy matched properties #2': { + 'topic': function () { + return _breakUp([ + [ + 'property', + ['property-name', 'font'], + ['property-value', 'ultra-condensed'], + ['property-value', 'italic'], + ['property-value', '18px'], + ['property-value', 'sans-serif'] + ] + ]); + }, + 'has 7 components': function (components) { + assert.lengthOf(components, 7); + }, + 'has font-style': function (components) { + assert.equal(components[0].name, 'font-style'); + assert.deepEqual(components[0].value, [['property-value', 'italic']]); + }, + 'has font-variant': function (components) { + assert.equal(components[1].name, 'font-variant'); + assert.deepEqual(components[1].value, [['property-value', 'normal']]); + }, + 'has font-weight': function (components) { + assert.equal(components[2].name, 'font-weight'); + assert.deepEqual(components[2].value, [['property-value', 'normal']]); + }, + 'has font-stretch': function (components) { + assert.equal(components[3].name, 'font-stretch'); + assert.deepEqual(components[3].value, [['property-value', 'ultra-condensed']]); + } + }, + 'repeated fuzzy matched value': { + 'topic': function () { + return _breakUp([ + [ + 'property', + ['property-name', 'font'], + ['property-value', 'italic', [[0, 13, undefined]]], + ['property-value', 'italic'], + ['property-value', '18px'], + ['property-value', 'sans-serif'] + ] + ]); + }, + 'has 0 components': function (components) { + assert.lengthOf(components, 0); + } + }, + 'line-height and font-size as functions': { + 'topic': function () { + return _breakUp([ + [ + 'property', + ['property-name', 'font'], + ['property-value', 'calc(27px / 2)', [[0, 13, undefined]]], + ['property-value', '/'], + ['property-value', 'calc(31px / 2)'], + ['property-value', 'sans-serif'] + ] + ]); + }, + 'has 0 components': function (components) { + assert.lengthOf(components, 0); + } + }, + 'missing font size value': { + 'topic': function () { + return _breakUp([ + [ + 'property', + ['property-name', 'font'], + ['property-value', 'italic', [[0, 13, undefined]]], + ['property-value', 'sans-serif'] + ] + ]); + }, + 'has 0 components': function (components) { + assert.lengthOf(components, 0); + } + }, + 'missing font family value': { + 'topic': function () { + return _breakUp([ + [ + 'property', + ['property-name', 'font'], + ['property-value', 'italic', [[0, 13, undefined]]], + ['property-value', '12px'] + ] + ]); + }, + 'has 0 components': function (components) { + assert.lengthOf(components, 0); + } + }, + 'missing font family value after line height': { + 'topic': function () { + return _breakUp([ + [ + 'property', + ['property-name', 'font'], + ['property-value', 'italic', [[0, 13, undefined]]], + ['property-value', '12px'], + ['property-value', '/'], + ['property-value', '12px'] + ] + ]); + }, + 'has 0 components': function (components) { + assert.lengthOf(components, 0); + } + }, + 'missing font family when only commas given': { + 'topic': function () { + return _breakUp([ + [ + 'property', + ['property-name', 'font'], + ['property-value', 'italic', [[0, 13, undefined]]], + ['property-value', '12px'], + ['property-value', ','], + ['property-value', ','] + ] + ]); + }, + 'has 0 components': function (components) { + assert.lengthOf(components, 0); + } + }, + 'missing all values': { + 'topic': function () { + return _breakUp([ + [ + 'property', + ['property-name', 'font', [[0, 13, undefined]]] + ] + ]); + }, + 'has 0 components': function (components) { + assert.lengthOf(components, 0); + } + }, + 'values after font family': { + 'topic': function () { + return _breakUp([ + [ + 'property', + ['property-name', 'font'], + ['property-value', '12px'], + ['property-value', 'Helvetica'], + ['property-value', ','], + ['property-value', 'sans-serif'], + ['property-value', 'italic'] + ] + ]); + }, + 'has 7 components': function (components) { + assert.lengthOf(components, 7); + }, + 'has font-family': function (components) { + assert.equal(components[6].name, 'font-family'); + assert.deepEqual(components[6].value, [['property-value', 'Helvetica'], ['property-value', 'sans-serif italic']]); + } + }, + 'single inherit': { + 'topic': function () { + return _breakUp([ + [ + 'property', + ['property-name', 'font'], + ['property-value', 'inherit'] + ] + ]); + }, + 'has 7 components': function (components) { + assert.lengthOf(components, 7); + }, + 'has font-style': function (components) { + assert.equal(components[0].name, 'font-style'); + assert.deepEqual(components[0].value, [['property-value', 'inherit']]); + }, + 'has font-variant': function (components) { + assert.equal(components[1].name, 'font-variant'); + assert.deepEqual(components[1].value, [['property-value', 'inherit']]); + }, + 'has font-weight': function (components) { + assert.equal(components[2].name, 'font-weight'); + assert.deepEqual(components[2].value, [['property-value', 'inherit']]); + }, + 'has font-stretch': function (components) { + assert.equal(components[3].name, 'font-stretch'); + assert.deepEqual(components[3].value, [['property-value', 'inherit']]); + }, + 'has font-size': function (components) { + assert.equal(components[4].name, 'font-size'); + assert.deepEqual(components[4].value, [['property-value', 'inherit']]); + }, + 'has line-height': function (components) { + assert.equal(components[5].name, 'line-height'); + assert.deepEqual(components[5].value, [['property-value', 'inherit']]); + }, + 'has font-family': function (components) { + assert.equal(components[6].name, 'font-family'); + assert.deepEqual(components[6].value, [['property-value', 'inherit']]); + } + }, + 'multiple inherit': { + 'topic': function () { + return _breakUp([ + [ + 'property', + ['property-name', 'font'], + ['property-value', 'inherit', [[0, 13, undefined]]], + ['property-value', 'inherit'] + ] + ]); + }, + 'has 0 components': function (components) { + assert.lengthOf(components, 0); + } + }, + 'mixed inherit #1': { + 'topic': function () { + return _breakUp([ + [ + 'property', + ['property-name', 'font'], + ['property-value', 'inherit', [[0, 13, undefined]]], + ['property-value', '12px'], + ['property-value', 'sans-serif'] + ] + ]); + }, + 'has 0 components': function (components) { + assert.lengthOf(components, 0); + } + }, + 'mixed inherit #2': { + 'topic': function () { + return _breakUp([ + [ + 'property', + ['property-name', 'font'], + ['property-value', 'bold', [[0, 13, undefined]]], + ['property-value', 'inherit'], + ['property-value', '12px'], + ['property-value', 'sans-serif'] + ] + ]); + }, + 'has 0 components': function (components) { + assert.lengthOf(components, 0); + } + } + }, 'four values': { 'four given': { 'topic': function () { diff --git a/test/optimizer/level-2/properties/override-properties-test.js b/test/optimizer/level-2/properties/override-properties-test.js index 6c18c62a..1d85a85e 100644 --- a/test/optimizer/level-2/properties/override-properties-test.js +++ b/test/optimizer/level-2/properties/override-properties-test.js @@ -1606,6 +1606,118 @@ vows.describe(optimizeProperties) } } }) + .addBatch({ + 'font shorthand and longhand': { + 'topic': function () { + return _optimize('.block{font:12px sans-serif;font-weight:bold}'); + }, + 'into': function (properties) { + assert.deepEqual(properties, [ + [ + 'property', + ['property-name', 'font', [[1, 7, undefined]]], + ['property-value', 'bold', [[1, 40, undefined]]], + ['property-value', '12px', [[1, 12, undefined]]], + ['property-value', 'sans-serif', [[1, 17, undefined]]] + ] + ]); + } + }, + 'font shorthand and line-height': { + 'topic': function () { + return _optimize('.block{font:12px sans-serif;line-height:16px}'); + }, + 'into': function (properties) { + assert.deepEqual(properties, [ + [ + 'property', + ['property-name', 'font', [[1, 7, undefined]]], + ['property-value', '12px', [[1, 12, undefined]]], + ['property-value', '/'], + ['property-value', '16px', [[1, 40, undefined]]], + ['property-value', 'sans-serif', [[1, 17, undefined]]] + ] + ]); + } + }, + 'font longhand and shorthand': { + 'topic': function () { + return _optimize('.block{font-stretch:extra-condensed;font:12px sans-serif}'); + }, + 'into': function (properties) { + assert.deepEqual(properties, [ + [ + 'property', + ['property-name', 'font', [[1, 36, undefined]]], + ['property-value', '12px', [[1, 41, undefined]]], + ['property-value', 'sans-serif', [[1, 46, undefined]]] + ] + ]); + } + }, + 'font shorthand with overriddable shorthand': { + 'topic': function () { + return _optimize('.block{font:bold 14px serif;font:12px sans-serif}'); + }, + 'into': function (properties) { + assert.deepEqual(properties, [ + [ + 'property', + ['property-name', 'font', [[1, 7, undefined]]], + ['property-value', '12px', [[1, 33, undefined]]], + ['property-value', 'sans-serif', [[1, 38, undefined]]] + ] + ]); + } + }, + 'font shorthand with non-overriddable shorthand': { + 'topic': function () { + return _optimize('.block{font:bold 14px serif;font:16px -moz-sans-serif}'); + }, + 'into': function (properties) { + assert.deepEqual(properties, [ + [ + 'property', + ['property-name', 'font', [[1, 7, undefined]]], + ['property-value', 'bold', [[1, 12, undefined]]], + ['property-value', '14px', [[1, 17, undefined]]], + ['property-value', 'serif', [[1, 22, undefined]]] + ], + [ + 'property', + ['property-name', 'font', [[1, 28, undefined]]], + ['property-value', '16px', [[1, 33, undefined]]], + ['property-value', '-moz-sans-serif', [[1, 38, undefined]]] + ] + ]); + } + }, + 'font shorthand after non-component longhands': { + 'topic': function () { + return _optimize('.block{font-kerning:none;font-synthesis:none;font:14px serif}'); + }, + 'into': function (properties) { + assert.deepEqual(properties, [ + [ + 'property', + ['property-name', 'font-kerning', [[1, 7, undefined]]], + ['property-value', 'none', [[1, 20, undefined]]] + ], + [ + 'property', + ['property-name', 'font-synthesis', [[1, 25, undefined]]], + ['property-value', 'none', [[1, 40, undefined]]] + ], + [ + 'property', + ['property-name', 'font', [[1, 45, undefined]]], + ['property-value', '14px', [[1, 50, undefined]]], + ['property-value', 'serif', [[1, 55, undefined]]] + ] + ]); + } + } + }) .addBatch({ 'padding !important then not !important': { 'topic': function () { diff --git a/test/optimizer/level-2/restore-test.js b/test/optimizer/level-2/restore-test.js index 27f42036..e7c8f799 100644 --- a/test/optimizer/level-2/restore-test.js +++ b/test/optimizer/level-2/restore-test.js @@ -688,6 +688,124 @@ vows.describe(restore) ]); } }, + 'font with all non-default values': { + 'topic': function () { + return _restore( + _breakUp([ + 'property', + ['property-name', 'font'], + ['property-value', 'italic'], + ['property-value', 'small-caps'], + ['property-value', 'bold'], + ['property-value', 'ultra-condensed'], + ['property-value', '12px'], + ['property-value', '/'], + ['property-value', '16px'], + ['property-value', 'sans-serif'] + ]) + ); + }, + 'gives right value back': function (restoredValue) { + assert.deepEqual(restoredValue, [ + ['property-value', 'italic'], + ['property-value', 'small-caps'], + ['property-value', 'bold'], + ['property-value', 'ultra-condensed'], + ['property-value', '12px'], + ['property-value', '/'], + ['property-value', '16px'], + ['property-value', 'sans-serif'] + ]); + } + }, + 'font with some default values': { + 'topic': function () { + return _restore( + _breakUp([ + 'property', + ['property-name', 'font'], + ['property-value', 'normal'], + ['property-value', 'small-caps'], + ['property-value', 'normal'], + ['property-value', 'ultra-condensed'], + ['property-value', '12px'], + ['property-value', '/'], + ['property-value', '16px'], + ['property-value', 'sans-serif'] + ]) + ); + }, + 'gives right value back': function (restoredValue) { + assert.deepEqual(restoredValue, [ + ['property-value', 'small-caps'], + ['property-value', 'ultra-condensed'], + ['property-value', '12px'], + ['property-value', '/'], + ['property-value', '16px'], + ['property-value', 'sans-serif'] + ]); + } + }, + 'font without line height': { + 'topic': function () { + return _restore( + _breakUp([ + 'property', + ['property-name', 'font'], + ['property-value', '12px'], + ['property-value', 'sans-serif'] + ]) + ); + }, + 'gives right value back': function (restoredValue) { + assert.deepEqual(restoredValue, [ + ['property-value', '12px'], + ['property-value', 'sans-serif'] + ]); + } + }, + 'font with multiple font family values': { + 'topic': function () { + return _restore( + _breakUp([ + 'property', + ['property-name', 'font'], + ['property-value', '12px'], + ['property-value', '"Helvetica Neue"'], + ['property-value', ','], + ['property-value', 'Helvetica'], + ['property-value', ','], + ['property-value', 'sans-serif'] + ]) + ); + }, + 'gives right value back': function (restoredValue) { + assert.deepEqual(restoredValue, [ + ['property-value', '12px'], + ['property-value', '"Helvetica Neue"'], + ['property-value', ','], + ['property-value', 'Helvetica'], + ['property-value', ','], + ['property-value', 'sans-serif'] + ]); + } + }, + 'font with inherit': { + 'topic': function () { + return _restore( + _breakUp([ + 'property', + ['property-name', 'font'], + ['property-value', 'inherit'] + ]) + ); + }, + 'gives right value back': function (restoredValue) { + assert.deepEqual(restoredValue, [ + ['property-value', 'inherit'] + ]); + } + }, 'list with some values': { 'topic': function () { return _restore(