From 1832ea639b5df1670ddb0f39961a8648b644db89 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Sat, 20 May 2017 22:10:37 -0400 Subject: [PATCH] Implement tests for BaseActivity --- .../uhabits/activities/ActivityComponent.java | 2 + .../uhabits/activities/BaseActivity.java | 62 +++------ .../activities/BaseExceptionHandler.java | 69 ++++++++++ .../habits/list/ListHabitsActivity.java | 2 +- .../habits/list/ListHabitsScreen.java | 2 +- .../uhabits/activities/BaseActivityTest.java | 124 +++++++++++++++++- .../habits/list/ListHabitsScreenTest.java | 2 +- 7 files changed, 211 insertions(+), 52 deletions(-) create mode 100644 app/src/main/java/org/isoron/uhabits/activities/BaseExceptionHandler.java diff --git a/app/src/main/java/org/isoron/uhabits/activities/ActivityComponent.java b/app/src/main/java/org/isoron/uhabits/activities/ActivityComponent.java index e80a9f540..918a70b4b 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/ActivityComponent.java +++ b/app/src/main/java/org/isoron/uhabits/activities/ActivityComponent.java @@ -29,6 +29,8 @@ import dagger.*; dependencies = { AppComponent.class }) public interface ActivityComponent { + BaseActivity getActivity(); + ColorPickerDialogFactory getColorPickerDialogFactory(); ThemeSwitcher getThemeSwitcher(); diff --git a/app/src/main/java/org/isoron/uhabits/activities/BaseActivity.java b/app/src/main/java/org/isoron/uhabits/activities/BaseActivity.java index 311749159..2167311cb 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/BaseActivity.java +++ b/app/src/main/java/org/isoron/uhabits/activities/BaseActivity.java @@ -26,11 +26,9 @@ import android.support.v7.app.*; import android.view.*; import org.isoron.uhabits.*; -import org.isoron.uhabits.activities.habits.list.*; -import org.isoron.uhabits.models.*; -import org.isoron.uhabits.models.sqlite.*; -import static android.R.anim.*; +import static android.R.anim.fade_in; +import static android.R.anim.fade_out; /** * Base class for all activities in the application. @@ -41,18 +39,16 @@ import static android.R.anim.*; * {@link BaseScreen}. *

* A BaseActivity also installs an {@link java.lang.Thread.UncaughtExceptionHandler} - * to the main thread that logs the exception to the disk before the application - * crashes. + * to the main thread. By default, this handler is an instance of + * BaseExceptionHandler, which logs the exception to the disk before the application + * crashes. To the default handler, you should override the method + * getExceptionHandler. */ abstract public class BaseActivity extends AppCompatActivity - implements Thread.UncaughtExceptionHandler { @Nullable private BaseMenu baseMenu; - @Nullable - private Thread.UncaughtExceptionHandler androidExceptionHandler; - @Nullable private BaseScreen screen; @@ -80,13 +76,13 @@ abstract public class BaseActivity extends AppCompatActivity return baseMenu.onItemSelected(item); } - public void restartWithFade() + public void restartWithFade(Class cls) { - new Handler().postDelayed(() -> { - Intent intent = new Intent(this, ListHabitsActivity.class); + new Handler().postDelayed(() -> + { finish(); overridePendingTransition(fade_in, fade_out); - startActivity(intent); + startActivity(new Intent(this, cls)); }, 500); // HACK: Let the menu disappear first } @@ -111,35 +107,6 @@ abstract public class BaseActivity extends AppCompatActivity dialog.show(); } - @Override - public void uncaughtException(@Nullable Thread thread, - @Nullable Throwable ex) - { - if (ex == null) return; - - try - { - ex.printStackTrace(); - new BaseSystem(this).dumpBugReportToFile(); - } - catch (Exception e) - { - // ignored - } - - if (ex.getCause() instanceof InconsistentDatabaseException) - { - HabitsApplication app = (HabitsApplication) getApplication(); - HabitList habits = app.getComponent().getHabitList(); - habits.repair(); - System.exit(0); - } - - if (androidExceptionHandler != null) - androidExceptionHandler.uncaughtException(thread, ex); - else System.exit(1); - } - @Override protected void onActivityResult(int request, int result, Intent data) { @@ -151,9 +118,7 @@ abstract public class BaseActivity extends AppCompatActivity protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - - androidExceptionHandler = Thread.getDefaultUncaughtExceptionHandler(); - Thread.setDefaultUncaughtExceptionHandler(this); + Thread.setDefaultUncaughtExceptionHandler(getExceptionHandler()); HabitsApplication app = (HabitsApplication) getApplicationContext(); @@ -165,4 +130,9 @@ abstract public class BaseActivity extends AppCompatActivity component.getThemeSwitcher().apply(); } + + protected Thread.UncaughtExceptionHandler getExceptionHandler() + { + return new BaseExceptionHandler(this); + } } diff --git a/app/src/main/java/org/isoron/uhabits/activities/BaseExceptionHandler.java b/app/src/main/java/org/isoron/uhabits/activities/BaseExceptionHandler.java new file mode 100644 index 000000000..5ea4638d9 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/activities/BaseExceptionHandler.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2017 Álinson Santos Xavier + * + * This file is part of Loop Habit Tracker. + * + * Loop Habit Tracker is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Loop Habit Tracker is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +package org.isoron.uhabits.activities; + +import android.support.annotation.*; + +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.models.sqlite.*; + +public class BaseExceptionHandler implements Thread.UncaughtExceptionHandler +{ + @Nullable + private Thread.UncaughtExceptionHandler originalHandler; + + @NonNull + private BaseActivity activity; + + public BaseExceptionHandler(@NonNull BaseActivity activity) + { + this.activity = activity; + originalHandler = Thread.getDefaultUncaughtExceptionHandler(); + } + + @Override + public void uncaughtException(@Nullable Thread thread, + @Nullable Throwable ex) + { + if (ex == null) return; + + try + { + ex.printStackTrace(); + new BaseSystem(activity).dumpBugReportToFile(); + } + catch (Exception e) + { + e.printStackTrace(); + } + + if (ex.getCause() instanceof InconsistentDatabaseException) + { + HabitsApplication app = (HabitsApplication) activity.getApplication(); + HabitList habits = app.getComponent().getHabitList(); + habits.repair(); + System.exit(0); + } + + if (originalHandler != null) + originalHandler.uncaughtException(thread, ex); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.java index 1c68b49ff..ff8e50d8a 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.java +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.java @@ -111,7 +111,7 @@ public class ListHabitsActivity extends BaseActivity if (prefs.getTheme() == ThemeSwitcher.THEME_DARK && prefs.isPureBlackEnabled() != pureBlack) { - restartWithFade(); + restartWithFade(ListHabitsActivity.class); } super.onResume(); diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.java index aeda45bcf..1a778101c 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.java +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.java @@ -353,7 +353,7 @@ public class ListHabitsScreen extends BaseScreen public void toggleNightMode() { themeSwitcher.toggleNightMode(); - activity.restartWithFade(); + activity.restartWithFade(ListHabitsActivity.class); } private void onOpenDocumentResult(int resultCode, Intent data) diff --git a/app/src/test/java/org/isoron/uhabits/activities/BaseActivityTest.java b/app/src/test/java/org/isoron/uhabits/activities/BaseActivityTest.java index e034422b1..ec7835b8c 100644 --- a/app/src/test/java/org/isoron/uhabits/activities/BaseActivityTest.java +++ b/app/src/test/java/org/isoron/uhabits/activities/BaseActivityTest.java @@ -19,30 +19,116 @@ package org.isoron.uhabits.activities; +import android.content.*; import android.os.*; +import android.support.v4.app.*; +import android.support.v7.app.*; import android.support.v7.widget.Toolbar; import android.view.*; import android.widget.*; import org.isoron.uhabits.*; +import org.isoron.uhabits.activities.common.dialogs.*; import org.junit.*; import org.junit.runner.*; import org.robolectric.*; import org.robolectric.annotation.*; +import org.robolectric.shadows.*; +import static org.hamcrest.core.IsEqual.*; +import static org.junit.Assert.*; import static org.mockito.Mockito.*; import static org.robolectric.Robolectric.*; +import static org.robolectric.Shadows.*; @RunWith(RobolectricTestRunner.class) @Config(constants = BuildConfig.class) public class BaseActivityTest { + private static boolean hasCrashed = false; + + @Test + public void activityResultTest() + { + ScreenActivity activity = spy(setupActivity(ScreenActivity.class)); + activity.onActivityResult(0, 0, null); + verify(activity.screen).onResult(0, 0, null); + } + + @Test + public void componentTest() + { + EmptyActivity activity = setupActivity(EmptyActivity.class); + ActivityComponent component = activity.getComponent(); + assertThat(component.getActivity(), equalTo(activity)); + } + + @Test + public void dialogFragmentTest() + { + EmptyActivity activity = setupActivity(EmptyActivity.class); + FragmentManager manager = activity.getSupportFragmentManager(); + ColorPickerDialog d = new ColorPickerDialogFactory(activity).create(0); + + activity.showDialog(d, "picker"); + assertTrue(d.getDialog().isShowing()); + assertThat(d, equalTo(manager.findFragmentByTag("picker"))); + } + + @Test + public void dialogTest() + { + EmptyActivity activity = setupActivity(EmptyActivity.class); + AlertDialog dialog = + new AlertDialog.Builder(activity).setTitle("Hello world").create(); + + activity.showDialog(dialog); + assertTrue(dialog.isShowing()); + } + + @Test + public void restartTest() throws Exception + { + EmptyActivity activity = setupActivity(EmptyActivity.class); + + activity.restartWithFade(EmptyActivity.class); + ShadowLooper.runUiThreadTasksIncludingDelayedTasks(); + + Intent nextStartedActivity = shadowOf(activity).getNextStartedActivity(); + assertNotNull(nextStartedActivity); + + assertTrue(activity.isFinishing()); + assertThat(shadowOf(nextStartedActivity).getIntentClass(), + equalTo(EmptyActivity.class)); + } + + @Test + public void exceptionHandlerTest() throws InterruptedException + { + assertFalse(hasCrashed); + + Thread crashThread = new Thread() + { + @Override + public void run() + { + Looper.prepare(); + CrashActivity activity = setupActivity(CrashActivity.class); + activity.crash(); + } + }; + + crashThread.start(); + crashThread.join(); + assertTrue(hasCrashed); + } + @Test public void menuTest() { MenuActivity activity = setupActivity(MenuActivity.class); - verify(activity.baseMenu).onCreate( - eq(activity.getMenuInflater()), any()); + verify(activity.baseMenu).onCreate(eq(activity.getMenuInflater()), + any()); Menu menu = activity.toolbar.getMenu(); MenuItem item = menu.getItem(0); @@ -50,7 +136,26 @@ public class BaseActivityTest verify(activity.baseMenu).onItemSelected(item); } - public static class MenuActivity extends BaseActivity + static class CrashActivity extends BaseActivity + { + public void crash() + { + throw new RuntimeException("crash!"); + } + + @Override + protected Thread.UncaughtExceptionHandler getExceptionHandler() + { + return (t, e) -> hasCrashed = true; + } + } + + static class EmptyActivity extends BaseActivity + { + + } + + static class MenuActivity extends BaseActivity { public BaseMenu baseMenu; @@ -78,4 +183,17 @@ public class BaseActivityTest setBaseMenu(baseMenu); } } + + static class ScreenActivity extends BaseActivity + { + private BaseScreen screen; + + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + screen = spy(new BaseScreen(this)); + setScreen(screen); + } + } } diff --git a/app/src/test/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreenTest.java b/app/src/test/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreenTest.java index 749b5685f..2e1e86b6f 100644 --- a/app/src/test/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreenTest.java +++ b/app/src/test/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreenTest.java @@ -282,6 +282,6 @@ public class ListHabitsScreenTest extends BaseUnitTest { screen.toggleNightMode(); verify(themeSwitcher).toggleNightMode(); - verify(activity).restartWithFade(); + verify(activity).restartWithFade(ListHabitsActivity.class); } } \ No newline at end of file