Skip to content

Commit

Permalink
[css-typed-om] Refactor the per-property test harness.
Browse files Browse the repository at this point in the history
This patch refactors the per-property test harness quite
a bit to deal with property specific behaviour:

- We allow tests to override the expectation/asserts
  for specified and computed values. So it means that a
  property might compute 'auto' to '0px' and we can easily
  assert that by passing a callback to 'computed'.

- We moved margin-top to margin and test all the margin
  properties (except the margin shorthand).

- We made margin properties work by setting the correct
  metadata in the CSSProperties.json5.

- We removed unitless zero tests. They don't seem to
  be required by the spec.

Bug: 774887
Change-Id: I08605ac6af01576ff9f6c878c2ca9e280c9948e1
  • Loading branch information
darrnshn authored and chromium-wpt-export-bot committed Feb 14, 2018
1 parent 1e4fe87 commit 6be85c3
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 85 deletions.
9 changes: 4 additions & 5 deletions css/css-typed-om/the-stylepropertymap/properties/display.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,14 @@
<script src="../../resources/testhelper.js"></script>
<script src="resources/testsuite.js"></script>
<body>
<div id="log">
<div id="log"></div>
<script>
'use strict';

runPropertyTests('display', [
{
specified: '<ident>',
examples: [new CSSKeywordValue('none'), new CSSKeywordValue('block')]
},
{ syntax: 'none' },
{ syntax: 'block' },
// and other keywords
]);

</script>
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<!doctype html>
<meta charset="utf-8">
<title>'margin-top' property</title>
<title>margin properties</title>
<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
Expand All @@ -9,15 +9,22 @@
<script src="../../resources/testhelper.js"></script>
<script src="resources/testsuite.js"></script>
<body>
<div id="log">
<div id="log"></div>
<script>
'use strict';

runPropertyTests('margin-top', [
{ specified: '0' },
{ specified: '<ident>', examples: [new CSSKeywordValue('auto')] },
{ specified: '<percentage>' },
{ specified: '<length>' },
]);
for (const suffix of ['top', 'left', 'right', 'bottom']) {
runPropertyTests('margin-' + suffix, [
{
syntax: 'auto',
// Depending on which CSS spec is implemented, the computed value
// can be 'auto' or a browser specific value.
// FIXME: Figure out how to test this.
computed: () => {}
},
{ syntax: '<percentage>' },
{ syntax: '<length>' },
]);
}

</script>
197 changes: 125 additions & 72 deletions css/css-typed-om/the-stylepropertymap/properties/resources/testsuite.js
Original file line number Diff line number Diff line change
@@ -1,115 +1,168 @@
const gTestSyntax = {
'0': {
description: 'unitless zero',
set: true,
examples: [
new CSSUnitValue(0, 'number'),
],
},
const gTestSyntaxExamples = {
'<length>': {
description: 'a length',
get: true,
set: true,
examples: [
new CSSUnitValue(0, 'px'),
new CSSUnitValue(-3.14, 'em'),
new CSSUnitValue(3.14, 'cm'),
{
description: "zero px",
input: new CSSUnitValue(0, 'px')
},
{
description: "a negative em",
input: new CSSUnitValue(-3.14, 'em'),
defaultComputed: value => {
// 'ems' are relative units, so just check that it computes to px
assert_class_string(value, 'CSSUnitValue',
'"em" lengths must compute to a CSSUnitValue');
assert_equals(value.unit, 'px', 'unit');
}
},
{
description: "a positive cm",
input: new CSSUnitValue(3.14, 'cm'),
defaultComputed: value => {
// 'cms' are relative units, so just check that it computes to px
assert_class_string(value, 'CSSUnitValue',
'"cm" lengths must compute to a CSSUnitValue');
assert_equals(value.unit, 'px', 'unit');
}
},
],
},
'<percentage>': {
description: 'a percent',
get: true,
set: true,
examples: [
new CSSUnitValue(0, 'percent'),
new CSSUnitValue(-3.14, 'percent'),
new CSSUnitValue(3.14, 'percent'),
{
description: "zero percent",
input: new CSSUnitValue(0, 'percent')
},
{
description: "a negative percent",
input: new CSSUnitValue(-3.14, 'percent')
},
{
description: "a positive percent",
input: new CSSUnitValue(3.14, 'percent')
},
],
},
'<time>': {
description: 'a time',
get: true,
set: true,
examples: [
new CSSUnitValue(0, 's'),
new CSSUnitValue(-3.14, 'ms'),
new CSSUnitValue(3.14, 's'),
{
description: "zero seconds",
input: new CSSUnitValue(0, 's')
},
{
description: "negative milliseconds",
input: new CSSUnitValue(-3.14, 'ms')
},
{
description: "positive seconds",
input: new CSSUnitValue(3.14, 's')
},
],
},
'<ident>': {
description: 'a CSSKeywordValue',
set: true,
get: true,
// user-specified examples
examples: null,
},
};

function testGet(propertyName, values, description) {
// Test setting a value in a style map and then getting it from the inline and
// computed styles.
function testPropertyValid(propertyName, examples, specified, computed, description) {
test(t => {
let element = createDivWithStyle(t);
let styleMap = element.attributeStyleMap;

for (const styleValue of values) {
element.style[propertyName] = styleValue.toString();

getComputedStyle(element); // Force a style recalc.
const result = styleMap.get(propertyName);
assert_style_value_equals(result, styleValue);
}
}, `Can get ${description} from '${propertyName}'`);
}

function testSet(propertyName, values, description) {
test(t => {
let element = createDivWithStyle(t);
let styleMap = element.attributeStyleMap;
for (const example of examples) {
element.attributeStyleMap.set(propertyName, example.input);

for (const styleValue of values) {
styleMap.set(propertyName, styleValue);
// specified style
const specifiedResult = element.attributeStyleMap.get(propertyName);
if (specified || example.defaultSpecified) {
(specified || example.defaultSpecified)(specifiedResult);
} else {
assert_not_equals(specifiedResult, null,
'Specified value must not be null');
assert_true(specifiedResult instanceof CSSStyleValue,
'Specified value must be a CSSStyleValue');
assert_style_value_equals(specifiedResult, example.input,
`Setting ${example.description} and getting its specified value`);
}

getComputedStyle(element); // Force a style recalc.
assert_equals(element.style[propertyName], styleValue.toString());
// computed style
const computedResult = element.computedStyleMap().get(propertyName);
if (computed || example.defaultComputed) {
(computed || example.defaultComputed)(computedResult);
} else {
assert_not_equals(computedResult, null,
'Computed value must not be null');
assert_true(computedResult instanceof CSSStyleValue,
'Computed value must be a CSSStyleValue');
assert_style_value_equals(computedResult, example.input,
`Setting ${example.description} and getting its computed value`);
}
}
}, `Can set '${propertyName}' to ${description}`);
}

function testSetInvalid(propertyName, values, description) {
// Test that styleMap.set throws for invalid values
function testPropertyInvalid(propertyName, examples, description) {
test(t => {
let element = createDivWithStyle(t);
let styleMap = element.attributeStyleMap;

for (const styleValue of values) {
assert_throws(new TypeError(), () => styleMap.set(propertyName, styleValue));
let styleMap = createInlineStyleMap(t);
for (const example of examples) {
assert_throws(new TypeError(), () => styleMap.set(propertyName, example.input));
}
}, `Setting '${propertyName}' to ${description} throws TypeError`);
}

function createKeywordExample(keyword) {
return {
description: `the '${keyword}' keyword`,
examples: [ { input: new CSSKeywordValue(keyword) } ]
};
}

// Run a battery of StylePropertyMap tests on |propertyName|.
// Second argument is a list of test cases. A test case has the form:
//
// {
// syntax: "<length>",
// specified: /* a callback */ (optional)
// computed: /* a callback */ (optional)
// }
//
// If a callback is passed to |specified|, then the callback will be passed
// the result of calling get() on the inline style map (specified values).
// The callback should check if the result is expected using assert_* functions.
// If no callback is passed, then we assert that the result is the same as
// the input.
//
// Same goes for |computed|, but with the computed style map (computed values).
function runPropertyTests(propertyName, testCases) {
let productionsTested = new Set();
let syntaxTested = new Set();

for (const testCase of testCases) {
const syntax = gTestSyntax[testCase.specified];
if (!syntax)
throw new Error(`'${testCase.specified}' is not a valid production`);
// Retrieve test examples for this test case's syntax. If the syntax
// looks like a keyword, then create an example on the fly.
const syntaxExamples = testCase.syntax.match(/^[a-z\-]+$/) ?
createKeywordExample(testCase.syntax) :
gTestSyntaxExamples[testCase.syntax];

const examples = testCase.examples || syntax.examples;
if (!examples)
throw new Error(`'${testCase.specified}' tests require explicit examples`);
if (!syntaxExamples)
throw new Error(`'${testCase.syntax}' is not a valid CSS component`);

if (syntax.get)
testGet(propertyName, examples, syntax.description);
if (syntax.set)
testSet(propertyName, examples, syntax.description);
testPropertyValid(propertyName,
syntaxExamples.examples,
testCase.specified,
testCase.computed,
syntaxExamples.description);

productionsTested.add(testCase.specified);
syntaxTested.add(testCase.syntax);
}

// Also test that styleMap.set rejects invalid CSSStyleValues.
for (const [production, syntax] of Object.entries(gTestSyntax)) {
if (!productionsTested.has(production)) {
if (syntax.set && syntax.examples)
testSetInvalid(propertyName, syntax.examples, syntax.description);
for (const [syntax, syntaxExamples] of Object.entries(gTestSyntaxExamples)) {
if (!syntaxTested.has(syntax)) {
testPropertyInvalid(propertyName,
syntaxExamples.examples,
syntaxExamples.description);
}
}
}

0 comments on commit 6be85c3

Please sign in to comment.