mirror of
https://github.com/servo/servo
synced 2026-04-28 18:37:39 +02:00
232 lines
8.8 KiB
JavaScript
232 lines
8.8 KiB
JavaScript
(function(root) {
|
|
'use strict';
|
|
//
|
|
var index = 0;
|
|
var suite = root.generalParallelTest = {
|
|
// prepare individual test
|
|
setup: function(data, options) {
|
|
suite._setupDom(data, options);
|
|
suite._setupEvents(data, options);
|
|
},
|
|
// clone fixture and prepare data containers
|
|
_setupDom: function(data, options) {
|
|
// clone fixture into off-viewport test-canvas
|
|
data.fixture = document.getElementById('fixture').cloneNode(true);
|
|
data.fixture.id = 'test-' + (index++);
|
|
(document.getElementById('offscreen') || document.body).appendChild(data.fixture);
|
|
|
|
// data container for #fixture > .container > .transition
|
|
data.transition = {
|
|
node: data.fixture.querySelector('.transition'),
|
|
values: [],
|
|
events: [],
|
|
computedStyle: function(property) {
|
|
return computedStyle(data.transition.node, property);
|
|
}
|
|
};
|
|
|
|
// data container for #fixture > .container
|
|
data.container = {
|
|
node: data.transition.node.parentNode,
|
|
values: [],
|
|
events: [],
|
|
computedStyle: function(property) {
|
|
return computedStyle(data.container.node, property);
|
|
}
|
|
};
|
|
|
|
// data container for #fixture > .container > .transition[:before | :after]
|
|
if (data.pseudo) {
|
|
data.pseudo = {
|
|
name: data.pseudo,
|
|
values: [],
|
|
computedStyle: function(property) {
|
|
return computedStyle(data.transition.node, property, ':' + data.pseudo.name);
|
|
}
|
|
};
|
|
}
|
|
},
|
|
// bind TransitionEnd event listeners
|
|
_setupEvents: function(data, options) {
|
|
['transition', 'container'].forEach(function(elem) {
|
|
var handler = function(event) {
|
|
event.stopPropagation();
|
|
var name = event.propertyName;
|
|
var time = Math.round(event.elapsedTime * 1000) / 1000;
|
|
var pseudo = event.pseudoElement ? (':' + event.pseudoElement) : '';
|
|
data[elem].events.push(name + pseudo + ":" + time + "s");
|
|
};
|
|
data[elem].node.addEventListener('transitionend', handler, false);
|
|
data[elem]._events = {'transitionend': handler};
|
|
});
|
|
},
|
|
// cleanup after individual test
|
|
teardown: function(data, options) {
|
|
// data.fixture.remove();
|
|
if (data.fixture.parentNode) {
|
|
data.fixture.parentNode.removeChild(data.fixture);
|
|
}
|
|
},
|
|
// invoked prior to running a slice of tests
|
|
sliceStart: function(options, tests) {
|
|
// inject styles into document
|
|
setStyle(options.styles);
|
|
// kick off value collection loop
|
|
generalParallelTest.startValueCollection(options);
|
|
},
|
|
// invoked after running a slice of tests
|
|
sliceDone: function(options, tests) {
|
|
// stop value collection loop
|
|
generalParallelTest.stopValueCollection(options);
|
|
// reset styles cache
|
|
options.styles = {};
|
|
},
|
|
// called once all tests are done
|
|
done: function(options) {
|
|
// reset document styles
|
|
setStyle();
|
|
reflow();
|
|
},
|
|
// add styles of individual test to slice cache
|
|
addStyles: function(data, options, styles) {
|
|
if (!options.styles) {
|
|
options.styles = {};
|
|
}
|
|
|
|
Object.keys(styles).forEach(function(key) {
|
|
var selector = '#' + data.fixture.id
|
|
// fixture must become #fixture.fixture rather than a child selector
|
|
+ (key.substring(0, 8) === '.fixture' ? '' : ' ')
|
|
+ key;
|
|
|
|
options.styles[selector] = styles[key];
|
|
});
|
|
},
|
|
// set style and compute values for container and transition
|
|
getStyle: function(data) {
|
|
reflow();
|
|
// grab current styles: "initial state"
|
|
suite._getStyleFor(data, 'from');
|
|
// apply target state
|
|
suite._addClass(data, 'to', true);
|
|
// grab current styles: "target state"
|
|
suite._getStyleFor(data, 'to');
|
|
// remove target state
|
|
suite._removeClass(data, 'to', true);
|
|
|
|
// clean up the mess created for value collection
|
|
data.container._values = [];
|
|
data.transition._values = [];
|
|
if (data.pseudo) {
|
|
data.pseudo._values = [];
|
|
}
|
|
},
|
|
// grab current styles and store in respective element's data container
|
|
_getStyleFor: function(data, key) {
|
|
data.container[key] = data.container.computedStyle(data.property);
|
|
data.transition[key] = data.transition.computedStyle(data.property);
|
|
if (data.pseudo) {
|
|
data.pseudo[key] = data.pseudo.computedStyle(data.property);
|
|
}
|
|
},
|
|
// add class to test's elements and possibly reflow
|
|
_addClass: function(data, className, forceReflow) {
|
|
data.container.node.classList.add(className);
|
|
data.transition.node.classList.add(className);
|
|
if (forceReflow) {
|
|
reflow();
|
|
}
|
|
},
|
|
// remove class from test's elements and possibly reflow
|
|
_removeClass: function(data, className, forceReflow) {
|
|
data.container.node.classList.remove(className);
|
|
data.transition.node.classList.remove(className);
|
|
if (forceReflow) {
|
|
reflow();
|
|
}
|
|
},
|
|
// add transition and to classes to container and transition
|
|
startTransition: function(data) {
|
|
// add transition-defining class
|
|
suite._addClass(data, 'how', true);
|
|
// add target state (without reflowing)
|
|
suite._addClass(data, 'to', false);
|
|
},
|
|
// requestAnimationFrame runLoop to collect computed values
|
|
startValueCollection: function(options) {
|
|
var raf = window.requestAnimationFrame || function(callback){
|
|
setTimeout(callback, 20);
|
|
};
|
|
|
|
// flag denoting if the runLoop should continue (true) or exit (false)
|
|
options._collectValues = true;
|
|
|
|
function runLoop() {
|
|
if (!options._collectValues) {
|
|
// test's are done, stop annoying the CPU
|
|
return;
|
|
}
|
|
|
|
// collect current style for test's elements
|
|
options.tests.forEach(function(data) {
|
|
if (!data.property) {
|
|
return;
|
|
}
|
|
|
|
['transition', 'container', 'pseudo'].forEach(function(elem) {
|
|
var pseudo = null;
|
|
if (!data[elem] || (elem === 'pseudo' && !data.pseudo)) {
|
|
return;
|
|
}
|
|
|
|
var current = data[elem].computedStyle(data.property);
|
|
var values = data[elem].values;
|
|
var length = values.length;
|
|
if (!length || values[length - 1] !== current) {
|
|
values.push(current);
|
|
}
|
|
});
|
|
});
|
|
|
|
// rinse and repeat
|
|
raf(runLoop);
|
|
}
|
|
|
|
runLoop();
|
|
},
|
|
// stop requestAnimationFrame runLoop collecting computed values
|
|
stopValueCollection: function(options) {
|
|
options._collectValues = false;
|
|
},
|
|
|
|
// generate test.step function asserting collected events match expected
|
|
assertExpectedEventsFunc: function(data, elem, expected) {
|
|
return function() {
|
|
var _result = data[elem].events.sort().join(" ");
|
|
var _expected = typeof expected === 'string' ? expected : expected.sort().join(" ");
|
|
assert_equals(_result, _expected, "Expected TransitionEnd events triggered on ." + elem);
|
|
};
|
|
},
|
|
// generate test.step function asserting collected values are neither initial nor target
|
|
assertIntermediateValuesFunc: function(data, elem) {
|
|
return function() {
|
|
// the first value (index: 0) is always going to be the initial value
|
|
// the last value is always going to be the target value
|
|
var values = data[elem].values;
|
|
if (data.flags.discrete) {
|
|
// a discrete value will just switch from one state to another without having passed intermediate states.
|
|
assert_equals(values[0], data[elem].from, "must be initial value while transitioning on ." + elem);
|
|
assert_equals(values[1], data[elem].to, "must be target value after transitioning on ." + elem);
|
|
assert_equals(values.length, 2, "discrete property only has 2 values ." + elem);
|
|
} else {
|
|
assert_not_equals(values[1], data[elem].from, "may not be initial value while transitioning on ." + elem);
|
|
assert_not_equals(values[1], data[elem].to, "may not be target value while transitioning on ." + elem);
|
|
}
|
|
|
|
// TODO: first value must be initial, last value must be target
|
|
};
|
|
}
|
|
};
|
|
|
|
})(window);
|