summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/css/css-properties-values-api/resources/utils.js
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/css/css-properties-values-api/resources/utils.js')
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/resources/utils.js245
1 files changed, 245 insertions, 0 deletions
diff --git a/testing/web-platform/tests/css/css-properties-values-api/resources/utils.js b/testing/web-platform/tests/css/css-properties-values-api/resources/utils.js
new file mode 100644
index 0000000000..a952a4feed
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/resources/utils.js
@@ -0,0 +1,245 @@
+let next_property_id = 1;
+
+// Generate a unique property name on the form --prop-N.
+function generate_name() {
+ return `--prop-${next_property_id++}`;
+}
+
+// Produce a compatible initial value for the specified syntax.
+function any_initial_value(syntax) {
+ let components = syntax.split('|').map(x => x.trim())
+ let first_component = components[0];
+
+ if (first_component.endsWith('+') || first_component.endsWith('#'))
+ first_component = first_component.slice(0, -1);
+
+ switch (first_component) {
+ case '*':
+ case '<custom-ident>':
+ return 'NULL';
+ case '<angle>':
+ return '0deg';
+ case '<color>':
+ return 'rgb(0, 0, 0)';
+ case '<image>':
+ case '<url>':
+ return 'url(0)';
+ case '<integer>':
+ case '<length-percentage>':
+ case '<length>':
+ case '<number>':
+ return '0';
+ case '<percentage>':
+ return '0%';
+ case '<resolution>':
+ return '0dpi';
+ case '<time>':
+ return '0s';
+ case '<transform-function>':
+ case '<transform-list>':
+ return 'matrix(0, 0, 0, 0, 0, 0)';
+ default:
+ // We assume syntax is a specific custom ident.
+ return first_component;
+ }
+}
+
+// Registers a unique property on the form '--prop-N' and returns the name.
+// Any value except 'syntax' may be omitted, in which case the property will
+// not inherit, and some undefined (but compatible) initial value will be
+// generated. If a single string is used as the argument, it is assumed to be
+// the syntax.
+function generate_property(reg) {
+ // Verify that only valid keys are specified. This prevents the caller from
+ // accidentally supplying 'inherited' instead of 'inherits', for example.
+ if (typeof(reg) === 'object') {
+ const permitted = new Set(['name', 'syntax', 'initialValue', 'inherits']);
+ if (!Object.keys(reg).every(k => permitted.has(k)))
+ throw new Error('generate_property: invalid parameter');
+ }
+
+ let syntax = typeof(reg) === 'string' ? reg : reg.syntax;
+ let initial = typeof(reg.initialValue) === 'undefined' ? any_initial_value(syntax)
+ : reg.initialValue;
+ let inherits = typeof(reg.inherits) === 'undefined' ? false : reg.inherits;
+
+ let name = generate_name();
+ CSS.registerProperty({
+ name: name,
+ syntax: syntax,
+ initialValue: initial,
+ inherits: inherits
+ });
+ return name;
+}
+
+function all_syntaxes() {
+ return [
+ '*',
+ '<angle>',
+ '<color>',
+ '<custom-ident>',
+ '<image>',
+ '<integer>',
+ '<length-percentage>',
+ '<length>',
+ '<number>',
+ '<percentage>',
+ '<resolution>',
+ '<time>',
+ '<transform-function>',
+ '<transform-list>',
+ '<url>'
+ ]
+}
+
+function with_style_node(text, fn) {
+ let node = document.createElement('style');
+ node.textContent = text;
+ try {
+ document.body.append(node);
+ fn(node);
+ } finally {
+ node.remove();
+ }
+}
+
+function with_at_property(desc, fn) {
+ let name = typeof(desc.name) === 'undefined' ? generate_name() : desc.name;
+ let text = `@property ${name} {`;
+ if (typeof(desc.syntax) !== 'undefined')
+ text += `syntax:${desc.syntax};`;
+ if (typeof(desc.initialValue) !== 'undefined')
+ text += `initial-value:${desc.initialValue};`;
+ if (typeof(desc.inherits) !== 'undefined')
+ text += `inherits:${desc.inherits};`;
+ text += '}';
+ with_style_node(text, (node) => fn(name, node.sheet.rules[0]));
+}
+
+function test_with_at_property(desc, fn, description) {
+ test(() => with_at_property(desc, fn), description);
+}
+
+function test_with_style_node(text, fn, description) {
+ test(() => with_style_node(text, fn), description);
+}
+
+function animation_test(property, values, description) {
+ const name = generate_name();
+ property.name = name;
+ CSS.registerProperty(property);
+
+ test(() => {
+ const duration = 1000;
+ const keyframes = {};
+ keyframes[name] = values.keyframes;
+
+ const iterations = 3;
+ const composite = values.composite || "replace";
+ const iterationComposite = values.iterationComposite || "replace";
+ const animation = target.animate(keyframes, { composite, iterationComposite, iterations, duration });
+ animation.pause();
+ // We seek to the middle of the third iteration which will allow to test cases where
+ // iterationComposite is set to something other than "replace".
+ animation.currentTime = duration * 2.5;
+
+ const assert_equals_function = values.assert_function || assert_equals;
+ assert_equals_function(getComputedStyle(target).getPropertyValue(name), values.expected);
+ }, description);
+};
+
+function discrete_animation_test(syntax, fromValue, toValue, description) {
+ test(() => {
+ const name = generate_name();
+
+ CSS.registerProperty({
+ name,
+ syntax,
+ inherits: false,
+ initialValue: fromValue
+ });
+
+ const duration = 1000;
+ const keyframes = [];
+ keyframes[name] = toValue;
+ const animation = target.animate(keyframes, duration);
+ animation.pause();
+
+ const checkAtProgress = (progress, expected) => {
+ animation.currentTime = duration * 0.25;
+ assert_equals(getComputedStyle(target).getPropertyValue(name), fromValue, `The correct value is used at progress = ${progress}`);
+ };
+
+ checkAtProgress(0, fromValue);
+ checkAtProgress(0.25, fromValue);
+ checkAtProgress(0.49, fromValue);
+ checkAtProgress(0.5, toValue);
+ checkAtProgress(0.75, toValue);
+ checkAtProgress(1, toValue);
+ }, description || `Animating a custom property of type ${syntax} is discrete`);
+}
+
+function transition_test(options, description) {
+ promise_test(async () => {
+ const customProperty = generate_name();
+
+ options.transitionProperty ??= customProperty;
+
+ CSS.registerProperty({
+ name: customProperty,
+ syntax: options.syntax,
+ inherits: false,
+ initialValue: options.from
+ });
+
+ assert_equals(getComputedStyle(target).getPropertyValue(customProperty), options.from, "Element has the expected initial value");
+
+ const transitionEventPromise = new Promise(resolve => {
+ let listener = event => {
+ target.removeEventListener("transitionrun", listener);
+ assert_equals(event.propertyName, customProperty, "TransitionEvent has the expected property name");
+ resolve();
+ };
+ target.addEventListener("transitionrun", listener);
+ });
+
+ target.style.transition = `${options.transitionProperty} 1s -500ms linear`;
+ if (options.behavior) {
+ target.style.transitionBehavior = options.behavior;
+ }
+ target.style.setProperty(customProperty, options.to);
+
+ const animations = target.getAnimations();
+ assert_equals(animations.length, 1, "A single animation is running");
+
+ const transition = animations[0];
+ assert_class_string(transition, "CSSTransition", "A CSSTransition is running");
+
+ transition.pause();
+ assert_equals(getComputedStyle(target).getPropertyValue(customProperty), options.expected, "Element has the expected animated value");
+
+ await transitionEventPromise;
+ }, description);
+}
+
+function no_transition_test(options, description) {
+ test(() => {
+ const customProperty = generate_name();
+
+ CSS.registerProperty({
+ name: customProperty,
+ syntax: options.syntax,
+ inherits: false,
+ initialValue: options.from
+ });
+
+ assert_equals(getComputedStyle(target).getPropertyValue(customProperty), options.from, "Element has the expected initial value");
+
+ target.style.transition = `${customProperty} 1s -500ms linear`;
+ target.style.setProperty(customProperty, options.to);
+
+ assert_equals(target.getAnimations().length, 0, "No animation was created");
+ assert_equals(getComputedStyle(target).getPropertyValue(customProperty), options.to, "Element has the expected final value");
+ }, description);
+};