kopia lustrzana https://github.com/ge0rg/aprsdroid
				
				
				
			Merge 936f588e4a into 859033e0d7
				
					
				
			
						commit
						49ece3a7f6
					
				|  | @ -0,0 +1,188 @@ | |||
| name: Android CI | ||||
| 
 | ||||
| on: | ||||
|   push: | ||||
|     branches: [ '**' ] | ||||
|   pull_request: | ||||
|     branches: [ '**' ] | ||||
|   workflow_dispatch: | ||||
| 
 | ||||
| jobs: | ||||
|   compile: | ||||
|     runs-on: ubuntu-latest | ||||
|     name: "Compile all sources" | ||||
| 
 | ||||
|     steps: | ||||
|     - name: Checkout project | ||||
|       uses: actions/checkout@v4 | ||||
|       with: | ||||
|         fetch-depth: 0 | ||||
|         submodules: recursive | ||||
| 
 | ||||
|     - name: Set up JDK 11 | ||||
|       uses: actions/setup-java@v4 | ||||
|       with: | ||||
|         java-version: '11' | ||||
|         distribution: 'adopt' | ||||
|         cache: gradle | ||||
| 
 | ||||
|     - name: Load build outputs | ||||
|       uses: actions/cache@v4 | ||||
|       with: | ||||
|         path: build | ||||
|         key: build-${{ github.sha }} | ||||
| 
 | ||||
|     - name: Create properties file with empty API key | ||||
|       run: echo mapsApiKey="\"${{ secrets.mapsApiKey }}\"" >> local.properties | ||||
| 
 | ||||
|     - name: Build App | ||||
|       run: ./gradlew assemble --stacktrace | ||||
| 
 | ||||
|     - name: Build unit tests | ||||
|       run: ./gradlew assembleDebugUnitTest assembleReleaseUnitTest --stacktrace | ||||
| 
 | ||||
|     - name: Build instrumentation tests | ||||
|       run: ./gradlew assembleAndroidTest --stacktrace | ||||
| 
 | ||||
|   unit-test: | ||||
|     name: "Run all unit tests" | ||||
|     needs: compile | ||||
|     runs-on: ubuntu-latest | ||||
| 
 | ||||
|     steps: | ||||
|     - name: Checkout project | ||||
|       uses: actions/checkout@v4 | ||||
|       with: | ||||
|         fetch-depth: 0 | ||||
|         submodules: recursive | ||||
| 
 | ||||
|     - name: Set up JDK 11 | ||||
|       uses: actions/setup-java@v4 | ||||
|       with: | ||||
|         java-version: '11' | ||||
|         distribution: 'adopt' | ||||
|         cache: gradle | ||||
| 
 | ||||
|     - name: Load build outputs | ||||
|       uses: actions/cache@v4 | ||||
|       with: | ||||
|         path: build | ||||
|         key: build-${{ github.sha }} | ||||
| 
 | ||||
|     - name: Run Unit Tests | ||||
|       run: ./gradlew test --stacktrace | ||||
| 
 | ||||
|     - name: Run Linter | ||||
|       run: ./gradlew lint --stacktrace | ||||
|       continue-on-error: true | ||||
| 
 | ||||
|     - name: Upload reports | ||||
|       uses: actions/upload-artifact@v4 | ||||
|       with: | ||||
|         name: Unit Test Reports | ||||
|         path: build/reports | ||||
|       if: failure() | ||||
| 
 | ||||
|   instrumentation: | ||||
|     name: "Testing on API ${{ matrix.api-level }} for ${{ matrix.target }}" | ||||
|     needs: compile | ||||
|     # macOS provided hardware-accelerated emulator | ||||
|     # but now so does Linux | ||||
|     runs-on: ubuntu-latest | ||||
|     strategy: | ||||
|       fail-fast: false | ||||
|       matrix: | ||||
|         api-level: [ 15, 21, 24, 31 ] | ||||
|         target: [ default, google_apis, google_apis_playstore ] | ||||
|         exclude: | ||||
|           - api-level: 15 | ||||
|             target: google_apis_playstore | ||||
|           - api-level: 21 | ||||
|             target: google_apis_playstore | ||||
|           - api-level: 24 | ||||
|             target: google_apis | ||||
|           - api-level: 31 | ||||
|             target: google_apis | ||||
| 
 | ||||
|     steps: | ||||
|     - name: Checkout project | ||||
|       uses: actions/checkout@v4 | ||||
|       with: | ||||
|         fetch-depth: 0 | ||||
|         submodules: recursive | ||||
| 
 | ||||
|     - name: Enable KVM | ||||
|       run: | | ||||
|         echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules | ||||
|         sudo udevadm control --reload-rules | ||||
|         sudo udevadm trigger --name-match=kvm | ||||
| 
 | ||||
|     - name: Set up JDK 11 | ||||
|       uses: actions/setup-java@v4 | ||||
|       with: | ||||
|         java-version: '11' | ||||
|         distribution: 'adopt' | ||||
|         cache: gradle | ||||
| 
 | ||||
|     - name: Load build outputs | ||||
|       uses: actions/cache@v4 | ||||
|       with: | ||||
|         path: build | ||||
|         key: build-${{ github.sha }} | ||||
| 
 | ||||
|     - name: AVD cache | ||||
|       uses: actions/cache@v4 | ||||
|       id: avd-cache | ||||
|       with: | ||||
|         path: | | ||||
|           ~/.android/avd/* | ||||
|           ~/.android/adb* | ||||
|         key: avd-${{ matrix.api-level }}-${{ matrix.target }}-sd | ||||
| 
 | ||||
|     - name: Set up JRE 17 | ||||
|       uses: actions/setup-java@v4 | ||||
|       with: | ||||
|         java-version: '17' | ||||
|         distribution: 'temurin' | ||||
|         java-package: 'jre' | ||||
|         #cache: gradle | ||||
| 
 | ||||
|     - name: Create AVD and generate snapshot for caching | ||||
|       if: steps.avd-cache.outputs.cache-hit != 'true' | ||||
|       uses: reactivecircus/android-emulator-runner@v2 | ||||
|       with: | ||||
|         api-level: ${{ matrix.api-level }} | ||||
|         target: ${{ matrix.target }} | ||||
|         arch: ${{ matrix.api-level >= 30 && 'x86_64' || 'x86' }} | ||||
|         force-avd-creation: false | ||||
|         sdcard-path-or-size: '64M' | ||||
|         emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none | ||||
|         disable-animations: false | ||||
|         script: echo "Generated AVD snapshot for caching." | ||||
| 
 | ||||
|     - name: Run Instrumented Tests | ||||
|       uses: reactivecircus/android-emulator-runner@v2 | ||||
|       with: | ||||
|         api-level: ${{ matrix.api-level }} | ||||
|         target: ${{ matrix.target }} | ||||
|         arch: ${{ matrix.api-level >= 30 && 'x86_64' || 'x86' }} | ||||
|         profile: Nexus 6 | ||||
|         force-avd-creation: false | ||||
|         sdcard-path-or-size: '64M' | ||||
|         emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none | ||||
|         disable-animations: true | ||||
|         script: adb logcat -c && adb logcat -f /sdcard/logcat.txt & JAVA_HOME="${JAVA_HOME_11_X64}" ./gradlew connectedCheck --stacktrace || ( adb pull /sdcard/logcat.txt build/reports/; exit 1 ) | ||||
| 
 | ||||
|     - name: Upload reports | ||||
|       uses: actions/upload-artifact@v4 | ||||
|       with: | ||||
|         name: Instrument Test Reports API ${{ matrix.api-level }} ${{ matrix.target }} | ||||
|         path: build/reports | ||||
|       if: failure() | ||||
| 
 | ||||
|     - name: Save successful debug APK | ||||
|       uses: actions/upload-artifact@v4 | ||||
|       with: | ||||
|         name: Debug APK | ||||
|         path: build/outputs/apk/debug/aprsdroid-debug.apk | ||||
|       if: matrix.api-level == 31 && matrix.target == 'google_apis' | ||||
|  | @ -8,6 +8,8 @@ messages. | |||
| 
 | ||||
| APRSdroid is Open Source Software written in Scala and licensed under the GPLv2. | ||||
| 
 | ||||
| master: [](../../actions/workflows/android.yml) | ||||
| 
 | ||||
| Quick links: | ||||
| 
 | ||||
| - [Google Play](https://play.google.com/store/apps/details?id=org.aprsdroid.app) | ||||
|  |  | |||
|  | @ -0,0 +1,57 @@ | |||
| package org.aprsdroid.app; | ||||
| 
 | ||||
| import static org.hamcrest.MatcherAssert.assertThat; | ||||
| import static org.hamcrest.Matchers.closeTo; | ||||
| 
 | ||||
| import org.aprsdroid.app.testing.CoordinateMatcher; | ||||
| import org.junit.Test; | ||||
| 
 | ||||
| import scala.Tuple2; | ||||
| 
 | ||||
| public class CoordinateTest { | ||||
|     // Reference data generated from https://www.pgc.umn.edu/apps/convert/
 | ||||
|     private static final String providedNLatitude = "77° 15' 30\" N"; | ||||
|     private static final float expectedNLatitude = 77.258333f; | ||||
|     private static final String providedELongitude = "164° 45' 15\" E"; | ||||
|     private static final float expectedELongitude = 164.754167f; | ||||
|     private static final String providedSLatitude = "45° 30' 45\" S"; | ||||
|     private static final float expectedSLatitude = -45.5125f; | ||||
|     private static final String providedWLongitude = "97° 20' 40\" W"; | ||||
|     private static final float expectedWLongitude = -97.344444f; | ||||
| 
 | ||||
|     @Test | ||||
|     public void givenLocationInNEHemisphere_whenFormattedAsDMSString_thenParseBackIntoDecimalValue() { | ||||
|         Tuple2<String, String> actual = AprsPacket$.MODULE$.formatCoordinates(expectedNLatitude, expectedELongitude); | ||||
|         float floatLatitude = CoordinateMatcher.matchLatitude(actual._1); | ||||
|         float floatLongitude = CoordinateMatcher.matchLongitude(actual._2); | ||||
|         assertThat("Latitude", (double) floatLatitude, closeTo((double) expectedNLatitude, 1e-7)); | ||||
|         assertThat("Longitude", (double) floatLongitude, closeTo((double) expectedELongitude, 1e-7)); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void givenLocationInNWHemisphere_whenFormattedAsDMSString_thenParseBackIntoDecimalValue() { | ||||
|         Tuple2<String, String> actual = AprsPacket$.MODULE$.formatCoordinates(expectedNLatitude, expectedWLongitude); | ||||
|         float floatLatitude = CoordinateMatcher.matchLatitude(actual._1); | ||||
|         float floatLongitude = CoordinateMatcher.matchLongitude(actual._2); | ||||
|         assertThat("Latitude", (double) floatLatitude, closeTo((double) expectedNLatitude, 1e-7)); | ||||
|         assertThat("Longitude", (double) floatLongitude, closeTo((double) expectedWLongitude, 1e-7)); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void givenLocationInSEHemisphere_whenFormattedAsDMSString_thenParseBackIntoDecimalValue() { | ||||
|         Tuple2<String, String> actual = AprsPacket$.MODULE$.formatCoordinates(expectedSLatitude, expectedELongitude); | ||||
|         float floatLatitude = CoordinateMatcher.matchLatitude(actual._1); | ||||
|         float floatLongitude = CoordinateMatcher.matchLongitude(actual._2); | ||||
|         assertThat("Latitude", (double) floatLatitude, closeTo((double) expectedSLatitude, 1e-7)); | ||||
|         assertThat("Longitude", (double) floatLongitude, closeTo((double) expectedELongitude, 1e-7)); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void givenLocationInSWHemisphere_whenFormattedAsDMSString_thenParseBackIntoDecimalValue() { | ||||
|         Tuple2<String, String> actual = AprsPacket$.MODULE$.formatCoordinates(expectedSLatitude, expectedWLongitude); | ||||
|         float floatLatitude = CoordinateMatcher.matchLatitude(actual._1); | ||||
|         float floatLongitude = CoordinateMatcher.matchLongitude(actual._2); | ||||
|         assertThat("Latitude", (double) floatLatitude, closeTo((double) expectedSLatitude, 1e-7)); | ||||
|         assertThat("Longitude", (double) floatLongitude, closeTo((double) expectedWLongitude, 1e-7)); | ||||
|     } | ||||
| } | ||||
|  | @ -2,8 +2,8 @@ package org.aprsdroid.app; | |||
| 
 | ||||
| import android.content.Context; | ||||
| 
 | ||||
| import android.support.test.InstrumentationRegistry; | ||||
| import android.support.test.runner.AndroidJUnit4; | ||||
| import androidx.test.platform.app.InstrumentationRegistry; | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
|  |  | |||
|  | @ -0,0 +1,112 @@ | |||
| package org.aprsdroid.app; | ||||
| 
 | ||||
| import static androidx.test.espresso.Espresso.onView; | ||||
| import static androidx.test.espresso.action.ViewActions.click; | ||||
| import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard; | ||||
| import static androidx.test.espresso.action.ViewActions.typeText; | ||||
| import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist; | ||||
| import static androidx.test.espresso.assertion.ViewAssertions.matches; | ||||
| import static androidx.test.espresso.matcher.RootMatchers.isDialog; | ||||
| import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant; | ||||
| import static androidx.test.espresso.matcher.ViewMatchers.isRoot; | ||||
| import static androidx.test.espresso.matcher.ViewMatchers.withHint; | ||||
| import static androidx.test.espresso.matcher.ViewMatchers.withId; | ||||
| import static androidx.test.espresso.matcher.ViewMatchers.withText; | ||||
| import static org.hamcrest.core.AllOf.allOf; | ||||
| import static org.hamcrest.core.StringContains.containsString; | ||||
| 
 | ||||
| import android.content.SharedPreferences; | ||||
| 
 | ||||
| import androidx.test.ext.junit.rules.ActivityScenarioRule; | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||
| 
 | ||||
| import org.aprsdroid.app.testing.SharedPreferencesRule; | ||||
| import org.junit.Assert; | ||||
| import org.junit.Rule; | ||||
| import org.junit.Test; | ||||
| import org.junit.rules.RuleChain; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| public class FirstRunDialog { | ||||
|     private final String pref_callsign = "callsign"; | ||||
|     private final String pref_passcode = "passcode"; | ||||
|     public ActivityScenarioRule activityRule = new ActivityScenarioRule<>(LogActivity.class); | ||||
|     public SharedPreferencesRule prefsRule = new SharedPreferencesRule() { | ||||
|         @Override | ||||
|         protected void modifyPreferences(SharedPreferences preferences) { | ||||
|             preferences.edit().clear().commit(); | ||||
|         } | ||||
|     }; | ||||
|     @Rule | ||||
|     public RuleChain rules = RuleChain.outerRule(prefsRule).around(activityRule); | ||||
| 
 | ||||
|     @Test | ||||
|     public void givenAFirstTimeRun_whenProvidedABadPasscode_ThenDialogStaysOpen() { | ||||
|         onView(isRoot()) | ||||
|                 .inRoot(isDialog()) | ||||
|                 .check(matches(allOf( | ||||
|                         hasDescendant(withText(containsString("Welcome to APRSdroid"))), | ||||
|                         hasDescendant(withId(R.id.callsign)), | ||||
|                         hasDescendant(withId(R.id.passcode))))); | ||||
|         onView(withId(R.id.callsign)) | ||||
|                 .check(matches(withHint(containsString("Callsign")))) | ||||
|                 .perform(typeText("XA1AAA"), closeSoftKeyboard()); | ||||
|         onView(withId(R.id.passcode)) | ||||
|                 .check(matches(withHint(containsString("Passcode")))) | ||||
|                 .perform(typeText("12345"), closeSoftKeyboard()); | ||||
|         onView(withId(android.R.id.button1)).perform(click());  // OK Button
 | ||||
|         onView(isRoot()) | ||||
|                 .inRoot(isDialog()) | ||||
|                 .check(matches(allOf( | ||||
|                         hasDescendant(withText(containsString("Welcome to APRSdroid"))), | ||||
|                         hasDescendant(withId(R.id.callsign)), | ||||
|                         hasDescendant(withId(R.id.passcode))))); | ||||
|         try { | ||||
|             Thread.sleep(5000); | ||||
|         } catch (InterruptedException ex) { | ||||
|         } | ||||
|         Assert.assertTrue(true); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void givenAFirstTimeRun_whenProvidedAGoodPasscode_ThenDialogCloses() { | ||||
|         onView(isRoot()) | ||||
|                 .inRoot(isDialog()) | ||||
|                 .check(matches(allOf( | ||||
|                         hasDescendant(withText(containsString("Welcome to APRSdroid"))), | ||||
|                         hasDescendant(withId(R.id.callsign)), | ||||
|                         hasDescendant(withId(R.id.passcode))))); | ||||
|         onView(withId(R.id.callsign)) | ||||
|                 .check(matches(withHint(containsString("Callsign")))) | ||||
|                 .perform(typeText("XA1AAA"), closeSoftKeyboard()); | ||||
|         onView(withId(R.id.passcode)) | ||||
|                 .check(matches(withHint(containsString("Passcode")))) | ||||
|                 .perform(typeText("23459"), closeSoftKeyboard()); | ||||
|         onView(withId(android.R.id.button1)).perform(click());  // OK Button
 | ||||
|         onView(allOf( | ||||
|                 isRoot(), | ||||
|                 hasDescendant(withText(containsString("Welcome to APRSdroid"))), | ||||
|                 hasDescendant(withId(R.id.callsign)), | ||||
|                 hasDescendant(withId(R.id.passcode)))) | ||||
|                 .check(doesNotExist()); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void givenAFirstTimeRun_whenProvidedAGoodPasscode_ThenPrefsSaved() { | ||||
|         String expected_callsign = "XA1AAA"; | ||||
|         String expected_passcode = "23459"; | ||||
|         SharedPreferences prefs = prefsRule.getPreferences(); | ||||
|         Assert.assertNull("Callsign", prefs.getString(pref_callsign, null)); | ||||
|         Assert.assertNull("Passcode", prefs.getString(pref_passcode, null)); | ||||
|         onView(withId(R.id.callsign)) | ||||
|                 .check(matches(withHint(containsString("Callsign")))) | ||||
|                 .perform(typeText(expected_callsign), closeSoftKeyboard()); | ||||
|         onView(withId(R.id.passcode)) | ||||
|                 .check(matches(withHint(containsString("Passcode")))) | ||||
|                 .perform(typeText(expected_passcode), closeSoftKeyboard()); | ||||
|         onView(withId(android.R.id.button1)).perform(click());  // OK Button
 | ||||
|         Assert.assertEquals("Callsign", expected_callsign, prefs.getString(pref_callsign, null)); | ||||
|         Assert.assertEquals("Passcode", expected_passcode, prefs.getString(pref_passcode, null)); | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,464 @@ | |||
| package org.aprsdroid.app; | ||||
| 
 | ||||
| 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.isEnabled; | ||||
| import static androidx.test.espresso.matcher.ViewMatchers.withId; | ||||
| import static androidx.test.espresso.matcher.ViewMatchers.withText; | ||||
| import static org.hamcrest.MatcherAssert.assertThat; | ||||
| import static org.hamcrest.Matchers.closeTo; | ||||
| import static org.hamcrest.Matchers.equalTo; | ||||
| import static org.hamcrest.Matchers.not; | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.content.SharedPreferences; | ||||
| import android.util.Log; | ||||
| 
 | ||||
| import androidx.test.espresso.action.GeneralLocation; | ||||
| import androidx.test.espresso.action.GeneralSwipeAction; | ||||
| import androidx.test.espresso.action.Press; | ||||
| import androidx.test.espresso.action.Swipe; | ||||
| import androidx.test.ext.junit.rules.ActivityScenarioRule; | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||
| import androidx.test.filters.FlakyTest; | ||||
| import androidx.test.platform.app.InstrumentationRegistry; | ||||
| 
 | ||||
| import com.google.android.gms.common.ConnectionResult; | ||||
| import com.google.android.gms.common.GoogleApiAvailability; | ||||
| 
 | ||||
| import org.aprsdroid.app.testing.DMSLocationAssertion; | ||||
| import org.aprsdroid.app.testing.SharedPreferencesRule; | ||||
| import org.aprsdroid.app.testing.SpecificDMSLocationAssertion; | ||||
| import org.junit.Assume; | ||||
| import org.junit.Rule; | ||||
| import org.junit.Test; | ||||
| import org.junit.experimental.runners.Enclosed; | ||||
| import org.junit.rules.RuleChain; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| @RunWith(Enclosed.class) | ||||
| public class MapModeTest { | ||||
|     private static final String TAG = "APRSdroid-MapModeTest"; | ||||
|     private static final Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); | ||||
| 
 | ||||
|     private static final ActivityScenarioRule<GoogleMapAct> activityRule = | ||||
|             new ActivityScenarioRule<>(new Intent(appContext, GoogleMapAct.class) | ||||
|                     .putExtra("info", R.string.p_source_from_map_save)); | ||||
| 
 | ||||
|     @RunWith(AndroidJUnit4.class) | ||||
|     public static class GivenDefaultHomeLocation { | ||||
|         private final SharedPreferencesRule prefsRule = new SharedPreferencesRule() { | ||||
|             @Override | ||||
|             protected void modifyPreferences(SharedPreferences preferences) { | ||||
|                 preferences.edit().clear().commit(); | ||||
|             } | ||||
|         }; | ||||
| 
 | ||||
|         @Rule | ||||
|         public final RuleChain rules = RuleChain.outerRule(prefsRule).around(activityRule); | ||||
| 
 | ||||
|         @Test | ||||
|         public void whenFirstLoaded_thenSaveDisabled() { | ||||
|             onView(withId(R.id.info)) | ||||
|                     .check(matches(withText(""))); | ||||
|             onView(withId(R.id.accept)) | ||||
|                     .check(matches(not(isEnabled()))); | ||||
|         } | ||||
| 
 | ||||
|         @Test | ||||
|         public void whenMapIsDragged_thenPositionAndButtonShown() { | ||||
|             Assume.assumeThat("Google Play Services requires", | ||||
|                     GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(appContext), | ||||
|                     equalTo(ConnectionResult.SUCCESS)); | ||||
|             onView(withId(R.id.mapview)) | ||||
|                     .perform(new GeneralSwipeAction(Swipe.SLOW, GeneralLocation.CENTER, | ||||
|                             GeneralLocation.CENTER_LEFT, Press.THUMB)); | ||||
|             onView(withId(R.id.info)) | ||||
|                     .check(matches(not(withText("")))); | ||||
|             onView(withId(R.id.accept)) | ||||
|                     .check(matches(isEnabled())); | ||||
|         } | ||||
| 
 | ||||
|         @Test | ||||
|         public void whenMapIsDragged_thenPositionIsValidCoordinates() { | ||||
|             Assume.assumeThat("Google Play Services requires", | ||||
|                     GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(appContext), | ||||
|                     equalTo(ConnectionResult.SUCCESS)); | ||||
|             onView(withId(R.id.mapview)) | ||||
|                     .perform(new GeneralSwipeAction(Swipe.SLOW, GeneralLocation.CENTER, | ||||
|                             GeneralLocation.CENTER_LEFT, Press.THUMB)); | ||||
|             onView(withId(R.id.info)) | ||||
|                     .check(new DMSLocationAssertion()); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @RunWith(AndroidJUnit4.class) | ||||
|     public static class GivenSavedLocationInNEHemisphere { | ||||
|         private static final float expectedLatitude = 37.50123f; | ||||
|         private static final float expectedLongitude = 88.25034f; | ||||
|         private static final float expectedZoom = 4.0f; | ||||
| 
 | ||||
|         private final SharedPreferencesRule prefsRule = new SharedPreferencesRule() { | ||||
|             @Override | ||||
|             protected void modifyPreferences(SharedPreferences preferences) { | ||||
|                 preferences.edit() | ||||
|                         .putFloat("map_lat", expectedLatitude) | ||||
|                         .putFloat("map_lon", expectedLongitude) | ||||
|                         .putFloat("map_zoom", expectedZoom) | ||||
|                         .commit(); | ||||
|             } | ||||
|         }; | ||||
| 
 | ||||
|         @Rule | ||||
|         public final RuleChain rules = RuleChain.outerRule(prefsRule).around(activityRule); | ||||
| 
 | ||||
|         @Test | ||||
|         public void whenFirstLoaded_thenSaveDisabled() { | ||||
|             onView(withId(R.id.info)) | ||||
|                     .check(matches(withText(""))); | ||||
|             onView(withId(R.id.accept)) | ||||
|                     .check(matches(not(isEnabled()))); | ||||
|         } | ||||
| 
 | ||||
|         @Test | ||||
|         public void whenMapIsDragged_thenPositionAndButtonShown() { | ||||
|             Assume.assumeThat("Google Play Services requires", | ||||
|                     GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(appContext), | ||||
|                     equalTo(ConnectionResult.SUCCESS)); | ||||
|             onView(withId(R.id.mapview)) | ||||
|                     .perform(new GeneralSwipeAction(Swipe.SLOW, GeneralLocation.CENTER, | ||||
|                             GeneralLocation.CENTER_LEFT, Press.THUMB)); | ||||
|             onView(withId(R.id.info)) | ||||
|                     .check(matches(not(withText("")))); | ||||
|             onView(withId(R.id.accept)) | ||||
|                     .check(matches(isEnabled())); | ||||
|         } | ||||
| 
 | ||||
|         @Test | ||||
|         @FlakyTest | ||||
|         public void whenMapIsDraggedBackAndForth_thenPositionIsOriginalCoordinates() { | ||||
|             Assume.assumeThat("Google Play Services requires", | ||||
|                     GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(appContext), | ||||
|                     equalTo(ConnectionResult.SUCCESS)); | ||||
|             onView(withId(R.id.mapview)) | ||||
|                     .perform(new GeneralSwipeAction(Swipe.SLOW, GeneralLocation.CENTER, | ||||
|                             GeneralLocation.CENTER_LEFT, Press.THUMB)); | ||||
|             onView(withId(R.id.mapview)) | ||||
|                     .perform(new GeneralSwipeAction(Swipe.SLOW, GeneralLocation.CENTER_LEFT, | ||||
|                             GeneralLocation.CENTER, Press.THUMB)); | ||||
|             onView(withId(R.id.info)) | ||||
|                     .check(new SpecificDMSLocationAssertion(expectedLatitude, expectedLongitude)); | ||||
|         } | ||||
| 
 | ||||
|         @Test | ||||
|         @FlakyTest | ||||
|         public void whenMapIsDraggedBackAndForthAndSaved_thenPositionIsSavedCorrectly() { | ||||
|             Assume.assumeThat("Google Play Services requires", | ||||
|                     GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(appContext), | ||||
|                     equalTo(ConnectionResult.SUCCESS)); | ||||
|             try { | ||||
|                 Thread.sleep(500); | ||||
|             } catch (InterruptedException ex) { | ||||
|                 Log.w(TAG, "Sleep was interrupted: " + ex); | ||||
|             } | ||||
|             prefsRule.getPreferences() | ||||
|                     .edit() | ||||
|                     .remove("map_lat") | ||||
|                     .remove("map_lon") | ||||
|                     .remove("map_zoom") | ||||
|                     .commit(); | ||||
|             onView(withId(R.id.mapview)) | ||||
|                     .perform(new GeneralSwipeAction(Swipe.SLOW, GeneralLocation.CENTER, | ||||
|                             GeneralLocation.CENTER_LEFT, Press.THUMB)); | ||||
|             onView(withId(R.id.mapview)) | ||||
|                     .perform(new GeneralSwipeAction(Swipe.SLOW, GeneralLocation.CENTER_LEFT, | ||||
|                             GeneralLocation.CENTER, Press.THUMB)); | ||||
|             onView(withId(R.id.accept)) | ||||
|                     .perform(click()); | ||||
|             float actualLatitude = prefsRule.getPreferences().getFloat("map_lat", 0.0f); | ||||
|             float actualLongitude = prefsRule.getPreferences().getFloat("map_lon", 0.0f); | ||||
|             float actualZoom = prefsRule.getPreferences().getFloat("map_zoom", 0.0f); | ||||
|             assertThat("Latitude", (double) actualLatitude, closeTo(expectedLatitude, 5e-2)); | ||||
|             assertThat("Longitude", (double) actualLongitude, closeTo(expectedLongitude, 5e-2)); | ||||
|             assertThat("Zoom", (double) actualZoom, closeTo(expectedZoom, 1e-7)); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @RunWith(AndroidJUnit4.class) | ||||
|     public static class GivenSavedLocationInSEHemisphere { | ||||
|         private static final float expectedLatitude = -37.50123f; | ||||
|         private static final float expectedLongitude = 88.25034f; | ||||
|         private static final float expectedZoom = 4.5f; | ||||
| 
 | ||||
|         private final SharedPreferencesRule prefsRule = new SharedPreferencesRule() { | ||||
|             @Override | ||||
|             protected void modifyPreferences(SharedPreferences preferences) { | ||||
|                 preferences.edit() | ||||
|                         .putFloat("map_lat", expectedLatitude) | ||||
|                         .putFloat("map_lon", expectedLongitude) | ||||
|                         .putFloat("map_zoom", expectedZoom) | ||||
|                         .commit(); | ||||
|             } | ||||
|         }; | ||||
| 
 | ||||
|         @Rule | ||||
|         public final RuleChain rules = RuleChain.outerRule(prefsRule).around(activityRule); | ||||
| 
 | ||||
|         @Test | ||||
|         public void whenFirstLoaded_thenSaveDisabled() { | ||||
|             onView(withId(R.id.info)) | ||||
|                     .check(matches(withText(""))); | ||||
|             onView(withId(R.id.accept)) | ||||
|                     .check(matches(not(isEnabled()))); | ||||
|         } | ||||
| 
 | ||||
|         @Test | ||||
|         public void whenMapIsDragged_thenPositionAndButtonShown() { | ||||
|             Assume.assumeThat("Google Play Services requires", | ||||
|                     GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(appContext), | ||||
|                     equalTo(ConnectionResult.SUCCESS)); | ||||
|             onView(withId(R.id.mapview)) | ||||
|                     .perform(new GeneralSwipeAction(Swipe.SLOW, GeneralLocation.CENTER, | ||||
|                             GeneralLocation.CENTER_LEFT, Press.THUMB)); | ||||
|             onView(withId(R.id.info)) | ||||
|                     .check(matches(not(withText("")))); | ||||
|             onView(withId(R.id.accept)) | ||||
|                     .check(matches(isEnabled())); | ||||
|         } | ||||
| 
 | ||||
|         @Test | ||||
|         @FlakyTest | ||||
|         public void whenMapIsDraggedBackAndForth_thenPositionIsOriginalCoordinates() { | ||||
|             Assume.assumeThat("Google Play Services requires", | ||||
|                     GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(appContext), | ||||
|                     equalTo(ConnectionResult.SUCCESS)); | ||||
|             onView(withId(R.id.mapview)) | ||||
|                     .perform(new GeneralSwipeAction(Swipe.SLOW, GeneralLocation.CENTER, | ||||
|                             GeneralLocation.CENTER_LEFT, Press.THUMB)); | ||||
|             onView(withId(R.id.mapview)) | ||||
|                     .perform(new GeneralSwipeAction(Swipe.SLOW, GeneralLocation.CENTER_LEFT, | ||||
|                             GeneralLocation.CENTER, Press.THUMB)); | ||||
|             onView(withId(R.id.info)) | ||||
|                     .check(new SpecificDMSLocationAssertion(expectedLatitude, expectedLongitude)); | ||||
|         } | ||||
| 
 | ||||
|         @Test | ||||
|         @FlakyTest | ||||
|         public void whenMapIsDraggedBackAndForthAndSaved_thenPositionIsSavedCorrectly() { | ||||
|             Assume.assumeThat("Google Play Services requires", | ||||
|                     GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(appContext), | ||||
|                     equalTo(ConnectionResult.SUCCESS)); | ||||
|             try { | ||||
|                 Thread.sleep(500); | ||||
|             } catch (InterruptedException ex) { | ||||
|                 Log.w(TAG, "Sleep was interrupted: " + ex); | ||||
|             } | ||||
|             prefsRule.getPreferences() | ||||
|                     .edit() | ||||
|                     .remove("map_lat") | ||||
|                     .remove("map_lon") | ||||
|                     .remove("map_zoom") | ||||
|                     .commit(); | ||||
|             onView(withId(R.id.mapview)) | ||||
|                     .perform(new GeneralSwipeAction(Swipe.SLOW, GeneralLocation.CENTER, | ||||
|                             GeneralLocation.CENTER_LEFT, Press.THUMB)); | ||||
|             onView(withId(R.id.mapview)) | ||||
|                     .perform(new GeneralSwipeAction(Swipe.SLOW, GeneralLocation.CENTER_LEFT, | ||||
|                             GeneralLocation.CENTER, Press.THUMB)); | ||||
|             onView(withId(R.id.accept)) | ||||
|                     .perform(click()); | ||||
|             float actualLatitude = prefsRule.getPreferences().getFloat("map_lat", 0.0f); | ||||
|             float actualLongitude = prefsRule.getPreferences().getFloat("map_lon", 0.0f); | ||||
|             float actualZoom = prefsRule.getPreferences().getFloat("map_zoom", 0.0f); | ||||
|             assertThat("Latitude", (double) actualLatitude, closeTo(expectedLatitude, 5e-2)); | ||||
|             assertThat("Longitude", (double) actualLongitude, closeTo(expectedLongitude, 5e-2)); | ||||
|             assertThat("Zoom", (double) actualZoom, closeTo(expectedZoom, 1e-7)); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @RunWith(AndroidJUnit4.class) | ||||
|     public static class GivenSavedLocationInNWHemisphere { | ||||
|         private static final float expectedLatitude = 37.50123f; | ||||
|         private static final float expectedLongitude = -88.25034f; | ||||
|         private static final float expectedZoom = 5.0f; | ||||
| 
 | ||||
|         private final SharedPreferencesRule prefsRule = new SharedPreferencesRule() { | ||||
|             @Override | ||||
|             protected void modifyPreferences(SharedPreferences preferences) { | ||||
|                 preferences.edit() | ||||
|                         .putFloat("map_lat", expectedLatitude) | ||||
|                         .putFloat("map_lon", expectedLongitude) | ||||
|                         .putFloat("map_zoom", expectedZoom) | ||||
|                         .commit(); | ||||
|             } | ||||
|         }; | ||||
| 
 | ||||
|         @Rule | ||||
|         public final RuleChain rules = RuleChain.outerRule(prefsRule).around(activityRule); | ||||
| 
 | ||||
|         @Test | ||||
|         public void whenFirstLoaded_thenSaveDisabled() { | ||||
|             onView(withId(R.id.info)) | ||||
|                     .check(matches(withText(""))); | ||||
|             onView(withId(R.id.accept)) | ||||
|                     .check(matches(not(isEnabled()))); | ||||
|         } | ||||
| 
 | ||||
|         @Test | ||||
|         public void whenMapIsDragged_thenPositionAndButtonShown() { | ||||
|             Assume.assumeThat("Google Play Services requires", | ||||
|                     GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(appContext), | ||||
|                     equalTo(ConnectionResult.SUCCESS)); | ||||
|             onView(withId(R.id.mapview)) | ||||
|                     .perform(new GeneralSwipeAction(Swipe.SLOW, GeneralLocation.CENTER, | ||||
|                             GeneralLocation.CENTER_LEFT, Press.THUMB)); | ||||
|             onView(withId(R.id.info)) | ||||
|                     .check(matches(not(withText("")))); | ||||
|             onView(withId(R.id.accept)) | ||||
|                     .check(matches(isEnabled())); | ||||
|         } | ||||
| 
 | ||||
|         @Test | ||||
|         @FlakyTest | ||||
|         public void whenMapIsDraggedBackAndForth_thenPositionIsOriginalCoordinates() { | ||||
|             Assume.assumeThat("Google Play Services requires", | ||||
|                     GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(appContext), | ||||
|                     equalTo(ConnectionResult.SUCCESS)); | ||||
|             onView(withId(R.id.mapview)) | ||||
|                     .perform(new GeneralSwipeAction(Swipe.SLOW, GeneralLocation.CENTER, | ||||
|                             GeneralLocation.CENTER_LEFT, Press.THUMB)); | ||||
|             onView(withId(R.id.mapview)) | ||||
|                     .perform(new GeneralSwipeAction(Swipe.SLOW, GeneralLocation.CENTER_LEFT, | ||||
|                             GeneralLocation.CENTER, Press.THUMB)); | ||||
|             onView(withId(R.id.info)) | ||||
|                     .check(new SpecificDMSLocationAssertion(expectedLatitude, expectedLongitude)); | ||||
|         } | ||||
| 
 | ||||
|         @Test | ||||
|         @FlakyTest | ||||
|         public void whenMapIsDraggedBackAndForthAndSaved_thenPositionIsSavedCorrectly() { | ||||
|             Assume.assumeThat("Google Play Services requires", | ||||
|                     GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(appContext), | ||||
|                     equalTo(ConnectionResult.SUCCESS)); | ||||
|             try { | ||||
|                 Thread.sleep(500); | ||||
|             } catch (InterruptedException ex) { | ||||
|                 Log.w(TAG, "Sleep was interrupted: " + ex); | ||||
|             } | ||||
|             prefsRule.getPreferences() | ||||
|                     .edit() | ||||
|                     .remove("map_lat") | ||||
|                     .remove("map_lon") | ||||
|                     .remove("map_zoom") | ||||
|                     .commit(); | ||||
|             onView(withId(R.id.mapview)) | ||||
|                     .perform(new GeneralSwipeAction(Swipe.SLOW, GeneralLocation.CENTER, | ||||
|                             GeneralLocation.CENTER_LEFT, Press.THUMB)); | ||||
|             onView(withId(R.id.mapview)) | ||||
|                     .perform(new GeneralSwipeAction(Swipe.SLOW, GeneralLocation.CENTER_LEFT, | ||||
|                             GeneralLocation.CENTER, Press.THUMB)); | ||||
|             onView(withId(R.id.accept)) | ||||
|                     .perform(click()); | ||||
|             float actualLatitude = prefsRule.getPreferences().getFloat("map_lat", 0.0f); | ||||
|             float actualLongitude = prefsRule.getPreferences().getFloat("map_lon", 0.0f); | ||||
|             float actualZoom = prefsRule.getPreferences().getFloat("map_zoom", 0.0f); | ||||
|             assertThat("Latitude", (double) actualLatitude, closeTo(expectedLatitude, 5e-2)); | ||||
|             assertThat("Longitude", (double) actualLongitude, closeTo(expectedLongitude, 5e-2)); | ||||
|             assertThat("Zoom", (double) actualZoom, closeTo(expectedZoom, 1e-7)); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @RunWith(AndroidJUnit4.class) | ||||
|     public static class GivenSavedLocationInSWHemisphere { | ||||
|         private static final float expectedLatitude = -37.50123f; | ||||
|         private static final float expectedLongitude = -88.25034f; | ||||
|         private static final float expectedZoom = 5.5f; | ||||
| 
 | ||||
|         private final SharedPreferencesRule prefsRule = new SharedPreferencesRule() { | ||||
|             @Override | ||||
|             protected void modifyPreferences(SharedPreferences preferences) { | ||||
|                 preferences.edit() | ||||
|                         .putFloat("map_lat", expectedLatitude) | ||||
|                         .putFloat("map_lon", expectedLongitude) | ||||
|                         .putFloat("map_zoom", expectedZoom) | ||||
|                         .commit(); | ||||
|             } | ||||
|         }; | ||||
| 
 | ||||
|         @Rule | ||||
|         public final RuleChain rules = RuleChain.outerRule(prefsRule).around(activityRule); | ||||
| 
 | ||||
|         @Test | ||||
|         public void whenFirstLoaded_thenSaveDisabled() { | ||||
|             onView(withId(R.id.info)) | ||||
|                     .check(matches(withText(""))); | ||||
|             onView(withId(R.id.accept)) | ||||
|                     .check(matches(not(isEnabled()))); | ||||
|         } | ||||
| 
 | ||||
|         @Test | ||||
|         public void whenMapIsDragged_thenPositionAndButtonShown() { | ||||
|             Assume.assumeThat("Google Play Services requires", | ||||
|                     GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(appContext), | ||||
|                     equalTo(ConnectionResult.SUCCESS)); | ||||
|             onView(withId(R.id.mapview)) | ||||
|                     .perform(new GeneralSwipeAction(Swipe.SLOW, GeneralLocation.CENTER, | ||||
|                             GeneralLocation.CENTER_LEFT, Press.THUMB)); | ||||
|             onView(withId(R.id.info)) | ||||
|                     .check(matches(not(withText("")))); | ||||
|             onView(withId(R.id.accept)) | ||||
|                     .check(matches(isEnabled())); | ||||
|         } | ||||
| 
 | ||||
|         @Test | ||||
|         @FlakyTest | ||||
|         public void whenMapIsDraggedBackAndForth_thenPositionIsOriginalCoordinates() { | ||||
|             Assume.assumeThat("Google Play Services requires", | ||||
|                     GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(appContext), | ||||
|                     equalTo(ConnectionResult.SUCCESS)); | ||||
|             onView(withId(R.id.mapview)) | ||||
|                     .perform(new GeneralSwipeAction(Swipe.SLOW, GeneralLocation.CENTER, | ||||
|                             GeneralLocation.CENTER_LEFT, Press.THUMB)); | ||||
|             onView(withId(R.id.mapview)) | ||||
|                     .perform(new GeneralSwipeAction(Swipe.SLOW, GeneralLocation.CENTER_LEFT, | ||||
|                             GeneralLocation.CENTER, Press.THUMB)); | ||||
|             onView(withId(R.id.info)) | ||||
|                     .check(new SpecificDMSLocationAssertion(expectedLatitude, expectedLongitude)); | ||||
|         } | ||||
| 
 | ||||
|         @Test | ||||
|         @FlakyTest | ||||
|         public void whenMapIsDraggedBackAndForthAndSaved_thenPositionIsSavedCorrectly() { | ||||
|             Assume.assumeThat("Google Play Services requires", | ||||
|                     GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(appContext), | ||||
|                     equalTo(ConnectionResult.SUCCESS)); | ||||
|             try { | ||||
|                 Thread.sleep(500); | ||||
|             } catch (InterruptedException ex) { | ||||
|                 Log.w(TAG, "Sleep was interrupted: " + ex); | ||||
|             } | ||||
|             prefsRule.getPreferences() | ||||
|                     .edit() | ||||
|                     .remove("map_lat") | ||||
|                     .remove("map_lon") | ||||
|                     .remove("map_zoom") | ||||
|                     .commit(); | ||||
|             onView(withId(R.id.mapview)) | ||||
|                     .perform(new GeneralSwipeAction(Swipe.SLOW, GeneralLocation.CENTER, | ||||
|                             GeneralLocation.CENTER_LEFT, Press.THUMB)); | ||||
|             onView(withId(R.id.mapview)) | ||||
|                     .perform(new GeneralSwipeAction(Swipe.SLOW, GeneralLocation.CENTER_LEFT, | ||||
|                             GeneralLocation.CENTER, Press.THUMB)); | ||||
|             onView(withId(R.id.accept)) | ||||
|                     .perform(click()); | ||||
|             float actualLatitude = prefsRule.getPreferences().getFloat("map_lat", 0.0f); | ||||
|             float actualLongitude = prefsRule.getPreferences().getFloat("map_lon", 0.0f); | ||||
|             float actualZoom = prefsRule.getPreferences().getFloat("map_zoom", 0.0f); | ||||
|             assertThat("Latitude", (double) actualLatitude, closeTo(expectedLatitude, 5e-2)); | ||||
|             assertThat("Longitude", (double) actualLongitude, closeTo(expectedLongitude, 5e-2)); | ||||
|             assertThat("Zoom", (double) actualZoom, closeTo(expectedZoom, 1e-7)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,27 @@ | |||
| package org.aprsdroid.app.testing; | ||||
| 
 | ||||
| import static org.hamcrest.Matchers.instanceOf; | ||||
| 
 | ||||
| import android.view.View; | ||||
| import android.widget.TextView; | ||||
| 
 | ||||
| import androidx.test.espresso.NoMatchingViewException; | ||||
| import androidx.test.espresso.ViewAssertion; | ||||
| 
 | ||||
| import org.junit.Assert; | ||||
| 
 | ||||
| public class DMSLocationAssertion implements ViewAssertion { | ||||
|     protected void checkCoordinates(float latitude, float longitude) { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void check(View view, NoMatchingViewException noViewFoundException) { | ||||
|         if (view == null) | ||||
|             throw noViewFoundException; | ||||
|         Assert.assertThat(view, instanceOf(TextView.class)); | ||||
|         TextView text = (TextView) view; | ||||
|         float latitude = CoordinateMatcher.matchLatitude(text.getText()); | ||||
|         float longitude = CoordinateMatcher.matchLongitude(text.getText()); | ||||
|         checkCoordinates(latitude, longitude); | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,29 @@ | |||
| package org.aprsdroid.app.testing; | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.content.SharedPreferences; | ||||
| import android.preference.PreferenceManager; | ||||
| 
 | ||||
| import androidx.test.platform.app.InstrumentationRegistry; | ||||
| 
 | ||||
| import org.junit.rules.TestRule; | ||||
| import org.junit.runner.Description; | ||||
| import org.junit.runners.model.Statement; | ||||
| 
 | ||||
| public abstract class SharedPreferencesRule implements TestRule { | ||||
|     private SharedPreferences preferences; | ||||
| 
 | ||||
|     protected abstract void modifyPreferences(SharedPreferences preferences); | ||||
| 
 | ||||
|     public SharedPreferences getPreferences() { | ||||
|         return preferences; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Statement apply(Statement base, Description description) { | ||||
|         Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); | ||||
|         preferences = PreferenceManager.getDefaultSharedPreferences(appContext); | ||||
|         modifyPreferences(preferences); | ||||
|         return base; | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,22 @@ | |||
| package org.aprsdroid.app.testing; | ||||
| 
 | ||||
| import static org.hamcrest.Matchers.closeTo; | ||||
| 
 | ||||
| import org.junit.Assert; | ||||
| 
 | ||||
| public class SpecificDMSLocationAssertion extends DMSLocationAssertion { | ||||
|     private final float expectedLatitude; | ||||
|     private final float expectedLongitude; | ||||
| 
 | ||||
|     public SpecificDMSLocationAssertion(float myExpectedLatitude, float myExpectedLongitude) { | ||||
|         expectedLatitude = myExpectedLatitude; | ||||
|         expectedLongitude = myExpectedLongitude; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void checkCoordinates(float latitude, float longitude) { | ||||
|         super.checkCoordinates(latitude, longitude); | ||||
|         Assert.assertThat("Latitude", (double) latitude, closeTo((double) expectedLatitude, 0.05)); | ||||
|         Assert.assertThat("Longitude", (double) longitude, closeTo((double) expectedLongitude, 0.05)); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										19
									
								
								build.gradle
								
								
								
								
							
							
						
						
									
										19
									
								
								build.gradle
								
								
								
								
							|  | @ -6,7 +6,7 @@ buildscript { | |||
| 		google() | ||||
| 	} | ||||
| 	dependencies { | ||||
| 		classpath 'com.android.tools.build:gradle:3.5.1' | ||||
| 		classpath 'com.android.tools.build:gradle:3.5.4' | ||||
| 		// NOTE: Do not place your application dependencies here; they belong | ||||
| 		// in the individual module build.gradle files | ||||
| 
 | ||||
|  | @ -81,7 +81,7 @@ android { | |||
| 
 | ||||
| 		resValue "string", "google_maps_key", mapsApiKey() | ||||
| 
 | ||||
| 		testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" | ||||
| 		testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' | ||||
| 	} | ||||
| 	useLibrary 'org.apache.http.legacy' | ||||
| 	compileOptions { | ||||
|  | @ -131,10 +131,10 @@ android { | |||
| 			jniLibs.srcDirs = ['libs'] | ||||
| 		} | ||||
| 		androidTest { | ||||
| 			java.srcDirs = ['androidTest/java'] | ||||
| 			java.srcDirs = ['androidTest/java', 'sharedTest/java'] | ||||
| 		} | ||||
| 		test { | ||||
| 			java.srcDirs = ['test/java'] | ||||
| 			java.srcDirs = ['test/java', 'sharedTest/java'] | ||||
| 		} | ||||
| 	} | ||||
| 	lintOptions { | ||||
|  | @ -158,7 +158,12 @@ dependencies { | |||
| 
 | ||||
| 
 | ||||
| 	implementation 'com.squareup.okio:okio:2.1.0' | ||||
| 	androidTestImplementation 'com.android.support.test:runner:1.0.2' | ||||
| 	androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' | ||||
| 	testImplementation 'junit:junit:4.12' | ||||
| 
 | ||||
| 	testImplementation 'junit:junit:4.13.1' | ||||
| 	testImplementation 'org.hamcrest:hamcrest-core:1.3' | ||||
| 	testImplementation 'org.hamcrest:hamcrest-library:1.3' | ||||
| 	androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' | ||||
| 	androidTestImplementation 'androidx.test:runner:1.4.0' | ||||
| 	androidTestImplementation 'androidx.test:rules:1.4.0' | ||||
| 	androidTestImplementation 'androidx.test.ext:junit:1.1.3' | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,57 @@ | |||
| package org.aprsdroid.app.testing; | ||||
| 
 | ||||
| import static org.hamcrest.Matchers.greaterThanOrEqualTo; | ||||
| 
 | ||||
| import org.junit.Assert; | ||||
| 
 | ||||
| import java.util.Objects; | ||||
| import java.util.regex.Matcher; | ||||
| import java.util.regex.Pattern; | ||||
| 
 | ||||
| public class CoordinateMatcher { | ||||
|     private static final String NUMBER = "(-?\\d+(?:\\.\\d*)?)"; | ||||
|     private static final String dms_latitude_pattern = NUMBER + "°\\s*" + NUMBER + "'\\s*" + NUMBER + "\"\\s*([NS])"; | ||||
|     private static final String dms_longitude_pattern = NUMBER + "°\\s*" + NUMBER + "'\\s*" + NUMBER + "\"\\s*([EW])"; | ||||
|     private static final Pattern dms_latitude_regex = Pattern.compile(dms_latitude_pattern, Pattern.CASE_INSENSITIVE); | ||||
|     private static final Pattern dms_longitude_regex = Pattern.compile(dms_longitude_pattern, Pattern.CASE_INSENSITIVE); | ||||
| 
 | ||||
|     private static float convertField(CharSequence string, Pattern regex, String name) { | ||||
|         Matcher matcher = regex.matcher(string); | ||||
|         Assert.assertTrue(name + " not found", matcher.find()); | ||||
|         float value = 0; | ||||
|         try { | ||||
|             int degrees = Integer.parseInt(Objects.requireNonNull(matcher.group(1))); | ||||
|             Assert.assertThat(name + " degrees", degrees, greaterThanOrEqualTo(0)); | ||||
|             value = (float) degrees; | ||||
|         } catch (NumberFormatException ex) { | ||||
|             Assert.fail(name + " degree field not an integer"); | ||||
|         } | ||||
|         try { | ||||
|             int minutes = Integer.parseInt(Objects.requireNonNull(matcher.group(2))); | ||||
|             Assert.assertThat(name + " minutes", minutes, greaterThanOrEqualTo(0)); | ||||
|             value += (float) minutes / 60.0f; | ||||
|         } catch (NumberFormatException ex) { | ||||
|             Assert.fail(name + " minute field not an integer"); | ||||
|         } | ||||
|         try { | ||||
|             float seconds = Float.parseFloat(Objects.requireNonNull(matcher.group(3))); | ||||
|             Assert.assertThat(name + " seconds", seconds, greaterThanOrEqualTo(0.0f)); | ||||
|             value += seconds / 3600.0f; | ||||
|         } catch (NumberFormatException ex) { | ||||
|             Assert.fail(name + " seconds field not an number"); | ||||
|         } | ||||
|         String direction = Objects.requireNonNull(matcher.group(4)); | ||||
|         if (direction.equalsIgnoreCase("S") || direction.equalsIgnoreCase("W")) { | ||||
|             value *= -1.0f; | ||||
|         } | ||||
|         return value; | ||||
|     } | ||||
| 
 | ||||
|     public static float matchLatitude(CharSequence string) { | ||||
|         return convertField(string, dms_latitude_regex, "Latitude"); | ||||
|     } | ||||
| 
 | ||||
|     public static float matchLongitude(CharSequence string) { | ||||
|         return convertField(string, dms_longitude_regex, "Longitude"); | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,43 @@ | |||
| package org.aprsdroid.app; | ||||
| 
 | ||||
| import static org.hamcrest.MatcherAssert.assertThat; | ||||
| import static org.hamcrest.Matchers.closeTo; | ||||
| 
 | ||||
| import org.aprsdroid.app.testing.CoordinateMatcher; | ||||
| import org.junit.Test; | ||||
| 
 | ||||
| public class CoordinateTest { | ||||
|     // Reference data generated from https://www.pgc.umn.edu/apps/convert/
 | ||||
|     private static final String providedNLatitude = "77° 15' 30\" N"; | ||||
|     private static final float expectedNLatitude = 77.258333f; | ||||
|     private static final String providedELongitude = "164° 45' 15\" E"; | ||||
|     private static final float expectedELongitude = 164.754167f; | ||||
|     private static final String providedSLatitude = "45° 30' 45\" S"; | ||||
|     private static final float expectedSLatitude = -45.5125f; | ||||
|     private static final String providedWLongitude = "97° 20' 40\" W"; | ||||
|     private static final float expectedWLongitude = -97.344444f; | ||||
| 
 | ||||
|     @Test | ||||
|     public void givenDMSLatitudeInN_whenParsingString_ThenShouldMatchDecimal() { | ||||
|         float value = CoordinateMatcher.matchLatitude(providedNLatitude); | ||||
|         assertThat("Latitude", (double) value, closeTo((double) expectedNLatitude, 1e-7)); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void givenDMSLongitudeInE_whenParsingString_ThenShouldMatchDecimal() { | ||||
|         float value = CoordinateMatcher.matchLongitude(providedELongitude); | ||||
|         assertThat("Longitude", (double) value, closeTo((double) expectedELongitude, 1e-7)); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void givenDMSLatitudeInS_whenParsingString_ThenShouldMatchDecimal() { | ||||
|         float value = CoordinateMatcher.matchLatitude(providedSLatitude); | ||||
|         assertThat("Latitude", (double) value, closeTo((double) expectedSLatitude, 1e-7)); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void givenDMSLongitudeInW_whenParsingString_ThenShouldMatchDecimal() { | ||||
|         float value = CoordinateMatcher.matchLongitude(providedWLongitude); | ||||
|         assertThat("Longitude", (double) value, closeTo((double) expectedWLongitude, 1e-7)); | ||||
|     } | ||||
| } | ||||
		Ładowanie…
	
		Reference in New Issue
	
	 Loren M. Lang
						Loren M. Lang