text: (function(root){
  
  /*
   * General Value Types definition
   * they return an object of arrays of type { <name>: [<start-value>, <end-value>], ... }
   */
  var values = {
      'length' : function() {
          // http://www.w3.org/TR/css3-values/#lengths
          return {
              // CSS Values and Module Level 3
              // ch: ['1ch', '10ch'],
              // rem: ['1rem', '10rem'],
              // vw: ['1vw', '10vw'],
              // vh: ['1vh', '10vh'],
              // vmin: ['1vmin', '10vmin'],
              // vmax: ['1vmax', '10vmax'],
              // CSS Values and Module Level 2
              pt: ['1pt', '10pt'],
              pc: ['1pc', '10pc'],
              px: ['1px', '10px'],
              // CSS Values and Module Level 1
              em: ['1em', '10em'],
              ex: ['1ex', '10ex'],
              mm: ['1mm', '10mm'],
              cm: ['1cm', '10cm'],
              'in': ['1in', '10in']
          };
      },
      'length-em': function() {
          return {
              em: ['1.1em', '1.5em']
          };
      },
      'percentage': function() {
          // http://www.w3.org/TR/css3-values/#percentages
          return {
              '%': ['33%', '80%']
          };
      },
      'color': function() {
          // http://www.w3.org/TR/css3-values/#colors
          // http://www.w3.org/TR/css3-color/
          return {
              rgba: ['rgba(100,100,100,1)', 'rgba(10,10,10,0.4)']
          };
      },
      'rectangle': function() {
          // http://www.w3.org/TR/CSS2/visufx.html#value-def-shape
          return {
              rectangle: ['rect(10px,10px,10px,10px)', 'rect(15px,15px,5px,5px)']
          };
      },
      'font-weight': function() {
          // http://www.w3.org/TR/css3-fonts/#font-weight-prop
          return {
              keyword: ["normal", "bold"],
              numeric: ["100", "900"]
          };
      },
      'number': function() {
          // http://www.w3.org/TR/css3-values/#number
          return {
              integer: ["1", "10"],
              decimal: ["1.1", "9.55"]
          };
      },
      'number[0,1]': function() {
          // http://www.w3.org/TR/css3-values/#number
          // applies to [0,1]-ranged properties like opacity
          return {
              "zero-to-one": ["0.2", "0.9"]
          };
      },
      'integer': function() {
          // http://www.w3.org/TR/css3-values/#integer
          return {
              integer: ["1", "10"]
          };
      },
      'shadow': function() {
          // http://www.w3.org/TR/css-text-decor-3/#text-shadow-property
          return {
              shadow: ['rgba(0,0,0,0.1) 5px 6px 7px', 'rgba(10,10,10,0.9) 5px 6px 7px']
          };
      },
      'visibility': function() {
          // http://www.w3.org/TR/CSS2/visufx.html#visibility
          return {
              keyword: ['visible', 'hidden', {discrete: true}]
          };
      },
      // types reqired for non-specified properties
      'border-radius': function() {
          return {
              px: ['1px', '10px'],
              "px-px": ['1px 3px', '10px 13px']
          };
      },
      'image' : function() {
          var prefix = getValueVendorPrefix('background-image', 'linear-gradient(top, hsl(0, 80%, 70%), #bada55)');
          return {
              // Chrome implements this
              url: ['url(support/one.gif)', 'url(support/two.gif)'],
              data: ['url(data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=)', 'url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==)'],
              // A hunch, as from the spec:
              // http://www.w3.org/TR/css3-transitions/#animatable-types
              // gradient: interpolated via the positions and colors of each stop. They must have the same type (radial or linear) and same number of stops in order to be animated. Note: [CSS3-IMAGES] may extend this definition.
              gradient: [prefix + 'linear-gradient(top, hsl(0, 80%, 70%), #bada55)', prefix + 'linear-gradient(top, #bada55, hsl(0, 80%, 70%))']
          };
      },
      'background-size': function() {
          return {
              keyword: ['cover', 'contain']
          };
      },
      'box-shadow': function() {
          // http://www.w3.org/TR/css3-background/#ltshadowgt
          return {
              shadow: ['60px -16px teal', '60px -16px red']
          };
      },
      'vertical': function() {
          return {
              keyword: ['top', 'bottom']
          };
      },
      'horizontal': function() {
          return {
              keyword: ['left', 'right']
          };
      },
      'font-stretch': function() {
          return {
              keyword: ['condensed', 'expanded']
          };
      },
      'transform': function() {
          return {
              rotate: ['rotate(10deg)', 'rotate(20deg)']
          };
      },
      'position': function() {
          return {
              'static to absolute': ['static', 'absolute', {discrete: true}],
              'relative to absolute': ['relative', 'absolute', {discrete: true}],
              'absolute to fixed': ['absolute', 'fixed', {discrete: true}]
          };
      },
      'display': function() {
          return {
              'static to absolute': ['none', 'block', {discrete: true}],
              'block to inline-block': ['block', 'inline-block', {discrete: true}]
          };
      },
      'object-view-box': function() {
          return {
              inset: ['inset(10% 10% 20% 20%)', 'inset(20% 20% 30% 30%)'],
              rect: ['rect(10px 20px 30px 40px)', 'rect(20px 30px 40px 50px)'],
              xywh: ['xywh(10px 20px 30px 40px)', 'xywh(20px 30px 40px 50px)'],
          };
      }
  };
  
  /*
   * Property to Type table
   * (as stated in specification)
   */
  var properties = {
      'background-color': ['color'],
      'background-position': ['length', 'percentage'],
  
      'border-top-width': ['length'],
      'border-right-width': ['length'],
      'border-bottom-width': ['length'],
      'border-left-width': ['length'],
  
      'border-top-color': ['color'],
      'border-right-color': ['color'],
      'border-bottom-color': ['color'],
      'border-left-color': ['color'],
  
      'padding-bottom': ['length'],
      'padding-left': ['length'],
      'padding-right': ['length'],
      'padding-top': ['length'],
  
      'margin-bottom': ['length'],
      'margin-left': ['length'],
      'margin-right': ['length'],
      'margin-top': ['length'],
  
      'height': ['length', 'percentage'],
      'width': ['length', 'percentage'],
      'min-height': ['length', 'percentage'],
      'min-width': ['length', 'percentage'],
      'max-height': ['length', 'percentage'],
      'max-width': ['length', 'percentage'],
  
      'top': ['length', 'percentage'],
      'right': ['length', 'percentage'],
      'bottom': ['length', 'percentage'],
      'left': ['length', 'percentage'],
  
      'color': ['color'],
      'font-size': ['length', 'percentage'],
      'font-weight': ['font-weight'],
      'line-height': ['number', 'length', 'percentage'],
      'letter-spacing': ['length'],
      // Note: percentage is Level3 and not implemented anywhere yet
      // https://drafts.csswg.org/css3-text/#word-spacing
      'word-spacing': ['length', 'percentage'],
      'text-indent': ['length', 'percentage'],
      'text-shadow': ['shadow'],
  
      'outline-color': ['color'],
      // outline-offset <integer> used to be an error in the spec
      'outline-offset': ['length'],
      'outline-width': ['length'],
  
      'clip': ['rectangle'],
  
      'vertical-align': ['length', 'percentage'],
      'opacity': ['number[0,1]'],
      'visibility': ['visibility'],
      'z-index': ['integer']
  };
  
  /*
   * Property to Type table
   * (missing value-types of specified properties)
   */
  var missing_properties = {
      'margin-bottom': ['percentage'],
      'margin-left': ['percentage'],
      'margin-right': ['percentage'],
      'margin-top': ['percentage'],
      'padding-bottom': ['percentage'],
      'padding-left': ['percentage'],
      'padding-right': ['percentage'],
      'padding-top': ['percentage'],
      'vertical-align': ['vertical']
  };
  
  /*
   * Property to Type table
   * (properties that haven't been specified but implemented)
   */
  var unspecified_properties = {
      // http://oli.jp/2010/css-animatable-properties/
      'border-top-left-radius': ['border-radius'],
      'border-top-right-radius': ['border-radius'],
      'border-bottom-left-radius': ['border-radius'],
      'border-bottom-right-radius': ['border-radius'],
      'background-image': ['image'],
      'background-size': ['background-size'],
      // https://drafts.csswg.org/css3-background/#the-box-shadow
      // Animatable:   yes, except between inner and outer shadows (Transition to/from an absent shadow is a transition to/from ‘0 0 transparent’ or ‘0 0 transparent inset’, as appropriate.)
      'box-shadow': ['box-shadow'],
      'font-size-adjust': ['number'],
      'font-stretch': ['font-stretch'],
      'marker-offset': ['length'],
      'text-decoration-color': ['color'],
      'column-count': ['integer'],
      'column-gap': ['length'],
      'column-rule-color': ['color'],
      'column-rule-width': ['length'],
      'column-width': ['length'],
      'transform': ['transform'],
      'transform-origin': ['horizontal'],
      'zoom': ['number'],
      'outline-radius-topleft': ['length', 'percentage'],
      'outline-radius-topright': ['length', 'percentage'],
      'outline-radius-bottomright': ['length', 'percentage'],
      'outline-radius-bottomleft': ['length', 'percentage'],
      'display': ['display'],
      'position': ['position'],
      'object-view-box': ['object-view-box']
  };
  
  /*
   * additional styles required to actually render
   * (different browsers expect different environment)
   */
  var additional_styles = {
      // all browsers
      'border-top-width': {'border-top-style' : 'solid'},
      'border-right-width': {'border-right-style' : 'solid'},
      'border-bottom-width': {'border-bottom-style' : 'solid'},
      'border-left-width': {'border-left-style' : 'solid'},
      'top': {'position': 'absolute'},
      'right': {'position': 'absolute'},
      'bottom': {'position': 'absolute'},
      'left': {'position': 'absolute'},
      'z-index': {'position': 'absolute'},
      'outline-offset': {'outline-style': 'solid'},
      'outline-width': {'outline-style': 'solid'},
      'word-spacing': {'width': '100px', 'height': '100px'},
      // unspecified properties
      'column-rule-width': {'column-rule-style': 'solid'},
      'position': {'width': '50px', 'height': '50px', top: '10px', left: '50px'}
  };
  
  /*
   * additional styles required *on the parent* to actually render
   * (different browsers expect different environment)
   */
  var parent_styles = {
      'border-top-width': {'border-top-style' : 'solid'},
      'border-right-width': {'border-right-style' : 'solid'},
      'border-bottom-width': {'border-bottom-style' : 'solid'},
      'border-left-width': {'border-left-style' : 'solid'},
      'height': {'width': '100px', 'height': '100px'},
      'min-height': {'width': '100px', 'height': '100px'},
      'max-height': {'width': '100px', 'height': '100px'},
      'width': {'width': '100px', 'height': '100px'},
      'min-width': {'width': '100px', 'height': '100px'},
      'max-width': {'width': '100px', 'height': '100px'},
      // unspecified properties
      'position': {'position': 'relative', 'width': '100px', 'height': '100px'},
      // inheritance tests
      'top': {'width': '100px', 'height': '100px', 'position': 'relative'},
      'right': {'width': '100px', 'height': '100px', 'position': 'relative'},
      'bottom': {'width': '100px', 'height': '100px', 'position': 'relative'},
      'left': {'width': '100px', 'height': '100px', 'position': 'relative'}
  };
  
  
  function assemble(props) {
      var tests = [];
  
      // assemble tests
      for (var property in props) {
          props[property].forEach(function(type) {
              var _values = values[type](property);
              Object.keys(_values).forEach(function(unit) {
                  var data = {
                      name: property + ' ' + type + '(' + unit + ')',
                      property: property,
                      valueType : type,
                      unit : unit,
                      parentStyle: extend({}, parent_styles[property] || {}),
                      from: extend({}, additional_styles[property] || {}),
                      to: {}
                  };
  
                  data.from[property] = _values[unit][0];
                  data.to[property] = _values[unit][1];
                  data.flags = _values[unit][2] || {};
  
                  tests.push(data);
              });
          });
      }
  
      return tests;
  }
  
  root.getPropertyTests = function() {
      return assemble(properties);
  };
  
  root.getMissingPropertyTests = function() {
      return assemble(missing_properties);
  };
  
  root.getUnspecifiedPropertyTests = function() {
      return assemble(unspecified_properties);
  };
  
  root.getFontSizeRelativePropertyTests = function() {
      var accepted = {};
  
      for (var key in properties) {
          if (!Object.prototype.hasOwnProperty.call(properties, key) || key === "font-size") {
              continue;
          }
  
          if (properties[key].indexOf('length') > -1) {
              accepted[key] = ['length-em'];
          }
      }
  
      return assemble(accepted);
  };
  
  root.filterPropertyTests = function(tests, names) {
      var allowed = {};
      var accepted = [];
  
      if (typeof names === "string") {
          names = [names];
      }
  
      if (!(names instanceof RegExp)) {
          names.forEach(function(name) {
              allowed[name] = true;
          });
      }
  
      tests.forEach(function(test) {
          if (names instanceof RegExp) {
              if (!test.name.match(names)) {
                  return;
              }
          } else if (!allowed[test.name]) {
              return;
          }
  
          accepted.push(test);
      });
  
      return accepted;
  };
  
  })(window);
  