From 51e8c2f11152ec912c3a84d2ec10caa01841c333 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Fri, 11 Mar 2016 10:35:52 -0500 Subject: [PATCH 1/4] Implement basic user interface tests --- app/build.gradle | 8 + .../org/isoron/uhabits/HabitMatchers.java | 79 ++++++ .../org/isoron/uhabits/HabitViewActions.java | 75 ++++++ .../org/isoron/uhabits/MainActivityTest.java | 252 ++++++++++++++++++ .../uhabits/fragments/HabitListAdapter.java | 4 +- 5 files changed, 416 insertions(+), 2 deletions(-) create mode 100644 app/src/androidTest/java/org/isoron/uhabits/HabitMatchers.java create mode 100644 app/src/androidTest/java/org/isoron/uhabits/HabitViewActions.java create mode 100644 app/src/androidTest/java/org/isoron/uhabits/MainActivityTest.java diff --git a/app/build.gradle b/app/build.gradle index cb85919a2..4f56050af 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,6 +8,8 @@ android { applicationId "org.isoron.uhabits" minSdkVersion 15 targetSdkVersion 23 + + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { @@ -27,5 +29,11 @@ dependencies { compile 'com.github.paolorotolo:appintro:3.4.0' compile project(':libs:drag-sort-listview:library') compile files('libs/ActiveAndroid.jar') + + androidTestCompile 'com.android.support:support-annotations:23.1.1' + androidTestCompile 'com.android.support.test:runner:0.4.1' + androidTestCompile 'com.android.support.test:rules:0.4.1' + androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.1' + androidTestCompile 'com.android.support.test.espresso:espresso-intents:2.2.1' } diff --git a/app/src/androidTest/java/org/isoron/uhabits/HabitMatchers.java b/app/src/androidTest/java/org/isoron/uhabits/HabitMatchers.java new file mode 100644 index 000000000..c96b24c16 --- /dev/null +++ b/app/src/androidTest/java/org/isoron/uhabits/HabitMatchers.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2016 Á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; + +import android.view.View; +import android.widget.Adapter; +import android.widget.AdapterView; + +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; +import org.isoron.uhabits.models.Habit; + +public class HabitMatchers +{ + public static Matcher withName(final String name) + { + return new TypeSafeMatcher() + { + @Override + public boolean matchesSafely(Habit habit) + { + return habit.name.equals(name); + } + + @Override + public void describeTo(Description description) + { + description.appendText("name should be ").appendText(name); + } + + @Override + public void describeMismatchSafely(Habit habit, Description description) + { + description.appendText("was ").appendText(habit.name); + } + }; + } + + public static Matcher containsHabit(final Matcher matcher) + { + return new TypeSafeMatcher() + { + @Override + protected boolean matchesSafely(View view) + { + Adapter adapter = ((AdapterView) view).getAdapter(); + for (int i = 0; i < adapter.getCount(); i++) + if (matcher.matches(adapter.getItem(i))) return true; + + return false; + } + + @Override + public void describeTo(Description description) + { + description.appendText("with class name: "); + matcher.describeTo(description); + } + }; + } +} diff --git a/app/src/androidTest/java/org/isoron/uhabits/HabitViewActions.java b/app/src/androidTest/java/org/isoron/uhabits/HabitViewActions.java new file mode 100644 index 000000000..5dd6f29a9 --- /dev/null +++ b/app/src/androidTest/java/org/isoron/uhabits/HabitViewActions.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2016 Á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; + +import android.support.test.espresso.UiController; +import android.support.test.espresso.ViewAction; +import android.support.test.espresso.action.GeneralClickAction; +import android.support.test.espresso.action.GeneralLocation; +import android.support.test.espresso.action.Press; +import android.support.test.espresso.action.Tap; +import android.support.test.espresso.matcher.ViewMatchers; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + +import org.hamcrest.Matcher; + +import java.security.InvalidParameterException; + +public class HabitViewActions +{ + public static ViewAction toggleAllCheckmarks() + { + final GeneralClickAction clickAction = + new GeneralClickAction(Tap.LONG, GeneralLocation.CENTER, Press.FINGER); + + return new ViewAction() + { + @Override + public Matcher getConstraints() + { + return ViewMatchers.isDisplayed(); + } + + @Override + public String getDescription() + { + return "toggleAllCheckmarks"; + } + + @Override + public void perform(UiController uiController, View view) + { + if (view.getId() != R.id.llButtons) + throw new InvalidParameterException("View must have id llButtons"); + + LinearLayout llButtons = (LinearLayout) view; + int count = llButtons.getChildCount(); + + for (int i = 0; i < count; i++) + { + TextView tvButton = (TextView) llButtons.getChildAt(i); + clickAction.perform(uiController, tvButton); + } + } + }; + } +} diff --git a/app/src/androidTest/java/org/isoron/uhabits/MainActivityTest.java b/app/src/androidTest/java/org/isoron/uhabits/MainActivityTest.java new file mode 100644 index 000000000..93d8451b4 --- /dev/null +++ b/app/src/androidTest/java/org/isoron/uhabits/MainActivityTest.java @@ -0,0 +1,252 @@ +package org.isoron.uhabits; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.espresso.NoMatchingViewException; +import android.support.test.espresso.intent.rule.IntentsTestRule; +import android.support.test.runner.AndroidJUnit4; +import android.test.suitebuilder.annotation.LargeTest; + +import org.isoron.uhabits.models.Habit; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.LinkedList; +import java.util.List; +import java.util.Random; + +import static android.support.test.espresso.Espresso.onData; +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu; +import static android.support.test.espresso.Espresso.pressBack; +import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.action.ViewActions.longClick; +import static android.support.test.espresso.action.ViewActions.replaceText; +import static android.support.test.espresso.action.ViewActions.scrollTo; +import static android.support.test.espresso.assertion.ViewAssertions.matches; +import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; +import static android.support.test.espresso.matcher.ViewMatchers.withClassName; +import static android.support.test.espresso.matcher.ViewMatchers.withContentDescription; +import static android.support.test.espresso.matcher.ViewMatchers.withId; +import static android.support.test.espresso.matcher.ViewMatchers.withText; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.isoron.uhabits.HabitMatchers.containsHabit; +import static org.isoron.uhabits.HabitMatchers.withName; +import static org.isoron.uhabits.HabitViewActions.toggleAllCheckmarks; + +@RunWith(AndroidJUnit4.class) +@LargeTest +public class MainActivityTest +{ + @Rule + public IntentsTestRule activityRule = new IntentsTestRule<>( + MainActivity.class); + + @Before + public void skipTutorial() + { + try + { + for (int i = 0; i < 10; i++) + onView(allOf(withClassName(endsWith("AppCompatImageButton")), + isDisplayed())).perform(click()); + } + catch (NoMatchingViewException e) + { + // ignored + } + } + + public String addHabit() + { + return addHabit(false); + } + + public String addHabit(boolean openDialogs) + { + String name = "New Habit " + new Random().nextInt(1000000); + String description = "Did you perform your new habit today?"; + String num = "4"; + String den = "8"; + + onView(withId(R.id.action_add)) + .perform(click()); + + typeHabitData(name, description, num, den); + + if(openDialogs) + { + onView(withId(R.id.buttonPickColor)) + .perform(click()); + pressBack(); + onView(withId(R.id.inputReminderTime)) + .perform(click()); + onView(withText("Done")) + .perform(click()); + onView(withId(R.id.inputReminderDays)) + .perform(click()); + onView(withText("OK")) + .perform(click()); + } + + onView(withId(R.id.buttonSave)) + .perform(click()); + + onData(allOf(is(instanceOf(Habit.class)), withName(name))) + .onChildView(withId(R.id.label)); + + return name; + } + + private void typeHabitData(String name, String description, String num, String den) + { + onView(withId(R.id.input_name)) + .perform(replaceText(name)); + onView(withId(R.id.input_description)) + .perform(replaceText(description)); + onView(withId(R.id.input_freq_num)) + .perform(replaceText(num)); + onView(withId(R.id.input_freq_den)) + .perform(replaceText(den)); + } + + private void selectHabits(List names) + { + boolean first = true; + for(String name : names) + { + onData(allOf(is(instanceOf(Habit.class)), withName(name))) + .onChildView(withId(R.id.label)) + .perform(first ? longClick() : click()); + + first = false; + } + } + + private void assertHabitsDontExist(List names) + { + for(String name : names) + onView(withId(R.id.listView)) + .check(matches(not(containsHabit(withName(name))))); + } + + private void assertHabitExists(String name) + { + List names = new LinkedList<>(); + names.add(name); + assertHabitsExist(names); + } + + private void assertHabitsExist(List names) + { + for(String name : names) + onData(allOf(is(instanceOf(Habit.class)), withName(name))) + .check(matches(isDisplayed())); + } + + private void deleteHabit(String name) + { + LinkedList names = new LinkedList<>(); + names.add(name); + deleteHabits(names); + } + + private void deleteHabits(List names) + { + Context context = InstrumentationRegistry.getTargetContext(); + + selectHabits(names); + + openActionBarOverflowOrOptionsMenu(context); + + onView(withText(R.string.delete)) + .perform(click()); + onView(withText("OK")) + .perform(click()); + + assertHabitsDontExist(names); + } + + @Test + public void testArchiveHabits() + { + List names = new LinkedList<>(); + Context context = InstrumentationRegistry.getTargetContext(); + + for(int i = 0; i < 3; i++) + names.add(addHabit()); + + selectHabits(names); + onView(withContentDescription(R.string.archive)) + .perform(click()); + assertHabitsDontExist(names); + + openActionBarOverflowOrOptionsMenu(context); + onView(withText(R.string.show_archived)) + .perform(click()); + + assertHabitsExist(names); + deleteHabits(names); + } + + @Test + public void testAddInvalidHabit() + { + typeHabitData("", "", "15", "7"); + onView(withId(R.id.buttonSave)).perform(click()); + onView(withId(R.id.input_name)).check(matches(isDisplayed())); + } + + @Test + public void testToggleCheckmarks() + { + String name = addHabit(); + + onData(allOf(is(instanceOf(Habit.class)), withName(name))) + .onChildView(withId(R.id.llButtons)) + .perform(toggleAllCheckmarks()); + + deleteHabit(name); + } + + @Test + public void testAddHabit() + { + String name = addHabit(true); + + onData(allOf(is(instanceOf(Habit.class)), withName(name))) + .onChildView(withId(R.id.label)) + .perform(click()); + + onView(withId(R.id.punchcardView)) + .perform(scrollTo()); + } + + @Test + public void testEditHabit() + { + String name = addHabit(); + + onData(allOf(is(instanceOf(Habit.class)), withName(name))) + .onChildView(withId(R.id.label)) + .perform(longClick()); + + onView(withContentDescription(R.string.edit)) + .perform(click()); + + String modifiedName = "Modified " + new Random().nextInt(10000); + typeHabitData(modifiedName, "", "1", "1"); + + onView(withId(R.id.buttonSave)) + .perform(click()); + + assertHabitExists(modifiedName); + deleteHabit(modifiedName); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/fragments/HabitListAdapter.java b/app/src/main/java/org/isoron/uhabits/fragments/HabitListAdapter.java index 432582701..3ef39713e 100644 --- a/app/src/main/java/org/isoron/uhabits/fragments/HabitListAdapter.java +++ b/app/src/main/java/org/isoron/uhabits/fragments/HabitListAdapter.java @@ -59,7 +59,7 @@ class HabitListAdapter extends BaseAdapter } @Override - public Object getItem(int position) + public Habit getItem(int position) { return loader.habitsList.get(position); } @@ -67,7 +67,7 @@ class HabitListAdapter extends BaseAdapter @Override public long getItemId(int position) { - return ((Habit) getItem(position)).getId(); + return (getItem(position)).getId(); } @Override From 1102d05a610ec5ffa9e643655c263f4809b153c1 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Fri, 11 Mar 2016 11:31:14 -0500 Subject: [PATCH 2/4] Test unarchiving habits --- .../isoron/uhabits/MainActivityActions.java | 160 ++++++++++++++++++ .../org/isoron/uhabits/MainActivityTest.java | 147 +++------------- 2 files changed, 183 insertions(+), 124 deletions(-) create mode 100644 app/src/androidTest/java/org/isoron/uhabits/MainActivityActions.java diff --git a/app/src/androidTest/java/org/isoron/uhabits/MainActivityActions.java b/app/src/androidTest/java/org/isoron/uhabits/MainActivityActions.java new file mode 100644 index 000000000..46c5c99cc --- /dev/null +++ b/app/src/androidTest/java/org/isoron/uhabits/MainActivityActions.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2016 Á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; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; + +import org.isoron.uhabits.models.Habit; + +import java.util.LinkedList; +import java.util.List; +import java.util.Random; + +import static android.support.test.espresso.Espresso.onData; +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu; +import static android.support.test.espresso.Espresso.pressBack; +import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.action.ViewActions.longClick; +import static android.support.test.espresso.action.ViewActions.replaceText; +import static android.support.test.espresso.assertion.ViewAssertions.matches; +import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; +import static android.support.test.espresso.matcher.ViewMatchers.withId; +import static android.support.test.espresso.matcher.ViewMatchers.withText; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.isoron.uhabits.HabitMatchers.containsHabit; +import static org.isoron.uhabits.HabitMatchers.withName; + +public class MainActivityActions +{ + public static String addHabit() + { + return addHabit(false); + } + + public static String addHabit(boolean openDialogs) + { + String name = "New Habit " + new Random().nextInt(1000000); + String description = "Did you perform your new habit today?"; + String num = "4"; + String den = "8"; + + onView(withId(R.id.action_add)) + .perform(click()); + + typeHabitData(name, description, num, den); + + if(openDialogs) + { + onView(withId(R.id.buttonPickColor)) + .perform(click()); + pressBack(); + onView(withId(R.id.inputReminderTime)) + .perform(click()); + onView(withText("Done")) + .perform(click()); + onView(withId(R.id.inputReminderDays)) + .perform(click()); + onView(withText("OK")) + .perform(click()); + } + + onView(withId(R.id.buttonSave)) + .perform(click()); + + onData(allOf(is(instanceOf(Habit.class)), withName(name))) + .onChildView(withId(R.id.label)); + + return name; + } + + public static void typeHabitData(String name, String description, String num, String den) + { + onView(withId(R.id.input_name)) + .perform(replaceText(name)); + onView(withId(R.id.input_description)) + .perform(replaceText(description)); + onView(withId(R.id.input_freq_num)) + .perform(replaceText(num)); + onView(withId(R.id.input_freq_den)) + .perform(replaceText(den)); + } + + public static void selectHabits(List names) + { + boolean first = true; + for(String name : names) + { + onData(allOf(is(instanceOf(Habit.class)), withName(name))) + .onChildView(withId(R.id.label)) + .perform(first ? longClick() : click()); + + first = false; + } + } + + public static void assertHabitsDontExist(List names) + { + for(String name : names) + onView(withId(R.id.listView)) + .check(matches(not(containsHabit(withName(name))))); + } + + public static void assertHabitExists(String name) + { + List names = new LinkedList<>(); + names.add(name); + assertHabitsExist(names); + } + + public static void assertHabitsExist(List names) + { + for(String name : names) + onData(allOf(is(instanceOf(Habit.class)), withName(name))) + .check(matches(isDisplayed())); + } + + public static void deleteHabit(String name) + { + LinkedList names = new LinkedList<>(); + names.add(name); + deleteHabits(names); + } + + public static void deleteHabits(List names) + { + Context context = InstrumentationRegistry.getTargetContext(); + + selectHabits(names); + + openActionBarOverflowOrOptionsMenu(context); + + onView(withText(R.string.delete)) + .perform(click()); + onView(withText("OK")) + .perform(click()); + + assertHabitsDontExist(names); + } +} diff --git a/app/src/androidTest/java/org/isoron/uhabits/MainActivityTest.java b/app/src/androidTest/java/org/isoron/uhabits/MainActivityTest.java index 93d8451b4..7557dd16c 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/MainActivityTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/MainActivityTest.java @@ -20,10 +20,8 @@ import java.util.Random; import static android.support.test.espresso.Espresso.onData; import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu; -import static android.support.test.espresso.Espresso.pressBack; import static android.support.test.espresso.action.ViewActions.click; import static android.support.test.espresso.action.ViewActions.longClick; -import static android.support.test.espresso.action.ViewActions.replaceText; import static android.support.test.espresso.action.ViewActions.scrollTo; import static android.support.test.espresso.assertion.ViewAssertions.matches; import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; @@ -35,10 +33,16 @@ import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.endsWith; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; -import static org.isoron.uhabits.HabitMatchers.containsHabit; import static org.isoron.uhabits.HabitMatchers.withName; import static org.isoron.uhabits.HabitViewActions.toggleAllCheckmarks; +import static org.isoron.uhabits.MainActivityActions.addHabit; +import static org.isoron.uhabits.MainActivityActions.assertHabitExists; +import static org.isoron.uhabits.MainActivityActions.assertHabitsDontExist; +import static org.isoron.uhabits.MainActivityActions.assertHabitsExist; +import static org.isoron.uhabits.MainActivityActions.deleteHabit; +import static org.isoron.uhabits.MainActivityActions.deleteHabits; +import static org.isoron.uhabits.MainActivityActions.selectHabits; +import static org.isoron.uhabits.MainActivityActions.typeHabitData; @RunWith(AndroidJUnit4.class) @LargeTest @@ -63,116 +67,6 @@ public class MainActivityTest } } - public String addHabit() - { - return addHabit(false); - } - - public String addHabit(boolean openDialogs) - { - String name = "New Habit " + new Random().nextInt(1000000); - String description = "Did you perform your new habit today?"; - String num = "4"; - String den = "8"; - - onView(withId(R.id.action_add)) - .perform(click()); - - typeHabitData(name, description, num, den); - - if(openDialogs) - { - onView(withId(R.id.buttonPickColor)) - .perform(click()); - pressBack(); - onView(withId(R.id.inputReminderTime)) - .perform(click()); - onView(withText("Done")) - .perform(click()); - onView(withId(R.id.inputReminderDays)) - .perform(click()); - onView(withText("OK")) - .perform(click()); - } - - onView(withId(R.id.buttonSave)) - .perform(click()); - - onData(allOf(is(instanceOf(Habit.class)), withName(name))) - .onChildView(withId(R.id.label)); - - return name; - } - - private void typeHabitData(String name, String description, String num, String den) - { - onView(withId(R.id.input_name)) - .perform(replaceText(name)); - onView(withId(R.id.input_description)) - .perform(replaceText(description)); - onView(withId(R.id.input_freq_num)) - .perform(replaceText(num)); - onView(withId(R.id.input_freq_den)) - .perform(replaceText(den)); - } - - private void selectHabits(List names) - { - boolean first = true; - for(String name : names) - { - onData(allOf(is(instanceOf(Habit.class)), withName(name))) - .onChildView(withId(R.id.label)) - .perform(first ? longClick() : click()); - - first = false; - } - } - - private void assertHabitsDontExist(List names) - { - for(String name : names) - onView(withId(R.id.listView)) - .check(matches(not(containsHabit(withName(name))))); - } - - private void assertHabitExists(String name) - { - List names = new LinkedList<>(); - names.add(name); - assertHabitsExist(names); - } - - private void assertHabitsExist(List names) - { - for(String name : names) - onData(allOf(is(instanceOf(Habit.class)), withName(name))) - .check(matches(isDisplayed())); - } - - private void deleteHabit(String name) - { - LinkedList names = new LinkedList<>(); - names.add(name); - deleteHabits(names); - } - - private void deleteHabits(List names) - { - Context context = InstrumentationRegistry.getTargetContext(); - - selectHabits(names); - - openActionBarOverflowOrOptionsMenu(context); - - onView(withText(R.string.delete)) - .perform(click()); - onView(withText("OK")) - .perform(click()); - - assertHabitsDontExist(names); - } - @Test public void testArchiveHabits() { @@ -187,6 +81,15 @@ public class MainActivityTest .perform(click()); assertHabitsDontExist(names); + openActionBarOverflowOrOptionsMenu(context); + onView(withText(R.string.show_archived)) + .perform(click()); + + assertHabitsExist(names); + selectHabits(names); + onView(withContentDescription(R.string.unarchive)) + .perform(click()); + openActionBarOverflowOrOptionsMenu(context); onView(withText(R.string.show_archived)) .perform(click()); @@ -198,28 +101,24 @@ public class MainActivityTest @Test public void testAddInvalidHabit() { + onView(withId(R.id.action_add)) + .perform(click()); + typeHabitData("", "", "15", "7"); + onView(withId(R.id.buttonSave)).perform(click()); onView(withId(R.id.input_name)).check(matches(isDisplayed())); } @Test - public void testToggleCheckmarks() + public void testAddHabitAndToggleCheckmarks() { - String name = addHabit(); + String name = addHabit(true); onData(allOf(is(instanceOf(Habit.class)), withName(name))) .onChildView(withId(R.id.llButtons)) .perform(toggleAllCheckmarks()); - deleteHabit(name); - } - - @Test - public void testAddHabit() - { - String name = addHabit(true); - onData(allOf(is(instanceOf(Habit.class)), withName(name))) .onChildView(withId(R.id.label)) .perform(click()); From 2b4063311043e5b0e14a1c2adcdaf79be8930f6e Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Fri, 11 Mar 2016 12:25:38 -0500 Subject: [PATCH 3/4] Add more tests for settings and about --- app/build.gradle | 3 + .../org/isoron/uhabits/HabitViewActions.java | 55 ++++++++++++++++++ .../isoron/uhabits/MainActivityActions.java | 11 +++- .../{MainActivityTest.java => MainTest.java} | 58 ++++++++++++++++++- .../uhabits/ShowHabitActivityActions.java | 34 +++++++++++ 5 files changed, 156 insertions(+), 5 deletions(-) rename app/src/androidTest/java/org/isoron/uhabits/{MainActivityTest.java => MainTest.java} (72%) create mode 100644 app/src/androidTest/java/org/isoron/uhabits/ShowHabitActivityActions.java diff --git a/app/build.gradle b/app/build.gradle index 4f56050af..a1a1bb4bf 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -17,6 +17,9 @@ android { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' } + debug { + testCoverageEnabled = true + } } lintOptions { diff --git a/app/src/androidTest/java/org/isoron/uhabits/HabitViewActions.java b/app/src/androidTest/java/org/isoron/uhabits/HabitViewActions.java index 5dd6f29a9..7c9a7e0a2 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/HabitViewActions.java +++ b/app/src/androidTest/java/org/isoron/uhabits/HabitViewActions.java @@ -21,6 +21,7 @@ package org.isoron.uhabits; import android.support.test.espresso.UiController; import android.support.test.espresso.ViewAction; +import android.support.test.espresso.action.CoordinatesProvider; import android.support.test.espresso.action.GeneralClickAction; import android.support.test.espresso.action.GeneralLocation; import android.support.test.espresso.action.Press; @@ -33,6 +34,7 @@ import android.widget.TextView; import org.hamcrest.Matcher; import java.security.InvalidParameterException; +import java.util.Random; public class HabitViewActions { @@ -72,4 +74,57 @@ public class HabitViewActions } }; } + + public static ViewAction clickAt(final int x, final int y) + { + return new GeneralClickAction(Tap.SINGLE, new CoordinatesProvider() + { + @Override + public float[] calculateCoordinates(View view) + { + int[] locations = new int[2]; + view.getLocationOnScreen(locations); + + final float locationX = locations[0] + x; + final float locationY = locations[1] + y; + + return new float[]{locationX, locationY}; + } + }, Press.FINGER); + } + + public static ViewAction clickAtRandomLocations(final int count) + { + return new ViewAction() + { + @Override + public Matcher getConstraints() + { + return ViewMatchers.isDisplayed(); + } + + @Override + public String getDescription() + { + return "clickAtRandomLocations"; + } + + @Override + public void perform(UiController uiController, View view) + { + int width = view.getWidth(); + int height = view.getHeight(); + Random random = new Random(); + + for(int i = 0; i < count; i++) + { + int x = random.nextInt(width); + int y = random.nextInt(height); + + ViewAction action = clickAt(x, y); + action.perform(uiController, view); + } + } + }; + } } diff --git a/app/src/androidTest/java/org/isoron/uhabits/MainActivityActions.java b/app/src/androidTest/java/org/isoron/uhabits/MainActivityActions.java index 46c5c99cc..dd3675323 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/MainActivityActions.java +++ b/app/src/androidTest/java/org/isoron/uhabits/MainActivityActions.java @@ -24,6 +24,8 @@ import android.support.test.InstrumentationRegistry; import org.isoron.uhabits.models.Habit; +import java.util.Arrays; +import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Random; @@ -101,6 +103,11 @@ public class MainActivityActions .perform(replaceText(den)); } + public static void selectHabit(String name) + { + selectHabits(Collections.singletonList(name)); + } + public static void selectHabits(List names) { boolean first = true; @@ -137,9 +144,7 @@ public class MainActivityActions public static void deleteHabit(String name) { - LinkedList names = new LinkedList<>(); - names.add(name); - deleteHabits(names); + deleteHabits(Collections.singletonList(name)); } public static void deleteHabits(List names) diff --git a/app/src/androidTest/java/org/isoron/uhabits/MainActivityTest.java b/app/src/androidTest/java/org/isoron/uhabits/MainTest.java similarity index 72% rename from app/src/androidTest/java/org/isoron/uhabits/MainActivityTest.java rename to app/src/androidTest/java/org/isoron/uhabits/MainTest.java index 7557dd16c..aa1576ab4 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/MainActivityTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/MainTest.java @@ -3,6 +3,7 @@ package org.isoron.uhabits; import android.content.Context; import android.support.test.InstrumentationRegistry; import android.support.test.espresso.NoMatchingViewException; +import android.support.test.espresso.action.ViewActions; import android.support.test.espresso.intent.rule.IntentsTestRule; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.LargeTest; @@ -20,11 +21,16 @@ import java.util.Random; import static android.support.test.espresso.Espresso.onData; import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu; +import static android.support.test.espresso.Espresso.pressBack; import static android.support.test.espresso.action.ViewActions.click; import static android.support.test.espresso.action.ViewActions.longClick; import static android.support.test.espresso.action.ViewActions.scrollTo; +import static android.support.test.espresso.action.ViewActions.swipeLeft; +import static android.support.test.espresso.action.ViewActions.swipeRight; +import static android.support.test.espresso.action.ViewActions.swipeUp; import static android.support.test.espresso.assertion.ViewAssertions.matches; import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; +import static android.support.test.espresso.matcher.ViewMatchers.isRoot; import static android.support.test.espresso.matcher.ViewMatchers.withClassName; import static android.support.test.espresso.matcher.ViewMatchers.withContentDescription; import static android.support.test.espresso.matcher.ViewMatchers.withId; @@ -34,6 +40,7 @@ import static org.hamcrest.Matchers.endsWith; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.isoron.uhabits.HabitMatchers.withName; +import static org.isoron.uhabits.HabitViewActions.clickAtRandomLocations; import static org.isoron.uhabits.HabitViewActions.toggleAllCheckmarks; import static org.isoron.uhabits.MainActivityActions.addHabit; import static org.isoron.uhabits.MainActivityActions.assertHabitExists; @@ -41,12 +48,14 @@ import static org.isoron.uhabits.MainActivityActions.assertHabitsDontExist; import static org.isoron.uhabits.MainActivityActions.assertHabitsExist; import static org.isoron.uhabits.MainActivityActions.deleteHabit; import static org.isoron.uhabits.MainActivityActions.deleteHabits; +import static org.isoron.uhabits.MainActivityActions.selectHabit; import static org.isoron.uhabits.MainActivityActions.selectHabits; import static org.isoron.uhabits.MainActivityActions.typeHabitData; +import static org.isoron.uhabits.ShowHabitActivityActions.openHistoryEditor; @RunWith(AndroidJUnit4.class) @LargeTest -public class MainActivityTest +public class MainTest { @Rule public IntentsTestRule activityRule = new IntentsTestRule<>( @@ -111,7 +120,7 @@ public class MainActivityTest } @Test - public void testAddHabitAndToggleCheckmarks() + public void testAddHabitAndViewStats() { String name = addHabit(true); @@ -123,6 +132,9 @@ public class MainActivityTest .onChildView(withId(R.id.label)) .perform(click()); + onView(withId(R.id.scoreView)) + .perform(swipeRight()); + onView(withId(R.id.punchcardView)) .perform(scrollTo()); } @@ -146,6 +158,48 @@ public class MainActivityTest .perform(click()); assertHabitExists(modifiedName); + + selectHabit(modifiedName); + onView(withContentDescription(R.string.color_picker_default_title)) + .perform(click()); + pressBack(); + deleteHabit(modifiedName); } + + @Test + public void testEditHistory() + { + String name = addHabit(); + + onData(allOf(is(instanceOf(Habit.class)), withName(name))) + .onChildView(withId(R.id.label)) + .perform(click()); + + openHistoryEditor(); + onView(withClassName(endsWith("HabitHistoryView"))) + .perform(clickAtRandomLocations(20)); + + pressBack(); + onView(withId(R.id.historyView)) + .perform(scrollTo(), swipeRight(), swipeLeft()); + } + + @Test + public void testSettingsAndAbout() + { + Context context = InstrumentationRegistry.getContext(); + + openActionBarOverflowOrOptionsMenu(context); + onView(withText(R.string.settings)) + .perform(click()); + pressBack(); + + openActionBarOverflowOrOptionsMenu(context); + onView(withText(R.string.about)) + .perform(click()); + onView(isRoot()) + .perform(swipeUp()); + pressBack(); + } } diff --git a/app/src/androidTest/java/org/isoron/uhabits/ShowHabitActivityActions.java b/app/src/androidTest/java/org/isoron/uhabits/ShowHabitActivityActions.java new file mode 100644 index 000000000..cacc9f21f --- /dev/null +++ b/app/src/androidTest/java/org/isoron/uhabits/ShowHabitActivityActions.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2016 Á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; + +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.action.ViewActions.scrollTo; +import static android.support.test.espresso.matcher.ViewMatchers.withId; + +public class ShowHabitActivityActions +{ + public static void openHistoryEditor() + { + onView(withId(R.id.btEditHistory)) + .perform(scrollTo(), click()); + } +} From 8b10138cd637b8c61447bb6435e0f5649aa96bf8 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Fri, 11 Mar 2016 13:20:43 -0500 Subject: [PATCH 4/4] Fix tests on pre-Lollipop devices --- .../isoron/uhabits/MainActivityActions.java | 33 +++++++++++++------ .../java/org/isoron/uhabits/MainTest.java | 16 ++++----- 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/app/src/androidTest/java/org/isoron/uhabits/MainActivityActions.java b/app/src/androidTest/java/org/isoron/uhabits/MainActivityActions.java index dd3675323..3ce26da37 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/MainActivityActions.java +++ b/app/src/androidTest/java/org/isoron/uhabits/MainActivityActions.java @@ -24,7 +24,6 @@ import android.support.test.InstrumentationRegistry; import org.isoron.uhabits.models.Habit; -import java.util.Arrays; import java.util.Collections; import java.util.LinkedList; import java.util.List; @@ -32,13 +31,14 @@ import java.util.Random; import static android.support.test.espresso.Espresso.onData; import static android.support.test.espresso.Espresso.onView; -import static android.support.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu; +import static android.support.test.espresso.Espresso.openContextualActionModeOverflowMenu; import static android.support.test.espresso.Espresso.pressBack; import static android.support.test.espresso.action.ViewActions.click; import static android.support.test.espresso.action.ViewActions.longClick; import static android.support.test.espresso.action.ViewActions.replaceText; import static android.support.test.espresso.assertion.ViewAssertions.matches; import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; +import static android.support.test.espresso.matcher.ViewMatchers.withContentDescription; import static android.support.test.espresso.matcher.ViewMatchers.withId; import static android.support.test.espresso.matcher.ViewMatchers.withText; import static org.hamcrest.Matchers.allOf; @@ -149,17 +149,30 @@ public class MainActivityActions public static void deleteHabits(List names) { - Context context = InstrumentationRegistry.getTargetContext(); - selectHabits(names); - - openActionBarOverflowOrOptionsMenu(context); - - onView(withText(R.string.delete)) - .perform(click()); + clickActionModeMenuItem(R.string.delete); onView(withText("OK")) .perform(click()); - assertHabitsDontExist(names); } + + public static void clickActionModeMenuItem(int stringId) + { + try + { + onView(withText(stringId)).perform(click()); + } + catch (Exception e1) + { + try + { + onView(withContentDescription(stringId)).perform(click()); + } + catch(Exception e2) + { + openContextualActionModeOverflowMenu(); + onView(withText(stringId)).perform(click()); + } + } + } } diff --git a/app/src/androidTest/java/org/isoron/uhabits/MainTest.java b/app/src/androidTest/java/org/isoron/uhabits/MainTest.java index aa1576ab4..37bd87989 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/MainTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/MainTest.java @@ -3,7 +3,6 @@ package org.isoron.uhabits; import android.content.Context; import android.support.test.InstrumentationRegistry; import android.support.test.espresso.NoMatchingViewException; -import android.support.test.espresso.action.ViewActions; import android.support.test.espresso.intent.rule.IntentsTestRule; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.LargeTest; @@ -32,7 +31,6 @@ import static android.support.test.espresso.assertion.ViewAssertions.matches; import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; import static android.support.test.espresso.matcher.ViewMatchers.isRoot; import static android.support.test.espresso.matcher.ViewMatchers.withClassName; -import static android.support.test.espresso.matcher.ViewMatchers.withContentDescription; import static android.support.test.espresso.matcher.ViewMatchers.withId; import static android.support.test.espresso.matcher.ViewMatchers.withText; import static org.hamcrest.Matchers.allOf; @@ -46,6 +44,7 @@ import static org.isoron.uhabits.MainActivityActions.addHabit; import static org.isoron.uhabits.MainActivityActions.assertHabitExists; import static org.isoron.uhabits.MainActivityActions.assertHabitsDontExist; import static org.isoron.uhabits.MainActivityActions.assertHabitsExist; +import static org.isoron.uhabits.MainActivityActions.clickActionModeMenuItem; import static org.isoron.uhabits.MainActivityActions.deleteHabit; import static org.isoron.uhabits.MainActivityActions.deleteHabits; import static org.isoron.uhabits.MainActivityActions.selectHabit; @@ -86,8 +85,8 @@ public class MainTest names.add(addHabit()); selectHabits(names); - onView(withContentDescription(R.string.archive)) - .perform(click()); + + clickActionModeMenuItem(R.string.archive); assertHabitsDontExist(names); openActionBarOverflowOrOptionsMenu(context); @@ -96,8 +95,7 @@ public class MainTest assertHabitsExist(names); selectHabits(names); - onView(withContentDescription(R.string.unarchive)) - .perform(click()); + clickActionModeMenuItem(R.string.unarchive); openActionBarOverflowOrOptionsMenu(context); onView(withText(R.string.show_archived)) @@ -148,8 +146,7 @@ public class MainTest .onChildView(withId(R.id.label)) .perform(longClick()); - onView(withContentDescription(R.string.edit)) - .perform(click()); + clickActionModeMenuItem(R.string.edit); String modifiedName = "Modified " + new Random().nextInt(10000); typeHabitData(modifiedName, "", "1", "1"); @@ -160,8 +157,7 @@ public class MainTest assertHabitExists(modifiedName); selectHabit(modifiedName); - onView(withContentDescription(R.string.color_picker_default_title)) - .perform(click()); + clickActionModeMenuItem(R.string.color_picker_default_title); pressBack(); deleteHabit(modifiedName);