blob: b04e044a8b5aeb4e722c388c8c1d0371e05ce27e [file] [log] [blame]
// Copyright 2015 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cobalt/web_animations/keyframe_effect_read_only.h"
#include "cobalt/cssom/length_value.h"
#include "cobalt/cssom/number_value.h"
#include "cobalt/cssom/property_definitions.h"
#include "cobalt/cssom/rgba_color_value.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cobalt {
namespace web_animations {
TEST(KeyframeEffectReadOnlyDataTest, IsPropertyAnimated) {
std::vector<Keyframe::Data> keyframes;
Keyframe::Data frame1(0.0);
frame1.AddPropertyValue(cssom::kColorProperty,
new cssom::RGBAColorValue(0, 100, 0, 100));
keyframes.push_back(frame1);
Keyframe::Data frame2(0.5);
frame2.AddPropertyValue(cssom::kOpacityProperty, new cssom::NumberValue(0.5));
keyframes.push_back(frame2);
Keyframe::Data frame3(1.0);
frame3.AddPropertyValue(cssom::kBackgroundColorProperty,
new cssom::RGBAColorValue(0, 100, 0, 100));
frame3.AddPropertyValue(cssom::kTopProperty,
new cssom::LengthValue(1.0f, cssom::kPixelsUnit));
keyframes.push_back(frame3);
KeyframeEffectReadOnly::Data keyframe_effect(keyframes);
// Properties that are referenced by keyframes should be reported as being
// animated.
EXPECT_TRUE(keyframe_effect.IsPropertyAnimated(cssom::kColorProperty));
EXPECT_TRUE(keyframe_effect.IsPropertyAnimated(cssom::kOpacityProperty));
EXPECT_TRUE(
keyframe_effect.IsPropertyAnimated(cssom::kBackgroundColorProperty));
EXPECT_TRUE(keyframe_effect.IsPropertyAnimated(cssom::kTopProperty));
// Properties we did not reference in keyframes should not be reported as
// being animated.
EXPECT_FALSE(keyframe_effect.IsPropertyAnimated(cssom::kLeftProperty));
EXPECT_FALSE(keyframe_effect.IsPropertyAnimated(cssom::kFontSizeProperty));
EXPECT_FALSE(keyframe_effect.IsPropertyAnimated(cssom::kTransformProperty));
}
namespace {
std::vector<Keyframe::Data> AddNoiseKeyframes(
const std::vector<Keyframe::Data>& keyframes) {
// Adds noise keyframes to the set of keyframes. Noise here means other
// keyframes with a "NULL" target property so that they are guaranteed to
// not match any property that any of these tests wish to animate.
// Additionally, existing keyframes will have a "NULL" target property added
// to them.
std::vector<Keyframe::Data> noisy_keyframes;
// Add an initial noise keyframe at offset 0 to start the sequence.
Keyframe::Data noise_keyframe_begin(0.0);
noise_keyframe_begin.AddPropertyValue(cssom::kNoneProperty,
new cssom::NumberValue(0));
noisy_keyframes.push_back(noise_keyframe_begin);
for (std::vector<Keyframe::Data>::const_iterator iter = keyframes.begin();
iter != keyframes.end(); ++iter) {
Keyframe::Data noise_keyframe(*iter->offset());
noise_keyframe.AddPropertyValue(cssom::kNoneProperty,
new cssom::NumberValue(0));
// Add one noise keyframe before the real keyframe
noisy_keyframes.push_back(noise_keyframe);
// Add the real keyframe, with a noise property added to it.
Keyframe::Data real_keyframe = *iter;
real_keyframe.AddPropertyValue(cssom::kNoneProperty,
new cssom::NumberValue(0));
noisy_keyframes.push_back(real_keyframe);
// Add one noise keyframe after the real keyframe.
noisy_keyframes.push_back(noise_keyframe);
}
// Add a final noise keyframe at offset 1 to end the sequence.
Keyframe::Data noise_keyframe_end(1.0);
noise_keyframe_end.AddPropertyValue(cssom::kNoneProperty,
new cssom::NumberValue(0));
noisy_keyframes.push_back(noise_keyframe_end);
return noisy_keyframes;
}
KeyframeEffectReadOnly::Data CreateKeyframeEffectWithTwoNumberKeyframes(
cssom::PropertyKey target_property, bool add_noise_keyframes,
double offset1, float value1, double offset2, float value2) {
std::vector<Keyframe::Data> keyframes;
Keyframe::Data frame1(offset1);
frame1.AddPropertyValue(target_property, new cssom::NumberValue(value1));
keyframes.push_back(frame1);
Keyframe::Data frame2(offset2);
frame2.AddPropertyValue(target_property, new cssom::NumberValue(value2));
keyframes.push_back(frame2);
return KeyframeEffectReadOnly::Data(
add_noise_keyframes ? AddNoiseKeyframes(keyframes) : keyframes);
}
KeyframeEffectReadOnly::Data CreateKeyframeEffectWithThreeNumberKeyframes(
cssom::PropertyKey target_property, bool add_noise_keyframes,
double offset1, float value1, double offset2, float value2, double offset3,
float value3) {
std::vector<Keyframe::Data> keyframes;
Keyframe::Data frame1(offset1);
frame1.AddPropertyValue(target_property, new cssom::NumberValue(value1));
keyframes.push_back(frame1);
Keyframe::Data frame2(offset2);
frame2.AddPropertyValue(target_property, new cssom::NumberValue(value2));
keyframes.push_back(frame2);
Keyframe::Data frame3(offset3);
frame3.AddPropertyValue(target_property, new cssom::NumberValue(value3));
keyframes.push_back(frame3);
return KeyframeEffectReadOnly::Data(
add_noise_keyframes ? AddNoiseKeyframes(keyframes) : keyframes);
}
template <typename T>
scoped_refptr<T> ComputeAnimatedPropertyValueTyped(
const KeyframeEffectReadOnly::Data& effect,
cssom::PropertyKey target_property,
const scoped_refptr<cssom::PropertyValue>& underlying_value,
double iteration_progress, double current_iteration) {
scoped_refptr<cssom::PropertyValue> animated =
effect.ComputeAnimatedPropertyValue(target_property, underlying_value,
iteration_progress,
current_iteration);
scoped_refptr<T> animated_typed = dynamic_cast<T*>(animated.get());
DCHECK(animated_typed);
return animated_typed;
}
} // namespace
// We parameterize the following suite of tests on whether or not "noise
// keyframes" are to be included in the tests. Since all of these tests will
// be targeting a single property, "noise keyframes" means that the keyframe
// effects will contain keyframes for properties that are not our target
// property, and the keyframes that do contain our target property will also
// refer to our noise property. Essentially, all test results need to be
// the same regardless of whether noise is present or not, as when animating
// a single property, we only care about keyframes that contain that property.
class KeyframeEffectReadOnlyDataSingleParameterKeyframeTests
: public ::testing::TestWithParam<bool> {};
TEST_P(KeyframeEffectReadOnlyDataSingleParameterKeyframeTests,
NoPropertySpecificKeyframes) {
// If we create a keyframe effect that animates opacity, then applying it
// to all other properties should simply return the underlying value for
// the other properties.
KeyframeEffectReadOnly::Data effect =
CreateKeyframeEffectWithTwoNumberKeyframes(
cssom::kOpacityProperty, GetParam(), 0.0, 0.25f, 1.0, 0.75f);
scoped_refptr<cssom::NumberValue> underlying_value =
new cssom::NumberValue(0.1f);
scoped_refptr<cssom::NumberValue> animated =
ComputeAnimatedPropertyValueTyped<cssom::NumberValue>(
effect, cssom::kLeftProperty, underlying_value, 0.5, 0);
EXPECT_TRUE(animated->Equals(*underlying_value));
}
TEST_P(KeyframeEffectReadOnlyDataSingleParameterKeyframeTests,
Offset0AutoPopulatesWithUnderlyingValue) {
// If our keyframe effect does not specify a value for offset 0, it should
// be created automatically from the underlying value.
KeyframeEffectReadOnly::Data effect =
CreateKeyframeEffectWithTwoNumberKeyframes(
cssom::kOpacityProperty, GetParam(), 0.5, 0.8f, 1.0, 1.0f);
scoped_refptr<cssom::NumberValue> underlying_value =
new cssom::NumberValue(0.1f);
scoped_refptr<cssom::NumberValue> animated =
ComputeAnimatedPropertyValueTyped<cssom::NumberValue>(
effect, cssom::kOpacityProperty, underlying_value, 0.25, 0);
EXPECT_FLOAT_EQ(0.45f, animated->value());
}
TEST_P(KeyframeEffectReadOnlyDataSingleParameterKeyframeTests,
Offset1AutoPopulatesWithUnderlyingValue) {
// If our keyframe effect does not specify a value for offset 0, it should
// be created automatically from the underlying value.
KeyframeEffectReadOnly::Data effect =
CreateKeyframeEffectWithTwoNumberKeyframes(
cssom::kOpacityProperty, GetParam(), 0.0, 0.0f, 0.5, 0.8f);
scoped_refptr<cssom::NumberValue> underlying_value =
new cssom::NumberValue(0.1f);
scoped_refptr<cssom::NumberValue> animated =
ComputeAnimatedPropertyValueTyped<cssom::NumberValue>(
effect, cssom::kOpacityProperty, underlying_value, 0.75, 0);
EXPECT_FLOAT_EQ(0.45f, animated->value());
}
TEST_P(KeyframeEffectReadOnlyDataSingleParameterKeyframeTests,
NegativeIterationProgressReturnsFirstOffset0Value) {
// If iteration progress is less than 0 and we have multiple keyframes with
// offset 0, we should return the value of the first of these.
// Step 10 from:
// https://www.w3.org/TR/2015/WD-web-animations-1-20150707/#the-effect-value-of-a-keyframe-animation-effect
KeyframeEffectReadOnly::Data effect =
CreateKeyframeEffectWithTwoNumberKeyframes(
cssom::kOpacityProperty, GetParam(), 0.0, 0.2f, 0.0, 0.8f);
scoped_refptr<cssom::NumberValue> underlying_value =
new cssom::NumberValue(0.1f);
scoped_refptr<cssom::NumberValue> animated =
ComputeAnimatedPropertyValueTyped<cssom::NumberValue>(
effect, cssom::kOpacityProperty, underlying_value, -1.0, 0);
EXPECT_FLOAT_EQ(0.2f, animated->value());
}
TEST_P(KeyframeEffectReadOnlyDataSingleParameterKeyframeTests,
IterationProgressGreaterThan1ReturnsLastOffset1Value) {
// If iteration progress is greater than 1 and we have multiple keyframes with
// offset 1, we should return the value of the last of these.
// Step 10 from:
// https://www.w3.org/TR/2015/WD-web-animations-1-20150707/#the-effect-value-of-a-keyframe-animation-effect
KeyframeEffectReadOnly::Data effect =
CreateKeyframeEffectWithTwoNumberKeyframes(
cssom::kOpacityProperty, GetParam(), 1.0, 0.2f, 1.0, 0.8f);
scoped_refptr<cssom::NumberValue> underlying_value =
new cssom::NumberValue(0.1f);
scoped_refptr<cssom::NumberValue> animated =
ComputeAnimatedPropertyValueTyped<cssom::NumberValue>(
effect, cssom::kOpacityProperty, underlying_value, 2.0, 0);
EXPECT_FLOAT_EQ(0.8f, animated->value());
}
TEST_P(KeyframeEffectReadOnlyDataSingleParameterKeyframeTests,
IterationProgressLessThan0Extrapolates) {
// If iteration progress is greater than 1, we should extrapolate from the
// last keyframe.
KeyframeEffectReadOnly::Data effect =
CreateKeyframeEffectWithTwoNumberKeyframes(
cssom::kOpacityProperty, GetParam(), 0.0, 0.2f, 1.0, 0.8f);
scoped_refptr<cssom::NumberValue> underlying_value =
new cssom::NumberValue(0.1f);
scoped_refptr<cssom::NumberValue> animated =
ComputeAnimatedPropertyValueTyped<cssom::NumberValue>(
effect, cssom::kOpacityProperty, underlying_value, -1.0, 0);
EXPECT_FLOAT_EQ(-0.4f, animated->value());
}
TEST_P(KeyframeEffectReadOnlyDataSingleParameterKeyframeTests,
IterationProgressLessThan0ExtrapolatesWithoutExplicit0OffsetKeyframe) {
// If iteration progress is greater than 1, we should extrapolate from the
// last keyframe.
KeyframeEffectReadOnly::Data effect =
CreateKeyframeEffectWithTwoNumberKeyframes(
cssom::kOpacityProperty, GetParam(), 0.5, 0.2f, 1.0, 0.8f);
scoped_refptr<cssom::NumberValue> underlying_value =
new cssom::NumberValue(0.1f);
scoped_refptr<cssom::NumberValue> animated =
ComputeAnimatedPropertyValueTyped<cssom::NumberValue>(
effect, cssom::kOpacityProperty, underlying_value, -1.0, 0);
EXPECT_FLOAT_EQ(-0.1f, animated->value());
}
TEST_P(KeyframeEffectReadOnlyDataSingleParameterKeyframeTests,
IterationProgressGreaterThan1Extrapolates) {
// If iteration progress is greater than 1, we should extrapolate from the
// last keyframe.
KeyframeEffectReadOnly::Data effect =
CreateKeyframeEffectWithTwoNumberKeyframes(
cssom::kOpacityProperty, GetParam(), 0.0, 0.2f, 1.0, 0.8f);
scoped_refptr<cssom::NumberValue> underlying_value =
new cssom::NumberValue(0.1f);
scoped_refptr<cssom::NumberValue> animated =
ComputeAnimatedPropertyValueTyped<cssom::NumberValue>(
effect, cssom::kOpacityProperty, underlying_value, 2.0, 0);
EXPECT_FLOAT_EQ(1.4f, animated->value());
}
TEST_P(
KeyframeEffectReadOnlyDataSingleParameterKeyframeTests,
IterationProgressGreaterThan1ExtrapolatesWithoutExplicit1OffsetKeyframe) {
// If iteration progress is greater than 1, we should extrapolate from the
// last keyframe.
KeyframeEffectReadOnly::Data effect =
CreateKeyframeEffectWithTwoNumberKeyframes(
cssom::kOpacityProperty, GetParam(), 0.0, 0.2f, 0.5, 0.8f);
scoped_refptr<cssom::NumberValue> underlying_value =
new cssom::NumberValue(0.9f);
scoped_refptr<cssom::NumberValue> animated =
ComputeAnimatedPropertyValueTyped<cssom::NumberValue>(
effect, cssom::kOpacityProperty, underlying_value, 2.0, 0);
EXPECT_FLOAT_EQ(1.1f, animated->value());
}
TEST_P(KeyframeEffectReadOnlyDataSingleParameterKeyframeTests,
LastKeyframeWithSameOffsetIsChosenAsFirstEndpoint) {
// If we have two keyframes with the same offset, and that offset is chosen
// as the offset of the first interval endpoint, then we use the last keyframe
// with the given offset.
KeyframeEffectReadOnly::Data effect =
CreateKeyframeEffectWithTwoNumberKeyframes(
cssom::kOpacityProperty, GetParam(), 0.5, 0.2f, 0.5, 0.8f);
scoped_refptr<cssom::NumberValue> underlying_value =
new cssom::NumberValue(0.9f);
scoped_refptr<cssom::NumberValue> animated =
ComputeAnimatedPropertyValueTyped<cssom::NumberValue>(
effect, cssom::kOpacityProperty, underlying_value, 0.75, 0);
EXPECT_FLOAT_EQ(0.85f, animated->value());
}
TEST_P(KeyframeEffectReadOnlyDataSingleParameterKeyframeTests,
KeyframeAfterFirstIntervalEndpointIsSecondIntervalEndpoint) {
// Check that the very next keyframe after our first interval endpoint is
// the one chosen as the second interval endpoint. E.g., if we have two
// candidates for the second endpoint with the same offset, the first of
// those is chosen.
KeyframeEffectReadOnly::Data effect =
CreateKeyframeEffectWithTwoNumberKeyframes(
cssom::kOpacityProperty, GetParam(), 0.5, 0.2f, 0.5, 0.8f);
scoped_refptr<cssom::NumberValue> underlying_value =
new cssom::NumberValue(0.1f);
scoped_refptr<cssom::NumberValue> animated =
ComputeAnimatedPropertyValueTyped<cssom::NumberValue>(
effect, cssom::kOpacityProperty, underlying_value, 0.25, 0);
EXPECT_FLOAT_EQ(0.15f, animated->value());
}
TEST_P(KeyframeEffectReadOnlyDataSingleParameterKeyframeTests,
AllIntervalsOfMultiIntervalEffectEvaluateCorrectly) {
// Check that all 4 intervals in a 3-keyframe effect evaluate to the correct
// values.
KeyframeEffectReadOnly::Data effect =
CreateKeyframeEffectWithThreeNumberKeyframes(cssom::kOpacityProperty,
GetParam(), 0.25, 0.1f, 0.5,
0.5f, 0.75, 0.9f);
scoped_refptr<cssom::NumberValue> underlying_value =
new cssom::NumberValue(0.0f);
scoped_refptr<cssom::NumberValue> animated1 =
ComputeAnimatedPropertyValueTyped<cssom::NumberValue>(
effect, cssom::kOpacityProperty, underlying_value, 0.125, 0);
EXPECT_FLOAT_EQ(0.05f, animated1->value());
scoped_refptr<cssom::NumberValue> animated2 =
ComputeAnimatedPropertyValueTyped<cssom::NumberValue>(
effect, cssom::kOpacityProperty, underlying_value, 0.375, 0);
EXPECT_FLOAT_EQ(0.3f, animated2->value());
scoped_refptr<cssom::NumberValue> animated3 =
ComputeAnimatedPropertyValueTyped<cssom::NumberValue>(
effect, cssom::kOpacityProperty, underlying_value, 0.625, 0);
EXPECT_FLOAT_EQ(0.7f, animated3->value());
scoped_refptr<cssom::NumberValue> animated4 =
ComputeAnimatedPropertyValueTyped<cssom::NumberValue>(
effect, cssom::kOpacityProperty, underlying_value, 0.875, 0);
EXPECT_FLOAT_EQ(0.45f, animated4->value());
}
INSTANTIATE_TEST_CASE_P(WithAndWithoutNoise,
KeyframeEffectReadOnlyDataSingleParameterKeyframeTests,
::testing::Bool());
} // namespace web_animations
} // namespace cobalt