Skip to content
This repository was archived by the owner on Aug 30, 2023. It is now read-only.

Commit ed203c8

Browse files
authored
Add UIKit behavioral tests. (#60)
* Add UIKit behavioral tests. These tests provide a baseline of expectations for how different key paths are expected to animate in UIKit. These tests serve both as educational ("What can I animate with UIKit?") as well as as a measure of our own adherance to the baseline expectations. For example, as a result of these unit tests it is now clear that we should not be additively animating opacity by default. * Remove innacurate comment. * Minor cleanup.
1 parent 9415c6d commit ed203c8

File tree

2 files changed

+147
-0
lines changed

2 files changed

+147
-0
lines changed

examples/apps/Catalog/MotionAnimatorCatalog.xcodeproj/project.pbxproj

+4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
2AA864EDA683CEF5FAA721BE /* Pods_UnitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2DBE814C7B88BAD6337052DB /* Pods_UnitTests.framework */; };
1111
660636021FACC24300C3DFB8 /* TimeScaleFactorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 660636011FACC24300C3DFB8 /* TimeScaleFactorTests.swift */; };
1212
6625876C1FB4DB9C00BC7DF1 /* InitialVelocityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6625876B1FB4DB9C00BC7DF1 /* InitialVelocityTests.swift */; };
13+
664F59941FCCE27E002EC56D /* UIKitBehavioralTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 664F59931FCCE27E002EC56D /* UIKitBehavioralTests.swift */; };
1314
666FAA841D384A6B000363DA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 666FAA831D384A6B000363DA /* AppDelegate.swift */; };
1415
666FAA8B1D384A6B000363DA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 666FAA8A1D384A6B000363DA /* Assets.xcassets */; };
1516
666FAA8E1D384A6B000363DA /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 666FAA8C1D384A6B000363DA /* LaunchScreen.storyboard */; };
@@ -51,6 +52,7 @@
5152
52820916F8FAA40E942A7333 /* Pods-UnitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-UnitTests.release.xcconfig"; path = "../../../Pods/Target Support Files/Pods-UnitTests/Pods-UnitTests.release.xcconfig"; sourceTree = "<group>"; };
5253
660636011FACC24300C3DFB8 /* TimeScaleFactorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeScaleFactorTests.swift; sourceTree = "<group>"; };
5354
6625876B1FB4DB9C00BC7DF1 /* InitialVelocityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitialVelocityTests.swift; sourceTree = "<group>"; };
55+
664F59931FCCE27E002EC56D /* UIKitBehavioralTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitBehavioralTests.swift; sourceTree = "<group>"; };
5456
666FAA801D384A6B000363DA /* MotionAnimatorCatalog.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MotionAnimatorCatalog.app; sourceTree = BUILT_PRODUCTS_DIR; };
5557
666FAA831D384A6B000363DA /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = Catalog/AppDelegate.swift; sourceTree = "<group>"; };
5658
666FAA8A1D384A6B000363DA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
@@ -222,6 +224,7 @@
222224
66FD99F91EE9FBBE00C53A82 /* MotionAnimatorTests.m */,
223225
668726491EF04B4C00113675 /* MotionAnimatorTests.swift */,
224226
660636011FACC24300C3DFB8 /* TimeScaleFactorTests.swift */,
227+
664F59931FCCE27E002EC56D /* UIKitBehavioralTests.swift */,
225228
);
226229
path = unit;
227230
sourceTree = "<group>";
@@ -499,6 +502,7 @@
499502
files = (
500503
6625876C1FB4DB9C00BC7DF1 /* InitialVelocityTests.swift in Sources */,
501504
66EF6F281FC33C4800C83A63 /* HeadlessLayerImplicitAnimationTests.swift in Sources */,
505+
664F59941FCCE27E002EC56D /* UIKitBehavioralTests.swift in Sources */,
502506
66EF6F2A1FC48D6A00C83A63 /* InstantAnimationTests.swift in Sources */,
503507
660636021FACC24300C3DFB8 /* TimeScaleFactorTests.swift in Sources */,
504508
66BF5A8F1FB0E4CB00E864F6 /* ImplicitAnimationTests.swift in Sources */,

tests/unit/UIKitBehavioralTests.swift

+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/*
2+
Copyright 2017-present The Material Motion Authors. All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import XCTest
18+
#if IS_BAZEL_BUILD
19+
import _MotionAnimator
20+
#else
21+
import MotionAnimator
22+
#endif
23+
24+
class ShapeLayerBackedView: UIView {
25+
override static var layerClass: AnyClass { return CAShapeLayer.self }
26+
27+
override init(frame: CGRect) {
28+
super.init(frame: frame)
29+
30+
let shapeLayer = self.layer as! CAShapeLayer
31+
shapeLayer.path = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 100, height: 100)).cgPath
32+
}
33+
34+
required init?(coder aDecoder: NSCoder) {
35+
fatalError("init(coder:) has not been implemented")
36+
}
37+
}
38+
39+
class UIKitBehavioralTests: XCTestCase {
40+
var view: UIView!
41+
42+
var originalImplementation: IMP?
43+
override func setUp() {
44+
super.setUp()
45+
46+
let window = UIWindow()
47+
window.makeKeyAndVisible()
48+
view = ShapeLayerBackedView()
49+
window.addSubview(view)
50+
51+
rebuildView()
52+
}
53+
54+
override func tearDown() {
55+
view = nil
56+
57+
super.tearDown()
58+
}
59+
60+
private func rebuildView() {
61+
let oldSuperview = view.superview!
62+
view.removeFromSuperview()
63+
view = ShapeLayerBackedView() // Need to animate a view's layer to get implicit animations.
64+
oldSuperview.addSubview(view)
65+
66+
// Connect our layers to the render server.
67+
CATransaction.flush()
68+
}
69+
70+
func testSomePropertiesImplicitlyAnimateAdditively() {
71+
let properties: [AnimatableKeyPath: Any] = [
72+
.cornerRadius: 3,
73+
.height: 100,
74+
.position: CGPoint(x: 50, y: 20),
75+
.rotation: 42,
76+
.scale: 2.5,
77+
.width: 25,
78+
.x: 12,
79+
.y: 23,
80+
]
81+
for (keyPath, value) in properties {
82+
rebuildView()
83+
84+
UIView.animate(withDuration: 0.01) {
85+
self.view.layer.setValue(value, forKeyPath: keyPath.rawValue)
86+
}
87+
88+
XCTAssertNotNil(view.layer.animationKeys(),
89+
"Expected \(keyPath.rawValue) to generate at least one animation.")
90+
if let animationKeys = view.layer.animationKeys() {
91+
for key in animationKeys {
92+
let animation = view.layer.animation(forKey: key) as! CABasicAnimation
93+
XCTAssertTrue(animation.isAdditive,
94+
"Expected \(key) to be additive as a result of animating "
95+
+ "\(keyPath.rawValue), but it was not: \(animation.debugDescription).")
96+
}
97+
}
98+
}
99+
}
100+
101+
func testSomePropertiesImplicitlyAnimateButNotAdditively() {
102+
let properties: [AnimatableKeyPath: Any] = [
103+
.backgroundColor: UIColor.blue,
104+
.opacity: 0.5,
105+
]
106+
for (keyPath, value) in properties {
107+
rebuildView()
108+
109+
UIView.animate(withDuration: 0.01) {
110+
self.view.layer.setValue(value, forKeyPath: keyPath.rawValue)
111+
}
112+
113+
XCTAssertNotNil(view.layer.animationKeys(),
114+
"Expected \(keyPath.rawValue) to generate at least one animation.")
115+
if let animationKeys = view.layer.animationKeys() {
116+
for key in animationKeys {
117+
let animation = view.layer.animation(forKey: key) as! CABasicAnimation
118+
XCTAssertFalse(animation.isAdditive,
119+
"Expected \(key) not to be additive as a result of animating "
120+
+ "\(keyPath.rawValue), but it was: \(animation.debugDescription).")
121+
}
122+
}
123+
}
124+
}
125+
126+
func testSomePropertiesDoNotImplicitlyAnimate() {
127+
let properties: [AnimatableKeyPath: Any] = [
128+
.strokeStart: 0.2,
129+
.strokeEnd: 0.5,
130+
]
131+
for (keyPath, value) in properties {
132+
rebuildView()
133+
134+
UIView.animate(withDuration: 0.01) {
135+
self.view.layer.setValue(value, forKeyPath: keyPath.rawValue)
136+
}
137+
138+
XCTAssertNil(view.layer.animationKeys(),
139+
"Expected \(keyPath.rawValue) not to generate any animations.")
140+
}
141+
}
142+
}
143+

0 commit comments

Comments
 (0)