From e61b065f2b3369e733b263e7438b432de7e11767 Mon Sep 17 00:00:00 2001 From: Peter Storch Date: Sat, 5 Mar 2022 21:48:34 +0100 Subject: [PATCH] fix #1139 CI build - fix NPEs in TrackPointCreator.fill() - fix locale dependant tests - wait for UI components - extract utility functions into EspressoUtils --- .../opentracks/EspressoDeleteTrackTest.java | 98 +--------- .../EspressoEditTrackRecordingTest.java | 155 ++-------------- .../dennisguse/opentracks/EspressoUITest.java | 59 +----- .../de/dennisguse/opentracks/LocaleRule.java | 55 ++++++ ...TrackRecordingServiceTestStateMachine.java | 2 +- .../VoiceAnnouncementUtilsTest.java | 6 + .../share/DescriptionGeneratorTest.java | 6 + .../EspressoAggregatedFilterTest.java | 4 +- .../EspressoCustomLayoutTest.java | 58 +----- .../opentracks/util/EspressoUtils.java | 173 ++++++++++++++++++ .../opentracks/util/StringUtilsTest.java | 5 + .../opentracks/util/TrackNameUtilsTest.java | 6 + .../services/handlers/TrackPointCreator.java | 11 +- 13 files changed, 293 insertions(+), 345 deletions(-) create mode 100644 src/androidTest/java/de/dennisguse/opentracks/LocaleRule.java create mode 100644 src/androidTest/java/de/dennisguse/opentracks/util/EspressoUtils.java diff --git a/src/androidTest/java/de/dennisguse/opentracks/EspressoDeleteTrackTest.java b/src/androidTest/java/de/dennisguse/opentracks/EspressoDeleteTrackTest.java index f0b9c567e..68a6177c0 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/EspressoDeleteTrackTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/EspressoDeleteTrackTest.java @@ -13,22 +13,20 @@ import static androidx.test.espresso.matcher.ViewMatchers.withId; import static androidx.test.espresso.matcher.ViewMatchers.withText; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.anything; +import static de.dennisguse.opentracks.util.EspressoUtils.childAtPosition; +import static de.dennisguse.opentracks.util.EspressoUtils.veryLongTouch; +import static de.dennisguse.opentracks.util.EspressoUtils.waitFor; +import static de.dennisguse.opentracks.util.EspressoUtils.withListSize; import android.app.ActivityManager; import android.app.Instrumentation; import android.content.Context; -import android.view.MotionEvent; import android.view.View; -import android.view.ViewGroup; -import android.view.ViewParent; import android.widget.ListView; -import androidx.test.espresso.Espresso; +import androidx.test.espresso.IdlingRegistry; import androidx.test.espresso.IdlingResource; -import androidx.test.espresso.UiController; -import androidx.test.espresso.ViewAction; import androidx.test.espresso.ViewInteraction; -import androidx.test.espresso.action.MotionEvents; import androidx.test.espresso.assertion.ViewAssertions; import androidx.test.ext.junit.rules.ActivityScenarioRule; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -37,7 +35,6 @@ import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.rule.GrantPermissionRule; import org.hamcrest.Description; -import org.hamcrest.Matcher; import org.hamcrest.TypeSafeMatcher; import org.junit.After; import org.junit.Before; @@ -65,12 +62,12 @@ public class EspressoDeleteTrackTest { Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); idlingResource = new MyIdlingResource(instrumentation.getTargetContext()); - Espresso.registerIdlingResources(idlingResource); + IdlingRegistry.getInstance().register(idlingResource); } @After public void unregisterIntentServiceIdlingResource() { - Espresso.unregisterIdlingResources(idlingResource); + IdlingRegistry.getInstance().unregister(idlingResource); } @Ignore("Test fails permanently") @@ -140,87 +137,6 @@ public class EspressoDeleteTrackTest { onView(withId(R.id.track_list)).check(ViewAssertions.matches(withListSize(countBefore - 1))); } - private static Matcher childAtPosition( - final Matcher parentMatcher, final int position) { - - return new TypeSafeMatcher<>() { - @Override - public void describeTo(Description description) { - description.appendText("Child at position " + position + " in parent "); - parentMatcher.describeTo(description); - } - - @Override - public boolean matchesSafely(View view) { - ViewParent parent = view.getParent(); - return parent instanceof ViewGroup && parentMatcher.matches(parent) - && view.equals(((ViewGroup) parent).getChildAt(position)); - } - }; - } - - public static Matcher withListSize (final int size) { - return new TypeSafeMatcher<>() { - @Override - public boolean matchesSafely(final View view) { - return ((ListView) view).getCount() == size; - } - - @Override - public void describeTo(final Description description) { - description.appendText("ListView should have " + size + " items"); - } - }; - } - - private static ViewAction waitFor(final long duration_ms) { - return new ViewAction() { - - @Override - public String getDescription() { - return "Wait for milliseconds."; - } - - @Override - public Matcher getConstraints() { - return isDisplayed(); - } - - @Override - public void perform(UiController uiController, final View view) { - uiController.loopMainThreadForAtLeast(duration_ms); - } - }; - } - - private static ViewAction veryLongTouch(final int duration_ms) { - return new ViewAction() { - @Override - public String getDescription() { - return "Perform long touch."; - } - - @Override - public Matcher getConstraints() { - return isDisplayed(); - } - - @Override - public void perform(UiController uiController, final View view) { - // Get view absolute position - int[] location = new int[2]; - view.getLocationOnScreen(location); - - // Offset coordinates by view position - float[] coordinates = new float[]{location[0] + 1, location[1] + 1}; - - // Send down event, pause, and send up - MotionEvent down = MotionEvents.sendDown(uiController, coordinates, new float[]{1f, 1f}).down; - uiController.loopMainThreadForAtLeast(duration_ms); - MotionEvents.sendUp(uiController, down, coordinates); - } - }; - } private int numberOfItemsListView() { final int[] counts = new int[1]; diff --git a/src/androidTest/java/de/dennisguse/opentracks/EspressoEditTrackRecordingTest.java b/src/androidTest/java/de/dennisguse/opentracks/EspressoEditTrackRecordingTest.java index f55b0cad4..aea53c0f6 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/EspressoEditTrackRecordingTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/EspressoEditTrackRecordingTest.java @@ -2,52 +2,43 @@ package de.dennisguse.opentracks; import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.Espresso.openContextualActionModeOverflowMenu; import static androidx.test.espresso.action.ViewActions.click; import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard; import static androidx.test.espresso.action.ViewActions.replaceText; import static androidx.test.espresso.action.ViewActions.scrollTo; import static androidx.test.espresso.assertion.ViewAssertions.matches; import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; -import static androidx.test.espresso.matcher.ViewMatchers.isRoot; import static androidx.test.espresso.matcher.ViewMatchers.withClassName; -import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription; import static androidx.test.espresso.matcher.ViewMatchers.withId; import static androidx.test.espresso.matcher.ViewMatchers.withParent; import static androidx.test.espresso.matcher.ViewMatchers.withText; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.is; +import static de.dennisguse.opentracks.util.EspressoUtils.childAtPosition; +import static de.dennisguse.opentracks.util.EspressoUtils.veryLongTouch; +import static de.dennisguse.opentracks.util.EspressoUtils.waitFor; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewParent; - -import androidx.test.espresso.PerformException; -import androidx.test.espresso.UiController; -import androidx.test.espresso.ViewAction; import androidx.test.espresso.ViewInteraction; -import androidx.test.espresso.action.MotionEvents; -import androidx.test.espresso.util.HumanReadables; -import androidx.test.espresso.util.TreeIterables; import androidx.test.ext.junit.rules.ActivityScenarioRule; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.LargeTest; import androidx.test.rule.GrantPermissionRule; -import org.hamcrest.Description; -import org.hamcrest.Matcher; -import org.hamcrest.TypeSafeMatcher; import org.hamcrest.core.IsInstanceOf; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.concurrent.TimeoutException; +import java.util.Locale; @LargeTest @RunWith(AndroidJUnit4.class) public class EspressoEditTrackRecordingTest { + @Rule + public final LocaleRule mLocaleRule = new LocaleRule(Locale.ENGLISH); + @Rule public ActivityScenarioRule mActivityTestRule = new ActivityScenarioRule<>(TrackListActivity.class); @@ -71,26 +62,10 @@ public class EspressoEditTrackRecordingTest { trackControllerRecordButton.perform(waitFor(15000)); // open menu - ViewInteraction overflowMenuButton = onView( - allOf(withContentDescription("More options"), - childAtPosition( - childAtPosition( - withId(R.id.toolbar), - 2), - 2), - isDisplayed())); - overflowMenuButton.perform(click()); + openContextualActionModeOverflowMenu(); - // click on edit - ViewInteraction appCompatTextView = onView( - allOf(withId(R.id.title), withText("Edit"), - childAtPosition( - childAtPosition( - withId(R.id.content), - 0), - 0), - isDisplayed())); - appCompatTextView.perform(click()); + // Click the item. + onView(withText(R.string.menu_edit)).perform(click()); // change name for "New Name" ViewInteraction textInputEditText = onView( @@ -147,112 +122,4 @@ public class EspressoEditTrackRecordingTest { } } - private static Matcher childAtPosition( - final Matcher parentMatcher, final int position) { - - return new TypeSafeMatcher<>() { - @Override - public void describeTo(Description description) { - description.appendText("Child at position " + position + " in parent "); - parentMatcher.describeTo(description); - } - - @Override - public boolean matchesSafely(View view) { - ViewParent parent = view.getParent(); - return parent instanceof ViewGroup && parentMatcher.matches(parent) - && view.equals(((ViewGroup) parent).getChildAt(position)); - } - }; - } - - public static ViewAction waitId(final int viewId, final long millis) { - return new ViewAction() { - @Override - public Matcher getConstraints() { - return isRoot(); - } - - @Override - public String getDescription() { - return "wait for a specific view with id <" + viewId + "> during " + millis + " millis."; - } - - @Override - public void perform(final UiController uiController, final View view) { - uiController.loopMainThreadUntilIdle(); - final long startTime = System.currentTimeMillis(); - final long endTime = startTime + millis; - final Matcher viewMatcher = withId(viewId); - - do { - for (View child : TreeIterables.breadthFirstViewTraversal(view)) { - // found view with required ID - if (viewMatcher.matches(child)) { - return; - } - } - - uiController.loopMainThreadForAtLeast(50); - } - while (System.currentTimeMillis() < endTime); - - // timeout happens - throw new PerformException.Builder() - .withActionDescription(this.getDescription()) - .withViewDescription(HumanReadables.describe(view)) - .withCause(new TimeoutException()) - .build(); - } - }; - } - - private static ViewAction waitFor(final long duration_ms) { - return new ViewAction() { - - @Override - public String getDescription() { - return "Wait for milliseconds."; - } - - @Override - public Matcher getConstraints() { - return isDisplayed(); - } - - @Override - public void perform(UiController uiController, final View view) { - uiController.loopMainThreadForAtLeast(duration_ms); - } - }; - } - - private static ViewAction veryLongTouch(final int duration_ms) { - return new ViewAction() { - @Override - public String getDescription() { - return "Perform long touch."; - } - - @Override - public Matcher getConstraints() { - return isDisplayed(); - } - - @Override - public void perform(UiController uiController, final View view) { - // Get view absolute position - int[] location = new int[2]; - view.getLocationOnScreen(location); - - // Offset coordinates by view position - float[] coordinates = new float[]{location[0] + 1, location[1] + 1}; - - // Send down event, pause, and send up - MotionEvent down = MotionEvents.sendDown(uiController, coordinates, new float[]{1f, 1f}).down; - uiController.loopMainThreadForAtLeast(duration_ms); - MotionEvents.sendUp(uiController, down, coordinates); - } - }; - } } diff --git a/src/androidTest/java/de/dennisguse/opentracks/EspressoUITest.java b/src/androidTest/java/de/dennisguse/opentracks/EspressoUITest.java index 8911992f0..0a93c0226 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/EspressoUITest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/EspressoUITest.java @@ -1,5 +1,15 @@ package de.dennisguse.opentracks; +import static androidx.test.espresso.Espresso.onData; +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.assertion.ViewAssertions.matches; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.withId; +import static org.hamcrest.Matchers.anything; +import static de.dennisguse.opentracks.util.EspressoUtils.selectTabAtIndex; +import static de.dennisguse.opentracks.util.EspressoUtils.waitFor; + import android.view.MotionEvent; import android.view.View; @@ -12,21 +22,11 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.LargeTest; import androidx.test.rule.GrantPermissionRule; -import com.google.android.material.tabs.TabLayout; - import org.hamcrest.Matcher; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import static androidx.test.espresso.Espresso.onData; -import static androidx.test.espresso.Espresso.onView; -import static androidx.test.espresso.action.ViewActions.click; -import static androidx.test.espresso.assertion.ViewAssertions.matches; -import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; -import static androidx.test.espresso.matcher.ViewMatchers.withId; -import static org.hamcrest.Matchers.anything; - @LargeTest @RunWith(AndroidJUnit4.class) public class EspressoUITest { @@ -136,43 +136,4 @@ public class EspressoUITest { }; } - private static ViewAction waitFor(final long duration_ms) { - return new ViewAction() { - - @Override - public String getDescription() { - return "Wait for milliseconds."; - } - - @Override - public Matcher getConstraints() { - return isDisplayed(); - } - - @Override - public void perform(UiController uiController, final View view) { - uiController.loopMainThreadForAtLeast(duration_ms); - } - }; - } - - private static ViewAction selectTabAtIndex(final int index) { - return new ViewAction() { - @Override - public String getDescription() { - return "Selecting tab."; - } - - @Override - public Matcher getConstraints() { - return isDisplayed(); - } - - @Override - public void perform(UiController uiController, View view) { - TabLayout tabLayout = (TabLayout) view; - tabLayout.getTabAt(index).select(); - } - }; - } } diff --git a/src/androidTest/java/de/dennisguse/opentracks/LocaleRule.java b/src/androidTest/java/de/dennisguse/opentracks/LocaleRule.java new file mode 100644 index 000000000..ce5a2f930 --- /dev/null +++ b/src/androidTest/java/de/dennisguse/opentracks/LocaleRule.java @@ -0,0 +1,55 @@ +package de.dennisguse.opentracks; + +import android.content.res.Configuration; +import android.content.res.Resources; +import android.util.DisplayMetrics; + +import androidx.test.core.app.ApplicationProvider; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import java.util.Locale; + +public class LocaleRule implements TestRule { + + private final Locale[] mLocales; + private Locale mDeviceLocale; + + + public LocaleRule(Locale... locales) { + assert locales != null && locales.length > 0; + mLocales = locales; + } + + @Override + public Statement apply(Statement base, Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + try { + mDeviceLocale = Locale.getDefault(); + for (Locale locale : mLocales) { + setLocale(locale); + base.evaluate(); + } + } finally { + if (mDeviceLocale != null) { + setLocale(mDeviceLocale); + } + } + } + }; + } + + private void setLocale(Locale locale) { + Resources resources = ApplicationProvider.getApplicationContext().getResources(); + Locale.setDefault(locale); + Configuration config = resources.getConfiguration(); + config.setLocale(locale); + DisplayMetrics displayMetrics = resources.getDisplayMetrics(); + resources.updateConfiguration(config, displayMetrics); + } + +} diff --git a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestStateMachine.java b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestStateMachine.java index c67ae57ad..658b8012c 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestStateMachine.java +++ b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestStateMachine.java @@ -215,7 +215,7 @@ public class TrackRecordingServiceTestStateMachine { // when service.pauseCurrentTrack(); - Thread.sleep(1000); + Thread.sleep(2000); // then assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue()); diff --git a/src/androidTest/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementUtilsTest.java b/src/androidTest/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementUtilsTest.java index 69d9d87d5..a0807d274 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementUtilsTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementUtilsTest.java @@ -9,11 +9,14 @@ import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import java.time.Duration; +import java.util.Locale; +import de.dennisguse.opentracks.LocaleRule; import de.dennisguse.opentracks.content.data.TestDataUtil; import de.dennisguse.opentracks.data.ContentProviderUtils; import de.dennisguse.opentracks.data.TrackPointIterator; @@ -29,6 +32,9 @@ import de.dennisguse.opentracks.ui.intervals.IntervalStatistics; @RunWith(AndroidJUnit4.class) public class VoiceAnnouncementUtilsTest { + @Rule + public final LocaleRule mLocaleRule = new LocaleRule(Locale.ENGLISH); + private final Context context = ApplicationProvider.getApplicationContext(); private ContentProviderUtils contentProviderUtils; diff --git a/src/androidTest/java/de/dennisguse/opentracks/share/DescriptionGeneratorTest.java b/src/androidTest/java/de/dennisguse/opentracks/share/DescriptionGeneratorTest.java index eaa0193c2..e8bdda71c 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/share/DescriptionGeneratorTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/share/DescriptionGeneratorTest.java @@ -24,6 +24,7 @@ import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -31,7 +32,9 @@ import java.time.Duration; import java.time.Instant; import java.time.OffsetDateTime; import java.time.ZoneId; +import java.util.Locale; +import de.dennisguse.opentracks.LocaleRule; import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.data.models.Distance; import de.dennisguse.opentracks.data.models.Speed; @@ -47,6 +50,9 @@ import de.dennisguse.opentracks.util.StringUtils; @RunWith(AndroidJUnit4.class) public class DescriptionGeneratorTest { + @Rule + public final LocaleRule mLocaleRule = new LocaleRule(Locale.ENGLISH); + private static final Instant START_TIME = Instant.ofEpochMilli(1288721514000L); private DescriptionGenerator descriptionGenerator; diff --git a/src/androidTest/java/de/dennisguse/opentracks/ui/aggregatedStatistics/EspressoAggregatedFilterTest.java b/src/androidTest/java/de/dennisguse/opentracks/ui/aggregatedStatistics/EspressoAggregatedFilterTest.java index e46147a12..eaf62f0ad 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/ui/aggregatedStatistics/EspressoAggregatedFilterTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/ui/aggregatedStatistics/EspressoAggregatedFilterTest.java @@ -9,6 +9,7 @@ import static androidx.test.espresso.matcher.ViewMatchers.withId; import static androidx.test.espresso.matcher.ViewMatchers.withParent; import static androidx.test.espresso.matcher.ViewMatchers.withText; import static org.hamcrest.Matchers.allOf; +import static de.dennisguse.opentracks.util.EspressoUtils.waitFor; import android.util.Pair; import android.view.View; @@ -73,7 +74,7 @@ public class EspressoAggregatedFilterTest { withParent(allOf(withId(R.id.filter_items), withParent(IsInstanceOf.instanceOf(android.view.ViewGroup.class)))), isDisplayed())); - checkBox.check(matches(isDisplayed())); + checkBox.perform(waitFor(2000)); // check there's an edit text for "from date" onView(withId(R.id.filter_date_edit_text_from)).check(matches(isDisplayed())); @@ -81,4 +82,5 @@ public class EspressoAggregatedFilterTest { // check there's an edit text for "to date" onView(withId(R.id.filter_date_edit_text_to)).check(matches(isDisplayed())); } + } diff --git a/src/androidTest/java/de/dennisguse/opentracks/ui/customRecordingLayout/EspressoCustomLayoutTest.java b/src/androidTest/java/de/dennisguse/opentracks/ui/customRecordingLayout/EspressoCustomLayoutTest.java index b55bd46f3..100fe47a1 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/ui/customRecordingLayout/EspressoCustomLayoutTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/ui/customRecordingLayout/EspressoCustomLayoutTest.java @@ -4,31 +4,22 @@ package de.dennisguse.opentracks.ui.customRecordingLayout; import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.action.ViewActions.click; import static androidx.test.espresso.matcher.ViewMatchers.assertThat; -import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; import static androidx.test.espresso.matcher.ViewMatchers.withId; import static org.hamcrest.CoreMatchers.is; +import static de.dennisguse.opentracks.util.EspressoUtils.veryLongTouch; -import android.view.MotionEvent; import android.view.View; -import android.view.ViewGroup; -import android.view.ViewParent; import androidx.recyclerview.widget.RecyclerView; import androidx.test.espresso.NoMatchingViewException; -import androidx.test.espresso.UiController; -import androidx.test.espresso.ViewAction; import androidx.test.espresso.ViewAssertion; import androidx.test.espresso.ViewInteraction; -import androidx.test.espresso.action.MotionEvents; import androidx.test.espresso.matcher.ViewMatchers; import androidx.test.ext.junit.rules.ActivityScenarioRule; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.LargeTest; import androidx.test.rule.GrantPermissionRule; -import org.hamcrest.Description; -import org.hamcrest.Matcher; -import org.hamcrest.TypeSafeMatcher; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -82,51 +73,4 @@ public class EspressoCustomLayoutTest { } } - private static Matcher childAtPosition( - final Matcher parentMatcher, final int position) { - - return new TypeSafeMatcher<>() { - @Override - public void describeTo(Description description) { - description.appendText("Child at position " + position + " in parent "); - parentMatcher.describeTo(description); - } - - @Override - public boolean matchesSafely(View view) { - ViewParent parent = view.getParent(); - return parent instanceof ViewGroup && parentMatcher.matches(parent) - && view.equals(((ViewGroup) parent).getChildAt(position)); - } - }; - } - - private static ViewAction veryLongTouch(final int duration_ms) { - return new ViewAction() { - @Override - public String getDescription() { - return "Perform long touch."; - } - - @Override - public Matcher getConstraints() { - return isDisplayed(); - } - - @Override - public void perform(UiController uiController, final View view) { - // Get view absolute position - int[] location = new int[2]; - view.getLocationOnScreen(location); - - // Offset coordinates by view position - float[] coordinates = new float[]{location[0] + 1, location[1] + 1}; - - // Send down event, pause, and send up - MotionEvent down = MotionEvents.sendDown(uiController, coordinates, new float[]{1f, 1f}).down; - uiController.loopMainThreadForAtLeast(duration_ms); - MotionEvents.sendUp(uiController, down, coordinates); - } - }; - } } diff --git a/src/androidTest/java/de/dennisguse/opentracks/util/EspressoUtils.java b/src/androidTest/java/de/dennisguse/opentracks/util/EspressoUtils.java new file mode 100644 index 000000000..bf618fb95 --- /dev/null +++ b/src/androidTest/java/de/dennisguse/opentracks/util/EspressoUtils.java @@ -0,0 +1,173 @@ +package de.dennisguse.opentracks.util; + +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.isRoot; +import static androidx.test.espresso.matcher.ViewMatchers.withId; +import static androidx.test.espresso.util.TreeIterables.breadthFirstViewTraversal; + +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.widget.ListView; + +import androidx.test.espresso.PerformException; +import androidx.test.espresso.UiController; +import androidx.test.espresso.ViewAction; +import androidx.test.espresso.action.MotionEvents; +import androidx.test.espresso.util.HumanReadables; + +import com.google.android.material.tabs.TabLayout; + +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +import java.util.concurrent.TimeoutException; + +public class EspressoUtils { + + public static Matcher childAtPosition( + final Matcher parentMatcher, final int position) { + + return new TypeSafeMatcher<>() { + @Override + public void describeTo(Description description) { + description.appendText("Child at position " + position + " in parent "); + parentMatcher.describeTo(description); + } + + @Override + public boolean matchesSafely(View view) { + ViewParent parent = view.getParent(); + return parent instanceof ViewGroup && parentMatcher.matches(parent) + && view.equals(((ViewGroup) parent).getChildAt(position)); + } + }; + } + + public static Matcher withListSize (final int size) { + return new TypeSafeMatcher<>() { + @Override + public boolean matchesSafely(final View view) { + return ((ListView) view).getCount() == size; + } + + @Override + public void describeTo(final Description description) { + description.appendText("ListView should have " + size + " items"); + } + }; + } + + public static ViewAction waitFor(final long duration_ms) { + return new ViewAction() { + + @Override + public String getDescription() { + return "Wait for milliseconds."; + } + + @Override + public Matcher getConstraints() { + return isDisplayed(); + } + + @Override + public void perform(UiController uiController, final View view) { + uiController.loopMainThreadForAtLeast(duration_ms); + } + }; + } + + public static ViewAction veryLongTouch(final int duration_ms) { + return new ViewAction() { + @Override + public String getDescription() { + return "Perform long touch."; + } + + @Override + public Matcher getConstraints() { + return isDisplayed(); + } + + @Override + public void perform(UiController uiController, final View view) { + // Get view absolute position + int[] location = new int[2]; + view.getLocationOnScreen(location); + + // Offset coordinates by view position + float[] coordinates = new float[]{location[0] + 1, location[1] + 1}; + + // Send down event, pause, and send up + MotionEvent down = MotionEvents.sendDown(uiController, coordinates, new float[]{1f, 1f}).down; + uiController.loopMainThreadForAtLeast(duration_ms); + MotionEvents.sendUp(uiController, down, coordinates); + } + }; + } + + public static ViewAction waitId(final int viewId, final long millis) { + return new ViewAction() { + @Override + public Matcher getConstraints() { + return isRoot(); + } + + @Override + public String getDescription() { + return "wait for a specific view with id <" + viewId + "> during " + millis + " millis."; + } + + @Override + public void perform(final UiController uiController, final View view) { + uiController.loopMainThreadUntilIdle(); + final long startTime = System.currentTimeMillis(); + final long endTime = startTime + millis; + final Matcher viewMatcher = withId(viewId); + + do { + for (View child : breadthFirstViewTraversal(view)) { + // found view with required ID + if (viewMatcher.matches(child)) { + return; + } + } + + uiController.loopMainThreadForAtLeast(50); + } + while (System.currentTimeMillis() < endTime); + + // timeout happens + throw new PerformException.Builder() + .withActionDescription(this.getDescription()) + .withViewDescription(HumanReadables.describe(view)) + .withCause(new TimeoutException()) + .build(); + } + }; + } + + public static ViewAction selectTabAtIndex(final int index) { + return new ViewAction() { + @Override + public String getDescription() { + return "Selecting tab."; + } + + @Override + public Matcher getConstraints() { + return isDisplayed(); + } + + @Override + public void perform(UiController uiController, View view) { + TabLayout tabLayout = (TabLayout) view; + tabLayout.getTabAt(index).select(); + } + }; + } + +} diff --git a/src/androidTest/java/de/dennisguse/opentracks/util/StringUtilsTest.java b/src/androidTest/java/de/dennisguse/opentracks/util/StringUtilsTest.java index 0dae88cb6..66304d783 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/util/StringUtilsTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/util/StringUtilsTest.java @@ -24,6 +24,7 @@ import android.content.Context; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -45,6 +46,7 @@ import java.util.Locale; import java.util.TimeZone; import java.util.stream.Collectors; +import de.dennisguse.opentracks.LocaleRule; import de.dennisguse.opentracks.data.models.Distance; import de.dennisguse.opentracks.data.models.Speed; @@ -56,6 +58,9 @@ import de.dennisguse.opentracks.data.models.Speed; @RunWith(AndroidJUnit4.class) public class StringUtilsTest { + @Rule + public final LocaleRule mLocaleRule = new LocaleRule(Locale.ENGLISH); + private final Context context = ApplicationProvider.getApplicationContext(); /** diff --git a/src/androidTest/java/de/dennisguse/opentracks/util/TrackNameUtilsTest.java b/src/androidTest/java/de/dennisguse/opentracks/util/TrackNameUtilsTest.java index 9fa0e192b..9e585aca6 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/util/TrackNameUtilsTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/util/TrackNameUtilsTest.java @@ -23,11 +23,14 @@ import android.content.Context; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import java.time.OffsetDateTime; +import java.util.Locale; +import de.dennisguse.opentracks.LocaleRule; import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.settings.PreferencesUtils; @@ -40,6 +43,9 @@ import de.dennisguse.opentracks.settings.PreferencesUtils; @RunWith(AndroidJUnit4.class) public class TrackNameUtilsTest { + @Rule + public final LocaleRule mLocaleRule = new LocaleRule(Locale.ENGLISH); + private static final Track.Id TRACK_ID = new Track.Id(1L); private static final OffsetDateTime START_TIME = OffsetDateTime.parse("2022-01-02T10:15:30.1234+01:00"); diff --git a/src/main/java/de/dennisguse/opentracks/services/handlers/TrackPointCreator.java b/src/main/java/de/dennisguse/opentracks/services/handlers/TrackPointCreator.java index 5b108c933..d6dc2da6e 100644 --- a/src/main/java/de/dennisguse/opentracks/services/handlers/TrackPointCreator.java +++ b/src/main/java/de/dennisguse/opentracks/services/handlers/TrackPointCreator.java @@ -85,8 +85,15 @@ public class TrackPointCreator implements BluetoothRemoteSensorManager.SensorDat Log.w(TAG, "Not started, should not be called."); return null; } - SensorDataSet sensorDataSet = remoteSensorManager.fill(trackPoint); - altitudeSumManager.fill(trackPoint); + SensorDataSet sensorDataSet = null; + BluetoothRemoteSensorManager localRemoteSensorManager = remoteSensorManager; + if (localRemoteSensorManager != null) { + sensorDataSet = localRemoteSensorManager.fill(trackPoint); + } + AltitudeSumManager localAltitudeSumManager = altitudeSumManager; + if (localAltitudeSumManager != null) { + localAltitudeSumManager.fill(trackPoint); + } return sensorDataSet; }