mirror of
https://github.com/iSoron/uhabits.git
synced 2025-12-06 09:08:52 -06:00
Implement tests for BaseActivity
This commit is contained in:
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user