<!DOCTYPE html>
<meta charset="utf-8">
<title>CSSOM serialize values</title>
<link rel="help" href="https://drafts.csswg.org/cssom/#serializing-css-values">
<meta name="author" title="Josh Matthews" href="mailto:josh@joshmatthews.net">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<body>
  <div id="log"></div>
  <div id="parent"></div>
  <script>
    function iterable(values) {
      var i = 0;
      return function() {
        if (i < values.length) {
          return values[i++];
        }
        return null;
      }
    }

    function color() {
      var colors = ['black', 'red', 'rgb(50, 75, 100)', 'rgba(5, 7, 10, 0.5)'];
      return iterable(colors);
    }

    function percentage() {
      var values = ["5%", {actual: ".5%", serialized: "0.5%"}];
      return iterable(values);
    }

    function negative_percentage() {
      var values = ["-5%", {actual: "-.5%", serialized: "-0.5%"}];
      return iterable(values);
    }

    function length() {
      var values = ["0px", "1px", {actual: ".1em", serialized: "0.1em"}];
      return iterable(values);
    }

    function negative_length() {
      var values = [{actual: "-0px", serialized: "0px"},
                    "-1px", {actual: "-.1em", serialized: "-0.1em"}];
      return iterable(values);
    }

    function degree() {
      var values = ["87deg"];
      return iterable(values);
    }

    function uri() {
      var values = ["url(\"http://localhost/\")",
        {actual: "url(http://localhost/)",
         serialized: "url(\"http://localhost/\")"}];
      return iterable(values);
    }

    function border_style() {
      var values = ['none', 'hidden', 'dotted', 'dashed', 'solid', 'double', 'groove', 'ridge',
                    'inset', 'outset'];
      return iterable(values);
    }

    function border_style_without_hidden() {
      var values = ['none', 'dotted', 'dashed', 'solid', 'double', 'groove', 'ridge',
                    'inset', 'outset'];
      return iterable(values);
    }

    function integer() {
      var values = ['0', '101', '-51'];
      return iterable(values);
    }

    function nonzero_positive_integer() {
      var values = ['101'];
      return iterable(values);
    }

    function shape() {
      var values = ['rect(1em, auto, 0.5px, 2000em)'];
      return iterable(values);
    }

    function string() {
      var values = ['"string"', {actual: "'string'", serialized: '"string"'}];
      return iterable(values);
    }

    function counter() {
      var values = ['counter(par-num)',
                    { actual: 'counter(par-num, decimal)', serialized: 'counter(par-num)' },
                    'counter(par-num, upper-roman)'];
      return iterable(values);
    }

    function attr() {
      var values = ['attr(foo-bar)', 'attr(foo_bar)'];
      return iterable(values);
    }

    function family_name() {
      var values = ['Arial', {actual: "'Lucida Grande'", serialized: '"Lucida Grande"'}];
      return iterable(values);
    }

    function generic_family() {
      var values = ['serif', 'sans-serif'];
      return iterable(values);
    }

    function absolute_size() {
      var values = ['xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large'];
      return iterable(values);
    }

    function relative_size() {
      var values = ['larger', 'smaller'];
      return iterable(values);
    }

    function number() {
      var values = ['0', {'actual': '-0', serialized: '0'}, '1000', '-5123', '0.9', '-0.09'];
      return iterable(values);
    }

    function positive_number() {
      var values = ['0', {'actual': '-0', serialized: '0'}, '1000', '0.9'];
      return iterable(values);
    }

    function generate_inline_style(name, value) {
      if (value) {
        return {'declaration': name + ": " + value.actual,
                'value': value.actual,
                'result': value.expected};
      }
      return null;
    }

    // A more elegant way of doing this would be better, but for now this exception list handles cases where "center" must be omitted.
    var minimal_results = {
        "background-position: 5% center": "5%",
        "background-position: 0.5% center": "0.5%",
        "background-position: -5% center": "-5%",
        "background-position: -0.5% center": "-0.5%",
        "background-position: 0px center": "0px",
        "background-position: 1px center": "1px",
        "background-position: 0.1em center": "0.1em",
        "background-position: 0px center": "0px",
        "background-position: -1px center": "-1px",
        "background-position: -0.1em center": "-0.1em",
        "background-position: left center": "left",
        "background-position: center top": "top",
        "background-position: center center": "center",
        "background-position: center bottom": "bottom",
        "background-position: right center": "right",
    };

    function create_result(propertyName, actual, expected) {
      var key = propertyName + ": " + expected
      if (key in minimal_results)
        expected = minimal_results[key]
      return {actual: actual, expected: expected}
    }

    function all_values(propertyName, values) {
      var results = [];
      for (var i = 0; i < values.length; i++) {
        var value = values[i];
        if (typeof value == "function") {
          var f = value();
          var result;
          while ((result = f()) != null) {
            if (typeof result == "object" && 'serialized' in result) {
              results.push(create_result(propertyName, result.actual, result.serialized));
            } else {
              results.push(create_result(propertyName, result, result));
            }
          }
        } else if (typeof value == "string") {
          results.push(create_result(propertyName, value, value));
        } else if (value instanceof Array) {
          var subresults = [];
          for (var j = 0; j < value.length; j++) {
            var subresult = all_values(propertyName, value[j]);
            if (!(subresult instanceof Array)) {
              subresult = [subresult];
            }
            subresults.push(subresult);
          }
          if (subresults.length > 1) {
            function choose_slices(vecs) {
              if (vecs.length == 1) {
                return vecs[0].map(function(v) { return [v]; });
              }
              var slice_results = [];
              var rest = choose_slices(vecs.slice(1, vecs.length));
              for (var a = 0; a < vecs[0].length; a++) {
                for (var b = 0; b < rest.length; b++) {
                  var result = vecs[0][a];
                  slice_results.push([result].concat(rest[b]));
                }
              }
              return slice_results;
            }

            subresults = choose_slices(subresults).map(function (a) {
              var actual = a.map(function(a) { return a.actual });
              var expected = a.map(function(a) { return a.expected });
              return create_result(propertyName, actual.join(' '), expected.join(' '))
            });
          }
          for (var j = 0; j < subresults.length; j++) {
            results = results.concat(subresults[j]);
          }
        } else if (value instanceof Object && 'serialized' in value) {
          results.push(create_result(propertyName, value.actual, value.serialized));
        } else if (typeof value == "number") {
          results.push(create_result(propertyName, value.toString(), value.toString()));
        } else {
          throw "unexpected value type: " + typeof(value);
        }
      }
      return results;
    }

    function create_value_generator(propertyName, property) {
      var results = all_values(propertyName, property.values);
      return iterable(results);
    }

    function to_idl(property) {
      return property.replace(/-\w/g, function(x){return x[1].toUpperCase()});
    }

    function run_individual_test(property, generator, initial) {
      var elem = document.createElement('div');
      document.getElementById('parent').appendChild(elem);
      var test_data = generator();
      var style = generate_inline_style(property, test_data);
      if (!style) {
        return false;
      }
      var t = async_test(style.declaration);

      t.add_cleanup(function() {
        document.getElementById('parent').removeChild(elem);
      });

      t.step(function() {
        elem.setAttribute('style', style.declaration);
        var expected = style.result;
        var serialized = elem.style[to_idl(property)];
        assert_equals(serialized, expected, property + ' raw inline style declaration');
        elem.setAttribute('style', '');
        elem.style[to_idl(property)] = style.value;
        assert_equals(elem.style[to_idl(property)], expected, property + ' style property');
      });
      t.done();
      return true;
    }

    function test_property(property) {
      var generator = create_value_generator(property[0], property[1]);
      while (run_individual_test(property[0], generator, property[1].initial)) {
      }
    }

    var properties = [
      ['background-attachment', {
        'values': ['scroll', 'fixed', 'inherit'],
        'initial': 'scroll',
      }],
      ['background-color', {
        'values': [color, 'transparent', 'inherit'],
        'initial': 'transparent',
      }],
      ['background-image', {
        'values': [uri, 'none', 'inherit'],
        'initial': 'none',
      }],
      ['background-position', {
        'values': [[[percentage, negative_percentage, length, negative_length,
                     'left', 'center', 'right'],
                    [percentage, negative_percentage, length, negative_length,
                     'top', 'center', 'bottom']],
                   'inherit'],
        'initial': '0% 0%',
      }],
      ['background-repeat', {
        'values': ['repeat', 'repeat-x', 'repeat-y', 'no-repeat', 'inherit'],
        'initial': 'repeat',
      }],
      //background
      ['border-collapse', {
        'values': ['collapse', 'separate', 'inherit'],
        'initial': 'separate',
      }],
      //border-color
      ['border-spacing', {
        'values': [length, 'inherit'],
        'initial': '0',
      }],
      //border-style
      //border-top, border-right, border-bottom, border-left
      ['border-top-color', {
        'values': [color, 'transparent', 'inherit'],
        'initial': 'black', //FIXME
      }],
      ['border-right-color', {
        'values': [color, 'transparent', 'inherit'],
        'initial': 'black', //FIXME
      }],
      ['border-bottom-color', {
        'values': [color, 'transparent', 'inherit'],
        'initial': 'black', //FIXME
      }],
      ['border-left-color', {
        'values': [color, 'transparent', 'inherit'],
        'initial': 'black', //FIXME
      }],
      ['border-top-style', {
        'values': [border_style, 'inherit'],
        'initial': null,
      }],
      ['border-right-style', {
        'values': [border_style, 'inherit'],
        'initial': null,
      }],
      ['border-bottom-style', {
        'values': [border_style, 'inherit'],
        'initial': null,
      }],
      ['border-left-style', {
        'values': [border_style, 'inherit'],
        'initial': null,
      }],
      ['border-top-width', {
        'values': ['thin', 'medium', 'thick', length, 'inherit'],
        'initial': 'medium',
      }],
      ['border-right-width', {
        'values': ['thin', 'medium', 'thick', length, 'inherit'],
        'initial': 'medium',
      }],
      ['border-bottom-width', {
        'values': ['thin', 'medium', 'thick', length, 'inherit'],
        'initial': 'medium',
      }],
      ['border-left-width', {
        'values': ['thin', 'medium', 'thick', length, 'inherit'],
        'initial': 'medium',
      }],
      //border-width
      //border
      ['bottom', {
        'values': [length, percentage, 'auto', 'inherit'],
        'initial': 'auto',
      }],
      ['caption-side', {
        'values': ['top', 'bottom', 'inherit'],
        'initial': 'top',
      }],
      ['clear', {
        'values': ['none', 'left', 'right', 'both', 'inherit'],
        'initial': 'none',
      }],
      ['clip', {
        'values': [shape, 'auto', 'inherit'],
        'initial': 'auto',
      }],
      ['color', {
        'values': [color, 'inherit'],
        'initial': 'black', //FIXME depends on user agent
      }],
      ['content', {
        'values': ['normal', 'none', string, uri, counter, attr, 'inherit'], //FIXME
        'initial': 'normal',
      }],
      //counter-increment
      //counter-reset
      ['cursor', {
        'values': [ 'auto', 'crosshair', 'default', 'pointer', 'move', 'e-resize', 'ne-resize',
                    'nw-resize', 'n-resize', 'se-resize', 'sw-resize', 's-resize', 'w-resize',
                    'text', 'wait', 'help', 'progress', 'inherit'],
        'initial': 'auto',
      }],
      ['direction', {
        'values': ['ltr', 'rtl', 'inherit'],
        'initial': 'ltr',
      }],
      ['display', {
        'values': ['inline', 'block', 'list-item', 'inline-block', 'table', 'inline-table',
                   'table-row-group', 'table-header-group', 'table-footer-group', 'table-row',
                   'table-column-group', 'table-column', 'table-cell', 'table-caption', 'none',
                   'inherit'],
        'initial': 'inline',
      }],
      ['empty-cells', {
         'values': ['show', 'hide', 'inherit'],
         'initial': 'show',
      }],
      ['float', {
        'values': ['left', 'right', 'none', 'inherit'],
        'initial': 'none',
        'property': 'cssFloat',
      }],
      ['font-family', {
        'values': [family_name, generic_family, 'inherit'],
        'initial': 'sans-serif', //FIXME depends on user agent
      }],
      ['font-size', {
        'values': [absolute_size, relative_size, length, percentage, 'inherit'],
        'initial': 'medium',
      }],
      ['font-style', {
        'values': ['normal', 'italic', 'oblique', 'inherit'],
        'initial': 'normal',
      }],
      ['font-variant', {
        'values': ['normal', 'small-caps', 'inherit'],
        'initial': 'normal',
      }],
      ['font-weight', {
        'values': ['normal', 'bold', 'bolder', 'lighter', 100, 200, 300, 400, 500, 600,
                   700, 800, 900, 'inherit'],
        'initial': 'normal',
      }],
      //font
      ['height', {
        'values': [length, percentage, 'auto', 'inherit'],
        'initial': 'auto',
      }],
      ['left', {
        'values': [length, percentage, 'auto', 'inherit'],
        'initial': 'auto',
      }],
      ['letter-spacing', {
        'values': ['normal', length, 'inherit'],
        'initial': 'normal',
      }],
      ['line-height', {
        'values': ['normal', positive_number, length, percentage, 'inherit'],
        'initial': 'normal',
      }],
      ['list-style-image', {
        'values': [uri, 'none', 'inherit'],
        'initial': 'none',
      }],
      ['list-style-position', {
        'values': ['inside', 'outside', 'inherit'],
        'initial': 'outside',
      }],
      ['list-style-type', {
        'values': ['disc', 'circle', 'square', 'disclosure-open', 'disclosure-closed',
                   'decimal', 'decimal-leading-zero', 'lower-roman',
                   'upper-roman', 'lower-greek', 'lower-latin', 'upper-latin', 'armenian', 'georgian',
                   'lower-alpha', 'upper-alpha', 'none', 'inherit'],
        'initial': 'disc',
      }],
      //list-style
      ['margin-right', {
        'values': [length, percentage, 'auto', 'inherit'],
        'initial': 0,
      }],
      ['margin-left', {
        'values': [length, percentage, 'auto', 'inherit'],
        'initial': 0,
      }],
      ['margin-top', {
        'values': [length, percentage, 'auto', 'inherit'],
        'initial': 0,
      }],
      ['margin-bottom', {
        'values': [length, percentage, 'auto', 'inherit'],
        'initial': 0,
      }],
      //margin
      ['max-height', {
        'values': [length, percentage, 'none', 'inherit'],
        'initial': 'none',
      }],
      ['max-width', {
        'values': [length, percentage, 'none', 'inherit'],
        'initial': 'none',
      }],
      ['min-height', {
        'values': [length, percentage, 'inherit'],
        'initial': 0,
      }],
      ['min-width', {
        'values': [length, percentage, 'inherit'],
        'initial': 0,
      }],
      ['orphans', {
        'values': [nonzero_positive_integer, 'inherit'],
        'initial': 2,
      }],
      ['outline-color', {
        'values': [color, 'invert', 'inherit'],
        'initial': 'invert',
      }],
      ['outline-style', {
        'values': [border_style_without_hidden, 'inherit'],
        'initial': 'none',
      }],
      ['outline-width', {
        'values': ['thin', 'medium', 'thick', length, 'inherit'],
        'initial': 'medium',
      }],
      //outline
      ['overflow', {
        'values': ['visible', 'hidden', 'scroll', 'auto', 'inherit'],
        'initial': 'visible',
      }],
      ['padding-top', {
        'values': [length, percentage, 'inherit'],
        'initial': 0,
      }],
      ['padding-right', {
        'values': [length, percentage, 'inherit'],
        'initial': 0,
      }],
      ['padding-bottom', {
        'values': [length, percentage, 'inherit'],
        'initial': 0,
      }],
      ['padding-left', {
        'values': [length, percentage, 'inherit'],
        'initial': 0,
      }],
      //padding
      ['page-break-after', {
        'values': ['auto', 'always', 'avoid', 'left', 'right', 'inherit'],
        'initial': 'auto',
      }],
      ['page-break-before', {
        'values': ['auto', 'always', 'avoid', 'left', 'right', 'inherit'],
        'initial': 'auto',
      }],
      ['page-break-inside', {
        'values': ['avoid', 'auto', 'inherit'],
        'initial': 'auto',
      }],
      ['position', {
        'values': ['static', 'relative', 'absolute', 'fixed', 'inherit'],
        'initial': 'static',
      }],
      //FIXME quotes
      ['right', {
        'values': [length, percentage, 'auto', 'inherit'],
        'initial': 'auto',
      }],
      ['table-layout', {
        'values': ['auto', 'fixed', 'inherit'],
        'initial': 'auto',
      }],
      ['text-align', {
        'values': ['left', 'right', 'center', 'justify', 'inherit'],
        'initial': null,
      }],
      ['text-decoration', {
        'values': ['none', 'underline', 'overline', 'line-through', 'blink', 'inherit'],
        'initial': 'none',
      }],
      ['text-indent', {
        'values': [length, percentage, 'inherit'],
        'initial': 0,
      }],
      ['text-transform', {
        'values': ['capitalize', 'uppercase', 'lowercase', 'none', 'inherit'],
        'initial': 'none',
      }],
      ['top', {
        'values': [length, percentage, 'auto', 'inherit'],
        'initial': 'auto',
      }],
      ['unicode-bidi', {
        'values': ['normal', 'embed', 'bidi-override', 'inherit'],
        'initial': 'normal',
      }],
      ['vertical-align', {
        'values': ['baseline', 'sub', 'super', 'top', 'text-top', 'middle', 'bottom', 'text-bottom',
                   percentage, length, 'inherit'],
        'initial': 'baseline',
      }],
      ['visibility', {
        'values': ['visible', 'hidden', 'collapse', 'inherit'],
        'initial': 'visible',
      }],
      ['white-space', {
        'values': ['normal', 'pre', 'nowrap', 'pre-wrap', 'pre-line', 'inherit'],
        'initial': 'normal',
      }],
      ['widows', {
        'values': [nonzero_positive_integer, 'inherit'],
        'initial': 2,
      }],
      ['width', {
        'values': [length, percentage, 'auto', 'inherit'],
        'initial': 'auto',
      }],
      ['word-spacing', {
        'values': ['normal', length, 'inherit'],
        'initial': 'normal',
      }],
      ['z-index', {
        'values': ['auto', integer, 'inherit'],
        'initial': 'auto',
      }],
    ]

    for (var index = 0; index < properties.length; index++) {
      test_property(properties[index]);
    }
  </script>
</body>
