forked from facebook/react-native
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Java unit tests for native animated module.
Summary:This change adds some basic unit tests for native animated traversal algorithm. The following tests are added: 1) Build simple animated nodes graph, verify that frame-based animation execute updates and when it runs out of the frames we no longer schedule updates for the native view 2) Build simple animated nodes graph and start short timing animation, verify that animation JS callback gets called. As a part of this change I'm fixing an issue that tests allowed me to discover, where I forgot to clear updates queue at the end of `runUpdates` method. It was causing the view to be updated even if there was no active animation for it (actually it was mitigated by another bug in `hasActiveAnimations` I'm fixing here too). I'm also adding Nullable annotation in a bunch of places. To lazy to send it as a separate change - sorry. Going forward I'm planning on adding more tests. Currently the number of nodes is pretty limited so it's difficult to construct more complex graphs, but once I land Add/Multiply Closes facebook#6858 Differential Revision: D3168549 Pulled By: astreet fb-gh-sync-id: 5295c75f3c7817775b5154bb808888650ff74e12 fbshipit-source-id: 5295c75f3c7817775b5154bb808888650ff74e12
- Loading branch information
Showing
7 changed files
with
234 additions
and
7 deletions.
There are no files selected for viewing
24 changes: 24 additions & 0 deletions
24
ReactAndroid/src/main/java/com/facebook/react/animated/BUCK
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
include_defs('//ReactAndroid/DEFS') | ||
|
||
android_library( | ||
name = 'animated', | ||
srcs = glob([ | ||
'*.java', | ||
]), | ||
deps = [ | ||
react_native_target('java/com/facebook/react/bridge:bridge'), | ||
react_native_target('java/com/facebook/react/uimanager:uimanager'), | ||
|
||
react_native_target('java/com/facebook/react/uimanager/annotations:annotations'), | ||
react_native_dep('third-party/java/infer-annotations:infer-annotations'), | ||
react_native_dep('third-party/java/jsr-305:jsr-305'), | ||
react_native_dep('third-party/android/support/v4:lib-support-v4'), | ||
], | ||
visibility = [ | ||
'PUBLIC', | ||
], | ||
) | ||
|
||
project_config( | ||
src_target = ':animated', | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
29 changes: 29 additions & 0 deletions
29
ReactAndroid/src/test/java/com/facebook/react/animated/BUCK
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
include_defs('//ReactAndroid/DEFS') | ||
|
||
robolectric3_test( | ||
name = 'animated', | ||
# Please change the contact to the oncall of your team | ||
contacts = ['[email protected]'], | ||
srcs = glob(['**/*.java']), | ||
deps = [ | ||
react_native_target('java/com/facebook/react/animated:animated'), | ||
react_native_target('java/com/facebook/react/bridge:bridge'), | ||
react_native_target('java/com/facebook/react/uimanager:uimanager'), | ||
react_native_target('java/com/facebook/react:react'), | ||
|
||
react_native_tests_target('java/com/facebook/react/bridge:testhelpers'), | ||
react_native_dep('libraries/fbcore/src/test/java/com/facebook/powermock:powermock'), | ||
react_native_dep('third-party/java/fest:fest'), | ||
react_native_dep('third-party/java/jsr-305:jsr-305'), | ||
react_native_dep('third-party/java/junit:junit'), | ||
react_native_dep('third-party/java/mockito:mockito'), | ||
react_native_dep('third-party/java/robolectric3/robolectric:robolectric'), | ||
], | ||
visibility = [ | ||
'PUBLIC' | ||
], | ||
) | ||
|
||
project_config( | ||
test_target = ':animated', | ||
) |
168 changes: 168 additions & 0 deletions
168
ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedNodeTraversalTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
/** | ||
* Copyright (c) 2015-present, Facebook, Inc. | ||
* All rights reserved. | ||
* | ||
* This source code is licensed under the BSD-style license found in the | ||
* LICENSE file in the root directory of this source tree. An additional grant | ||
* of patent rights can be found in the PATENTS file in the same directory. | ||
*/ | ||
|
||
package com.facebook.react.animated; | ||
|
||
import com.facebook.react.bridge.Arguments; | ||
import com.facebook.react.bridge.Callback; | ||
import com.facebook.react.bridge.JavaOnlyArray; | ||
import com.facebook.react.bridge.JavaOnlyMap; | ||
import com.facebook.react.bridge.ReadableMap; | ||
import com.facebook.react.uimanager.ReactStylesDiffMap; | ||
import com.facebook.react.uimanager.UIImplementation; | ||
|
||
import org.junit.After; | ||
import org.junit.Before; | ||
import org.junit.Rule; | ||
import org.junit.Test; | ||
import org.junit.runner.RunWith; | ||
import org.mockito.ArgumentCaptor; | ||
import org.mockito.invocation.InvocationOnMock; | ||
import org.mockito.stubbing.Answer; | ||
import org.powermock.api.mockito.PowerMockito; | ||
import org.powermock.core.classloader.annotations.PowerMockIgnore; | ||
import org.powermock.core.classloader.annotations.PrepareForTest; | ||
import org.powermock.modules.junit4.rule.PowerMockRule; | ||
import org.robolectric.RobolectricTestRunner; | ||
|
||
import static org.fest.assertions.api.Assertions.assertThat; | ||
import static org.mockito.Matchers.eq; | ||
import static org.mockito.Mockito.mock; | ||
import static org.mockito.Mockito.reset; | ||
import static org.mockito.Mockito.verify; | ||
import static org.mockito.Mockito.verifyNoMoreInteractions; | ||
|
||
/** | ||
* Tests the animated nodes graph traversal algorithm from {@link NativeAnimatedNodesManager}. | ||
*/ | ||
@PrepareForTest({Arguments.class}) | ||
@RunWith(RobolectricTestRunner.class) | ||
@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) | ||
public class NativeAnimatedNodeTraversalTest { | ||
|
||
private static long FRAME_LEN_NANOS = 1000000000L / 60L; | ||
private static long INITIAL_FRAME_TIME_NANOS = 14599233201256L; /* random */ | ||
|
||
@Rule | ||
public PowerMockRule rule = new PowerMockRule(); | ||
|
||
private long mFrameTimeNanos; | ||
private UIImplementation mUIImplementationMock; | ||
private NativeAnimatedNodesManager mNativeAnimatedNodesManager; | ||
|
||
private long nextFrameTime() { | ||
return mFrameTimeNanos += FRAME_LEN_NANOS; | ||
} | ||
|
||
@Before | ||
public void setUp() { | ||
PowerMockito.mockStatic(Arguments.class); | ||
PowerMockito.when(Arguments.createArray()).thenAnswer(new Answer<Object>() { | ||
@Override | ||
public Object answer(InvocationOnMock invocation) throws Throwable { | ||
return new JavaOnlyArray(); | ||
} | ||
}); | ||
PowerMockito.when(Arguments.createMap()).thenAnswer(new Answer<Object>() { | ||
@Override | ||
public Object answer(InvocationOnMock invocation) throws Throwable { | ||
return new JavaOnlyMap(); | ||
} | ||
}); | ||
|
||
mFrameTimeNanos = INITIAL_FRAME_TIME_NANOS; | ||
mUIImplementationMock = mock(UIImplementation.class); | ||
mNativeAnimatedNodesManager = new NativeAnimatedNodesManager(mUIImplementationMock); | ||
} | ||
|
||
/** | ||
* Generates a simple animated nodes graph and attaches the props node to a given {@param viewTag} | ||
* Parameter {@param opacity} is used as a initial value for the "opacity" attribute. | ||
* | ||
* Nodes are connected as follows (nodes IDs in parens): | ||
* ValueNode(1) -> StyleNode(2) -> PropNode(3) | ||
*/ | ||
private void createSimpleAnimatedViewWithOpacity(int viewTag, double opacity) { | ||
mNativeAnimatedNodesManager.createAnimatedNode( | ||
1, | ||
JavaOnlyMap.of("type", "value", "value", opacity)); | ||
mNativeAnimatedNodesManager.createAnimatedNode( | ||
2, | ||
JavaOnlyMap.of("type", "style", "style", JavaOnlyMap.of("opacity", 1))); | ||
mNativeAnimatedNodesManager.createAnimatedNode( | ||
3, | ||
JavaOnlyMap.of("type", "props", "props", JavaOnlyMap.of("style", 2))); | ||
mNativeAnimatedNodesManager.connectAnimatedNodes(1, 2); | ||
mNativeAnimatedNodesManager.connectAnimatedNodes(2, 3); | ||
mNativeAnimatedNodesManager.connectAnimatedNodeToView(3, viewTag); | ||
} | ||
|
||
@Test | ||
public void testFramesAnimation() { | ||
createSimpleAnimatedViewWithOpacity(1000, 0d); | ||
|
||
JavaOnlyArray frames = JavaOnlyArray.of(0d, 0.2d, 0.4d, 0.6d, 0.8d, 1d); | ||
Callback animationCallback = mock(Callback.class); | ||
mNativeAnimatedNodesManager.startAnimatingNode( | ||
1, | ||
JavaOnlyMap.of("type", "frames", "frames", frames, "toValue", 1d), | ||
animationCallback); | ||
|
||
ArgumentCaptor<ReactStylesDiffMap> stylesCaptor = | ||
ArgumentCaptor.forClass(ReactStylesDiffMap.class); | ||
|
||
reset(mUIImplementationMock); | ||
mNativeAnimatedNodesManager.runUpdates(nextFrameTime()); | ||
verify(mUIImplementationMock).synchronouslyUpdateViewOnUIThread(eq(1000), stylesCaptor.capture()); | ||
assertThat(stylesCaptor.getValue().getDouble("opacity", Double.NaN)).isEqualTo(0); | ||
|
||
for (int i = 0; i < frames.size(); i++) { | ||
reset(mUIImplementationMock); | ||
mNativeAnimatedNodesManager.runUpdates(nextFrameTime()); | ||
verify(mUIImplementationMock) | ||
.synchronouslyUpdateViewOnUIThread(eq(1000), stylesCaptor.capture()); | ||
assertThat(stylesCaptor.getValue().getDouble("opacity", Double.NaN)) | ||
.isEqualTo(frames.getDouble(i)); | ||
} | ||
|
||
reset(mUIImplementationMock); | ||
mNativeAnimatedNodesManager.runUpdates(nextFrameTime()); | ||
verifyNoMoreInteractions(mUIImplementationMock); | ||
} | ||
|
||
@Test | ||
public void testAnimationCallbackFinish() { | ||
createSimpleAnimatedViewWithOpacity(1000, 0d); | ||
|
||
JavaOnlyArray frames = JavaOnlyArray.of(0d, 1d); | ||
Callback animationCallback = mock(Callback.class); | ||
mNativeAnimatedNodesManager.startAnimatingNode( | ||
1, | ||
JavaOnlyMap.of("type", "frames", "frames", frames, "toValue", 1d), | ||
animationCallback); | ||
|
||
ArgumentCaptor<ReadableMap> callbackResponseCaptor = ArgumentCaptor.forClass(ReadableMap.class); | ||
|
||
reset(animationCallback); | ||
mNativeAnimatedNodesManager.runUpdates(nextFrameTime()); | ||
mNativeAnimatedNodesManager.runUpdates(nextFrameTime()); | ||
verifyNoMoreInteractions(animationCallback); | ||
|
||
reset(animationCallback); | ||
mNativeAnimatedNodesManager.runUpdates(nextFrameTime()); | ||
verify(animationCallback).invoke(callbackResponseCaptor.capture()); | ||
|
||
assertThat(callbackResponseCaptor.getValue().hasKey("finished")).isTrue(); | ||
assertThat(callbackResponseCaptor.getValue().getBoolean("finished")).isTrue(); | ||
|
||
reset(animationCallback); | ||
mNativeAnimatedNodesManager.runUpdates(nextFrameTime()); | ||
verifyNoMoreInteractions(animationCallback); | ||
} | ||
} |