See #254 - implements `font` compacting.
authorJakub Pawlowicz <contact@jakubpawlowicz.com>
Fri, 24 Mar 2017 14:42:10 +0000 (15:42 +0100)
committerJakub Pawlowicz <contact@jakubpawlowicz.com>
Wed, 5 Apr 2017 09:00:05 +0000 (11:00 +0200)
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.

README.md
lib/optimizer/level-2/break-up.js
lib/optimizer/level-2/can-override.js
lib/optimizer/level-2/compactable.js
lib/optimizer/level-2/restore.js
lib/optimizer/validator.js
test/fixtures/blueprint-min.css
test/fixtures/bootstrap-min.css
test/optimizer/level-2/break-up-test.js
test/optimizer/level-2/properties/override-properties-test.js
test/optimizer/level-2/restore-test.js

index f4e04bc..d130d65 100644 (file)
--- 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
 
index 9e44d5a..905516b 100644 (file)
@@ -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,
index 2617234..a6a49e6 100644 (file)
@@ -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'),
index 9cf334d..5858aee 100644 (file)
@@ -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': {
index 149688c..2f27ef5 100644 (file)
@@ -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
index 35f041e..04b0b3b 100644 (file)
@@ -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,
index c2ca18a..d4a4020 100644 (file)
@@ -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}
index abc8078..b3e5537 100644 (file)
@@ -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
+}
index 3f00a69..34ad49a 100644 (file)
@@ -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 () {
index 6c18c62..1d85a85 100644 (file)
@@ -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 () {
index 27f4203..e7c8f79 100644 (file)
@@ -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(