Implement tests for BaseActivity

pull/231/merge
Alinson S. Xavier 8 years ago
parent cd3944b90f
commit 1832ea639b

@ -29,6 +29,8 @@ import dagger.*;
dependencies = { AppComponent.class }) dependencies = { AppComponent.class })
public interface ActivityComponent public interface ActivityComponent
{ {
BaseActivity getActivity();
ColorPickerDialogFactory getColorPickerDialogFactory(); ColorPickerDialogFactory getColorPickerDialogFactory();
ThemeSwitcher getThemeSwitcher(); ThemeSwitcher getThemeSwitcher();

@ -26,11 +26,9 @@ import android.support.v7.app.*;
import android.view.*; import android.view.*;
import org.isoron.uhabits.*; 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. * Base class for all activities in the application.
@ -41,18 +39,16 @@ import static android.R.anim.*;
* {@link BaseScreen}. * {@link BaseScreen}.
* <p> * <p>
* A BaseActivity also installs an {@link java.lang.Thread.UncaughtExceptionHandler} * A BaseActivity also installs an {@link java.lang.Thread.UncaughtExceptionHandler}
* to the main thread that logs the exception to the disk before the application * to the main thread. By default, this handler is an instance of
* crashes. * 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 abstract public class BaseActivity extends AppCompatActivity
implements Thread.UncaughtExceptionHandler
{ {
@Nullable @Nullable
private BaseMenu baseMenu; private BaseMenu baseMenu;
@Nullable
private Thread.UncaughtExceptionHandler androidExceptionHandler;
@Nullable @Nullable
private BaseScreen screen; private BaseScreen screen;
@ -80,13 +76,13 @@ abstract public class BaseActivity extends AppCompatActivity
return baseMenu.onItemSelected(item); return baseMenu.onItemSelected(item);
} }
public void restartWithFade() public void restartWithFade(Class<?> cls)
{
new Handler().postDelayed(() ->
{ {
new Handler().postDelayed(() -> {
Intent intent = new Intent(this, ListHabitsActivity.class);
finish(); finish();
overridePendingTransition(fade_in, fade_out); overridePendingTransition(fade_in, fade_out);
startActivity(intent); startActivity(new Intent(this, cls));
}, 500); // HACK: Let the menu disappear first }, 500); // HACK: Let the menu disappear first
} }
@ -111,35 +107,6 @@ abstract public class BaseActivity extends AppCompatActivity
dialog.show(); 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 @Override
protected void onActivityResult(int request, int result, Intent data) protected void onActivityResult(int request, int result, Intent data)
{ {
@ -151,9 +118,7 @@ abstract public class BaseActivity extends AppCompatActivity
protected void onCreate(Bundle savedInstanceState) protected void onCreate(Bundle savedInstanceState)
{ {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
Thread.setDefaultUncaughtExceptionHandler(getExceptionHandler());
androidExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(this);
HabitsApplication app = (HabitsApplication) getApplicationContext(); HabitsApplication app = (HabitsApplication) getApplicationContext();
@ -165,4 +130,9 @@ abstract public class BaseActivity extends AppCompatActivity
component.getThemeSwitcher().apply(); component.getThemeSwitcher().apply();
} }
protected Thread.UncaughtExceptionHandler getExceptionHandler()
{
return new BaseExceptionHandler(this);
}
} }

@ -0,0 +1,69 @@
/*
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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);
}
}

@ -111,7 +111,7 @@ public class ListHabitsActivity extends BaseActivity
if (prefs.getTheme() == ThemeSwitcher.THEME_DARK && if (prefs.getTheme() == ThemeSwitcher.THEME_DARK &&
prefs.isPureBlackEnabled() != pureBlack) prefs.isPureBlackEnabled() != pureBlack)
{ {
restartWithFade(); restartWithFade(ListHabitsActivity.class);
} }
super.onResume(); super.onResume();

@ -353,7 +353,7 @@ public class ListHabitsScreen extends BaseScreen
public void toggleNightMode() public void toggleNightMode()
{ {
themeSwitcher.toggleNightMode(); themeSwitcher.toggleNightMode();
activity.restartWithFade(); activity.restartWithFade(ListHabitsActivity.class);
} }
private void onOpenDocumentResult(int resultCode, Intent data) private void onOpenDocumentResult(int resultCode, Intent data)

@ -19,30 +19,116 @@
package org.isoron.uhabits.activities; package org.isoron.uhabits.activities;
import android.content.*;
import android.os.*; import android.os.*;
import android.support.v4.app.*;
import android.support.v7.app.*;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.view.*; import android.view.*;
import android.widget.*; import android.widget.*;
import org.isoron.uhabits.*; import org.isoron.uhabits.*;
import org.isoron.uhabits.activities.common.dialogs.*;
import org.junit.*; import org.junit.*;
import org.junit.runner.*; import org.junit.runner.*;
import org.robolectric.*; import org.robolectric.*;
import org.robolectric.annotation.*; 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.mockito.Mockito.*;
import static org.robolectric.Robolectric.*; import static org.robolectric.Robolectric.*;
import static org.robolectric.Shadows.*;
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class) @Config(constants = BuildConfig.class)
public class BaseActivityTest 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 @Test
public void menuTest() public void menuTest()
{ {
MenuActivity activity = setupActivity(MenuActivity.class); MenuActivity activity = setupActivity(MenuActivity.class);
verify(activity.baseMenu).onCreate( verify(activity.baseMenu).onCreate(eq(activity.getMenuInflater()),
eq(activity.getMenuInflater()), any()); any());
Menu menu = activity.toolbar.getMenu(); Menu menu = activity.toolbar.getMenu();
MenuItem item = menu.getItem(0); MenuItem item = menu.getItem(0);
@ -50,7 +136,26 @@ public class BaseActivityTest
verify(activity.baseMenu).onItemSelected(item); 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; public BaseMenu baseMenu;
@ -78,4 +183,17 @@ public class BaseActivityTest
setBaseMenu(baseMenu); 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);
}
}
} }

@ -282,6 +282,6 @@ public class ListHabitsScreenTest extends BaseUnitTest
{ {
screen.toggleNightMode(); screen.toggleNightMode();
verify(themeSwitcher).toggleNightMode(); verify(themeSwitcher).toggleNightMode();
verify(activity).restartWithFade(); verify(activity).restartWithFade(ListHabitsActivity.class);
} }
} }
Loading…
Cancel
Save