package org.edx.mobile.view; import android.app.Activity; import android.content.Intent; import android.graphics.Typeface; import androidx.annotation.AnimRes; import androidx.appcompat.app.ActionBar; import android.view.View; import android.view.ViewGroup; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.widget.TextView; import org.edx.mobile.R; import org.edx.mobile.base.BaseFragmentActivity; import org.junit.Test; import org.robolectric.Robolectric; import org.robolectric.RuntimeEnvironment; import org.robolectric.Shadows; import org.robolectric.shadows.ShadowActivity; import org.robolectric.shadows.ShadowApplication; import org.robolectric.android.controller.ActivityController; import org.robolectric.util.Scheduler; import static org.assertj.android.api.Assertions.assertThat; import static org.assertj.core.api.Java6Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeNotNull; import static org.junit.Assume.assumeTrue; // TODO: Test network connectivity change events too, after we manage to mock them public abstract class BaseFragmentActivityTest extends UiTest { /** * Method for defining the subclass of {@link BaseFragmentActivity} that * is being tested. Should be overridden by subclasses. * * @return The {@link BaseFragmentActivity} subclass that is being tested */ protected Class<? extends BaseFragmentActivity> getActivityClass() { return BaseFragmentActivity.class; } /** * Method for constructing the {link Intent} to be used to start the * {link Activity} instance. Should be overridden by subclasses to * attach any additional data to be passed. * * @return The {@link Intent} used to start the {link Activity} */ protected Intent getIntent() { return new Intent(RuntimeEnvironment.application, getActivityClass()); } /** * Method for defining whether the transition animation is overridden * with custom animation on restart * * @return true if the transition animation is overridden on restart */ protected boolean appliesPrevTransitionOnRestart() { return false; } /** * Generic method for asserting pending transition animation * * @param shadowActivity The shadow activity * @param enterAnim The enter animation resource * @param exitAnim The exit animation resource */ private static void assertOverridePendingTransition(ShadowActivity shadowActivity, @AnimRes int enterAnim, @AnimRes int exitAnim) { assertEquals(enterAnim, shadowActivity .getPendingTransitionEnterAnimationResourceId()); assertEquals(exitAnim, shadowActivity .getPendingTransitionExitAnimationResourceId()); } /** * Testing overall lifecycle and setup */ @Test public void lifecycleTest() { ActivityController<? extends BaseFragmentActivity> controller = Robolectric.buildActivity(getActivityClass(), getIntent()); BaseFragmentActivity activity = controller.get(); ShadowActivity shadowActivity = Shadows.shadowOf(activity); controller.create().start(); // Action bar state initialization ActionBar bar = activity.getSupportActionBar(); if (bar != null) { int displayOptions = bar.getDisplayOptions(); assertTrue((displayOptions & ActionBar.DISPLAY_HOME_AS_UP) > 0); assertTrue((displayOptions & ActionBar.DISPLAY_SHOW_HOME) > 0); assertTrue(null == activity.findViewById(android.R.id.home)); } controller.postCreate(null).resume().postResume().visible(); // Action bar home button assertTrue(shadowActivity.clickMenuItem(android.R.id.home)); activity.finish(); assertThat(activity).isFinishing(); } /** * Generic method for asserting title setup * * @param activity The activity instance * @param title The expected title */ protected void assertTitle(BaseFragmentActivity activity, CharSequence title) { ActionBar bar = activity.getSupportActionBar(); assumeNotNull(bar); assumeNotNull(title); Typeface type = Typeface.createFromAsset( activity.getAssets(), "fonts/OpenSans-Semibold.ttf"); int titleId = activity.getResources().getIdentifier( "action_bar_title", "id", "android"); TextView titleTextView = (TextView) activity.findViewById(titleId); assumeNotNull(titleTextView); assertThat(titleTextView).hasCurrentTextColor( activity.getResources().getColor(R.color.edx_white)); assertEquals(type, titleTextView.getTypeface()); assertEquals(bar.getTitle(), title); } /** * Testing title setup */ @Test public void setTitleTest() { BaseFragmentActivity activity = Robolectric.buildActivity(getActivityClass(), getIntent()).create().get(); CharSequence title = "test"; activity.setTitle(title); assertTitle(activity, title); } /** * Generic method for asserting view animation method functionality * * @param view The animated view * @param trigger A {@link Runnable} that triggers the animation */ protected void assertAnimateLayouts(View view, Runnable trigger) { // The foreground scheduler needs to be paused so that the // temporary visibility of the animated View can be verified. Scheduler foregroundScheduler = ShadowApplication.getInstance() .getForegroundThreadScheduler(); boolean wasPaused = foregroundScheduler.isPaused(); if (!wasPaused) { foregroundScheduler.pause(); } assertThat(view).isGone(); trigger.run(); assertThat(view).isVisible(); Animation animation = view.getAnimation(); assertNotNull(animation); assertThat(animation.getStartTime()) .isLessThanOrEqualTo(AnimationUtils.currentAnimationTimeMillis()); assertThat(animation).hasStartOffset(0); foregroundScheduler.unPause(); assertThat(view).isGone(); if (wasPaused) { foregroundScheduler.pause(); } } /** * Testing view animation methods */ @Test public void animateLayoutsTest() { final BaseFragmentActivity activity = Robolectric.buildActivity( getActivityClass(), getIntent() ).setup().get(); final View view = new View(activity); view.setVisibility(View.GONE); activity.addContentView(view, new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); assertAnimateLayouts(view, new Runnable() { @Override public void run() { activity.animateLayouts(view); } }); activity.stopAnimation(view); assertThat(view).hasAnimation(null); } /** * Generic message for asserting show info method functionality * * @param activity The activity instance * @param message The message that is expected to be displayed * @param trigger A {@link Runnable} that triggers the showing of the * message */ protected void assertShowInfoMessage(final BaseFragmentActivity activity, final String message, final Runnable trigger) { final TextView messageView = (TextView) activity.findViewById(R.id.flying_message); assumeNotNull(messageView); assertAnimateLayouts(messageView, new Runnable() { @Override public void run() { trigger.run(); assertThat(messageView).hasText(message); } }); } /** * Testing show info method */ @Test public void showInfoMessageTest() { final BaseFragmentActivity activity = Robolectric.buildActivity(getActivityClass(), getIntent()).setup().get(); TextView messageView = new TextView(activity); messageView.setId(R.id.flying_message); messageView.setVisibility(View.GONE); activity.addContentView(messageView, new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); assertThat(messageView).hasText(""); final String message = "test"; assertShowInfoMessage(activity, message, new Runnable() { @Override public void run() { assumeTrue(activity.showInfoMessage(message)); } }); } /** * Generic method for asserting next started activity along with * the custom transition animation override * * @param currentActivity The current activity * @param nextActivityClass The class of the newly started activity */ protected Intent assertNextStartedActivity(BaseFragmentActivity currentActivity, Class<? extends Activity> nextActivityClass) { ShadowActivity shadowActivity = Shadows.shadowOf(currentActivity); Intent intent = shadowActivity.getNextStartedActivity(); assertNotNull(intent); assertThat(intent).hasComponent(currentActivity, nextActivityClass); return intent; } /** * Generic method for asserting next started activity along with * the custom transition animation override * * @param currentActivity The current activity * @param nextActivityClass The class of the newly started activity * @param requestCode The request code */ protected Intent assertNextStartedActivityForResult( BaseFragmentActivity currentActivity, Class<? extends Activity> nextActivityClass, int requestCode) { ShadowActivity shadowActivity = Shadows.shadowOf(currentActivity); ShadowActivity.IntentForResult intentForResult = shadowActivity.getNextStartedActivityForResult(); assertNotNull(intentForResult); assertThat(intentForResult.intent).hasComponent( currentActivity, nextActivityClass); assertEquals(requestCode, intentForResult.requestCode); return intentForResult.intent; } /** * Testing method for enabling and disabling UI interaction */ @Test public void tryToSetUIInteractionTest() { BaseFragmentActivity activity = Robolectric.buildActivity(getActivityClass(), getIntent()).setup().get(); assertFalse(activity.tryToSetUIInteraction(true)); assertFalse(activity.tryToSetUIInteraction(false)); } }