mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-01 00:32:11 +00:00
Bug 1818096 [wpt PR 38625] - [view-timeline]: Avoid reparse of keyframe rules containing timeline offsets, a=testonly
Automatic update from web-platform-tests [view-timeline]: Avoid reparse of keyframe rules containing timeline offsets A full reparse of the keyframes is wasteful, when we just need to re-sort. Previously, we had a bug where the effect invalidation caused the composited animation to lag. This is because validateSnapshot could trigger a second pass of layout update, but updateSnapshot could not. We now avoid this problem entirely. Instead, we track if any keyframe offsets are affected and simply clear the keyframe effect cache if needed. Injecting the neutral keyframes when processing the keyframe rules is wasteful since already handled for property specific keyframes. Removal of the neutral keyframes required updating getKeyframes to return the expected results. Overall, the process seems cleaner now. Added tests for keyframe retrieval as well as for interpolation at the keyframe boundaries. Bug: 1408475 Change-Id: I18fc726c6f42e414760eb52dd8478c6930690238 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4261369 Reviewed-by: Robert Flack <flackr@chromium.org> Commit-Queue: Kevin Ellis <kevers@chromium.org> Cr-Commit-Position: refs/heads/main@{#1112198} -- wpt-commits: c6b7fa7682f4ff3c5aa928443516473523234352 wpt-pr: 38625
This commit is contained in:
parent
3c2e448d1a
commit
3a153dcb62
@ -249,7 +249,7 @@ test(t => {
|
||||
"number of frames when @keyframes only has frames with " +
|
||||
"non-animatable properties");
|
||||
}, 'KeyframeEffect.getKeyframes() returns no frames for various kinds'
|
||||
+ ' of empty enimations');
|
||||
+ ' of empty animations');
|
||||
|
||||
test(t => {
|
||||
const div = addDiv(t);
|
||||
|
@ -0,0 +1,130 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="help" src="https://drafts.csswg.org/scroll-animations-1/#named-timeline-range">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="/web-animations/testcommon.js"></script>
|
||||
<script src="support/testcommon.js"></script>
|
||||
<script src="/web-animations/resources/keyframe-utils.js"></script>
|
||||
<title>Animation range and delay</title>
|
||||
</head>
|
||||
<style type="text/css">
|
||||
@keyframes anim {
|
||||
cover 0% {
|
||||
opacity: 0;
|
||||
margin-left: 0px;
|
||||
}
|
||||
cover 100% {
|
||||
opacity: 1;
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
#scroller {
|
||||
border: 10px solid lightgray;
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
width: 300px;
|
||||
height: 200px;
|
||||
}
|
||||
#block {
|
||||
margin-top: 800px;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
width: 100px;
|
||||
height: 50px;
|
||||
background-color: blue;
|
||||
view-timeline: block block;
|
||||
}
|
||||
#target {
|
||||
margin-bottom: 800px;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
z-index: -1;
|
||||
background-color: green;
|
||||
animation: anim auto both linear;
|
||||
/* using document timeline by default */
|
||||
animation-range-start: contain 0%;
|
||||
animation-range-end: contain 100%;
|
||||
view-timeline: target block;
|
||||
}
|
||||
|
||||
#target.with-view-timeline {
|
||||
animation-timeline: target;
|
||||
}
|
||||
#target.with-view-timeline.retarget {
|
||||
animation-timeline: block;
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<div id="scroller">
|
||||
<div id="block"></div>
|
||||
<div id="target"></div>
|
||||
</div>
|
||||
</body>
|
||||
<script type="text/javascript">
|
||||
async function runTest() {
|
||||
promise_test(async t => {
|
||||
await waitForNextFrame();
|
||||
const anim = document.getAnimations()[0];
|
||||
await anim.ready;
|
||||
await waitForNextFrame();
|
||||
|
||||
// Initially using a document timeline, so the keyframes should be
|
||||
// ignored.
|
||||
let frames = anim.effect.getKeyframes();
|
||||
let expected = [];
|
||||
assert_frame_lists_equal(frames, expected);
|
||||
|
||||
// Once a view-timeline is added, the kefyrames must update to reflect
|
||||
// the new keyframe offsets.
|
||||
target.classList.add('with-view-timeline');
|
||||
assert_equals(getComputedStyle(target).animationTimeline, 'target',
|
||||
'Switch to view timeline');
|
||||
await waitForNextFrame();
|
||||
|
||||
frames = anim.effect.getKeyframes();
|
||||
expected = [
|
||||
{ offset: -1, computedOffset: -1, easing: "linear", composite: "auto",
|
||||
marginLeft: "0px", opacity: "0" },
|
||||
{ offset: 0, computedOffset: 0, easing: "linear", composite: "replace",
|
||||
marginRight: "10px" },
|
||||
{ offset: 1, computedOffset: 1, easing: "linear", composite: "replace",
|
||||
marginLeft: "10px" },
|
||||
{ offset: 2, computedOffset: 2, easing: "linear", composite: "auto",
|
||||
marginRight: "0px", opacity: "1" },
|
||||
];
|
||||
assert_frame_lists_equal(frames, expected);
|
||||
|
||||
target.classList.add('retarget');
|
||||
assert_equals(getComputedStyle(target).animationTimeline, 'block',
|
||||
'Switch to another view timeline');
|
||||
await waitForNextFrame();
|
||||
frames = anim.effect.getKeyframes();
|
||||
expected = [
|
||||
{ offset: -1/3, computedOffset: -1/3, easing: "linear",
|
||||
composite: "auto", marginLeft: "0px", opacity: "0" },
|
||||
{ offset: 0, computedOffset: 0, easing: "linear", composite: "replace",
|
||||
marginRight: "10px" },
|
||||
{ offset: 1, computedOffset: 1, easing: "linear", composite: "replace",
|
||||
marginLeft: "10px" },
|
||||
{ offset: 4/3, computedOffset: 4/3, easing: "linear", composite: "auto",
|
||||
marginRight: "0px", opacity: "1" },
|
||||
];
|
||||
assert_frame_lists_equal(frames, expected);
|
||||
|
||||
target.classList.toggle('with-view-timeline');
|
||||
assert_equals(getComputedStyle(target).animationTimeline, 'auto',
|
||||
'Switch back to document timeline');
|
||||
frames = anim.effect.getKeyframes();
|
||||
expected = [];
|
||||
assert_frame_lists_equal(frames, expected);
|
||||
|
||||
}, 'getKeyframes with timeline-offsets');
|
||||
}
|
||||
|
||||
window.onload = runTest;
|
||||
</script>
|
@ -0,0 +1,110 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="help" src="https://drafts.csswg.org/scroll-animations-1/#named-timeline-range">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="/web-animations/testcommon.js"></script>
|
||||
<script src="support/testcommon.js"></script>
|
||||
<script src="/web-animations/resources/keyframe-utils.js"></script>
|
||||
<title>Animation range and delay</title>
|
||||
</head>
|
||||
<style type="text/css">
|
||||
@keyframes anim {
|
||||
cover 0% {
|
||||
margin-left: 0px;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
cover 100% {
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
#scroller {
|
||||
border: 10px solid lightgray;
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
width: 300px;
|
||||
height: 200px;
|
||||
}
|
||||
#block {
|
||||
margin-top: 800px;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
width: 100px;
|
||||
height: 50px;
|
||||
background-color: blue;
|
||||
view-timeline: t1 block;
|
||||
}
|
||||
#target {
|
||||
margin-bottom: 800px;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
z-index: -1;
|
||||
background-color: green;
|
||||
animation: anim auto both linear;
|
||||
animation-range-start: contain 0%;
|
||||
animation-range-end: contain 100%;
|
||||
animation-timeline: t1;
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<div id="scroller">
|
||||
<div id="block"></div>
|
||||
<div id="target"></div>
|
||||
</div>
|
||||
</body>
|
||||
<script type="text/javascript">
|
||||
async function runTest() {
|
||||
promise_test(async t => {
|
||||
await waitForNextFrame();
|
||||
const anim = document.getAnimations()[0];
|
||||
await anim.ready;
|
||||
await waitForNextFrame();
|
||||
|
||||
let frames = anim.effect.getKeyframes();
|
||||
let expected_resolved_offsets = [
|
||||
{ offset: -1/3, computedOffset: -1/3, easing: "linear",
|
||||
composite: "auto", marginLeft: "0px" },
|
||||
{ offset: 0, computedOffset: 0, easing: "linear", composite: "replace",
|
||||
marginRight: "10px", opacity: "1" },
|
||||
{ offset: 1/2, computedOffset: 1/2, easing: "linear",
|
||||
composite: "auto", opacity: "0.5" },
|
||||
{ offset: 1, computedOffset: 1, easing: "linear", composite: "replace",
|
||||
marginLeft: "10px", opacity: "1" },
|
||||
{ offset: 4/3, computedOffset: 4/3, easing: "linear", composite: "auto",
|
||||
marginRight: "0px" },
|
||||
];
|
||||
assert_frame_lists_equal(frames, expected_resolved_offsets,
|
||||
'Initial keyframes with active view-timeline');
|
||||
|
||||
block.style.display = 'none';
|
||||
// View-timeline becomes invalid. Keyframes with timeline offsets must be
|
||||
// ignored.
|
||||
frames = anim.effect.getKeyframes();
|
||||
let expected_unresolved_offsets = [
|
||||
{ offset: 0, computedOffset: 0, opacity: "1", easing: "linear",
|
||||
composite: "replace" },
|
||||
{ offset: 0.5, computedOffset: 0.5, opacity: "0.5", easing: "linear",
|
||||
composite: "auto", },
|
||||
{ offset: 1, computedOffset: 1, opacity: "1", easing: "linear",
|
||||
composite: "replace" }
|
||||
];
|
||||
assert_frame_lists_equal(frames, expected_unresolved_offsets,
|
||||
'Keyframes with invalid view timeline');
|
||||
|
||||
block.style.display = 'block';
|
||||
// Ensure that keyframes with timeline-offsets are restored.
|
||||
frames = anim.effect.getKeyframes();
|
||||
|
||||
assert_frame_lists_equal(frames, expected_resolved_offsets,
|
||||
'Keyframes with restored view timeline');
|
||||
}, 'Keyframes with timeline-offsets ignored when timeline is inactive');
|
||||
}
|
||||
|
||||
window.onload = runTest;
|
||||
</script>
|
@ -0,0 +1,74 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="help" src="https://drafts.csswg.org/scroll-animations-1/#named-timeline-range">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="/web-animations/testcommon.js"></script>
|
||||
<script src="support/testcommon.js"></script>
|
||||
<script src="/web-animations/resources/keyframe-utils.js"></script>
|
||||
<title>Animation range and delay</title>
|
||||
</head>
|
||||
<style type="text/css">
|
||||
@keyframes anim {
|
||||
cover 0% {
|
||||
margin-left: 0px;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
cover 100% {
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
#scroller {
|
||||
border: 10px solid lightgray;
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
width: 300px;
|
||||
height: 200px;
|
||||
}
|
||||
#target {
|
||||
margin-bottom: 800px;
|
||||
margin-top: 800px;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
z-index: -1;
|
||||
background-color: green;
|
||||
animation: anim auto both linear;
|
||||
/* using document timeline by default */
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<div id="scroller">
|
||||
<div id="target"></div>
|
||||
</div>
|
||||
</body>
|
||||
<script type="text/javascript">
|
||||
async function runTest() {
|
||||
promise_test(async t => {
|
||||
await waitForNextFrame();
|
||||
const anim = document.getAnimations()[0];
|
||||
await anim.ready;
|
||||
await waitForNextFrame();
|
||||
|
||||
// Using a document timeline, so only the 50% keyframe is used.
|
||||
let frames = anim.effect.getKeyframes();
|
||||
let expected = [
|
||||
{ offset: 0, computedOffset: 0, opacity: "1", easing: "linear",
|
||||
composite: "replace" },
|
||||
{ offset: 0.5, computedOffset: 0.5, opacity: "0.5", easing: "linear",
|
||||
composite: "auto", },
|
||||
{ offset: 1, computedOffset: 1, opacity: "1", easing: "linear",
|
||||
composite: "replace" }
|
||||
];
|
||||
assert_frame_lists_equal(frames, expected);
|
||||
}, 'Keyframes with timeline-offsets ignored when using document ' +
|
||||
'timeline');
|
||||
}
|
||||
|
||||
window.onload = runTest;
|
||||
</script>
|
@ -51,43 +51,56 @@
|
||||
// scrollTop=200 to 400 is the entry range
|
||||
container.scrollTop = 200;
|
||||
await waitForNextFrame();
|
||||
assert_equals(getComputedStyle(subject).opacity, '0', 'Effect at entry 0%');
|
||||
const anim = document.getAnimations()[0];
|
||||
assert_equals(getComputedStyle(subject).opacity, '0',
|
||||
'Effect at entry 0%');
|
||||
|
||||
container.scrollTop = 300;
|
||||
await waitForNextFrame();
|
||||
assert_equals(getComputedStyle(subject).opacity, '0.5', 'Effect at entry 50%');
|
||||
assert_equals(getComputedStyle(subject).opacity, '0.5',
|
||||
'Effect at entry 50%');
|
||||
|
||||
container.scrollTop = 400;
|
||||
await waitForNextFrame();
|
||||
assert_equals(getComputedStyle(subject).opacity, '1', 'Effect at entry 100%');
|
||||
assert_equals(getComputedStyle(subject).opacity, '1',
|
||||
'Effect at entry 100%');
|
||||
|
||||
// scrollTop=600-800 is the exit range
|
||||
container.scrollTop = 600;
|
||||
await waitForNextFrame();
|
||||
assert_equals(getComputedStyle(subject).opacity, '1', 'Effect at exit 0%');
|
||||
assert_equals(getComputedStyle(subject).opacity, '1',
|
||||
'Effect at exit 0%');
|
||||
|
||||
container.scrollTop = 700;
|
||||
await waitForNextFrame();
|
||||
assert_equals(getComputedStyle(subject).opacity, '0.5', 'Effect at exit 50%');
|
||||
assert_equals(getComputedStyle(subject).opacity, '0.5',
|
||||
'Effect at exit 50%');
|
||||
|
||||
container.scrollTop = 800;
|
||||
await waitForNextFrame();
|
||||
assert_equals(getComputedStyle(subject).opacity, '0', 'Effect at exit 100%');
|
||||
assert_equals(getComputedStyle(subject).opacity, '0',
|
||||
'Effect at exit 100%');
|
||||
|
||||
// First change scrollTop so that you are at entry 100%, then resize the container in a way
|
||||
// that scrollTop is the same, but now the animation is at entry 50% and check opacity.
|
||||
// After changing the height of container, scrollTop=300-500 is the entry range
|
||||
// First change scrollTop so that you are at entry 100%, then resize the
|
||||
// container in a way that scrollTop is the same, but now the animation is
|
||||
// at entry 50% and check opacity. After changing the height of container,
|
||||
// scrollTop=300-500 is the entry range
|
||||
container.scrollTop = 400;
|
||||
await waitForNextFrame();
|
||||
assert_equals(getComputedStyle(subject).opacity, '1', 'Effect at entry 100%');
|
||||
assert_equals(getComputedStyle(subject).opacity, '1',
|
||||
'Effect at entry 100% (post resize)');
|
||||
|
||||
container.style.height = '300px';
|
||||
await waitForNextFrame();
|
||||
assert_equals(getComputedStyle(subject).opacity, '0.5', 'Effect at entry 50%');
|
||||
assert_equals(getComputedStyle(subject).opacity, '0.5',
|
||||
'Effect at entry 50% (post resize)');
|
||||
|
||||
// After changing the height of container, scrollTop=600-800 is still the exit range
|
||||
// After changing the height of container, scrollTop=600-800 is still the
|
||||
// exit range
|
||||
container.scrollTop = 700;
|
||||
await waitForNextFrame();
|
||||
assert_equals(getComputedStyle(subject).opacity, '0.5', 'Effect at exit 50%');
|
||||
assert_equals(getComputedStyle(subject).opacity, '0.5',
|
||||
'Effect at exit 50% (post resize)');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
@ -0,0 +1,118 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="help" src="https://drafts.csswg.org/scroll-animations-1/#named-timeline-range">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="/web-animations/testcommon.js"></script>
|
||||
<script src="support/testcommon.js"></script>
|
||||
<title>Animation range and delay</title>
|
||||
</head>
|
||||
<style type="text/css">
|
||||
@keyframes anim {
|
||||
cover 0% { /* resolves to -100% */
|
||||
opacity: 0;
|
||||
transform: none;
|
||||
margin-left: 0px;
|
||||
/* missing margin-right -- requires neutral keyframe at 0% */
|
||||
}
|
||||
cover 100% { /* resolves to 200% */
|
||||
opacity: 1;
|
||||
transform: translateX(300px);
|
||||
margin-right: 0px;
|
||||
/* missing margin-left -- requires neutral keyframe at 100% */
|
||||
}
|
||||
}
|
||||
#scroller {
|
||||
border: 10px solid lightgray;
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
width: 300px;
|
||||
height: 200px;
|
||||
}
|
||||
#target {
|
||||
margin: 800px 10px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
z-index: -1;
|
||||
background-color: green;
|
||||
animation: anim auto both linear;
|
||||
animation-timeline: t1;
|
||||
animation-range-start: contain 0%;
|
||||
animation-range-end: contain 100%;
|
||||
view-timeline: t1 block;
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<div id=scroller>
|
||||
<div id=target></div>
|
||||
</div>
|
||||
</body>
|
||||
<script type="text/javascript">
|
||||
async function runTest() {
|
||||
function assert_progress_equals(anim, expected, errorMessage) {
|
||||
assert_approx_equals(
|
||||
anim.effect.getComputedTiming().progress,
|
||||
expected, 1e-6, errorMessage);
|
||||
}
|
||||
|
||||
function assert_opacity_equals(expected, errorMessage) {
|
||||
assert_approx_equals(
|
||||
parseFloat(getComputedStyle(target).opacity), expected, 1e-6,
|
||||
errorMessage);
|
||||
}
|
||||
|
||||
function assert_translate_x_equals(expected, errorMessage) {
|
||||
const style = getComputedStyle(target).transform;
|
||||
const regex = /matrix\(([^\)]*)\)/;
|
||||
const captureGroupIndex = 1;
|
||||
const translateIndex = 4;
|
||||
const match = style.match(regex)[captureGroupIndex];
|
||||
const translateX = parseFloat(match.split(',')[translateIndex].trim());
|
||||
assert_approx_equals(translateX, expected, 1e-6, errorMessage);
|
||||
}
|
||||
|
||||
function assert_property_equals(property, expected, errorMessage) {
|
||||
const value = parseFloat(getComputedStyle(target)[property]);
|
||||
assert_approx_equals(value, expected, 1e-6, errorMessage);
|
||||
}
|
||||
|
||||
promise_test(async t => {
|
||||
await waitForNextFrame();
|
||||
const anim = document.getAnimations()[0];
|
||||
await anim.ready;
|
||||
await waitForNextFrame();
|
||||
|
||||
// @ contain 0%
|
||||
scroller.scrollTop = 700;
|
||||
await waitForNextFrame();
|
||||
assert_progress_equals(anim, 0, 'progress at contain 0%');
|
||||
assert_translate_x_equals(100, 'translation at contain 0%');
|
||||
assert_opacity_equals(1/3, 'opacity at contain 0%');
|
||||
assert_property_equals('margin-left', 5, 'margin-left at contain 0%');
|
||||
assert_property_equals('margin-right', 10, 'margin-right at contain 0%');
|
||||
|
||||
// @ contain 50%
|
||||
scroller.scrollTop = 750;
|
||||
await waitForNextFrame();
|
||||
assert_progress_equals(anim, 0.5, 'progress at contain 50%');
|
||||
assert_translate_x_equals(150, 'translation at contain 50%');
|
||||
assert_opacity_equals(0.5, 'opacity at contain 50%');
|
||||
assert_property_equals('margin-left', 7.5, 'margin-left at contain 50%');
|
||||
assert_property_equals('margin-right', 7.5, 'margin-right at contain 50%');
|
||||
|
||||
// @ contain 100%
|
||||
scroller.scrollTop = 800;
|
||||
await waitForNextFrame();
|
||||
assert_progress_equals(anim, 1, 'progress at contain 100%');
|
||||
assert_translate_x_equals(200, 'translation at contain 100%');
|
||||
assert_opacity_equals(2/3, 'opacity at contain 100%');
|
||||
assert_property_equals('margin-left', 10, 'margin-left at contain 100%');
|
||||
assert_property_equals('margin-right', 5, 'margin-right at contain 100%');
|
||||
}, 'ViewTimeline with timeline offset keyframes outside [0,1]');
|
||||
}
|
||||
|
||||
window.onload = runTest;
|
||||
</script>
|
||||
</html>
|
@ -16,10 +16,11 @@
|
||||
* @param {Array.<ComputedKeyframe>} a - actual computed keyframes
|
||||
* @param {Array.<ComputedKeyframe>} b - expected computed keyframes
|
||||
*/
|
||||
function assert_frame_lists_equal(a, b) {
|
||||
assert_equals(a.length, b.length, 'number of frames');
|
||||
function assert_frame_lists_equal(a, b, message) {
|
||||
assert_equals(a.length, b.length, `number of frames: ${(message || '')}`);
|
||||
for (let i = 0; i < Math.min(a.length, b.length); i++) {
|
||||
assert_frames_equal(a[i], b[i], `ComputedKeyframe #${i}`);
|
||||
assert_frames_equal(a[i], b[i],
|
||||
`ComputedKeyframe #${i}: ${(message || '')}`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,6 +31,9 @@ function assert_frames_equal(a, b, name) {
|
||||
`properties on ${name} should match`);
|
||||
// Iterates sorted keys to ensure stable failures.
|
||||
for (const p of Object.keys(a).sort()) {
|
||||
assert_equals(a[p], b[p], `value for '${p}' on ${name}`);
|
||||
if (typeof a[p] == 'number')
|
||||
assert_approx_equals(a[p], b[p], 1e-6, `value for '${p}' on ${name}`);
|
||||
else
|
||||
assert_equals(a[p], b[p], `value for '${p}' on ${name}`);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user