-
Notifications
You must be signed in to change notification settings - Fork 3.3k
/
Copy pathtestcommon.js
175 lines (163 loc) · 5.5 KB
/
testcommon.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Use this variable if you specify duration or some other properties
* for script animation.
* E.g., div.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC);
*
* NOTE: Creating animations with short duration may cause intermittent
* failures in asynchronous test. For example, the short duration animation
* might be finished when animation.ready has been fulfilled because of slow
* platforms or busyness of the main thread.
* Setting short duration to cancel its animation does not matter but
* if you don't want to cancel the animation, consider using longer duration.
*/
const MS_PER_SEC = 1000;
/* The recommended minimum precision to use for time values[1].
*
* [1] https://drafts.csswg.org/web-animations/#precision-of-time-values
*/
var TIME_PRECISION = 0.0005; // ms
/*
* Allow implementations to substitute an alternative method for comparing
* times based on their precision requirements.
*/
function assert_times_equal(actual, expected, description) {
assert_approx_equals(actual, expected, TIME_PRECISION * 2, description);
}
/*
* Compare a time value based on its precision requirements with a fixed value.
*/
function assert_time_equals_literal(actual, expected, description) {
assert_approx_equals(actual, expected, TIME_PRECISION, description);
}
/**
* Appends a div to the document body.
*
* @param t The testharness.js Test object. If provided, this will be used
* to register a cleanup callback to remove the div when the test
* finishes.
*
* @param attrs A dictionary object with attribute names and values to set on
* the div.
*/
function addDiv(t, attrs) {
var div = document.createElement('div');
if (attrs) {
for (var attrName in attrs) {
div.setAttribute(attrName, attrs[attrName]);
}
}
document.body.appendChild(div);
if (t && typeof t.add_cleanup === 'function') {
t.add_cleanup(function() {
if (div.parentNode) {
div.remove();
}
});
}
return div;
}
/**
* Appends a style div to the document head.
*
* @param t The testharness.js Test object. If provided, this will be used
* to register a cleanup callback to remove the style element
* when the test finishes.
*
* @param rules A dictionary object with selector names and rules to set on
* the style sheet.
*/
function addStyle(t, rules) {
var extraStyle = document.createElement('style');
document.head.appendChild(extraStyle);
if (rules) {
var sheet = extraStyle.sheet;
for (var selector in rules) {
sheet.insertRule(selector + '{' + rules[selector] + '}',
sheet.cssRules.length);
}
}
if (t && typeof t.add_cleanup === 'function') {
t.add_cleanup(function() {
extraStyle.remove();
});
}
}
/**
* Promise wrapper for requestAnimationFrame.
*/
function waitForFrame() {
return new Promise(function(resolve, reject) {
window.requestAnimationFrame(resolve);
});
}
/**
* Waits for a requestAnimationFrame callback in the next refresh driver tick.
*/
function waitForNextFrame() {
const timeAtStart = document.timeline.currentTime;
return new Promise(resolve => {
window.requestAnimationFrame(() => {
if (timeAtStart === document.timeline.currentTime) {
window.requestAnimationFrame(resolve);
} else {
resolve();
}
});
});
}
/**
* Returns a Promise that is resolved after the given number of consecutive
* animation frames have occured (using requestAnimationFrame callbacks).
*
* @param frameCount The number of animation frames.
* @param onFrame An optional function to be processed in each animation frame.
*/
function waitForAnimationFrames(frameCount, onFrame) {
const timeAtStart = document.timeline.currentTime;
return new Promise(function(resolve, reject) {
function handleFrame() {
if (onFrame && typeof onFrame === 'function') {
onFrame();
}
if (timeAtStart != document.timeline.currentTime &&
--frameCount <= 0) {
resolve();
} else {
window.requestAnimationFrame(handleFrame); // wait another frame
}
}
window.requestAnimationFrame(handleFrame);
});
}
/**
* Wrapper that takes a sequence of N animations and returns:
*
* Promise.all([animations[0].ready, animations[1].ready, ... animations[N-1].ready]);
*/
function waitForAllAnimations(animations) {
return Promise.all(animations.map(animation => animation.ready));
}
/**
* Flush the computed style for the given element. This is useful, for example,
* when we are testing a transition and need the initial value of a property
* to be computed so that when we synchronouslyet set it to a different value
* we actually get a transition instead of that being the initial value.
*/
function flushComputedStyle(elem) {
var cs = getComputedStyle(elem);
cs.marginLeft;
}
// Waits for a given animation being ready to restyle.
async function waitForAnimationReadyToRestyle(aAnimation) {
await aAnimation.ready;
// If |aAnimation| begins at the current timeline time, we will not process
// restyling in the initial frame because of aligning with the refresh driver,
// the animation frame in which the ready promise is resolved happens to
// coincide perfectly with the start time of the animation. In this case no
// restyling is needed in the frame so we have to wait one more frame.
if (animationStartsRightNow(aAnimation)) {
await waitForNextFrame();
}
}