diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /testing/web-platform/tests/web-animations/interfaces/Animation/commitStyles.html | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/web-animations/interfaces/Animation/commitStyles.html')
-rw-r--r-- | testing/web-platform/tests/web-animations/interfaces/Animation/commitStyles.html | 577 |
1 files changed, 577 insertions, 0 deletions
diff --git a/testing/web-platform/tests/web-animations/interfaces/Animation/commitStyles.html b/testing/web-platform/tests/web-animations/interfaces/Animation/commitStyles.html new file mode 100644 index 0000000000..9a7dbea8b8 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/commitStyles.html @@ -0,0 +1,577 @@ +<!doctype html> +<meta charset=utf-8> +<title>Animation.commitStyles</title> +<link rel="help" href="https://drafts.csswg.org/web-animations/#dom-animation-commitstyles"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<style> +.pseudo::before {content: '';} +.pseudo::after {content: '';} +.pseudo::marker {content: '';} +</style> +<body> +<div id="log"></div> +<script> +'use strict'; + +function assert_numeric_style_equals(opacity, expected, description) { + return assert_approx_equals( + parseFloat(opacity), + expected, + 0.0001, + description + ); +} + +test(t => { + const div = createDiv(t); + div.style.opacity = '0.1'; + + const animation = div.animate( + { opacity: 0.2 }, + { duration: 1, fill: 'forwards' } + ); + animation.finish(); + + animation.commitStyles(); + + // Cancel the animation so we can inspect the underlying style + animation.cancel(); + + assert_numeric_style_equals(getComputedStyle(div).opacity, 0.2); +}, 'Commits styles'); + +test(t => { + const div = createDiv(t); + div.style.translate = '100px'; + div.style.rotate = '45deg'; + div.style.scale = '2'; + + const animation = div.animate( + { translate: '200px', + rotate: '90deg', + scale: 3 }, + { duration: 1, fill: 'forwards' } + ); + animation.finish(); + + animation.commitStyles(); + + // Cancel the animation so we can inspect the underlying style + animation.cancel(); + + assert_equals(getComputedStyle(div).translate, '200px'); + assert_equals(getComputedStyle(div).rotate, '90deg'); + assert_numeric_style_equals(getComputedStyle(div).scale, 3); +}, 'Commits styles for individual transform properties'); + +promise_test(async t => { + const div = createDiv(t); + div.style.opacity = '0.1'; + + const animA = div.animate( + { opacity: 0.2 }, + { duration: 1, fill: 'forwards' } + ); + const animB = div.animate( + { opacity: 0.3 }, + { duration: 1, fill: 'forwards' } + ); + + await animA.finished; + + animB.cancel(); + + animA.commitStyles(); + + assert_numeric_style_equals(getComputedStyle(div).opacity, 0.2); +}, 'Commits styles for an animation that has been removed'); + +test(t => { + const div = createDiv(t); + div.style.margin = '10px'; + + const animation = div.animate( + { margin: '20px' }, + { duration: 1, fill: 'forwards' } + ); + animation.finish(); + + animation.commitStyles(); + + animation.cancel(); + + assert_equals(div.style.marginLeft, '20px'); +}, 'Commits shorthand styles'); + +test(t => { + const div = createDiv(t); + div.style.marginLeft = '10px'; + + const animation = div.animate( + { marginInlineStart: '20px' }, + { duration: 1, fill: 'forwards' } + ); + animation.finish(); + + animation.commitStyles(); + + animation.cancel(); + + assert_equals(getComputedStyle(div).marginLeft, '20px'); +}, 'Commits logical properties'); + +test(t => { + const div = createDiv(t); + div.style.marginLeft = '10px'; + + const animation = div.animate( + { marginInlineStart: '20px' }, + { duration: 1, fill: 'forwards' } + ); + animation.finish(); + + animation.commitStyles(); + + animation.cancel(); + + assert_equals(div.style.marginLeft, '20px'); +}, 'Commits logical properties as physical properties'); + +test(t => { + const div = createDiv(t); + div.style.marginLeft = '10px'; + + const animation = div.animate({ opacity: [0.2, 0.7] }, 1000); + animation.currentTime = 500; + animation.commitStyles(); + animation.cancel(); + + assert_numeric_style_equals(getComputedStyle(div).opacity, 0.45); +}, 'Commits values calculated mid-interval'); + +test(t => { + const div = createDiv(t); + div.style.setProperty('--target', '0.5'); + + const animation = div.animate( + { opacity: 'var(--target)' }, + { duration: 1, fill: 'forwards' } + ); + animation.finish(); + animation.commitStyles(); + animation.cancel(); + + assert_numeric_style_equals(getComputedStyle(div).opacity, 0.5); + + // Changes to the variable should have no effect + div.style.setProperty('--target', '1'); + + assert_numeric_style_equals(getComputedStyle(div).opacity, 0.5); +}, 'Commits variable references as their computed values'); + + +test(t => { + const div = createDiv(t); + div.style.setProperty('--target', '0.5'); + div.style.opacity = 'var(--target)'; + const animation = div.animate( + { '--target': 0.8 }, + { duration: 1, fill: 'forwards' } + ); + animation.finish(); + animation.commitStyles(); + animation.cancel(); + + assert_numeric_style_equals(getComputedStyle(div).opacity, 0.8); +}, 'Commits custom variables'); + +test(t => { + const div = createDiv(t); + div.style.fontSize = '10px'; + + const animation = div.animate( + { width: '10em' }, + { duration: 1, fill: 'forwards' } + ); + animation.finish(); + animation.commitStyles(); + animation.cancel(); + + assert_numeric_style_equals(getComputedStyle(div).width, 100); + + div.style.fontSize = '20px'; + assert_numeric_style_equals(getComputedStyle(div).width, 100, + "Changes to the font-size should have no effect"); +}, 'Commits em units as pixel values'); + +test(t => { + const div = createDiv(t); + div.style.fontSize = '10px'; + + const animation = div.animate( + { lineHeight: '1.5' }, + { duration: 1, fill: 'forwards' } + ); + animation.finish(); + animation.commitStyles(); + animation.cancel(); + + assert_numeric_style_equals(getComputedStyle(div).lineHeight, 15); + assert_equals(div.style.lineHeight, "1.5", "line-height is committed as a relative value"); + + div.style.fontSize = '20px'; + assert_numeric_style_equals(getComputedStyle(div).lineHeight, 30, + "Changes to the font-size should affect the committed line-height"); + +}, 'Commits relative line-height'); + +test(t => { + const div = createDiv(t); + const animation = div.animate( + { transform: 'translate(20px, 20px)' }, + { duration: 1, fill: 'forwards' } + ); + animation.finish(); + animation.commitStyles(); + animation.cancel(); + assert_equals(getComputedStyle(div).transform, 'matrix(1, 0, 0, 1, 20, 20)'); +}, 'Commits transforms'); + +test(t => { + const div = createDiv(t); + const animation = div.animate( + { transform: 'translate(20px, 20px)' }, + { duration: 1, fill: 'forwards' } + ); + animation.finish(); + animation.commitStyles(); + animation.cancel(); + assert_equals(div.style.transform, 'translate(20px, 20px)'); +}, 'Commits transforms as a transform list'); + +test(t => { + const div = createDiv(t); + div.style.width = '200px'; + div.style.height = '200px'; + + const animation = div.animate({ transform: ["translate(100%, 0%)", "scale(3)"] }, 1000); + animation.currentTime = 500; + animation.commitStyles(); + animation.cancel(); + + // TODO(https://github.com/w3c/csswg-drafts/issues/2854): + // We can't check the committed value directly since it is not specced yet in this case, + // but it should still produce the correct resolved value. + assert_equals(getComputedStyle(div).transform, "matrix(2, 0, 0, 2, 100, 0)", + "Resolved transform is correct after commit."); +}, 'Commits matrix-interpolated relative transforms'); + +test(t => { + const div = createDiv(t); + div.style.width = '200px'; + div.style.height = '200px'; + + const animation = div.animate({ transform: ["none", "none"] }, 1000); + animation.currentTime = 500; + animation.commitStyles(); + animation.cancel(); + + assert_equals(div.style.transform, "none", + "Resolved transform is correct after commit."); +}, 'Commits "none" transform'); + +promise_test(async t => { + const div = createDiv(t); + div.style.opacity = '0.1'; + + const animA = div.animate( + { opacity: '0.2' }, + { duration: 1, fill: 'forwards' } + ); + const animB = div.animate( + { opacity: '0.2', composite: 'add' }, + { duration: 1, fill: 'forwards' } + ); + const animC = div.animate( + { opacity: '0.3', composite: 'add' }, + { duration: 1, fill: 'forwards' } + ); + + animA.persist(); + animB.persist(); + + await animB.finished; + + // The values above have been chosen such that various error conditions + // produce results that all differ from the desired result: + // + // Expected result: + // + // animA + animB = 0.4 + // + // Likely error results: + // + // <underlying> = 0.1 + // (Commit didn't work at all) + // + // animB = 0.2 + // (Didn't add at all when resolving) + // + // <underlying> + animB = 0.3 + // (Added to the underlying value instead of lower-priority animations when + // resolving) + // + // <underlying> + animA + animB = 0.5 + // (Didn't respect the composite mode of lower-priority animations) + // + // animA + animB + animC = 0.7 + // (Resolved the whole stack, not just up to the target effect) + // + + animB.commitStyles(); + + animA.cancel(); + animB.cancel(); + animC.cancel(); + + assert_numeric_style_equals(getComputedStyle(div).opacity, 0.4); +}, 'Commits the intermediate value of an animation in the middle of stack'); + +promise_test(async t => { + const div = createDiv(t); + div.style.opacity = '0.1'; + + const animA = div.animate( + { opacity: '0.2', composite: 'add' }, + { duration: 1, fill: 'forwards' } + ); + const animB = div.animate( + { opacity: '0.2', composite: 'add' }, + { duration: 1, fill: 'forwards' } + ); + const animC = div.animate( + { opacity: '0.3', composite: 'add' }, + { duration: 1, fill: 'forwards' } + ); + + animA.persist(); + animB.persist(); + await animB.finished; + + // The error cases are similar to the above test with one additional case; + // verifying that the animations composite on top of the correct underlying + // base style. + // + // Expected result: + // + // <underlying> + animA + animB = 0.5 + // + // Additional error results: + // + // <underlying> + animA + animB + animC + animA + animB = 1.0 (saturates) + // (Added to the computed value instead of underlying value when + // resolving) + // + // animA + animB = 0.4 + // Failed to composite on top of underlying value. + // + + animB.commitStyles(); + + animA.cancel(); + animB.cancel(); + animC.cancel(); + + assert_numeric_style_equals(getComputedStyle(div).opacity, 0.5); +}, 'Commit composites on top of the underlying value'); + +promise_test(async t => { + const div = createDiv(t); + div.style.opacity = '0.1'; + + // Setup animation + const animation = div.animate( + { opacity: 0.2 }, + { duration: 1, fill: 'forwards' } + ); + animation.finish(); + + // Setup observer + const mutationRecords = []; + const observer = new MutationObserver(mutations => { + mutationRecords.push(...mutations); + }); + observer.observe(div, { attributes: true, attributeOldValue: true }); + + animation.commitStyles(); + + // Wait for mutation records to be dispatched + await Promise.resolve(); + + assert_equals(mutationRecords.length, 1, 'Should have one mutation record'); + + const mutation = mutationRecords[0]; + assert_equals(mutation.type, 'attributes'); + assert_equals(mutation.oldValue, 'opacity: 0.1;'); + + observer.disconnect(); +}, 'Triggers mutation observers when updating style'); + +promise_test(async t => { + const div = createDiv(t); + div.style.opacity = '0.2'; + + // Setup animation + const animation = div.animate( + { opacity: 0.2 }, + { duration: 1, fill: 'forwards' } + ); + animation.finish(); + + // Setup observer + const mutationRecords = []; + const observer = new MutationObserver(mutations => { + mutationRecords.push(...mutations); + }); + observer.observe(div, { attributes: true }); + + animation.commitStyles(); + + // Wait for mutation records to be dispatched + await Promise.resolve(); + + assert_equals(mutationRecords.length, 0, 'Should have no mutation records'); + + observer.disconnect(); +}, 'Does NOT trigger mutation observers when the change to style is redundant'); + +test(t => { + + const div = createDiv(t); + div.classList.add('pseudo'); + const animation = div.animate( + { opacity: 0 }, + { duration: 1, fill: 'forwards', pseudoElement: '::before' } + ); + + assert_throws_dom('NoModificationAllowedError', () => { + animation.commitStyles(); + }); +}, 'Throws if the target element is a pseudo element'); + +test(t => { + const animation = createDiv(t).animate( + { opacity: 0 }, + { duration: 1, fill: 'forwards' } + ); + + const nonStyleElement + = document.createElementNS('http://example.org/test', 'test'); + document.body.appendChild(nonStyleElement); + animation.effect.target = nonStyleElement; + + assert_throws_dom('NoModificationAllowedError', () => { + animation.commitStyles(); + }); + + nonStyleElement.remove(); +}, 'Throws if the target element is not something with a style attribute'); + +test(t => { + const div = createDiv(t); + const animation = div.animate( + { opacity: 0 }, + { duration: 1, fill: 'forwards' } + ); + + div.style.display = 'none'; + + assert_throws_dom('InvalidStateError', () => { + animation.commitStyles(); + }); +}, 'Throws if the target effect is display:none'); + +test(t => { + const container = createDiv(t); + const div = createDiv(t); + container.append(div); + + const animation = div.animate( + { opacity: 0 }, + { duration: 1, fill: 'forwards' } + ); + + container.style.display = 'none'; + + assert_throws_dom('InvalidStateError', () => { + animation.commitStyles(); + }); +}, "Throws if the target effect's ancestor is display:none"); + +test(t => { + const container = createDiv(t); + const div = createDiv(t); + container.append(div); + + const animation = div.animate( + { opacity: 0 }, + { duration: 1, fill: 'forwards' } + ); + + container.style.display = 'contents'; + + // Should NOT throw + animation.commitStyles(); +}, 'Treats display:contents as rendered'); + +test(t => { + const container = createDiv(t); + const div = createDiv(t); + container.append(div); + + const animation = div.animate( + { opacity: 0 }, + { duration: 1, fill: 'forwards' } + ); + + div.style.display = 'contents'; + container.style.display = 'none'; + + assert_throws_dom('InvalidStateError', () => { + animation.commitStyles(); + }); +}, 'Treats display:contents in a display:none subtree as not rendered'); + +test(t => { + const div = createDiv(t); + const animation = div.animate( + { opacity: 0 }, + { duration: 1, fill: 'forwards' } + ); + + div.remove(); + + assert_throws_dom('InvalidStateError', () => { + animation.commitStyles(); + }); +}, 'Throws if the target effect is disconnected'); + +test(t => { + const div = createDiv(t); + div.classList.add('pseudo'); + const animation = div.animate( + { opacity: 0 }, + { duration: 1, fill: 'forwards', pseudoElement: '::before' } + ); + + div.remove(); + + assert_throws_dom('NoModificationAllowedError', () => { + animation.commitStyles(); + }); +}, 'Checks the pseudo element condition before the not rendered condition'); + +</script> +</body> |