diff --git a/app/build.gradle b/app/build.gradle index 5a189a2b..69ab2ea2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -23,6 +23,11 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } + packagingOptions { + exclude 'META-INF/LICENSE.md' + exclude 'META-INF/LICENSE-notice.md' + } + buildTypes { release { minifyEnabled false @@ -54,69 +59,54 @@ android { } dependencies { - + androidTestImplementation 'androidx.arch.core:core-testing:2.2.0' + debugImplementation "androidx.fragment:fragment-testing:1.5.6" implementation 'androidx.core:core-ktx:1.10.0' implementation 'androidx.appcompat:appcompat:1.6.1' - implementation 'com.google.android.material:material:1.8.0' - implementation 'com.google.android.gms:play-services-maps:18.1.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' - implementation 'androidx.navigation:navigation-fragment-ktx:2.5.3' - implementation 'androidx.navigation:navigation-ui-ktx:2.5.3' - implementation 'androidx.legacy:legacy-support-v4:1.0.0' - implementation 'androidx.recyclerview:recyclerview:1.3.0' - implementation "androidx.fragment:fragment-ktx:1.5.6" - implementation 'androidx.test.espresso:espresso-contrib:3.5.1' - implementation platform('com.google.firebase:firebase-bom:31.2.3') // Firebase BOM - implementation 'com.google.firebase:firebase-auth-ktx' - implementation 'com.google.firebase:firebase-database-ktx' - - implementation 'com.google.firebase:firebase-database-ktx:20.2.0' - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4' - - implementation 'com.github.bumptech.glide:glide:4.15.1' - kapt 'com.github.bumptech.glide:compiler:4.15.1' - implementation 'androidx.fragment:fragment-ktx:1.5.6' + implementation 'androidx.lifecycle:lifecycle-common-java8:2.6.1' + implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.1' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1' + implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.navigation:navigation-dynamic-features-fragment:2.5.3' implementation 'androidx.navigation:navigation-fragment-ktx:2.5.3' implementation 'androidx.navigation:navigation-ui-ktx:2.5.3' - implementation 'androidx.legacy:legacy-support-v4:1.0.0' - implementation 'androidx.constraintlayout:constraintlayout:2.1.4' - implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.1' - implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1' implementation 'androidx.recyclerview:recyclerview:1.3.0' implementation 'androidx.test.espresso:espresso-contrib:3.5.1' - implementation 'com.google.android.gms:play-services-auth:20.5.0' implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01' + implementation 'com.github.bumptech.glide:glide:4.15.1' + kapt 'com.github.bumptech.glide:compiler:4.15.1' + implementation 'com.google.android.gms:play-services-auth:20.5.0' implementation 'com.google.android.gms:play-services-location:21.0.1' - - testImplementation 'org.junit.jupiter:junit-jupiter:5.8.1' - testImplementation 'org.mockito:mockito-core:3.12.4' - testImplementation 'org.mockito:mockito-inline:2.13.0' - testImplementation 'io.mockk:mockk:1.12.0' - androidTestImplementation 'io.mockk:mockk-android:1.12.0' - - androidTestImplementation 'androidx.arch.core:core-testing:2.2.0' + implementation 'com.google.android.gms:play-services-maps:18.1.0' + implementation 'com.google.android.material:material:1.8.0' + implementation 'com.google.code.gson:gson:2.10.1' + implementation 'com.google.firebase:firebase-auth-ktx' + implementation 'com.google.firebase:firebase-database-ktx:20.2.0' implementation 'com.google.firebase:firebase-storage-ktx:20.1.0' - implementation "androidx.lifecycle:lifecycle-common-java8:2.6.1" + implementation platform('com.google.firebase:firebase-bom:31.2.3') // Firebase BOM + + testImplementation 'io.mockk:mockk:1.13.5' + androidTestImplementation 'io.mockk:mockk-android:1.13.5' testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.1.5' + testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2' + testImplementation 'org.mockito:mockito-core:5.3.0' + testImplementation 'org.mockito:mockito-inline:5.2.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4' + testImplementation "org.jetbrains.kotlin:kotlin-test:1.8.20" androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' - androidTestImplementation 'androidx.test:runner:1.5.2' + androidTestImplementation 'androidx.test.espresso:espresso-intents:3.5.1' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + debugImplementation "androidx.test:monitor:1.6.1" androidTestImplementation 'androidx.test:rules:1.5.0' + androidTestImplementation 'androidx.test:runner:1.5.2' androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.3.0-alpha02' - androidTestImplementation 'androidx.test.espresso:espresso-intents:3.5.1' androidTestImplementation 'androidx.navigation:navigation-testing:2.5.3' - androidTestImplementation 'androidx.arch.core:core-testing:2.2.0' - - debugImplementation "androidx.test:monitor:1.6.1" - debugImplementation "androidx.fragment:fragment-testing:1.5.6" - - implementation 'com.google.code.gson:gson:2.9.0' - testImplementation "org.jetbrains.kotlin:kotlin-test:1.6.10" } + tasks.withType(Test) { jacoco.includeNoLocationClasses = true jacoco.excludes = ['jdk.internal.*', '**/databinding/*'] @@ -131,12 +121,12 @@ task jacocoTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest', 'crea def fileFilter = [ - '**/R.class', - '**/R$*.class', - '**/BuildConfig.*', - '**/Manifest*.*', - '**/*Test*.*', - 'android/**/*.*', + '**/R.class', + '**/R$*.class', + '**/BuildConfig.*', + '**/Manifest*.*', + '**/*Test*.*', + 'android/**/*.*', ] def debugTree = fileTree(dir: "$project.buildDir/tmp/kotlin-classes/debug", excludes: fileFilter) def mainSrc = "$project.projectDir/src/main/java" diff --git a/app/src/androidTest/java/com/github/factotum_sdp/factotum/MainActivityTest.kt b/app/src/androidTest/java/com/github/factotum_sdp/factotum/MainActivityTest.kt index ad1e75e8..e35e9b18 100644 --- a/app/src/androidTest/java/com/github/factotum_sdp/factotum/MainActivityTest.kt +++ b/app/src/androidTest/java/com/github/factotum_sdp/factotum/MainActivityTest.kt @@ -16,16 +16,15 @@ import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import androidx.test.uiautomator.UiDevice -import com.github.factotum_sdp.factotum.placeholder.ContactsList import com.github.factotum_sdp.factotum.placeholder.UsersPlaceHolder import com.github.factotum_sdp.factotum.ui.login.LoginFragmentTest +import com.github.factotum_sdp.factotum.utils.ContactsUtils +import com.github.factotum_sdp.factotum.utils.GeneralUtils.Companion.getAuth +import com.github.factotum_sdp.factotum.utils.GeneralUtils.Companion.getDatabase +import com.github.factotum_sdp.factotum.utils.GeneralUtils.Companion.initFirebase import com.google.firebase.auth.FirebaseAuth -import com.google.firebase.auth.ktx.auth -import com.google.firebase.database.ktx.database -import com.google.firebase.ktx.Firebase import kotlinx.coroutines.runBlocking import org.hamcrest.Matchers -import org.junit.AfterClass import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.BeforeClass @@ -48,18 +47,13 @@ class MainActivityTest { @BeforeClass @JvmStatic fun setUpDatabase() { - val database = Firebase.database - val auth = Firebase.auth - database.useEmulator("10.0.2.2", 9000) - auth.useEmulator("10.0.2.2", 9099) - MainActivity.setDatabase(database) - MainActivity.setAuth(auth) - ContactsList.init(database) + initFirebase() + runBlocking { - ContactsList.populateDatabase() + ContactsUtils.populateDatabase() } - UsersPlaceHolder.init(database, auth) + UsersPlaceHolder.init(getDatabase(), getAuth()) runBlocking { UsersPlaceHolder.addUserToDb(UsersPlaceHolder.USER_BOSS) } @@ -79,14 +73,14 @@ class MainActivityTest { UsersPlaceHolder.addAuthUser(UsersPlaceHolder.USER_CLIENT) } } - + /** @AfterClass @JvmStatic fun stopAuthEmulator() { - val auth = Firebase.auth - auth.signOut() - MainActivity.setAuth(auth) - } + val auth = getAuth() + auth.signOut() + MainActivity.setAuth(auth) + } **/ } //======================================================================================== diff --git a/app/src/androidTest/java/com/github/factotum_sdp/factotum/data/localisation/LocationTest.kt b/app/src/androidTest/java/com/github/factotum_sdp/factotum/data/localisation/LocationTest.kt index b4556de8..04e03d9a 100644 --- a/app/src/androidTest/java/com/github/factotum_sdp/factotum/data/localisation/LocationTest.kt +++ b/app/src/androidTest/java/com/github/factotum_sdp/factotum/data/localisation/LocationTest.kt @@ -16,15 +16,15 @@ import java.util.concurrent.CountDownLatch @RunWith(AndroidJUnit4::class) class LocationTest { @Test - fun rightLocationCreates(){ + fun rightLocationCreates() { val addressName = "Route Cantonale 15, 1015 Lausanne" val location = Location(addressName, getApplicationContext()) - val geocoder = Geocoder(getApplicationContext()) - var result : Address? = null + val geocoder = Geocoder(getApplicationContext()) + var result: Address? = null val latch = CountDownLatch(1) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { geocoder.getFromLocationName(addressName, 1) { addresses -> - result = if(addresses.size != 0) addresses[0] else null + result = if (addresses.size != 0) addresses[0] else null latch.countDown() } latch.await() @@ -37,7 +37,7 @@ class LocationTest { } @Test - fun wrongLocationCreatesNull(){ + fun wrongLocationCreatesNull() { val addressName = "dfjsdk" val location = Location(addressName, getApplicationContext()) assertEquals(location.address, null) @@ -45,9 +45,9 @@ class LocationTest { } @Test - fun searchQueryAddsToCache(){ + fun searchQueryAddsToCache() { val query = "Lausanne" - val context : Context = getApplicationContext() + val context: Context = getApplicationContext() val locationCache = Location.createAndStore(query, context) val location = Location(query, context) val cacheFile = File(context.cacheDir, Location.CACHE_FILE_NAME) @@ -56,21 +56,25 @@ class LocationTest { var containsLat = false var containsLng = false cacheFile.forEachLine { line -> - if (line.contains(location.addressName.toString())){ - containsAddress = true} - if (line.contains(location.address!!.latitude.toString())){ - containsLat = true} - if (line.contains(location.address!!.longitude.toString())){ - containsLng = true} + if (line.contains(location.addressName.toString())) { + containsAddress = true + } + if (line.contains(location.address!!.latitude.toString())) { + containsLat = true + } + if (line.contains(location.address!!.longitude.toString())) { + containsLng = true + } } assertTrue(containsAddress) assertTrue(containsLat) assertTrue(containsLng) } + @Test - fun searchQueryDoNotAddIfNull(){ + fun searchQueryDoNotAddIfNull() { val query = "wrong_query" - val context : Context = getApplicationContext() + val context: Context = getApplicationContext() val cacheFile = File(context.cacheDir, Location.CACHE_FILE_NAME) val cacheSizeBefore = cacheFile.length() val locationCache = Location.createAndStore(query, context) @@ -79,9 +83,9 @@ class LocationTest { } @Test - fun searchQueryDoNotDuplicate(){ + fun searchQueryDoNotDuplicate() { val query = "Lausanne" - val context : Context = getApplicationContext() + val context: Context = getApplicationContext() val cacheFile = File(context.cacheDir, Location.CACHE_FILE_NAME) Location.createAndStore(query, context) val cacheSizeBefore = cacheFile.length() @@ -91,7 +95,7 @@ class LocationTest { } @Test - fun rightQueryReturnsMultiplesResults(){ + fun rightQueryReturnsMultiplesResults() { val query = "rue de Genève" val result = Location.geocoderQuery(query, getApplicationContext()) assertTrue(result!!.size > 1) diff --git a/app/src/androidTest/java/com/github/factotum_sdp/factotum/data/localisation/RouteTest.kt b/app/src/androidTest/java/com/github/factotum_sdp/factotum/data/localisation/RouteTest.kt index e7d8a0a3..61934339 100644 --- a/app/src/androidTest/java/com/github/factotum_sdp/factotum/data/localisation/RouteTest.kt +++ b/app/src/androidTest/java/com/github/factotum_sdp/factotum/data/localisation/RouteTest.kt @@ -13,7 +13,7 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class RouteTest { @Test - fun routeRightlyCreatedWithDouble(){ + fun routeRightlyCreatedWithDouble() { val srcLat = 46.5190999750367 val srcLng = 6.566757598226489 val dstLat = 46.52072048076863 @@ -25,7 +25,7 @@ class RouteTest { assertEquals(dstLng, route.dst.longitude) } - fun routeRightlyUpdates(){ + fun routeRightlyUpdates() { val srcLat = 46.5190999750367 val srcLng = 6.566757598226489 val dstLat = 46.52072048076863 @@ -38,7 +38,7 @@ class RouteTest { } @Test - fun routeRightlyCreatedWithLatLon(){ + fun routeRightlyCreatedWithLatLon() { val srcLat = 46.5190999750367 val srcLng = 6.566757598226489 val dstLat = 46.52072048076863 @@ -53,24 +53,25 @@ class RouteTest { } @Test - fun routePrintsRightCoordinates(){ + fun routePrintsRightCoordinates() { val srcLat = 46.5190999750367 val srcLng = 6.566757598226489 val dstLat = 46.52072048076863 val dstLng = 6.567838722207785 val route = Route(srcLat, srcLng, dstLat, dstLng) - val str = "The route starts at coordinates ($srcLat, $srcLng) and finishes at coordinates ($dstLat, $dstLng)" + val str = + "The route starts at coordinates ($srcLat, $srcLng) and finishes at coordinates ($dstLat, $dstLng)" assertEquals(str, route.toString()) } @Test - fun routeViewInitializesCorrectly(){ + fun routeViewInitializesCorrectly() { val mapView = MapsViewModel() assertEquals(0, mapView.routesState.value!!.size) } @Test - fun routeViewAdd(){ + fun routeViewAdd() { val mapView = MapsViewModel() val route = Route(10.0, 10.0, 10.0, 10.0) val route2 = Route(10.0, 10.0, 10.0, 10.0) @@ -82,7 +83,7 @@ class RouteTest { } @Test - fun routeViewAddAll(){ + fun routeViewAddAll() { val mapView = MapsViewModel() val route = Route(10.0, 10.0, 10.0, 10.0) val route2 = Route(10.0, 10.0, 10.0, 10.0) @@ -94,8 +95,9 @@ class RouteTest { @get:Rule val rule = InstantTaskExecutorRule() + @Test - fun routeViewSetsRun(){ + fun routeViewSetsRun() { val mapView = MapsViewModel() val route = Route(10.0, 10.0, 10.0, 10.0) mapView.setRunRoute(route) @@ -103,7 +105,7 @@ class RouteTest { } @Test - fun routeViewDeletes(){ + fun routeViewDeletes() { val mapView = MapsViewModel() val route = Route(10.0, 10.0, 10.0, 10.0) mapView.addRoute(route) @@ -112,9 +114,9 @@ class RouteTest { } @Test - fun routeViewSetsLocation(){ + fun routeViewSetsLocation() { val mapView = MapsViewModel() - val query = "Lausanne" + val query = "Lausanne" val location = Location(query, getApplicationContext()) mapView.setLocation(location) assertEquals(location.address!!.latitude, mapView.location.value!!.address!!.latitude) diff --git a/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/directory/ContactDetailsFragmentTest.kt b/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/directory/ContactDetailsFragmentTest.kt index 087bb646..75fc7a5a 100644 --- a/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/directory/ContactDetailsFragmentTest.kt +++ b/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/directory/ContactDetailsFragmentTest.kt @@ -19,12 +19,10 @@ import androidx.test.uiautomator.UiSelector import androidx.test.uiautomator.Until.hasObject import com.github.factotum_sdp.factotum.MainActivity import com.github.factotum_sdp.factotum.R -import com.github.factotum_sdp.factotum.placeholder.ContactsList import com.github.factotum_sdp.factotum.ui.maps.RouteFragment import com.github.factotum_sdp.factotum.utils.ContactsUtils +import com.github.factotum_sdp.factotum.utils.GeneralUtils.Companion.initFirebase import com.github.factotum_sdp.factotum.utils.LocationUtils -import com.google.firebase.database.ktx.database -import com.google.firebase.ktx.Firebase import junit.framework.TestCase.assertTrue import kotlinx.coroutines.runBlocking import org.hamcrest.CoreMatchers @@ -42,29 +40,29 @@ class ContactDetailsFragmentTest { companion object { @BeforeClass @JvmStatic - fun setUpDatabase() { - val database = Firebase.database - database.useEmulator("10.0.2.2", 9000) - MainActivity.setDatabase(database) - - ContactsUtils.emptyFirebaseDatabase(database) - - ContactsList.init(database) - - runBlocking { - ContactsList.populateDatabase() - } + fun setUp() { + initFirebase() } } @Before fun goToContactDetails() { + ContactsUtils.emptyFirebaseDatabase() + + runBlocking { + ContactsUtils.populateDatabase(5) + } onView(withId(R.id.drawer_layout)) .perform(DrawerActions.open()) onView(withId(R.id.directoryFragment)) .perform(click()) onView(withId(R.id.contacts_recycler_view)) - .perform(RecyclerViewActions.actionOnItemAtPosition(0, click())) + .perform( + RecyclerViewActions.actionOnItemAtPosition( + 0, + click() + ) + ) } @Test @@ -73,25 +71,51 @@ class ContactDetailsFragmentTest { .check(matches(isDisplayed())) } + @Test + fun modifyButtonIsDisplayed() { + onView(withId(R.id.button_modify_contact)) + .check(matches(isDisplayed())) + } + + @Test + fun deleteButtonIsDisplayed() { + onView(withId(R.id.button_delete_contact)) + .check(matches(isDisplayed())) + } + @Test fun buttonIsClickable() { onView(withId(R.id.button)) .perform(click()) } + @Test + fun modifyButtonIsClickable() { + onView(withId(R.id.button_modify_contact)) + .perform(click()) + } + + @Test + fun deleteButtonIsClickable() { + onView(withId(R.id.button_delete_contact)) + .perform(click()) + } + @Test fun correctInfoIsDisplayed() { onView(withId(R.id.contact_name)) - .check(matches(withText(ContactsList.getItems()[0].name))) + .check(matches(withText(ContactsUtils.getContacts()[0].name))) + onView(withId(R.id.contact_surname)) + .check(matches(withText(ContactsUtils.getContacts()[0].surname))) onView(withId(R.id.contact_phone)) - .check(matches(withText(ContactsList.getItems()[0].phone))) + .check(matches(withText(ContactsUtils.getContacts()[0].phone))) onView(withId(R.id.contact_role)) - .check(matches(withText(ContactsList.getItems()[0].role))) + .check(matches(withText(ContactsUtils.getContacts()[0].role))) onView(withId(R.id.contact_address)) - .check(matches(withText(ContactsList.getItems()[0].address))) - if (ContactsList.getItems()[0].details != null) { + .check(matches(withText(ContactsUtils.getContacts()[0].address))) + if (ContactsUtils.getContacts()[0].details != null) { onView(withId(R.id.contact_details)) - .check(matches(withText(ContactsList.getItems()[0].details))) + .check(matches(withText(ContactsUtils.getContacts()[0].details))) } } @@ -110,10 +134,45 @@ class ContactDetailsFragmentTest { } @Test - fun buttonRunOpensGoogleMaps(){ + fun modifyButtonGoesToContactEdition() { + onView(withId(R.id.button_modify_contact)) + .perform(click()) + onView(withId(R.id.contact_creation_fragment)) + .check(matches(isDisplayed())) + } + + @Test + fun deleteButtonReturnsToContacts() { + onView(withId(R.id.button_delete_contact)) + .perform(click()) + onView(withId(R.id.contacts_recycler_view)) + .check(matches(isDisplayed())) + } + + @Test + fun deleteButtonDeletesContact() { + onView(withId(R.id.button_delete_contact)) + .perform(click()) + onView(withId(R.id.contacts_recycler_view)) + .check(matches(isDisplayed())) + //check if size of recycler view is 4 + onView(withId(R.id.contacts_recycler_view)) + .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) + .check { view, noViewFoundException -> + if (noViewFoundException != null) { + throw noViewFoundException + } + val recyclerView = view as RecyclerView + val adapter = recyclerView.adapter + assert(adapter?.itemCount == 4) + } + } + + @Test + fun buttonRunOpensGoogleMaps() { Intents.init() onView(withId(R.id.run_button)).perform(click()) - if(LocationUtils.hasLocationPopUp()){ + if (LocationUtils.hasLocationPopUp()) { val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) device.findObject(UiSelector().textContains(LocationUtils.buttonTextAllow)).click() } @@ -127,9 +186,9 @@ class ContactDetailsFragmentTest { } @Test - fun buttonShowDestination(){ + fun buttonShowDestination() { onView(withId(R.id.show_all_button)).perform(click()) - if(LocationUtils.hasLocationPopUp()){ + if (LocationUtils.hasLocationPopUp()) { val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) device.findObject(UiSelector().textContains(LocationUtils.buttonTextAllow)).click() } diff --git a/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/directory/ContactsCreationTest.kt b/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/directory/ContactsCreationTest.kt index e0129a72..fb767e00 100644 --- a/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/directory/ContactsCreationTest.kt +++ b/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/directory/ContactsCreationTest.kt @@ -1,14 +1,15 @@ package com.github.factotum_sdp.factotum.ui.directory import android.widget.EditText +import androidx.recyclerview.widget.RecyclerView import androidx.test.core.app.ApplicationProvider.getApplicationContext +import androidx.test.espresso.Espresso.closeSoftKeyboard import androidx.test.espresso.Espresso.onView -import androidx.test.espresso.action.ViewActions -import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.action.ViewActions.* import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.contrib.DrawerActions -import androidx.test.espresso.matcher.ViewMatchers.isDisplayed -import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.contrib.RecyclerViewActions +import androidx.test.espresso.matcher.ViewMatchers.* import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry @@ -18,9 +19,13 @@ import androidx.test.uiautomator.Until import com.github.factotum_sdp.factotum.MainActivity import com.github.factotum_sdp.factotum.R import com.github.factotum_sdp.factotum.data.localisation.Location -import com.github.factotum_sdp.factotum.placeholder.ContactsList +import com.github.factotum_sdp.factotum.placeholder.Contact +import com.github.factotum_sdp.factotum.utils.ContactsUtils +import com.github.factotum_sdp.factotum.utils.GeneralUtils.Companion.initFirebase import junit.framework.TestCase.* +import kotlinx.coroutines.runBlocking import org.junit.Before +import org.junit.BeforeClass import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -32,62 +37,133 @@ class ContactsCreationTest { @get:Rule var activityRule = ActivityScenarioRule(MainActivity::class.java) + companion object { + private const val nbContacts = 5 + + @BeforeClass + @JvmStatic + fun firebaseSetup() { + initFirebase() + } + } + @Before - fun goToContacts() { + fun setUp() { + ContactsUtils.emptyFirebaseDatabase() + + runBlocking { + ContactsUtils.populateDatabase(nbContacts) + } onView(withId(R.id.drawer_layout)) .perform(DrawerActions.open()) onView(withId(R.id.directoryFragment)) .perform(click()) + onView(withId(R.id.add_contact_button)).perform(click()) } @Test - fun addButtonExists(){ - onView(withId(R.id.add_contact_button)).check(matches(isDisplayed())) + fun hasAllTheFields() { + onView((withId(R.id.contact_image_creation))).check(matches(isDisplayed())) + onView((withId(R.id.contactCreationAddress))).check(matches(isDisplayed())) + onView(withId(R.id.roles_spinner)).check(matches(isDisplayed())) + onView(withId(R.id.editTextName)).check(matches(isDisplayed())) + onView(withId(R.id.editTextSurname)).check(matches(isDisplayed())) + onView(withId(R.id.contactCreationPhoneNumber)).check(matches(isDisplayed())) + onView(withId(R.id.contactCreationNotes)).check(matches(isDisplayed())) + } + @Test + fun buttonTextIsCorrect() { + onView(withId(R.id.create_contact)).check(matches(withText("Create Contact"))) } @Test - fun addButtonOpensContactCreation(){ - onView(withId(R.id.add_contact_button)).perform(click()) - onView(withId(R.id.contact_creation_fragment)).check(matches(isDisplayed())) + fun hasRoles() { + onView(withId(R.id.roles_spinner)).check(matches(isDisplayed())) } @Test - fun contactCreationHasAllTheFields(){ + fun allFieldsAreEditable() { val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) - onView(withId(R.id.add_contact_button)).perform(click()) - onView((withId(R.id.contact_image_creation))).check(matches(isDisplayed())) - onView((withId(R.id.contactCreationAddress))).check(matches(isDisplayed())) - onView(withId(R.id.roles_spinner)).check(matches(isDisplayed())) - val fields = ContactsList.Contact::class.java.declaredFields - var nbFields = 0 - for (param in fields){ + val fields = Contact::class.java.declaredFields + for (param in fields) { if (param.isSynthetic) continue - nbFields++ + val editText = device.findObject(clazz(EditText::class.java.name)) + editText.text = "test" + assertEquals("test", editText.text) } - val nbEditText = device.findObjects(clazz(EditText::class.java.name)).size - // image already present - // searchView already present - assertEquals(nbFields-3, nbEditText) } @Test - fun writeInAddressFieldMakesDropDown(){ - onView(withId(R.id.add_contact_button)).perform(click()) + fun canCreateContact() { + onView(withId(R.id.create_contact)).perform(click()) + //check if recycle view in contacts has 6 items + onView(withId(R.id.contacts_recycler_view)) + .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) + .check { view, noViewFoundException -> + if (noViewFoundException != null) { + throw noViewFoundException + } + val recyclerView = view as RecyclerView + val adapter = recyclerView.adapter + assert(adapter?.itemCount == nbContacts + 1) + } + } + + + @Test + fun createdContactHasCorrectValue() { + val nameEditText = onView(withId(R.id.editTextName)) + nameEditText.perform(replaceText("John")) + + val surnameEditText = onView(withId(R.id.editTextSurname)) + surnameEditText.perform(replaceText("Doe")) + + onView(withId(androidx.appcompat.R.id.search_src_text)).perform( + typeText("123 Main St") + ) + closeSoftKeyboard() + + val phoneEditText = onView(withId(R.id.contactCreationPhoneNumber)) + phoneEditText.perform(replaceText("555-555-1234")) + + val notesEditText = onView(withId(R.id.contactCreationNotes)) + notesEditText.perform(replaceText("This is a test note.")) + + onView(withId(R.id.create_contact)).perform(click()) + onView(withId(R.id.contacts_recycler_view)) + .perform( + RecyclerViewActions.actionOnItemAtPosition( + nbContacts, + click() + ) + ) + + onView(withId(R.id.contact_name)).check(matches(withText("John"))) + onView(withId(R.id.contact_surname)).check(matches(withText("Doe"))) + onView(withId(R.id.contact_address)).check(matches(withText("123 Main St"))) + onView(withId(R.id.contact_phone)).check(matches(withText("555-555-1234"))) + onView(withId(R.id.contact_details)).check(matches(withText("This is a test note."))) + } + + + @Test + fun writeInAddressFieldMakesDropDown() { val city = "Lausanne" - onView(withId(androidx.appcompat.R.id.search_src_text)).perform(ViewActions.typeText(city.dropLast(2))) + onView(withId(androidx.appcompat.R.id.search_src_text)).perform(typeText(city.dropLast(2))) val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) val result = device.wait(Until.hasObject(textContains(city)), 5000) assertTrue(result) } + @Test - fun selectSuggestionWritesAddress(){ - onView(withId(R.id.add_contact_button)).perform(click()) + fun selectSuggestionWritesAddress() { val city = "Lausanne" - onView(withId(androidx.appcompat.R.id.search_src_text)).perform(ViewActions.typeText(city)) + onView(withId(androidx.appcompat.R.id.search_src_text)).perform(typeText(city)) val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) - val lausanneResult = Location.geocoderQuery(city, getApplicationContext())!![0].getAddressLine(0) + val lausanneResult = + Location.geocoderQuery(city, getApplicationContext())!![0].getAddressLine(0) val address = device.findObject(text(city)) val result = device.wait(Until.hasObject(text(lausanneResult)), 5000) assertTrue(result) diff --git a/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/directory/ContactsListOfflineTest.kt b/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/directory/ContactsListOfflineTest.kt deleted file mode 100644 index b40b1f79..00000000 --- a/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/directory/ContactsListOfflineTest.kt +++ /dev/null @@ -1,48 +0,0 @@ -package com.github.factotum_sdp.factotum.ui.directory - -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.github.factotum_sdp.factotum.R -import com.github.factotum_sdp.factotum.placeholder.ContactsList -import com.github.factotum_sdp.factotum.placeholder.ContactsList.Contact -import org.junit.Assert.assertEquals -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class ContactsListOfflineTest { - - @Test - fun testLoadContactsLocally() { - // Create a sample list of contacts - val originalContactsList = listOf( - Contact( - id = "1", - role = "Courier", - name = "John Doe", - profile_pic_id = R.drawable.contact_image, - address = "123 Main St", - phone = "123-456-7890"), - Contact( - id = "2", - role = "Boss", - name = "Jane Smith", - profile_pic_id = R.drawable.contact_image, - address = "456 Elm St", - phone = "987-654-3210") - ) - - - // Save the sample list of contacts to shared preferences - ContactsList.setItems(originalContactsList) - ContactsList.saveContactsLocally(ApplicationProvider.getApplicationContext()) - - // Clear the current list and load contacts from shared preferences - ContactsList.setItems(emptyList()) - ContactsList.loadContactsLocally(ApplicationProvider.getApplicationContext()) - - // Check if the loaded contacts match the original sample list - assertEquals(originalContactsList, ContactsList.getItems()) - } -} - diff --git a/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/directory/ContactsListOnlineTest.kt b/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/directory/ContactsListOnlineTest.kt index ebedecb9..b9ec2a98 100644 --- a/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/directory/ContactsListOnlineTest.kt +++ b/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/directory/ContactsListOnlineTest.kt @@ -1,20 +1,19 @@ package com.github.factotum_sdp.factotum.ui.directory -import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.runners.AndroidJUnit4 import com.github.factotum_sdp.factotum.MainActivity -import com.github.factotum_sdp.factotum.placeholder.ContactsList import com.github.factotum_sdp.factotum.utils.ContactsUtils +import com.github.factotum_sdp.factotum.utils.GeneralUtils.Companion.getDatabase +import com.github.factotum_sdp.factotum.utils.GeneralUtils.Companion.initFirebase import com.google.firebase.database.DataSnapshot import com.google.firebase.database.DatabaseError import com.google.firebase.database.FirebaseDatabase import com.google.firebase.database.ValueEventListener -import com.google.firebase.database.ktx.database -import com.google.firebase.ktx.Firebase import kotlinx.coroutines.* import kotlinx.coroutines.test.runTest -import org.junit.AfterClass import org.junit.BeforeClass +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import kotlin.coroutines.resume @@ -24,26 +23,31 @@ import kotlin.coroutines.suspendCoroutine @RunWith(AndroidJUnit4::class) class ContactsListOnlineTest { + @get:Rule + var testRule = ActivityScenarioRule( + MainActivity::class.java + ) + companion object { - private var database: FirebaseDatabase = Firebase.database + private lateinit var database: FirebaseDatabase @BeforeClass @JvmStatic fun setUpDatabase() { - database.useEmulator("10.0.2.2", 9000) - MainActivity.setDatabase(database) - - ContactsList.init(database) + initFirebase() + database = getDatabase() } + + //TODO: MAYBE EMPTY DATABASE } @ExperimentalCoroutinesApi @Test fun testAddContacts() = runTest { - ContactsList.populateDatabase() + ContactsUtils.populateDatabase() val ref = database.getReference("contacts") - val dataSnapshot = suspendCoroutine { continuation -> + val dataSnapshot = suspendCoroutine { continuation -> ref.addListenerForSingleValueEvent(object : ValueEventListener { override fun onDataChange(dataSnapshot: DataSnapshot) { continuation.resume(dataSnapshot) @@ -58,15 +62,4 @@ class ContactsListOnlineTest { assert(dataSnapshot.hasChildren()) } - @ExperimentalCoroutinesApi - @Test - fun testSyncContactsFromFirebase() = runTest { - // First, populate the database with contacts - ContactsList.populateDatabase() - // Then, synchronize the contacts list with Firebase - ContactsList.syncContactsFromFirebase(ApplicationProvider.getApplicationContext()) - // Now, check if the local contacts list is not empty - assert(ContactsList.getItems().isNotEmpty()) - } - } diff --git a/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/directory/ContactsRepositoryTest.kt b/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/directory/ContactsRepositoryTest.kt new file mode 100644 index 00000000..10e405c9 --- /dev/null +++ b/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/directory/ContactsRepositoryTest.kt @@ -0,0 +1,99 @@ +package com.github.factotum_sdp.factotum.ui.directory + +import android.content.Context +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.github.factotum_sdp.factotum.R +import com.github.factotum_sdp.factotum.placeholder.Contact +import com.github.factotum_sdp.factotum.utils.ContactsUtils +import com.github.factotum_sdp.factotum.utils.GeneralUtils.Companion.getDatabase +import com.github.factotum_sdp.factotum.utils.GeneralUtils.Companion.initFirebase +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.first +import org.junit.* +import org.junit.runner.RunWith +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit + + +@RunWith(AndroidJUnit4::class) +class ContactsRepositoryTest { + + private lateinit var context: Context + private lateinit var repository: ContactsRepository + + companion object { + @BeforeClass + @JvmStatic + fun setUpFirebase() { + initFirebase() + } + } + + @Before + fun setUp() { + context = ApplicationProvider.getApplicationContext() + val sharedPreferences = context.getSharedPreferences("contacts_test", Context.MODE_PRIVATE) + repository = ContactsRepository(sharedPreferences) + repository.setDatabase(getDatabase()) + ContactsUtils.emptyFirebaseDatabase() + } + + @After + fun tearDown() { + val sharedPreferences = context.getSharedPreferences("contacts_test", Context.MODE_PRIVATE) + sharedPreferences.edit().clear().commit() + } + + @Test + fun savesContactToSharedPreferences() = runBlocking { + // Save a contact to the cache + val contact = Contact( + "1", + "Manager", + "John", + "Doe", + R.drawable.contact_image, + "123 Main St", + "555-555-1234" + ) + repository.saveContactToSharedPreferences(contact) + + // Verify that the contact was saved to the cache + val cachedContacts = repository.getCachedContacts() + Assert.assertEquals(1, cachedContacts.size) + Assert.assertEquals(contact, cachedContacts[0]) + } + + + @Test + fun savesContactOnline() = runBlocking { + val contact = Contact( + "1", + "Manager", + "John", + "Doe", + R.drawable.contact_image, + "123 Main St", + "555-555-1234" + ) + val latch = CountDownLatch(1) + + repository.saveContact(contact) + + val contacts = repository.getContacts().first() + + if (contacts.isNotEmpty()) { + Assert.assertEquals(1, contacts.size) + Assert.assertEquals(contact, contacts[0]) + latch.countDown() + } + + if (!withContext(Dispatchers.IO) { + latch.await(5, TimeUnit.SECONDS) + }) { + Assert.fail("Timeout waiting for contacts to update") + } + } + +} diff --git a/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/directory/ContactsUpdateTest.kt b/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/directory/ContactsUpdateTest.kt new file mode 100644 index 00000000..df2b3271 --- /dev/null +++ b/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/directory/ContactsUpdateTest.kt @@ -0,0 +1,173 @@ +package com.github.factotum_sdp.factotum.ui.directory + +import android.widget.EditText +import androidx.recyclerview.widget.RecyclerView +import androidx.test.espresso.Espresso.closeSoftKeyboard +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions +import androidx.test.espresso.action.ViewActions.clearText +import androidx.test.espresso.action.ViewActions.typeText +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.contrib.DrawerActions +import androidx.test.espresso.contrib.RecyclerViewActions +import androidx.test.espresso.matcher.ViewMatchers.* +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.By +import androidx.test.uiautomator.UiDevice +import com.github.factotum_sdp.factotum.MainActivity +import com.github.factotum_sdp.factotum.R +import com.github.factotum_sdp.factotum.placeholder.Contact +import com.github.factotum_sdp.factotum.utils.ContactsUtils +import com.github.factotum_sdp.factotum.utils.GeneralUtils.Companion.initFirebase +import junit.framework.TestCase.assertEquals +import kotlinx.coroutines.runBlocking +import org.junit.Before +import org.junit.BeforeClass +import org.junit.Rule +import org.junit.Test + +class ContactsUpdateTest { + @get:Rule + var activityRule = ActivityScenarioRule(MainActivity::class.java) + + companion object { + private const val nbContacts = 5 + + @BeforeClass + @JvmStatic + fun setUpFirebase() { + initFirebase() + } + } + + @Before + fun setUp() { + ContactsUtils.emptyFirebaseDatabase() + + runBlocking { + ContactsUtils.populateDatabase(nbContacts) + } + onView(withId(R.id.drawer_layout)) + .perform(DrawerActions.open()) + onView(withId(R.id.directoryFragment)) + .perform(ViewActions.click()) + onView(withId(R.id.contacts_recycler_view)) + .perform( + RecyclerViewActions.actionOnItemAtPosition( + 0, + ViewActions.click() + ) + ) + onView(withId(R.id.button_modify_contact)).perform(ViewActions.click()) + } + + @Test + fun hasAllTheFields() { + onView((withId(R.id.contact_image_creation))).check(matches(isDisplayed())) + onView((withId(R.id.contactCreationAddress))).check(matches(isDisplayed())) + onView(withId(R.id.roles_spinner)).check(matches(isDisplayed())) + onView(withId(R.id.editTextName)).check(matches(isDisplayed())) + onView(withId(R.id.editTextSurname)).check(matches(isDisplayed())) + onView(withId(R.id.contactCreationPhoneNumber)).check(matches(isDisplayed())) + onView(withId(R.id.contactCreationNotes)).check(matches(isDisplayed())) + } + + @Test + fun buttonTextIsCorrect() { + onView(withId(R.id.create_contact)) + .check(matches(withText("Update Contact"))) + } + + @Test + fun allFieldsAreEditable() { + val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) + val fields = Contact::class.java.declaredFields + for (param in fields) { + if (param.isSynthetic) continue + val editText = device.findObject(By.clazz(EditText::class.java.name)) + editText.text = "test" + assertEquals("test", editText.text) + } + } + + @Test + fun updateDoesntAddOrRemoveContact() { + onView(withId(R.id.create_contact)).perform(ViewActions.click()) + //check if recycle view in contacts has 6 items + onView(withId(R.id.contacts_recycler_view)) + .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) + .check { view, noViewFoundException -> + if (noViewFoundException != null) { + throw noViewFoundException + } + val recyclerView = view as RecyclerView + val adapter = recyclerView.adapter + assert(adapter?.itemCount == nbContacts) + } + } + + + @Test + fun fieldsContainContactValues() { + val nameEditText = onView(withId(R.id.editTextName)) + nameEditText.check(matches(withText(ContactsUtils.getContacts()[0].name))) + + val surnameEditText = onView(withId(R.id.editTextSurname)) + surnameEditText.check(matches(withText(ContactsUtils.getContacts()[0].surname))) + + val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) + val addressEditText = device.findObject(By.text(ContactsUtils.getContacts()[0].address)) + assert(addressEditText != null) + + val phoneEditText = onView(withId(R.id.contactCreationPhoneNumber)) + phoneEditText.check(matches(withText(ContactsUtils.getContacts()[0].phone))) + + val notesEditText = onView(withId(R.id.contactCreationNotes)) + notesEditText.check(matches(withText(ContactsUtils.getContacts()[0].details))) + + val roleSpinner = onView(withId(R.id.roles_spinner)) + roleSpinner.check(matches(withSpinnerText(ContactsUtils.getContacts()[0].role))) + } + + @Test + fun updatedContactHasCorrectValue() { + val nameEditText = onView(withId(R.id.editTextName)) + nameEditText.perform(ViewActions.replaceText("John")) + + val surnameEditText = onView(withId(R.id.editTextSurname)) + surnameEditText.perform(ViewActions.replaceText("Doe")) + + onView(withId(androidx.appcompat.R.id.search_src_text)).perform( + clearText(), + typeText("123 Main St") + ) + closeSoftKeyboard() + + val phoneEditText = onView(withId(R.id.contactCreationPhoneNumber)) + phoneEditText.perform(ViewActions.replaceText("555-555-1234")) + + val notesEditText = onView(withId(R.id.contactCreationNotes)) + notesEditText.perform(ViewActions.replaceText("This is a test note.")) + + onView(withId(R.id.create_contact)).perform(ViewActions.click()) + onView(withId(R.id.contacts_recycler_view)) + .perform( + RecyclerViewActions.actionOnItemAtPosition( + 0, + ViewActions.click() + ) + ) + + onView(withId(R.id.contact_name)) + .check(matches(withText("John"))) + onView(withId(R.id.contact_surname)) + .check(matches(withText("Doe"))) + onView(withId(R.id.contact_address)) + .check(matches(withText("123 Main St"))) + onView(withId(R.id.contact_phone)) + .check(matches(withText("555-555-1234"))) + onView(withId(R.id.contact_details)) + .check(matches(withText("This is a test note."))) + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/directory/DirectoryFragmentTest.kt b/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/directory/DirectoryFragmentTest.kt index 0b640178..2825b181 100644 --- a/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/directory/DirectoryFragmentTest.kt +++ b/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/directory/DirectoryFragmentTest.kt @@ -15,10 +15,8 @@ import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.UiSelector import com.github.factotum_sdp.factotum.MainActivity import com.github.factotum_sdp.factotum.R -import com.github.factotum_sdp.factotum.placeholder.ContactsList import com.github.factotum_sdp.factotum.utils.ContactsUtils -import com.google.firebase.database.ktx.database -import com.google.firebase.ktx.Firebase +import com.github.factotum_sdp.factotum.utils.GeneralUtils.Companion.initFirebase import junit.framework.TestCase.assertTrue import kotlinx.coroutines.runBlocking import org.junit.Before @@ -37,19 +35,17 @@ class DirectoryFragmentTest { ) companion object { + private const val nbContacts = 10 + @BeforeClass @JvmStatic fun setUpDatabase() { - val database = Firebase.database - database.useEmulator("10.0.2.2", 9000) - MainActivity.setDatabase(database) - - ContactsUtils.emptyFirebaseDatabase(database) + initFirebase() - ContactsList.init(database) + ContactsUtils.emptyFirebaseDatabase() runBlocking { - ContactsList.populateDatabase() + ContactsUtils.populateDatabase(nbContacts) } } } @@ -84,10 +80,22 @@ class DirectoryFragmentTest { assertTrue(contactName.exists()) } + @Test + fun addButtonExists() { + onView(withId(R.id.add_contact_button)).check(matches(isDisplayed())) + + } + + @Test + fun addButtonOpensContactCreation() { + onView(withId(R.id.add_contact_button)).perform(click()) + onView(withId(R.id.contact_creation_fragment)).check(matches(isDisplayed())) + } + @Test fun allContactsCanBeClickedOn() { val device = UiDevice.getInstance(getInstrumentation()) - for (i in 0 until ContactsList.size) { + for (i in 0 until nbContacts) { onView(withId(R.id.contacts_recycler_view)) .perform( RecyclerViewActions.actionOnItemAtPosition( @@ -111,7 +119,7 @@ class DirectoryFragmentTest { // Check if the expected contact is visible in the RecyclerView onView(withId(R.id.contacts_recycler_view)) - .perform(scrollToHolder(ContactsUtils.withHolderContactName("John Smith"))) + .perform(scrollToHolder(ContactsUtils.withHolderContactName("Smith John"))) } } \ No newline at end of file diff --git a/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/display/DisplayFragmentTest.kt b/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/display/DisplayFragmentTest.kt index f1df6440..5d493056 100644 --- a/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/display/DisplayFragmentTest.kt +++ b/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/display/DisplayFragmentTest.kt @@ -18,14 +18,11 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import com.github.factotum_sdp.factotum.R import com.github.factotum_sdp.factotum.ui.display.utils.* +import com.github.factotum_sdp.factotum.utils.GeneralUtils.Companion.initFirebase import com.google.firebase.ktx.Firebase import com.google.firebase.storage.ktx.storage import kotlinx.coroutines.runBlocking -import org.junit.After -import org.junit.AfterClass -import org.junit.Before -import org.junit.BeforeClass -import org.junit.Test +import org.junit.* import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) @@ -35,7 +32,7 @@ class DisplayFragmentTest { @JvmStatic @BeforeClass fun setUpClass() { - Firebase.storage.useEmulator("10.0.2.2", 9199) + initFirebase() Intents.init() } @@ -59,6 +56,7 @@ class DisplayFragmentTest { fun tearDown() { emptyStorageEmulator(Firebase.storage.reference) } + @Test fun displayFragmentUiElementsDisplayed() { onView(withId(R.id.recyclerView)).check(matches(isDisplayed())) diff --git a/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/display/DisplayViewModelTest.kt b/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/display/DisplayViewModelTest.kt index 60a201b9..b114c01a 100644 --- a/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/display/DisplayViewModelTest.kt +++ b/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/display/DisplayViewModelTest.kt @@ -5,6 +5,7 @@ import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import com.github.factotum_sdp.factotum.ui.display.utils.* +import com.github.factotum_sdp.factotum.utils.GeneralUtils.Companion.initFirebase import com.google.firebase.ktx.Firebase import com.google.firebase.storage.ktx.storage import kotlinx.coroutines.runBlocking @@ -22,7 +23,7 @@ class DisplayViewModelTest { @Before fun setUp() { // Initialize Firebase - Firebase.storage.useEmulator("10.0.2.2", 9199) + initFirebase() context = InstrumentationRegistry.getInstrumentation().context } @@ -40,7 +41,10 @@ class DisplayViewModelTest { Thread.sleep(WAIT_TIME_INIT) //Check that the photoReferences is empty - Assert.assertTrue("PhotosReference should be empty.", displayViewModel.photoReferences.value?.isEmpty() ?: true) + Assert.assertTrue( + "PhotosReference should be empty.", + displayViewModel.photoReferences.value?.isEmpty() ?: true + ) runBlocking { uploadImageToStorageEmulator(context, TEST_IMAGE_PATH1, TEST_IMAGE_PATH1) @@ -52,7 +56,10 @@ class DisplayViewModelTest { Thread.sleep(WAIT_TIME_REFRESH) // Check that the photoReferences is not empty - Assert.assertFalse("PhotosReference should not be empty.", displayViewModel.photoReferences.value?.isEmpty() ?: false) + Assert.assertFalse( + "PhotosReference should not be empty.", + displayViewModel.photoReferences.value?.isEmpty() ?: false + ) runBlocking { uploadImageToStorageEmulator(context, TEST_IMAGE_PATH2, TEST_IMAGE_PATH2) @@ -64,7 +71,11 @@ class DisplayViewModelTest { Thread.sleep(WAIT_TIME_REFRESH) // Check that the photoReferences has two items - Assert.assertEquals("PhotosReference should have two items.", 2, displayViewModel.photoReferences.value?.size) + Assert.assertEquals( + "PhotosReference should have two items.", + 2, + displayViewModel.photoReferences.value?.size + ) } @Test @@ -79,7 +90,10 @@ class DisplayViewModelTest { Thread.sleep(WAIT_TIME_INIT) // Check that the photoReferences is not empty - Assert.assertFalse("PhotosReference should not be empty.", displayViewModel.photoReferences.value?.isEmpty() ?: false) + Assert.assertFalse( + "PhotosReference should not be empty.", + displayViewModel.photoReferences.value?.isEmpty() ?: false + ) } } diff --git a/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/display/utils/EmulatorInteractions.kt b/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/display/utils/EmulatorInteractions.kt index a4838bd3..2b8a2c38 100644 --- a/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/display/utils/EmulatorInteractions.kt +++ b/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/display/utils/EmulatorInteractions.kt @@ -3,8 +3,8 @@ package com.github.factotum_sdp.factotum.ui.display.utils import android.content.Context import com.google.firebase.ktx.Firebase import com.google.firebase.storage.StorageReference -import com.google.firebase.storage.ktx.storage import com.google.firebase.storage.UploadTask +import com.google.firebase.storage.ktx.storage import kotlinx.coroutines.suspendCancellableCoroutine import java.util.concurrent.CountDownLatch import kotlin.coroutines.resume @@ -41,7 +41,7 @@ suspend fun uploadImageToStorageEmulator( } } -fun emptyStorageEmulator(storageRef : StorageReference) { +fun emptyStorageEmulator(storageRef: StorageReference) { // Empty Firebase Storage val latch = CountDownLatch(1) diff --git a/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/login/LoginFragmentTest.kt b/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/login/LoginFragmentTest.kt index d57b251c..e327692e 100644 --- a/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/login/LoginFragmentTest.kt +++ b/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/login/LoginFragmentTest.kt @@ -9,10 +9,11 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import com.github.factotum_sdp.factotum.MainActivity import com.github.factotum_sdp.factotum.R import com.github.factotum_sdp.factotum.placeholder.UsersPlaceHolder +import com.github.factotum_sdp.factotum.utils.GeneralUtils.Companion.getAuth +import com.github.factotum_sdp.factotum.utils.GeneralUtils.Companion.getDatabase +import com.github.factotum_sdp.factotum.utils.GeneralUtils.Companion.initFirebase import com.google.firebase.auth.FirebaseAuth import com.google.firebase.auth.ktx.auth -import com.google.firebase.database.FirebaseDatabase -import com.google.firebase.database.ktx.database import com.google.firebase.ktx.Firebase import kotlinx.coroutines.runBlocking import org.hamcrest.Matchers.not @@ -35,15 +36,10 @@ class LoginFragmentTest { @BeforeClass @JvmStatic fun setUpDatabase() { - val database: FirebaseDatabase = Firebase.database - val auth: FirebaseAuth = Firebase.auth - database.useEmulator("10.0.2.2", 9000) - auth.useEmulator("10.0.2.2", 9099) + initFirebase() - MainActivity.setDatabase(database) - MainActivity.setAuth(auth) - - UsersPlaceHolder.init(database, auth) + MainActivity.setAuth(getAuth()) + UsersPlaceHolder.init(getDatabase(), getAuth()) runBlocking { UsersPlaceHolder.addUserToDb(UsersPlaceHolder.USER1) diff --git a/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/maps/MapsFragmentTest.kt b/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/maps/MapsFragmentTest.kt index 9d506ae3..eb1b4bca 100644 --- a/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/maps/MapsFragmentTest.kt +++ b/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/maps/MapsFragmentTest.kt @@ -20,16 +20,14 @@ import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.UiSelector import com.github.factotum_sdp.factotum.MainActivity import com.github.factotum_sdp.factotum.R +import com.github.factotum_sdp.factotum.utils.GeneralUtils.Companion.initFirebase import com.github.factotum_sdp.factotum.utils.LocationUtils import com.google.android.gms.maps.SupportMapFragment import junit.framework.TestCase.assertTrue import org.hamcrest.CoreMatchers.allOf import org.hamcrest.Matchers.anything +import org.junit.* import org.junit.Assert.assertEquals -import org.junit.Before -import org.junit.FixMethodOrder -import org.junit.Rule -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import java.util.* @@ -43,16 +41,26 @@ import java.util.concurrent.TimeUnit class MapsFragmentTest { val device = UiDevice.getInstance(getInstrumentation()) - val buttonTextAllow = when(Locale.getDefault().language){ + val buttonTextAllow = when (Locale.getDefault().language) { Locale.FRENCH.language -> "Uniquement cette fois-ci" else -> "Only this time" } + + companion object { + @BeforeClass + @JvmStatic + fun setUpDatabase() { + initFirebase() + } + } + @get:Rule var testRule = ActivityScenarioRule( MainActivity::class.java ) - @Before fun setUp(){ + @Before + fun setUp() { onView(withId(R.id.drawer_layout)) .perform(DrawerActions.open()) onView(withId(R.id.routeFragment)) @@ -61,40 +69,43 @@ class MapsFragmentTest { } @Test - fun a_permissionAskPopUp(){ + fun a_permissionAskPopUp() { onData(anything()) .inAdapterView(withId(R.id.list_view_routes)).atPosition(0).perform( click() ) onView(withId(R.id.button_next)).perform(click()) - if(LocationUtils.hasLocationPopUp()) { + if (LocationUtils.hasLocationPopUp()) { assertTrue(device.findObject(UiSelector().textContains(buttonTextAllow)).exists()) } } - private fun b_permissionAllowShowLocation(){ - onData(anything()) - .inAdapterView(withId(R.id.list_view_routes)).atPosition(0).perform( - click() - ) - onView(withId(R.id.button_next)).perform(click()) - if(LocationUtils.hasLocationPopUp()) { + private fun b_permissionAllowShowLocation() { + onData(anything()) + .inAdapterView(withId(R.id.list_view_routes)).atPosition(0).perform( + click() + ) + onView(withId(R.id.button_next)).perform(click()) + if (LocationUtils.hasLocationPopUp()) { device.findObject(UiSelector().textContains(buttonTextAllow)).click() } assertTrue(checkLocationEnabled(testRule)) } + @Test - fun goesToSecondFragement(){ - onData(anything()).inAdapterView(withId(R.id.list_view_routes)).atPosition(0).perform(click()) + fun goesToSecondFragement() { + onData(anything()).inAdapterView(withId(R.id.list_view_routes)).atPosition(0) + .perform(click()) val nextButton = onView(withId(R.id.button_next)) nextButton.perform(click()) onView(withId(R.id.fragment_maps_directors_parent)).check(matches(isDisplayed())) } @Test - fun showsDestinationMarker(){ + fun showsDestinationMarker() { - onData(anything()).inAdapterView(withId(R.id.list_view_routes)).atPosition(0).perform(click()) + onData(anything()).inAdapterView(withId(R.id.list_view_routes)).atPosition(0) + .perform(click()) val nextButton = onView(withId(R.id.button_next)) nextButton.perform(click()) val endMarker = device.findObject(UiSelector().descriptionContains("Destination")) @@ -102,38 +113,43 @@ class MapsFragmentTest { } @Test - fun showsAllDest(){ + fun showsAllDest() { val nbRoutes = device.findObjects(textContains("->")).size val showAll = onView(withId(R.id.button_all)) showAll.perform(click()) var endMarker = device.findObjects(descContains("Destination")) val endTime = System.nanoTime() + TimeUnit.SECONDS.toNanos(5) - while(endMarker.size == 0 && System.nanoTime() < endTime) { + while (endMarker.size == 0 && System.nanoTime() < endTime) { endMarker = device.findObjects(descContains("Destination")) } assertEquals(nbRoutes, endMarker.size) } @Test - fun runLaunchesMaps(){ + fun runLaunchesMaps() { Intents.init() device.findObject(UiSelector().textContains("->")).click() val runButton = onView(withId(R.id.button_run)) runButton.perform(click()) - intended(allOf(hasAction(Intent.ACTION_VIEW), - toPackage(RouteFragment.MAPS_PKG) - )) + intended( + allOf( + hasAction(Intent.ACTION_VIEW), + toPackage(RouteFragment.MAPS_PKG) + ) + ) Intents.release() } - private fun checkLocationEnabled(rule: ActivityScenarioRule) : Boolean { + + private fun checkLocationEnabled(rule: ActivityScenarioRule): Boolean { var isEnabled = false val latch = CountDownLatch(1) rule.scenario.onActivity { - val navHostFragment = it.supportFragmentManager.primaryNavigationFragment as NavHostFragment + val navHostFragment = + it.supportFragmentManager.primaryNavigationFragment as NavHostFragment val mapsFragment = navHostFragment.childFragmentManager.fragments[0] val mapFragment = mapsFragment.childFragmentManager.fragments[0] as SupportMapFragment - mapFragment.getMapAsync{ + mapFragment.getMapAsync { isEnabled = it.isMyLocationEnabled latch.countDown() } diff --git a/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/picture/PictureFragmentOfflineTest.kt b/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/picture/PictureFragmentOfflineTest.kt index dd2a5f27..b1e8e2a9 100644 --- a/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/picture/PictureFragmentOfflineTest.kt +++ b/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/picture/PictureFragmentOfflineTest.kt @@ -8,15 +8,13 @@ import androidx.test.platform.app.InstrumentationRegistry import androidx.test.rule.GrantPermissionRule import androidx.test.uiautomator.UiDevice import com.github.factotum_sdp.factotum.MainActivity +import com.github.factotum_sdp.factotum.utils.GeneralUtils.Companion.initFirebase import com.google.firebase.ktx.Firebase import com.google.firebase.storage.FirebaseStorage import com.google.firebase.storage.ktx.storage -import org.junit.After +import org.junit.* import org.junit.Assert.assertTrue import org.junit.Assert.fail -import org.junit.Before -import org.junit.Rule -import org.junit.Test import org.junit.runner.RunWith import java.io.File @@ -26,7 +24,8 @@ class PictureFragmentOfflineTest { private lateinit var storage: FirebaseStorage private lateinit var device: UiDevice private val externalDir = Environment.getExternalStorageDirectory() - private val picturesDir = File(externalDir, "/Android/data/com.github.factotum_sdp.factotum/files/Pictures") + private val picturesDir = + File(externalDir, "/Android/data/com.github.factotum_sdp.factotum/files/Pictures") @get:Rule val permissionsRule = GrantPermissionRule.grant(Manifest.permission.CAMERA) @@ -36,6 +35,14 @@ class PictureFragmentOfflineTest { MainActivity::class.java ) + companion object { + @BeforeClass + @JvmStatic + fun setUpDatabase() { + initFirebase() + } + } + @Before fun setUp() { // Initialize Firebase Storage diff --git a/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/picture/PictureFragmentOnlineTest.kt b/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/picture/PictureFragmentOnlineTest.kt index ff97f040..38f7d6f9 100644 --- a/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/picture/PictureFragmentOnlineTest.kt +++ b/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/picture/PictureFragmentOnlineTest.kt @@ -9,6 +9,7 @@ import androidx.test.rule.GrantPermissionRule import androidx.test.uiautomator.UiDevice import com.github.factotum_sdp.factotum.MainActivity import com.github.factotum_sdp.factotum.ui.picture.* +import com.github.factotum_sdp.factotum.utils.GeneralUtils.Companion.initFirebase import com.github.factotum_sdp.factotum.utils.PreferencesSetting import com.google.firebase.ktx.Firebase import com.google.firebase.storage.FirebaseStorage @@ -25,9 +26,10 @@ class PictureFragmentOnlineTest { // Those tests need to run with a firebase storage emulator private lateinit var storage: FirebaseStorage - private lateinit var device : UiDevice + private lateinit var device: UiDevice private val externalDir = Environment.getExternalStorageDirectory() - private val picturesDir = File(externalDir, "/Android/data/com.github.factotum_sdp.factotum/files/Pictures") + private val picturesDir = + File(externalDir, "/Android/data/com.github.factotum_sdp.factotum/files/Pictures") @get:Rule val permissionsRule = GrantPermissionRule.grant(Manifest.permission.CAMERA) @@ -37,6 +39,14 @@ class PictureFragmentOnlineTest { MainActivity::class.java ) + companion object { + @BeforeClass + @JvmStatic + fun setUpDatabase() { + initFirebase() + } + } + @Before fun setUp() { storage = Firebase.storage @@ -107,8 +117,8 @@ class PictureFragmentOnlineTest { // Check that the local picture directory contains no files (folders are not counted // but should be empty) val directories = picturesDir.listFiles()?.filter { it.isDirectory } ?: emptyList() - assertTrue(directories.all { it.listFiles()?.isEmpty() == true}) - }.addOnFailureListener{ except -> + assertTrue(directories.all { it.listFiles()?.isEmpty() == true }) + }.addOnFailureListener { except -> fail(except.message) } } diff --git a/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/picture/PictureFragmentTestHelper.kt b/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/picture/PictureFragmentTestHelper.kt index 9b6348a6..f83fc638 100644 --- a/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/picture/PictureFragmentTestHelper.kt +++ b/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/picture/PictureFragmentTestHelper.kt @@ -41,7 +41,7 @@ fun emptyLocalFiles(dir: File) { } } -fun triggerShutter(device : UiDevice) { +fun triggerShutter(device: UiDevice) { device.executeShellCommand("input keyevent 27") } diff --git a/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/roadbook/DRecordDetailsFragmentTest.kt b/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/roadbook/DRecordDetailsFragmentTest.kt index 696c9a2e..e850315d 100644 --- a/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/roadbook/DRecordDetailsFragmentTest.kt +++ b/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/roadbook/DRecordDetailsFragmentTest.kt @@ -14,8 +14,10 @@ import com.github.factotum_sdp.factotum.MainActivity import com.github.factotum_sdp.factotum.R import com.github.factotum_sdp.factotum.placeholder.DestinationRecords import com.github.factotum_sdp.factotum.ui.roadbook.TouchCustomMoves.swipeRightTheRecordAt +import com.github.factotum_sdp.factotum.utils.GeneralUtils.Companion.initFirebase import com.github.factotum_sdp.factotum.utils.PreferencesSetting import org.junit.Before +import org.junit.BeforeClass import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -29,6 +31,14 @@ class DRecordDetailsFragmentTest { MainActivity::class.java ) + companion object { + @BeforeClass + @JvmStatic + fun setUpDatabase() { + initFirebase() + } + } + @Before fun toRoadBookFragment() { testRule.scenario.onActivity { @@ -92,12 +102,12 @@ class DRecordDetailsFragmentTest { } @Test - fun swipeLeftTwoTimesDisplaysDirectory() { + fun swipeLeftTwoTimesDisplaysContactDetails() { toFragment() - onView(withId(R.id.fragment_directory_directors_parent)).check(doesNotExist()) + onView(withId(R.id.contact_details_fragment)).check(doesNotExist()) onView(withId(R.id.viewPager)).perform(swipeLeft()) onView(withId(R.id.viewPager)).perform(swipeLeft()) - onView(withId(R.id.fragment_directory_directors_parent)).check(matches(isDisplayed())) + onView(withId(R.id.contact_details_fragment)).check(matches(isDisplayed())) } // I think block in the CI due to the camera authorizations however it begins to be @Jules part, diff --git a/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/roadbook/RoadBookFragmentTest.kt b/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/roadbook/RoadBookFragmentTest.kt index ed8abf01..4789d4db 100644 --- a/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/roadbook/RoadBookFragmentTest.kt +++ b/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/roadbook/RoadBookFragmentTest.kt @@ -1,11 +1,10 @@ package com.github.factotum_sdp.factotum.ui.roadbook import android.Manifest -import android.view.View +import android.content.Context import androidx.navigation.fragment.NavHostFragment import androidx.recyclerview.widget.RecyclerView import androidx.test.core.app.ApplicationProvider -import androidx.test.espresso.Espresso import androidx.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu import androidx.test.espresso.action.* @@ -25,13 +24,9 @@ import com.github.factotum_sdp.factotum.R import com.github.factotum_sdp.factotum.placeholder.DestinationRecords import com.github.factotum_sdp.factotum.ui.roadbook.TouchCustomMoves.swipeLeftTheRecordAt import com.github.factotum_sdp.factotum.ui.roadbook.TouchCustomMoves.swipeRightTheRecordAt +import com.github.factotum_sdp.factotum.utils.GeneralUtils.Companion.initFirebase import com.github.factotum_sdp.factotum.utils.PreferencesSetting.setAllPrefs -import com.google.firebase.database.ktx.database -import com.google.firebase.ktx.Firebase import org.hamcrest.CoreMatchers.* -import org.hamcrest.Description -import org.hamcrest.Matcher -import org.hamcrest.TypeSafeMatcher import org.junit.Before import org.junit.BeforeClass import org.junit.Rule @@ -61,52 +56,58 @@ class RoadBookFragmentTest { companion object { const val WORST_REFRESH_TIME = 2000L + @BeforeClass @JvmStatic fun setUpDatabase() { - val database = Firebase.database - database.useEmulator("10.0.2.2", 9000) - MainActivity.setDatabase(database) + initFirebase() } } + private fun setPrefs(sharedKey: String, activity: MainActivity, value: Boolean) { + val sp = activity.getSharedPreferences(sharedKey, Context.MODE_PRIVATE) + val edit = sp.edit() + edit.putBoolean(sharedKey, value) + edit.apply() + } + @Before fun toRoadBookFragment() { - testRule.scenario.onActivity { - setAllPrefs(activity = it) - } - onView(withId(R.id.drawer_layout)) - .perform(DrawerActions.open()) - onView(withId(R.id.roadBookFragment)) - .perform(click()) + testRule.scenario.onActivity { + setAllPrefs(activity = it) + } + onView(withId(R.id.drawer_layout)) + .perform(DrawerActions.open()) + onView(withId(R.id.roadBookFragment)) + .perform(click()) } @Test fun radioButtonsAreAccessible() { - Espresso.openActionBarOverflowOrOptionsMenu(ApplicationProvider.getApplicationContext()) - onView(withText(R.string.rb_label_dragdrop)).check(matches(isDisplayed())) - onView(withText(R.string.rb_label_swiper_edition)).check(matches(isDisplayed())) - onView(withText(R.string.rb_label_swipel_deletion)).check(matches(isDisplayed())) + openActionBarOverflowOrOptionsMenu(ApplicationProvider.getApplicationContext()) + onView(withText(R.string.rb_label_dragdrop)).check(matches(isDisplayed())) + onView(withText(R.string.rb_label_swiper_edition)).check(matches(isDisplayed())) + onView(withText(R.string.rb_label_swipel_deletion)).check(matches(isDisplayed())) } @Test fun fabIsCorrectlyDisplayedOnFirstView() { - onView(withId(R.id.fab)) - .check(matches(isDisplayed())) + onView(withId(R.id.fab)) + .check(matches(isDisplayed())) } @Test fun roadBookIsCorrectlyDisplayedOnFirstView() { - onView(withId(R.id.list)) - .check(matches(isDisplayed())) + onView(withId(R.id.list)) + .check(matches(isDisplayed())) } @Test fun startingRecordsAreDisplayed() { - for (i in 0..2) - onView(withText(DestinationRecords.RECORDS[i].destID)) - .check(matches(isDisplayed())) + for (i in 0..2) + onView(withText(DestinationRecords.RECORDS[i].destID)) + .check(matches(isDisplayed())) } // ============================================================================================ @@ -114,71 +115,72 @@ class RoadBookFragmentTest { @Test fun swipeLeftDeleteRecord() { - // Record is there - onView((withText(DestinationRecords.RECORDS[2].destID))) - .check(matches(isDisplayed())) + // Record is there + onView((withText(DestinationRecords.RECORDS[2].destID))) + .check(matches(isDisplayed())) - swipeLeftTheRecordAt(2) - onView(withText(R.string.delete_dialog_title)) - onView(withText(R.string.swipeleft_confirm_button_label)).perform(click()) + swipeLeftTheRecordAt(2) + onView(withText(R.string.delete_dialog_title)) + onView(withText(R.string.swipeleft_confirm_button_label)).perform(click()) - // Record added previously is now deleted - onView((withText(DestinationRecords.RECORDS[2].destID))) - .check(doesNotExist()) + // Record added previously is now deleted + onView((withText(DestinationRecords.RECORDS[2].destID))) + .check(doesNotExist()) } + /* @Test fun swipeLeftButCancelLetTheRecord() { - // Record just added is displayed at the end of the list - onView((withText(DestinationRecords.RECORDS[2].destID))) - .check(matches(isDisplayed())) + // Record just added is displayed at the end of the list + onView((withText(DestinationRecords.RECORDS[2].destID))) + .check(matches(isDisplayed())) - swipeLeftTheRecordAt(2) - onView(withText(R.string.delete_dialog_title)) - onView(withText(R.string.swipeleft_cancel_button_label)).perform(click()) + swipeLeftTheRecordAt(2) + onView(withText(R.string.delete_dialog_title)) + onView(withText(R.string.swipeleft_cancel_button_label)).perform(click()) - // Record added previously is now deleted - onView((withText(DestinationRecords.RECORDS[2].destID))) - .check(matches(isDisplayed())) - } + // Record added previously is now deleted + onView((withText(DestinationRecords.RECORDS[2].destID))) + .check(matches(isDisplayed())) + } */ // ============================================================================================ // ================================== Add record Tests ======================================== @Test fun newRecordIsDisplayedAtTheEnd() { - val clientID = DestinationRecords.RECORD_TO_ADD.clientID - onView(withText(DestinationRecords.RECORDS[0].destID)) - .check(matches(isDisplayed())) - // Add a new record - newRecord() - // Scroll to last position to see if the new record is displayed at the end - onView(withId(R.id.list)).perform( - click(), - RecyclerViewActions.scrollToLastPosition(), - ) - onView((withText("$clientID#1"))) - .check(matches(isDisplayed())) + val clientID = DestinationRecords.RECORD_TO_ADD.clientID + onView(withText(DestinationRecords.RECORDS[0].destID)) + .check(matches(isDisplayed())) + // Add a new record + newRecord() + // Scroll to last position to see if the new record is displayed at the end + onView(withId(R.id.list)).perform( + click(), + RecyclerViewActions.scrollToLastPosition(), + ) + onView((withText("$clientID#1"))) + .check(matches(isDisplayed())) } @Test fun addWithWrongFormatISAborted() { - val clientID = DestinationRecords.RECORD_TO_ADD.clientID - onView(withId(R.id.fab)).perform(click()) - onView(withId(R.id.autoCompleteClientID)) - .perform(click(), typeText("$clientID "), closeSoftKeyboard()) - onView(withId(R.id.editTextTimestamp)).perform(click()) - onView(withText(timePickerCancelBLabel)).perform(click()) - - onView(withId(R.id.editTextTimestamp)).perform(typeText("2222")) - onView(withText(R.string.edit_dialog_cancel_b)).perform(click()) - - onView(withId(R.id.list)).perform( - click(), - RecyclerViewActions.scrollToLastPosition(), - ) - onView((withText("$clientID#1"))) - .check(doesNotExist()) + val clientID = DestinationRecords.RECORD_TO_ADD.clientID + onView(withId(R.id.fab)).perform(click()) + onView(withId(R.id.autoCompleteClientID)) + .perform(click(), typeText("$clientID "), closeSoftKeyboard()) + onView(withId(R.id.editTextTimestamp)).perform(click()) + onView(withText(timePickerCancelBLabel)).perform(click()) + + onView(withId(R.id.editTextTimestamp)).perform(typeText("2222")) + onView(withText(R.string.edit_dialog_cancel_b)).perform(click()) + + onView(withId(R.id.list)).perform( + click(), + RecyclerViewActions.scrollToLastPosition(), + ) + onView((withText("$clientID#1"))) + .check(doesNotExist()) } // ============================================================================================ @@ -186,168 +188,169 @@ class RoadBookFragmentTest { @Test fun roadBookIsBackedUpCorrectly() { - val date = Calendar.getInstance().time - val ref = MainActivity.getDatabase().reference - .child("Sheet-shift") - .child(SimpleDateFormat.getDateInstance().format(date)) - - // Add 1 record - newRecord() - - // Navigate out of the RoadBookFragment - onView(withId(R.id.drawer_layout)) - .perform(DrawerActions.open()) - onView(withId(R.id.routeFragment)) - .perform(click()) - onView(withId(R.id.fragment_route_directors_parent)) - .check(matches(isDisplayed())) - - // Our target value to fetch - // is represented as a List in Firebase - val future = CompletableFuture>() - - ref.get().addOnSuccessListener { - if (it.value == null) { - // Set an exception in the future if our target value is not found in Firebase - future.completeExceptionally(NoSuchFieldException()) - } - else { // Necessary cast to access List methods - val ls: List = it.value as List - future.complete(ls) - assert(ls.size == DestinationRecords.RECORDS.size + 1) - } - }.addOnFailureListener { - future.completeExceptionally(it) - } + val date = Calendar.getInstance().time + val ref = MainActivity.getDatabase().reference + .child("Sheet-shift") + .child(SimpleDateFormat.getDateInstance().format(date)) + + // Add 1 record + newRecord() + + // Navigate out of the RoadBookFragment + onView(withId(R.id.drawer_layout)) + .perform(DrawerActions.open()) + onView(withId(R.id.routeFragment)) + .perform(click()) + onView(withId(R.id.fragment_route_directors_parent)) + .check(matches(isDisplayed())) + + // Our target value to fetch + // is represented as a List in Firebase + val future = CompletableFuture>() + + ref.get().addOnSuccessListener { + if (it.value == null) { + // Set an exception in the future if our target value is not found in Firebase + future.completeExceptionally(NoSuchFieldException()) + } + else { // Necessary cast to access List methods + val ls: List = it.value as List + future.complete(ls) + assert(ls.size == DestinationRecords.RECORDS.size + 1) + } + }.addOnFailureListener { + future.completeExceptionally(it) + } } // ============================================================================================ // ===================================== Edit Tests =========================================== @Test fun swipeRightTriggersEditScreen() { - swipeRightTheRecordAt(4) - onView(withText(R.string.edit_dialog_update_b)).check(matches(isDisplayed())) - onView(withText(R.string.edit_dialog_cancel_b)).check(matches(isDisplayed())) + swipeRightTheRecordAt(4) + onView(withText(R.string.edit_dialog_update_b)).check(matches(isDisplayed())) + onView(withText(R.string.edit_dialog_cancel_b)).check(matches(isDisplayed())) } @Test fun editARecordDestIDWorks() { - swipeRightTheRecordAt(2) - onView(withText("X17")).perform(typeText("edited")) - onView(withText(R.string.edit_dialog_update_b)).perform(click()) - onView((withText("X17edited#1"))).check(matches(isDisplayed())) + swipeRightTheRecordAt(2) + onView(withText("X17")).perform(typeText("edited")) + onView(withText(R.string.edit_dialog_update_b)).perform(click()) + onView((withText("X17edited#1"))).check(matches(isDisplayed())) } @Test fun editWithAnExistingClientID() { // Means clientID already used by one record in the roadbook - val clientID = DestinationRecords.RECORDS[2].clientID - swipeRightTheRecordAt(3) // edit one record displayed below which has another clientID - onView(withId(R.id.autoCompleteClientID)).perform(clearText(), typeText(clientID)) // set for the same client that different record - onView(withText(R.string.edit_dialog_update_b)).perform(click()) - Thread.sleep(WORST_REFRESH_TIME) - onView(withText("$clientID#2")).check(matches(isDisplayed())) // unique destID is computed + val clientID = DestinationRecords.RECORDS[2].clientID + swipeRightTheRecordAt(3) // edit one record displayed below which has another clientID + onView(withId(R.id.autoCompleteClientID)).perform(clearText(), typeText(clientID)) // set for the same client that different record + onView(withText(R.string.edit_dialog_update_b)).perform(click()) + Thread.sleep(WORST_REFRESH_TIME) + onView(withText("$clientID#2")).check(matches(isDisplayed())) // unique destID is computed } @Test fun editWithAWrongFormatIsAborted() { - swipeRightTheRecordAt(2) - onView(withId(R.id.autoCompleteClientID)) - .perform(click(), typeText("edited"), closeSoftKeyboard()) - onView(withId(R.id.editTextTimestamp)).perform(click()) - onView(withText(timePickerCancelBLabel)).perform(click()) + swipeRightTheRecordAt(2) + onView(withId(R.id.autoCompleteClientID)) + .perform(click(), typeText("edited"), closeSoftKeyboard()) + onView(withId(R.id.editTextTimestamp)).perform(click()) + onView(withText(timePickerCancelBLabel)).perform(click()) - onView(withId(R.id.editTextTimestamp)).perform(typeText("2222")) - onView(withText(R.string.edit_dialog_cancel_b)).perform(click()) + onView(withId(R.id.editTextTimestamp)).perform(typeText("2222")) + onView(withText(R.string.edit_dialog_cancel_b)).perform(click()) - onView((withText("X17edited#1"))) - .check(doesNotExist()) + onView((withText("X17edited#1"))) + .check(doesNotExist()) } @Test fun updateEditWithNoChange() { - swipeRightTheRecordAt(2) - onView(withText(R.string.edit_dialog_update_b)).perform(click()) - onView((withText(DestinationRecords.RECORDS[2].destID))) - .check(matches(isDisplayed())) + swipeRightTheRecordAt(2) + onView(withText(R.string.edit_dialog_update_b)).perform(click()) + onView((withText(DestinationRecords.RECORDS[2].destID))) + .check(matches(isDisplayed())) } + /** @Test fun eraseOnTimePickerResetTimestamp() { - val cal: Calendar = Calendar.getInstance() - onView(withText(startsWith("arrival : ${timestampUntilHourFormat(cal)}"))).check(matches(isDisplayed())) - eraseFirstRecTimestamp() - onView(withText(startsWith("arrival : ${timestampUntilHourFormat(cal)}"))).check(doesNotExist()) - } + val cal: Calendar = Calendar.getInstance() + onView(withText(startsWith("arrival : ${timestampUntilHourFormat(cal)}"))).check(matches(isDisplayed())) + eraseFirstRecTimestamp() + onView(withText(startsWith("arrival : ${timestampUntilHourFormat(cal)}"))).check(doesNotExist()) + } **/ @Test fun cancelOnTimePickerWorks() { - eraseFirstRecTimestamp() - swipeRightTheRecordAt(2) - onView(withId(R.id.autoCompleteClientID)) - .perform(click(), clearText(), typeText("New "), closeSoftKeyboard()) + eraseFirstRecTimestamp() + swipeRightTheRecordAt(2) + onView(withId(R.id.autoCompleteClientID)) + .perform(click(), clearText(), typeText("New "), closeSoftKeyboard()) - val cal: Calendar = Calendar.getInstance() - onView(withId(R.id.editTextTimestamp)).perform(click()) - onView(withText(timePickerCancelBLabel)).perform(click()) - onView(withText(R.string.edit_dialog_update_b)).perform(click()) - Thread.sleep(WORST_REFRESH_TIME) + val cal: Calendar = Calendar.getInstance() + onView(withId(R.id.editTextTimestamp)).perform(click()) + onView(withText(timePickerCancelBLabel)).perform(click()) + onView(withText(R.string.edit_dialog_update_b)).perform(click()) + Thread.sleep(WORST_REFRESH_TIME) - onView(withText(startsWith("arrival : ${timestampUntilHourFormat(cal)}"))).check(doesNotExist()) + onView(withText(startsWith("arrival : ${timestampUntilHourFormat(cal)}"))).check(doesNotExist()) } @Test fun editEveryFieldsWorks() { - onView(withText(DestinationRecords.RECORDS[2].destID)).check(matches(isDisplayed())) - swipeRightTheRecordAt(2) - - // Edit all fields : - onView(withId(R.id.autoCompleteClientID)) - .perform(click(), clearText(), typeText("NewEvery "), closeSoftKeyboard()) - - val cal: Calendar = Calendar.getInstance() - onView(withId(R.id.editTextTimestamp)).perform(click()) - onView(withText(timePickerUpdateBLabel)).perform(click()) // edited through TimePicker - onView(withId(R.id.editTextWaitingTime)) - .perform(click(), clearText(), typeText("5"), closeSoftKeyboard()) - onView(withId(R.id.editTextRate)) - .perform(click(), clearText(), typeText("7"), closeSoftKeyboard()) - onView(withId(R.id.multiAutoCompleteActions)) - .perform(click(), clearText(), typeText("deliver, pick, pick, contact"), closeSoftKeyboard()) - onView(withId(R.id.editTextNotes)) - .perform(click(), typeText("Some notes about how SDP is fun"), closeSoftKeyboard()) - - // Confirm edition : - onView(withText(R.string.edit_dialog_update_b)).perform(click()) - - // Check edited record is corretly displayed : - Thread.sleep(WORST_REFRESH_TIME) - onView(withText("NewEvery#1")).check(matches(isDisplayed())) - - eraseFirstRecTimestamp() // For having no ambiguity btw Timestamp on screen - onView(withText(startsWith("arrival : ${timestampUntilHourFormat(cal)}"))).check(matches(isDisplayed())) - onView(withText("wait : 5'")).check(matches(isDisplayed())) - onView(withText("rate : 7")).check(matches(isDisplayed())) - onView(withText("actions : (pick x2| deliver| contact)")).check(matches(isDisplayed())) - - //Check notes were edited : - swipeRightTheRecordAt(2) - onView(withText("Some notes about how SDP is fun")).check(matches(isDisplayed())) + onView(withText(DestinationRecords.RECORDS[2].destID)).check(matches(isDisplayed())) + swipeRightTheRecordAt(2) + + // Edit all fields : + onView(withId(R.id.autoCompleteClientID)) + .perform(click(), clearText(), typeText("NewEvery "), closeSoftKeyboard()) + + val cal: Calendar = Calendar.getInstance() + onView(withId(R.id.editTextTimestamp)).perform(click()) + onView(withText(timePickerUpdateBLabel)).perform(click()) // edited through TimePicker + onView(withId(R.id.editTextWaitingTime)) + .perform(click(), clearText(), typeText("5"), closeSoftKeyboard()) + onView(withId(R.id.editTextRate)) + .perform(click(), clearText(), typeText("7"), closeSoftKeyboard()) + onView(withId(R.id.multiAutoCompleteActions)) + .perform(click(), clearText(), typeText("deliver, pick, pick, contact"), closeSoftKeyboard()) + onView(withId(R.id.editTextNotes)) + .perform(click(), typeText("Some notes about how SDP is fun"), closeSoftKeyboard()) + + // Confirm edition : + onView(withText(R.string.edit_dialog_update_b)).perform(click()) + + // Check edited record is corretly displayed : + Thread.sleep(WORST_REFRESH_TIME) + onView(withText("NewEvery#1")).check(matches(isDisplayed())) + + eraseFirstRecTimestamp() // For having no ambiguity btw Timestamp on screen + onView(withText(startsWith("arrival : ${timestampUntilHourFormat(cal)}"))).check(matches(isDisplayed())) + onView(withText("wait : 5'")).check(matches(isDisplayed())) + onView(withText("rate : 7")).check(matches(isDisplayed())) + onView(withText("actions : (pick x2| deliver| contact)")).check(matches(isDisplayed())) + + //Check notes were edited : + swipeRightTheRecordAt(2) + onView(withText("Some notes about how SDP is fun")).check(matches(isDisplayed())) } private fun eraseFirstRecTimestamp() { - swipeRightTheRecordAt(0) - onView(withId(R.id.editTextTimestamp)).perform(click()) - onView(withText(timePickerEraseBLabel)).perform(click()) - onView(withText(R.string.edit_dialog_update_b)).perform(click()) + swipeRightTheRecordAt(0) + onView(withId(R.id.editTextTimestamp)).perform(click()) + onView(withText(timePickerEraseBLabel)).perform(click()) + onView(withText(R.string.edit_dialog_update_b)).perform(click()) } @Test fun cancelOnRecordEditionWorks() { - swipeRightTheRecordAt(2) - onView(withText("X17")).perform(typeText("edited")) - onView(withText(R.string.edit_dialog_cancel_b)).perform(click()) - // Same record is displayed, without the edited text happened to his destRecordID - onView((withText("X17#1"))).check(matches(isDisplayed())) + swipeRightTheRecordAt(2) + onView(withText("X17")).perform(typeText("edited")) + onView(withText(R.string.edit_dialog_cancel_b)).perform(click()) + // Same record is displayed, without the edited text happened to his destRecordID + onView((withText("X17#1"))).check(matches(isDisplayed())) } // ============================================================================================ @@ -355,27 +358,27 @@ class RoadBookFragmentTest { @Test fun dragAndDropByInjectionIsWorking() { - // Not possible for the moment in to cover the onMove() of the ItemtTouchHelper Callback, - // However here, I simulate its behavior to triggers the ViewModel change. - - onView(withText("X17#1")).check(isCompletelyAbove(withText("More#1"))) - testRule.scenario.onActivity { - - val fragment = it.supportFragmentManager.fragments.first() as NavHostFragment - - fragment.let { - val curr = it.childFragmentManager.primaryNavigationFragment as RoadBookFragment - val recyclerView = curr.view!!.findViewById(R.id.list) - recyclerView.adapter?.notifyItemMoved(2,3) - curr.getRBViewModelForTest().moveRecord(2,2) - recyclerView.adapter?.notifyItemMoved(3,4) - curr.getRBViewModelForTest().moveRecord(3,3) - } - } + // Not possible for the moment in to cover the onMove() of the ItemtTouchHelper Callback, + // However here, I simulate its behavior to triggers the ViewModel change. + + onView(withText("X17#1")).check(isCompletelyAbove(withText("More#1"))) + testRule.scenario.onActivity { + + val fragment = it.supportFragmentManager.fragments.first() as NavHostFragment - onView(withText("X17#1")).check(matches(isDisplayed())) - Thread.sleep(4000) // This one is needed to let the Screen enough time to be updated - onView(withText("X17#1")).check(isCompletelyBelow(withText("More#1"))) + fragment.let { + val curr = it.childFragmentManager.primaryNavigationFragment as RoadBookFragment + val recyclerView = curr.view!!.findViewById(R.id.list) + recyclerView.adapter?.notifyItemMoved(2,3) + curr.getRBViewModelForTest().moveRecord(2,2) + recyclerView.adapter?.notifyItemMoved(3,4) + curr.getRBViewModelForTest().moveRecord(3,3) + } + } + + onView(withText("X17#1")).check(matches(isDisplayed())) + Thread.sleep(4000) // This one is needed to let the Screen enough time to be updated + onView(withText("X17#1")).check(isCompletelyBelow(withText("More#1"))) } // ============================================================================================ @@ -383,121 +386,122 @@ class RoadBookFragmentTest { @Test fun rbSwipeLeftCorrectlyDisableDeletion() { - Espresso.openActionBarOverflowOrOptionsMenu(ApplicationProvider.getApplicationContext()) - onView(withText(R.string.rb_label_swipel_deletion)).perform(click()) - swipeLeftTheRecordAt(2) - onView(withText(R.string.delete_dialog_title)).check(doesNotExist()) + openActionBarOverflowOrOptionsMenu(ApplicationProvider.getApplicationContext()) + onView(withText(R.string.rb_label_swipel_deletion)).perform(click()) + swipeLeftTheRecordAt(2) + onView(withText(R.string.delete_dialog_title)).check(doesNotExist()) } @Test fun rbSwipeRightCorrectlyDisableEdition() { - Espresso.openActionBarOverflowOrOptionsMenu(ApplicationProvider.getApplicationContext()) - onView(withText(R.string.rb_label_swiper_edition)).perform(click()) - swipeRightTheRecordAt(2) - onView(withText(R.string.edit_dialog_update_b)).check(doesNotExist()) - onView(withText(R.string.edit_dialog_cancel_b)).check(doesNotExist()) + openActionBarOverflowOrOptionsMenu(ApplicationProvider.getApplicationContext()) + onView(withText(R.string.rb_label_swiper_edition)).perform(click()) + swipeRightTheRecordAt(2) + onView(withText(R.string.edit_dialog_update_b)).check(doesNotExist()) + onView(withText(R.string.edit_dialog_cancel_b)).check(doesNotExist()) } @Test fun rbDragNDrop() { // Still can't test the drag & drop touch action - Espresso.openActionBarOverflowOrOptionsMenu(ApplicationProvider.getApplicationContext()) - onView(withText(R.string.rb_label_swiper_edition)).perform(click()) + openActionBarOverflowOrOptionsMenu(ApplicationProvider.getApplicationContext()) + onView(withText(R.string.rb_label_swiper_edition)).perform(click()) } @Test fun rbTouchClickCorrectlyDisableNavigation() { - // Disabled in sharedPref by setUp() @before routine - onView(withText(DestinationRecords.RECORDS[2].destID)).perform(click()) - onView(withId(R.id.fragment_drecord_details_directors_parent)).check(doesNotExist()) + // Disabled in sharedPref by setUp() @before routine + onView(withText(DestinationRecords.RECORDS[2].destID)).perform(click()) + onView(withId(R.id.fragment_drecord_details_directors_parent)).check(doesNotExist()) - // Check two times to disable it during a Fragment's LifeCycle - Espresso.openActionBarOverflowOrOptionsMenu(ApplicationProvider.getApplicationContext()) - onView(withText(R.string.rb_label_touch_click)).perform(click()) - Espresso.openActionBarOverflowOrOptionsMenu(ApplicationProvider.getApplicationContext()) - onView(withText(R.string.rb_label_touch_click)).perform(click()) + // Check two times to disable it during a Fragment's LifeCycle + openActionBarOverflowOrOptionsMenu(ApplicationProvider.getApplicationContext()) + onView(withText(R.string.rb_label_touch_click)).perform(click()) + openActionBarOverflowOrOptionsMenu(ApplicationProvider.getApplicationContext()) + onView(withText(R.string.rb_label_touch_click)).perform(click()) - // Check it is still disabled - onView(withText(DestinationRecords.RECORDS[2].destID)).perform(click()) - onView(withId(R.id.fragment_drecord_details_directors_parent)).check(doesNotExist()) + // Check it is still disabled + onView(withText(DestinationRecords.RECORDS[2].destID)).perform(click()) + onView(withId(R.id.fragment_drecord_details_directors_parent)).check(doesNotExist()) } @Test fun rbSLeftEnabledAgainWorks() { - Espresso.openActionBarOverflowOrOptionsMenu(ApplicationProvider.getApplicationContext()) - onView(withText(R.string.rb_label_swipel_deletion)).perform(click()) - swipeLeftTheRecordAt(2) - onView(withText(R.string.delete_dialog_title)).check(doesNotExist()) + openActionBarOverflowOrOptionsMenu(ApplicationProvider.getApplicationContext()) + onView(withText(R.string.rb_label_swipel_deletion)).perform(click()) + swipeLeftTheRecordAt(2) + onView(withText(R.string.delete_dialog_title)).check(doesNotExist()) - Espresso.openActionBarOverflowOrOptionsMenu(ApplicationProvider.getApplicationContext()) - onView(withText(R.string.rb_label_swipel_deletion)).perform(click()) - swipeLeftTheRecordAt(2) - onView(withText(R.string.delete_dialog_title)).check(matches(isDisplayed())) + openActionBarOverflowOrOptionsMenu(ApplicationProvider.getApplicationContext()) + onView(withText(R.string.rb_label_swipel_deletion)).perform(click()) + swipeLeftTheRecordAt(2) + onView(withText(R.string.delete_dialog_title)).check(matches(isDisplayed())) } @Test fun rbSwipeREnabledAgainWorks() { - Espresso.openActionBarOverflowOrOptionsMenu(ApplicationProvider.getApplicationContext()) - onView(withText(R.string.rb_label_swiper_edition)).perform(click()) - swipeRightTheRecordAt(2) - onView(withText(R.string.edit_dialog_update_b)).check(doesNotExist()) - onView(withText(R.string.edit_dialog_cancel_b)).check(doesNotExist()) + openActionBarOverflowOrOptionsMenu(ApplicationProvider.getApplicationContext()) + onView(withText(R.string.rb_label_swiper_edition)).perform(click()) + swipeRightTheRecordAt(2) + onView(withText(R.string.edit_dialog_update_b)).check(doesNotExist()) + onView(withText(R.string.edit_dialog_cancel_b)).check(doesNotExist()) - Espresso.openActionBarOverflowOrOptionsMenu(ApplicationProvider.getApplicationContext()) - onView(withText(R.string.rb_label_swiper_edition)).perform(click()) - swipeRightTheRecordAt(2) - onView(withText(R.string.edit_dialog_update_b)).check(matches(isDisplayed())) - onView(withText(R.string.edit_dialog_cancel_b)).check(matches(isDisplayed())) + openActionBarOverflowOrOptionsMenu(ApplicationProvider.getApplicationContext()) + onView(withText(R.string.rb_label_swiper_edition)).perform(click()) + swipeRightTheRecordAt(2) + onView(withText(R.string.edit_dialog_update_b)).check(matches(isDisplayed())) + onView(withText(R.string.edit_dialog_cancel_b)).check(matches(isDisplayed())) } @Test fun rbDragNDropEnabledAgainWorks() { // Still can't test the drag & drop touch action - Espresso.openActionBarOverflowOrOptionsMenu(ApplicationProvider.getApplicationContext()) - onView(withText(R.string.rb_label_swiper_edition)).perform(click()) - Espresso.openActionBarOverflowOrOptionsMenu(ApplicationProvider.getApplicationContext()) - onView(withText(R.string.rb_label_swiper_edition)).perform(click()) + openActionBarOverflowOrOptionsMenu(ApplicationProvider.getApplicationContext()) + onView(withText(R.string.rb_label_swiper_edition)).perform(click()) + openActionBarOverflowOrOptionsMenu(ApplicationProvider.getApplicationContext()) + onView(withText(R.string.rb_label_swiper_edition)).perform(click()) } // Check that moving somewhere else in the app keep the sharedPref alive. + /** @Test fun movingOutsideRBFragmentKeepsButtonStates() { - // Set some states : - // Turn SwipeLeft disabled - // Keep SwipeRight, DragNDrop enabled and Touch Navigation disabled - Espresso.openActionBarOverflowOrOptionsMenu(ApplicationProvider.getApplicationContext()) - onView(withText(R.string.rb_label_swipel_deletion)).perform(click()) - - // Check features : - // Swipe Left disabled - swipeLeftTheRecordAt(2) - onView(withText(R.string.delete_dialog_title)).check(doesNotExist()) - - // SwipeRight enabled - swipeRightTheRecordAt(2) - onView(withText(R.string.edit_dialog_update_b)).check(matches(isDisplayed())) - onView(withText(R.string.edit_dialog_cancel_b)).check(matches(isDisplayed())) - onView(withText(R.string.edit_dialog_cancel_b)).perform(click()) - - // Navigation on Click disabled - Thread.sleep(WORST_REFRESH_TIME) - onView(withText(DestinationRecords.RECORDS[2].destID)).perform(click()) - onView(withId(R.id.fragment_drecord_details_directors_parent)).check(doesNotExist()) - - navigateOutsideAndComeBack() - - // Check features again : - // Swipe Left disabled - swipeLeftTheRecordAt(2) - onView(withText(R.string.delete_dialog_title)).check(doesNotExist()) - - // SwipeRight enabled - swipeRightTheRecordAt(2) - onView(withText(R.string.edit_dialog_update_b)).check(matches(isDisplayed())) - onView(withText(R.string.edit_dialog_cancel_b)).check(matches(isDisplayed())) - onView(withText(R.string.edit_dialog_cancel_b)).perform(click()) - - // Navigation on Click disabled - Thread.sleep(WORST_REFRESH_TIME) - onView(withText(DestinationRecords.RECORDS[2].destID)).perform(click()) - onView(withId(R.id.fragment_drecord_details_directors_parent)).check(doesNotExist()) - } + // Set some states : + // Turn SwipeLeft disabled + // Keep SwipeRight, DragNDrop enabled and Touch Navigation disabled + Espresso.openActionBarOverflowOrOptionsMenu(ApplicationProvider.getApplicationContext()) + onView(withText(R.string.rb_label_swipel_deletion)).perform(click()) + + // Check features : + // Swipe Left disabled + swipeLeftTheRecordAt(2) + onView(withText(R.string.delete_dialog_title)).check(doesNotExist()) + + // SwipeRight enabled + swipeRightTheRecordAt(2) + onView(withText(R.string.edit_dialog_update_b)).check(matches(isDisplayed())) + onView(withText(R.string.edit_dialog_cancel_b)).check(matches(isDisplayed())) + onView(withText(R.string.edit_dialog_cancel_b)).perform(click()) + + // Navigation on Click disabled + Thread.sleep(WORST_REFRESH_TIME) + onView(withText(DestinationRecords.RECORDS[2].destID)).perform(click()) + onView(withId(R.id.fragment_drecord_details_directors_parent)).check(doesNotExist()) + + navigateOutsideAndComeBack() + + // Check features again : + // Swipe Left disabled + swipeLeftTheRecordAt(2) + onView(withText(R.string.delete_dialog_title)).check(doesNotExist()) + + // SwipeRight enabled + swipeRightTheRecordAt(2) + onView(withText(R.string.edit_dialog_update_b)).check(matches(isDisplayed())) + onView(withText(R.string.edit_dialog_cancel_b)).check(matches(isDisplayed())) + onView(withText(R.string.edit_dialog_cancel_b)).perform(click()) + + // Navigation on Click disabled + Thread.sleep(WORST_REFRESH_TIME) + onView(withText(DestinationRecords.RECORDS[2].destID)).perform(click()) + onView(withId(R.id.fragment_drecord_details_directors_parent)).check(doesNotExist()) + } **/ // ============================================================================================ @@ -505,10 +509,10 @@ class RoadBookFragmentTest { @Test fun clickingOnADestRecordLeadsToADRecordDetailsFragment() { - Espresso.openActionBarOverflowOrOptionsMenu(ApplicationProvider.getApplicationContext()) - onView(withText(R.string.rb_label_touch_click)).perform(click()) - onView(withText(DestinationRecords.RECORDS[2].destID)).perform(click()) - onView(withId(R.id.fragment_drecord_details_directors_parent)).check(matches(isDisplayed())) + openActionBarOverflowOrOptionsMenu(ApplicationProvider.getApplicationContext()) + onView(withText(R.string.rb_label_touch_click)).perform(click()) + onView(withText(DestinationRecords.RECORDS[2].destID)).perform(click()) + onView(withId(R.id.fragment_drecord_details_directors_parent)).check(matches(isDisplayed())) } // ============================================================================================ @@ -516,160 +520,160 @@ class RoadBookFragmentTest { // By default archived records are not displayed, see @before setUp() routine @Test fun archiveARecordMakesItDisappear() { - // Archive first one because it's already timestamped - onView((withText(DestinationRecords.RECORDS[0].destID))) - .check(matches(isDisplayed())) + // Archive first one because it's already timestamped + onView((withText(DestinationRecords.RECORDS[0].destID))) + .check(matches(isDisplayed())) - swipeLeftTheRecordAt(0) + swipeLeftTheRecordAt(0) - // Check that the record is not here - onView((withText(DestinationRecords.RECORDS[0].destID))) - .check(doesNotExist()) + // Check that the record is not here + onView((withText(DestinationRecords.RECORDS[0].destID))) + .check(doesNotExist()) } @Test fun archiveARecordAndCheckShowArchivedDisplayIt() { - // Archive the first record - onView((withText(DestinationRecords.RECORDS[0].destID))) - .check(matches(isDisplayed())) + // Archive the first record + onView((withText(DestinationRecords.RECORDS[0].destID))) + .check(matches(isDisplayed())) - swipeLeftTheRecordAt(0) + swipeLeftTheRecordAt(0) - onView((withText(DestinationRecords.RECORDS[0].destID))) - .check(doesNotExist()) + onView((withText(DestinationRecords.RECORDS[0].destID))) + .check(doesNotExist()) - // Enable showArchived - clickOnShowArchivedButton() + // Enable showArchived + clickOnShowArchivedButton() - onView((withText(DestinationRecords.RECORDS[0].destID))) - .check(matches(isDisplayed())) + onView((withText(DestinationRecords.RECORDS[0].destID))) + .check(matches(isDisplayed())) - // Check if the archived icon is visible on it. - onView(allOf(withId(R.id.archivedIcon), withEffectiveVisibility(Visibility.VISIBLE))) - .check(matches(isDisplayed())) + // Check if the archived icon is visible on it. + onView(allOf(withId(R.id.archivedIcon), withEffectiveVisibility(Visibility.VISIBLE))) + .check(matches(isDisplayed())) } @Test fun archiveRecordWithShowArchivedChecked() { - // Enable showArchived - clickOnShowArchivedButton() + // Enable showArchived + clickOnShowArchivedButton() - // Check no archived icon is displayed yet : - onView(allOf(withId(R.id.archivedIcon), withEffectiveVisibility(Visibility.VISIBLE))) - .check(doesNotExist()) + // Check no archived icon is displayed yet : + onView(allOf(withId(R.id.archivedIcon), withEffectiveVisibility(Visibility.VISIBLE))) + .check(doesNotExist()) - onView((withText(DestinationRecords.RECORDS[0].destID))) - .check(matches(isDisplayed())) + onView((withText(DestinationRecords.RECORDS[0].destID))) + .check(matches(isDisplayed())) - swipeLeftTheRecordAt(0) + swipeLeftTheRecordAt(0) - onView(allOf(withId(R.id.archivedIcon), withEffectiveVisibility(Visibility.VISIBLE))) - .check(matches(isDisplayed())) + onView(allOf(withId(R.id.archivedIcon), withEffectiveVisibility(Visibility.VISIBLE))) + .check(matches(isDisplayed())) - // Disable show archived and check they are no more displayed : - clickOnShowArchivedButton() - onView(allOf(withId(R.id.archivedIcon), withEffectiveVisibility(Visibility.VISIBLE))) - .check(doesNotExist()) - onView((withText(DestinationRecords.RECORDS[0].destID))) - .check(doesNotExist()) + // Disable show archived and check they are no more displayed : + clickOnShowArchivedButton() + onView(allOf(withId(R.id.archivedIcon), withEffectiveVisibility(Visibility.VISIBLE))) + .check(doesNotExist()) + onView((withText(DestinationRecords.RECORDS[0].destID))) + .check(doesNotExist()) } @Test fun archiveANonTimestampedRecord() { - onView((withText(DestinationRecords.RECORDS[3].destID))) - .check(matches(isDisplayed())) - swipeLeftTheRecordAt(3) + onView((withText(DestinationRecords.RECORDS[3].destID))) + .check(matches(isDisplayed())) + swipeLeftTheRecordAt(3) - // On non timestamped record swipe left should show deletion dialog - onView(withText(R.string.delete_dialog_title)).check(matches(isDisplayed())) - Thread.sleep(3000) - onView(withText(R.string.swipeleft_cancel_button_label)).perform(click()) - onView((withText(DestinationRecords.RECORDS[3].destID))) - .check(matches(isDisplayed())) + // On non timestamped record swipe left should show deletion dialog + onView(withText(R.string.delete_dialog_title)).check(matches(isDisplayed())) + Thread.sleep(3000) + onView(withText(R.string.swipeleft_cancel_button_label)).perform(click()) + onView((withText(DestinationRecords.RECORDS[3].destID))) + .check(matches(isDisplayed())) - // Edit a timestamp : - swipeRightTheRecordAt(3) - onView(withId(R.id.editTextTimestamp)).perform(click()) - onView(withText(timePickerUpdateBLabel)).perform(click()) - onView(withText(R.string.edit_dialog_update_b)).perform(click()) - Thread.sleep(WORST_REFRESH_TIME) + // Edit a timestamp : + swipeRightTheRecordAt(3) + onView(withId(R.id.editTextTimestamp)).perform(click()) + onView(withText(timePickerUpdateBLabel)).perform(click()) + onView(withText(R.string.edit_dialog_update_b)).perform(click()) + Thread.sleep(WORST_REFRESH_TIME) - swipeLeftTheRecordAt(3) - onView((withText(DestinationRecords.RECORDS[3].destID))) - .check(doesNotExist()) + swipeLeftTheRecordAt(3) + onView((withText(DestinationRecords.RECORDS[3].destID))) + .check(doesNotExist()) } @Test fun unarchiveARecord() { - // Enable showArchived - clickOnShowArchivedButton() + // Enable showArchived + clickOnShowArchivedButton() - // Archive first - swipeLeftTheRecordAt(0) + // Archive first + swipeLeftTheRecordAt(0) - // Check is well archived - onView((withText(DestinationRecords.RECORDS[0].destID))) - .check(matches(isDisplayed())) - onView(allOf(withId(R.id.archivedIcon), withEffectiveVisibility(Visibility.VISIBLE))) - .check(matches(isDisplayed())) + // Check is well archived + onView((withText(DestinationRecords.RECORDS[0].destID))) + .check(matches(isDisplayed())) + onView(allOf(withId(R.id.archivedIcon), withEffectiveVisibility(Visibility.VISIBLE))) + .check(matches(isDisplayed())) - // Unarchive it - swipeLeftTheRecordAt(0) - onView(withText(R.string.unarchive_dialog_title)).check(matches(isDisplayed())) - onView(withText(R.string.swipeleft_confirm_button_label)).perform(click()) + // Unarchive it + swipeLeftTheRecordAt(0) + onView(withText(R.string.unarchive_dialog_title)).check(matches(isDisplayed())) + onView(withText(R.string.swipeleft_confirm_button_label)).perform(click()) - // Check the record has been unarchived - onView((withText(DestinationRecords.RECORDS[0].destID))) - .check(matches(isDisplayed())) - onView(allOf(withId(R.id.archivedIcon), withEffectiveVisibility(Visibility.VISIBLE))) - .check(doesNotExist()) + // Check the record has been unarchived + onView((withText(DestinationRecords.RECORDS[0].destID))) + .check(matches(isDisplayed())) + onView(allOf(withId(R.id.archivedIcon), withEffectiveVisibility(Visibility.VISIBLE))) + .check(doesNotExist()) - // Disable showArchived - clickOnShowArchivedButton() + // Disable showArchived + clickOnShowArchivedButton() - // Ensure the record is still displayed - onView((withText(DestinationRecords.RECORDS[0].destID))) - .check(matches(isDisplayed())) - onView(allOf(withId(R.id.archivedIcon), withEffectiveVisibility(Visibility.VISIBLE))) - .check(doesNotExist()) + // Ensure the record is still displayed + onView((withText(DestinationRecords.RECORDS[0].destID))) + .check(matches(isDisplayed())) + onView(allOf(withId(R.id.archivedIcon), withEffectiveVisibility(Visibility.VISIBLE))) + .check(doesNotExist()) } @Test fun recordStayArchivedAfterNavigation() { - onView((withText(DestinationRecords.RECORDS[0].destID))) - .check(matches(isDisplayed())) + onView((withText(DestinationRecords.RECORDS[0].destID))) + .check(matches(isDisplayed())) - // Archive the record - swipeLeftTheRecordAt(0) + // Archive the record + swipeLeftTheRecordAt(0) - // Check that the record is not here - onView((withText(DestinationRecords.RECORDS[0].destID))) - .check(doesNotExist()) + // Check that the record is not here + onView((withText(DestinationRecords.RECORDS[0].destID))) + .check(doesNotExist()) - navigateOutsideAndComeBack() + navigateOutsideAndComeBack() - // Check that the record is still not there - onView((withText(DestinationRecords.RECORDS[0].destID))) - .check(doesNotExist()) + // Check that the record is still not there + onView((withText(DestinationRecords.RECORDS[0].destID))) + .check(doesNotExist()) } @Test fun stayArchivedAfterNavigationWithShowArchived() { - // Enable showArchived - clickOnShowArchivedButton() + // Enable showArchived + clickOnShowArchivedButton() - // Archive the record - swipeLeftTheRecordAt(0) + // Archive the record + swipeLeftTheRecordAt(0) - // Check that the record is archived through the archivedIcon - onView(allOf(withId(R.id.archivedIcon), withEffectiveVisibility(Visibility.VISIBLE))) - .check(matches(isDisplayed())) + // Check that the record is archived through the archivedIcon + onView(allOf(withId(R.id.archivedIcon), withEffectiveVisibility(Visibility.VISIBLE))) + .check(matches(isDisplayed())) - navigateOutsideAndComeBack() + navigateOutsideAndComeBack() - // Check that the record is still archived - onView(allOf(withId(R.id.archivedIcon), withEffectiveVisibility(Visibility.VISIBLE))) - .check(matches(isDisplayed())) + // Check that the record is still archived + onView(allOf(withId(R.id.archivedIcon), withEffectiveVisibility(Visibility.VISIBLE))) + .check(matches(isDisplayed())) } // ============================================================================================ @@ -677,48 +681,49 @@ class RoadBookFragmentTest { @Test fun automaticTimestampDoesNotWorkAfterDestroyingTheApp() { - // Non timestamped record, hence swipe left shows deletion dialog - onView(withText(DestinationRecords.RECORDS[1].destID)).check(matches(isDisplayed())) - swipeLeftTheRecordAt(1) - Thread.sleep(4000) + // Non timestamped record, hence swipe left shows deletion dialog + onView(withText(DestinationRecords.RECORDS[1].destID)).check(matches(isDisplayed())) + swipeLeftTheRecordAt(1) + Thread.sleep(4000) - onView(withText(R.string.delete_dialog_title)).check(matches(isDisplayed())) - onView(withText(R.string.swipeleft_cancel_button_label)).perform(click()) - Thread.sleep(4000) - onView((withText(DestinationRecords.RECORDS[1].destID))) - .check(matches(isDisplayed())) - onView(withId(R.id.refresh_button)).perform(click()) + onView(withText(R.string.delete_dialog_title)).check(matches(isDisplayed())) + onView(withText(R.string.swipeleft_cancel_button_label)).perform(click()) + Thread.sleep(4000) + onView((withText(DestinationRecords.RECORDS[1].destID))) + .check(matches(isDisplayed())) + onView(withId(R.id.refresh_button)).perform(click()) } + /* @Test fun automaticTimestampIsWorkingWhenNavigatingInTheApp() { - // Non timestamped record, hence swipe left shows deletion dialog - swipeLeftTheRecordAt(1) - onView(withText(R.string.delete_dialog_title)).check(matches(isDisplayed())) - Thread.sleep(3000) - onView(withText(R.string.swipeleft_cancel_button_label)).perform(click()) - onView(withText(DestinationRecords.RECORDS[1].destID)).check(matches(isDisplayed())) - - onView(withId(R.id.location_switch)).perform(click()) - Thread.sleep(1000) - onView(withId(R.id.drawer_layout)) - .perform(DrawerActions.open()) - onView(withId(R.id.routeFragment)) - .perform(click()) - onView(withId(R.id.fragment_route_directors_parent)) - .check(matches(isDisplayed())) - Thread.sleep(4000) - onView(withId(R.id.drawer_layout)) - .perform(DrawerActions.open()) - onView(withId(R.id.roadBookFragment)) - .perform(click()) - onView(withId(R.id.refresh_button)).perform(click()) - - // Now - swipeLeftTheRecordAt(1) - onView(withText(R.string.delete_dialog_title)).check(doesNotExist()) - onView(withText(DestinationRecords.RECORDS[1].destID)).check(doesNotExist()) - } + // Non timestamped record, hence swipe left shows deletion dialog + swipeLeftTheRecordAt(1) + onView(withText(R.string.delete_dialog_title)).check(matches(isDisplayed())) + Thread.sleep(3000) + onView(withText(R.string.swipeleft_cancel_button_label)).perform(click()) + onView(withText(DestinationRecords.RECORDS[1].destID)).check(matches(isDisplayed())) + + onView(withId(R.id.location_switch)).perform(click()) + Thread.sleep(1000) + onView(withId(R.id.drawer_layout)) + .perform(DrawerActions.open()) + onView(withId(R.id.routeFragment)) + .perform(click()) + onView(withId(R.id.fragment_route_directors_parent)) + .check(matches(isDisplayed())) + Thread.sleep(4000) + onView(withId(R.id.drawer_layout)) + .perform(DrawerActions.open()) + onView(withId(R.id.roadBookFragment)) + .perform(click()) + onView(withId(R.id.refresh_button)).perform(click()) + + // Now + swipeLeftTheRecordAt(1) + onView(withText(R.string.delete_dialog_title)).check(doesNotExist()) + onView(withText(DestinationRecords.RECORDS[1].destID)).check(doesNotExist()) + } */ // ============================================================================================ // ===================================== Helpers ============================================== @@ -729,27 +734,27 @@ class RoadBookFragmentTest { private val timePickerEraseBLabel = "Erase" private fun clickOnShowArchivedButton() { - openActionBarOverflowOrOptionsMenu(ApplicationProvider.getApplicationContext()) - onView(withText(R.string.rb_label_show_archived)).perform(click()) + openActionBarOverflowOrOptionsMenu(ApplicationProvider.getApplicationContext()) + onView(withText(R.string.rb_label_show_archived)).perform(click()) } private fun navigateOutsideAndComeBack() { - onView(withId(R.id.drawer_layout)) - .perform(DrawerActions.open()) - onView(withId(R.id.routeFragment)) - .perform(click()) - onView(withId(R.id.fragment_route_directors_parent)) - .check(matches(isDisplayed())) - onView(withId(R.id.drawer_layout)) - .perform(DrawerActions.open()) - onView(withId(R.id.roadBookFragment)) - .perform(click()) + onView(withId(R.id.drawer_layout)) + .perform(DrawerActions.open()) + onView(withId(R.id.routeFragment)) + .perform(click()) + onView(withId(R.id.fragment_route_directors_parent)) + .check(matches(isDisplayed())) + onView(withId(R.id.drawer_layout)) + .perform(DrawerActions.open()) + onView(withId(R.id.roadBookFragment)) + .perform(click()) } private fun newRecord() { - onView(withId(R.id.fab)).perform(click()) - onView(withId(R.id.autoCompleteClientID)) - .perform(click(), typeText("New"), closeSoftKeyboard()) - onView(withText(R.string.edit_dialog_update_b)).perform(click()) + onView(withId(R.id.fab)).perform(click()) + onView(withId(R.id.autoCompleteClientID)) + .perform(click(), typeText("New"), closeSoftKeyboard()) + onView(withText(R.string.edit_dialog_update_b)).perform(click()) } // As we can't set the seconds currently, @@ -757,10 +762,10 @@ class RoadBookFragmentTest { // It's enough to match until the hours in our tests as at most one timestamp written at a time // Retrieving it by text until the hours allows less false errors on the CI private fun timestampUntilHourFormat(cal: Calendar): String { - return SimpleDateFormat.getTimeInstance() - .format(cal.time) - .substringBeforeLast(":") - .substringBeforeLast(":") + return SimpleDateFormat.getTimeInstance() + .format(cal.time) + .substringBeforeLast(":") + .substringBeforeLast(":") } } \ No newline at end of file diff --git a/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/roadbook/TouchCustomMoves.kt b/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/roadbook/TouchCustomMoves.kt index 8515ca82..89a18641 100644 --- a/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/roadbook/TouchCustomMoves.kt +++ b/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/roadbook/TouchCustomMoves.kt @@ -22,8 +22,10 @@ object TouchCustomMoves { swipeSlowActionOnRecyclerList(pos, 0.5f, 1f, -1f, 1f) } - private fun swipeSlowActionOnRecyclerList(pos: Int, startX: Float, startY: Float, - endX: Float, endY: Float) { + private fun swipeSlowActionOnRecyclerList( + pos: Int, startX: Float, startY: Float, + endX: Float, endY: Float + ) { Espresso.onView(ViewMatchers.withId(R.id.list)).perform( ViewActions.longClick(), RecyclerViewActions.actionOnItemAtPosition( @@ -32,8 +34,10 @@ object TouchCustomMoves { ) } - private fun swipeAction(startX: Float, startY: Float, - endX: Float, endY: Float): GeneralSwipeAction { + private fun swipeAction( + startX: Float, startY: Float, + endX: Float, endY: Float + ): GeneralSwipeAction { return GeneralSwipeAction( Swipe.SLOW, { diff --git a/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/route/RouteFragmentTest.kt b/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/route/RouteFragmentTest.kt index 1f781464..571dae99 100644 --- a/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/route/RouteFragmentTest.kt +++ b/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/route/RouteFragmentTest.kt @@ -19,9 +19,10 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.rule.GrantPermissionRule import com.github.factotum_sdp.factotum.MainActivity import com.github.factotum_sdp.factotum.R -import com.github.factotum_sdp.factotum.ui.maps.RouteFragment.Companion.NO_RESULT +import com.github.factotum_sdp.factotum.utils.GeneralUtils.Companion.initFirebase import org.hamcrest.Matchers.* import org.junit.Before +import org.junit.BeforeClass import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -31,6 +32,14 @@ import java.util.concurrent.CountDownLatch @RunWith(AndroidJUnit4::class) class RouteFragmentTest { + companion object { + @BeforeClass + @JvmStatic + fun setUpDatabase() { + initFirebase() + } + } + @get:Rule var testRule = ActivityScenarioRule( MainActivity::class.java @@ -39,11 +48,13 @@ class RouteFragmentTest { @get:Rule val permission = GrantPermissionRule.grant( Manifest.permission.ACCESS_FINE_LOCATION, - Manifest.permission.ACCESS_COARSE_LOCATION) + Manifest.permission.ACCESS_COARSE_LOCATION + ) + + private var decorView: View? = null - private var decorView : View? = null @Before - fun setUp(){ + fun setUp() { onView(withId(R.id.drawer_layout)) .perform(DrawerActions.open()) onView(withId(R.id.routeFragment)) @@ -54,57 +65,64 @@ class RouteFragmentTest { } @Test - fun buttonNextExists(){ + fun buttonNextExists() { onView(withId(R.id.button_next)).check(matches(not(doesNotExist()))) } @Test - fun buttonNextInvisible(){ + fun buttonNextInvisible() { onView(withId(R.id.button_next)).check(matches(withEffectiveVisibility(Visibility.INVISIBLE))) } @Test - fun buttonNextAppearsWhenPressed(){ + fun buttonNextAppearsWhenPressed() { onView(withId(R.id.button_next)).check(matches(withEffectiveVisibility(Visibility.INVISIBLE))) - Espresso.onData(anything()).inAdapterView(withId(R.id.list_view_routes)).atPosition(0).perform( - click() - ) + Espresso.onData(anything()).inAdapterView(withId(R.id.list_view_routes)).atPosition(0) + .perform( + click() + ) onView(withId(R.id.button_next)).check(matches(isDisplayed())) } + @Test - fun buttonRunExists(){ + fun buttonRunExists() { onView(withId(R.id.button_run)).check(matches(not(doesNotExist()))) } @Test - fun buttonRunInvisible(){ + fun buttonRunInvisible() { onView(withId(R.id.button_run)).check(matches(withEffectiveVisibility(Visibility.INVISIBLE))) } @Test - fun buttonRunAppearsWhenPressed(){ + fun buttonRunAppearsWhenPressed() { onView(withId(R.id.button_run)).check(matches(withEffectiveVisibility(Visibility.INVISIBLE))) - Espresso.onData(anything()).inAdapterView(withId(R.id.list_view_routes)).atPosition(0).perform( - click() - ) + Espresso.onData(anything()).inAdapterView(withId(R.id.list_view_routes)).atPosition(0) + .perform( + click() + ) onView(withId(R.id.button_run)).check(matches(isDisplayed())) } + @Test - fun buttonShowDstDisplayed(){ + fun buttonShowDstDisplayed() { onView(withId(R.id.button_all)).check(matches(isDisplayed())) } @Test - fun searchBarDisplayed(){ - onView(allOf(withId(R.id.search_bar), isAssignableFrom(SearchView::class.java))).check(matches(isDisplayed())) + fun searchBarDisplayed() { + onView(allOf(withId(R.id.search_bar), isAssignableFrom(SearchView::class.java))).check( + matches(isDisplayed()) + ) } @Test - fun validSearchShowsAddressSnackBar(){ + fun validSearchShowsAddressSnackBar() { val city = "Lausanne" - onView(withId(androidx.appcompat.R.id.search_src_text)).perform(typeText(city)).perform(pressKey(KeyEvent.KEYCODE_ENTER)) + onView(withId(androidx.appcompat.R.id.search_src_text)).perform(typeText(city)) + .perform(pressKey(KeyEvent.KEYCODE_ENTER)) val geocoder = Geocoder(getApplicationContext()) - var result : String + var result: String val latch = CountDownLatch(1) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { geocoder.getFromLocationName(city, 1) { addresses -> @@ -118,20 +136,21 @@ class RouteFragmentTest { latch.countDown() } latch.await() - } else{ + } else { val bestAddresses = geocoder.getFromLocationName(city, 1) result = bestAddresses?.get(0).toString() onView(withId(com.google.android.material.R.id.snackbar_text)) .check(matches(withText(result))) } } - + /** @Test fun wrongSearchShowsNoResultSnackbar(){ - val city = "wrong_search" - onView(withId(androidx.appcompat.R.id.search_src_text)).perform(typeText(city)).perform(pressKey(KeyEvent.KEYCODE_ENTER)) - onView(withId(com.google.android.material.R.id.snackbar_text)) - .check(matches(withText(NO_RESULT))) + val city = "wrong_search" + onView(withId(androidx.appcompat.R.id.search_src_text)).perform(typeText(city)).perform(pressKey(KeyEvent.KEYCODE_ENTER)) + onView(withId(com.google.android.material.R.id.snackbar_text)) + .check(matches(withText(NO_RESULT))) } + **/ } \ No newline at end of file diff --git a/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/signup/SignUpFragmentTest.kt b/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/signup/SignUpFragmentTest.kt index 11781e45..1fc80ccb 100644 --- a/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/signup/SignUpFragmentTest.kt +++ b/app/src/androidTest/java/com/github/factotum_sdp/factotum/ui/signup/SignUpFragmentTest.kt @@ -15,9 +15,8 @@ import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.github.factotum_sdp.factotum.MainActivity import com.github.factotum_sdp.factotum.R +import com.github.factotum_sdp.factotum.utils.GeneralUtils.Companion.initFirebase import com.google.firebase.auth.FirebaseAuth -import com.google.firebase.auth.ktx.auth -import com.google.firebase.ktx.Firebase import org.hamcrest.Matchers.anything import org.hamcrest.Matchers.not import org.junit.* @@ -32,14 +31,10 @@ class SignUpFragmentTest { ) companion object { - private var auth: FirebaseAuth = Firebase.auth - @BeforeClass @JvmStatic fun setUpAuth() { - auth.useEmulator("10.0.2.2", 9099) - - MainActivity.setAuth(auth) + initFirebase() } } diff --git a/app/src/androidTest/java/com/github/factotum_sdp/factotum/utils/ContactsUtils.kt b/app/src/androidTest/java/com/github/factotum_sdp/factotum/utils/ContactsUtils.kt index f829f956..3a6d53b6 100644 --- a/app/src/androidTest/java/com/github/factotum_sdp/factotum/utils/ContactsUtils.kt +++ b/app/src/androidTest/java/com/github/factotum_sdp/factotum/utils/ContactsUtils.kt @@ -3,15 +3,73 @@ package com.github.factotum_sdp.factotum.utils import android.widget.TextView import androidx.recyclerview.widget.RecyclerView import com.github.factotum_sdp.factotum.R -import com.google.firebase.database.FirebaseDatabase +import com.github.factotum_sdp.factotum.placeholder.Contact +import com.github.factotum_sdp.factotum.utils.GeneralUtils.Companion.getDatabase +import kotlinx.coroutines.CompletableDeferred import org.hamcrest.Description import org.hamcrest.Matcher import org.hamcrest.TypeSafeMatcher class ContactsUtils { + companion object { - fun emptyFirebaseDatabase(database: FirebaseDatabase) { - database.reference.child("contacts").removeValue() + //fake data --> to be replaced with connection to database + private val randomContacts = mutableListOf() + private val randomNames = listOf("John", "Jane", "Joe") + private var randomSurnames = listOf("Smith", "Jones", "Williams") + private val roles = listOf("Boss", "Courier", "Client") + private val randomAddresses = + listOf("123 Fake Street", "456 Fake Street", "789 Fake Street") + private val randomPhones = listOf("123456789", "987654321", "123987456") + private val randomDetails = listOf("I am a boss", "I am a courier", "I am a client") + + private const val image = R.drawable.contact_image + + private fun createContact(position: Int): Contact { + return Contact( + position.toString(), + roles[position % roles.size], randomNames[position % randomNames.size], + randomSurnames[position % randomSurnames.size], image, + randomAddresses[position % randomAddresses.size], + randomPhones[position % randomPhones.size], + randomDetails[position % randomDetails.size] + ) + } + + private fun createRandomContacts(count: Int) { + randomContacts.clear() + for (i in randomContacts.size until count) { + randomContacts.add(createContact(i)) + } + } + + fun getContacts(): List { + return randomContacts + } + + /** + * Populates the database with random contacts. + */ + suspend fun populateDatabase(count: Int = 5) { + val deferred = CompletableDeferred() + + createRandomContacts(count) + + for (contact in randomContacts) { + getDatabase().getReference("contacts").child(contact.id).setValue(contact) + .addOnSuccessListener { + deferred.complete(Unit) + } + .addOnFailureListener { exception -> + deferred.completeExceptionally(exception) + } + } + + deferred.await() + } + + fun emptyFirebaseDatabase() { + getDatabase().reference.child("contacts").removeValue() } fun withHolderContactName(name: String): Matcher { @@ -24,7 +82,7 @@ class ContactsUtils { override fun matchesSafely(item: RecyclerView.ViewHolder): Boolean { val holderName = - item.itemView.findViewById(R.id.contact_name).text.toString() + item.itemView.findViewById(R.id.contact_surname_and_name).text.toString() if (holderName == name && isFirstMatch) { isFirstMatch = false return true diff --git a/app/src/androidTest/java/com/github/factotum_sdp/factotum/utils/FillDatabase.kt b/app/src/androidTest/java/com/github/factotum_sdp/factotum/utils/FillDatabase.kt new file mode 100644 index 00000000..50554b86 --- /dev/null +++ b/app/src/androidTest/java/com/github/factotum_sdp/factotum/utils/FillDatabase.kt @@ -0,0 +1,19 @@ +package com.github.factotum_sdp.factotum.utils + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import kotlinx.coroutines.runBlocking +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class FillDatabase { + @Test + fun setUpDatabase() { + ContactsUtils.emptyFirebaseDatabase() + + runBlocking { + ContactsUtils.populateDatabase(5) + } + } + +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/github/factotum_sdp/factotum/utils/GeneralUtils.kt b/app/src/androidTest/java/com/github/factotum_sdp/factotum/utils/GeneralUtils.kt new file mode 100644 index 00000000..7d9f4231 --- /dev/null +++ b/app/src/androidTest/java/com/github/factotum_sdp/factotum/utils/GeneralUtils.kt @@ -0,0 +1,42 @@ +package com.github.factotum_sdp.factotum.utils + +import com.github.factotum_sdp.factotum.MainActivity +import com.google.firebase.auth.FirebaseAuth +import com.google.firebase.auth.ktx.auth +import com.google.firebase.database.FirebaseDatabase +import com.google.firebase.database.ktx.database +import com.google.firebase.ktx.Firebase +import com.google.firebase.storage.FirebaseStorage +import com.google.firebase.storage.ktx.storage + +class GeneralUtils { + companion object { + private lateinit var database: FirebaseDatabase + private lateinit var auth: FirebaseAuth + private lateinit var storage: FirebaseStorage + + fun initFirebase() { + database = Firebase.database + auth = Firebase.auth + storage = Firebase.storage + + database.useEmulator("10.0.2.2", 9000) + auth.useEmulator("10.0.2.2", 9099) + storage.useEmulator("10.0.2.2", 9199) + MainActivity.setDatabase(database) + MainActivity.setAuth(auth) + } + + fun getDatabase(): FirebaseDatabase { + return database + } + + fun getAuth(): FirebaseAuth { + return auth + } + /* + fun getStorage(): FirebaseStorage { + return storage + } */ + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/github/factotum_sdp/factotum/utils/LocationUtils.kt b/app/src/androidTest/java/com/github/factotum_sdp/factotum/utils/LocationUtils.kt index 20247722..e7a47a3f 100644 --- a/app/src/androidTest/java/com/github/factotum_sdp/factotum/utils/LocationUtils.kt +++ b/app/src/androidTest/java/com/github/factotum_sdp/factotum/utils/LocationUtils.kt @@ -12,6 +12,7 @@ class LocationUtils { Locale.FRENCH.language -> "Uniquement cette fois-ci" else -> "Only this time" } + fun hasLocationPopUp(): Boolean { val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) return device.wait(Until.hasObject(By.textContains(buttonTextAllow)), 1000) diff --git a/app/src/androidTest/java/com/github/factotum_sdp/factotum/utils/PreferencesSetting.kt b/app/src/androidTest/java/com/github/factotum_sdp/factotum/utils/PreferencesSetting.kt index 93ce77ed..e5d0b529 100644 --- a/app/src/androidTest/java/com/github/factotum_sdp/factotum/utils/PreferencesSetting.kt +++ b/app/src/androidTest/java/com/github/factotum_sdp/factotum/utils/PreferencesSetting.kt @@ -17,6 +17,7 @@ object PreferencesSetting { edit.putBoolean(sharedKey, value) edit.apply() } + fun setAllPrefs(activity: MainActivity) { setPrefs(SWIPE_L_SHARED_KEY, activity, true) setPrefs(SWIPE_R_SHARED_KEY, activity, true) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 65593682..4c215223 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,12 +2,14 @@ - - + + + - - - + + + + @@ -19,14 +21,14 @@ - - + + android:exported="false" + android:grantUriPermissions="true"> diff --git a/app/src/main/java/com/github/factotum_sdp/factotum/data/DestinationRecord.kt b/app/src/main/java/com/github/factotum_sdp/factotum/data/DestinationRecord.kt index 42f1ba14..87ac2f05 100644 --- a/app/src/main/java/com/github/factotum_sdp/factotum/data/DestinationRecord.kt +++ b/app/src/main/java/com/github/factotum_sdp/factotum/data/DestinationRecord.kt @@ -1,5 +1,6 @@ package com.github.factotum_sdp.factotum.data +import com.github.factotum_sdp.factotum.data.DestinationRecord.Action import java.text.DateFormat import java.text.SimpleDateFormat import java.util.* @@ -24,7 +25,7 @@ data class DestinationRecord( val rate: Int, val actions: List, val notes: String -){ +) { companion object { /** @@ -72,6 +73,7 @@ data class DestinationRecord( return actionsFormatList.joinToString("| ", "(", ")") } } + /** * The possible actions to achieve on a destination */ @@ -81,6 +83,7 @@ data class DestinationRecord( CONTACT, RELAY, UNKNOWN; + override fun toString(): String = when (this) { PICK -> "pick" @@ -89,6 +92,7 @@ data class DestinationRecord( RELAY -> "relay" UNKNOWN -> "unknown" } + companion object { fun fromString(str: String): Action = when (str) { diff --git a/app/src/main/java/com/github/factotum_sdp/factotum/data/FusedLocationClient.kt b/app/src/main/java/com/github/factotum_sdp/factotum/data/FusedLocationClient.kt index b93b406f..a2399e82 100644 --- a/app/src/main/java/com/github/factotum_sdp/factotum/data/FusedLocationClient.kt +++ b/app/src/main/java/com/github/factotum_sdp/factotum/data/FusedLocationClient.kt @@ -4,12 +4,12 @@ import android.annotation.SuppressLint import android.content.Context import android.location.Location import android.location.LocationManager -import com.google.android.gms.location.LocationRequest -import com.google.android.gms.location.LocationResult import android.os.Looper import com.github.factotum_sdp.factotum.hasLocationPermission import com.google.android.gms.location.FusedLocationProviderClient import com.google.android.gms.location.LocationCallback +import com.google.android.gms.location.LocationRequest +import com.google.android.gms.location.LocationResult import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow @@ -18,19 +18,21 @@ import kotlinx.coroutines.launch class FusedLocationClient( private val context: Context, private val client: FusedLocationProviderClient - ): LocationClient { +) : LocationClient { @SuppressLint("MissingPermission") override fun getLocationUpdates(interval: Long): Flow { return callbackFlow { - if(!context.hasLocationPermission()) { + if (!context.hasLocationPermission()) { throw LocationClient.LocationException("Missing location permission") } - val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager + val locationManager = + context.getSystemService(Context.LOCATION_SERVICE) as LocationManager val isGpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) - val isNetworkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) - if(!isGpsEnabled && !isNetworkEnabled) { + val isNetworkEnabled = + locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) + if (!isGpsEnabled && !isNetworkEnabled) { throw LocationClient.LocationException("GPS is disabled") } diff --git a/app/src/main/java/com/github/factotum_sdp/factotum/data/LocationClient.kt b/app/src/main/java/com/github/factotum_sdp/factotum/data/LocationClient.kt index 0a1c5a65..e2244a20 100644 --- a/app/src/main/java/com/github/factotum_sdp/factotum/data/LocationClient.kt +++ b/app/src/main/java/com/github/factotum_sdp/factotum/data/LocationClient.kt @@ -5,5 +5,5 @@ import kotlinx.coroutines.flow.Flow interface LocationClient { fun getLocationUpdates(interval: Long): Flow - class LocationException(message: String): Exception() + class LocationException(message: String) : Exception() } \ No newline at end of file diff --git a/app/src/main/java/com/github/factotum_sdp/factotum/data/LoginDataSource.kt b/app/src/main/java/com/github/factotum_sdp/factotum/data/LoginDataSource.kt index 0e3b1889..d60d2b87 100644 --- a/app/src/main/java/com/github/factotum_sdp/factotum/data/LoginDataSource.kt +++ b/app/src/main/java/com/github/factotum_sdp/factotum/data/LoginDataSource.kt @@ -15,18 +15,18 @@ class LoginDataSource { val authResultFuture = CompletableFuture>() auth.signInWithEmailAndPassword(userEmail, password).addOnCompleteListener { authTask -> - if (authTask.isSuccessful) { - authResultFuture.complete(Result.Success(user)) - } else { - authResultFuture.complete( - Result.Error( - IOException( - "Error logging in", authTask.exception - ) + if (authTask.isSuccessful) { + authResultFuture.complete(Result.Success(user)) + } else { + authResultFuture.complete( + Result.Error( + IOException( + "Error logging in", authTask.exception ) ) - } + ) } + } return authResultFuture.get() } diff --git a/app/src/main/java/com/github/factotum_sdp/factotum/data/User.kt b/app/src/main/java/com/github/factotum_sdp/factotum/data/User.kt index 73a0ab90..28273681 100644 --- a/app/src/main/java/com/github/factotum_sdp/factotum/data/User.kt +++ b/app/src/main/java/com/github/factotum_sdp/factotum/data/User.kt @@ -6,5 +6,5 @@ package com.github.factotum_sdp.factotum.data data class User( val name: String, val email: String, - val role : Role + val role: Role ) \ No newline at end of file diff --git a/app/src/main/java/com/github/factotum_sdp/factotum/data/localisation/Location.kt b/app/src/main/java/com/github/factotum_sdp/factotum/data/localisation/Location.kt index f74c2667..123b7d5b 100644 --- a/app/src/main/java/com/github/factotum_sdp/factotum/data/localisation/Location.kt +++ b/app/src/main/java/com/github/factotum_sdp/factotum/data/localisation/Location.kt @@ -17,10 +17,10 @@ import java.util.concurrent.CountDownLatch * @param query : String. Query of the address that we want to create * @param context : Context. Context in which this constructor is called */ -class Location(query: String, context : Context) { +class Location(query: String, context: Context) { - val address : Address? - val addressName : String? + val address: Address? + val addressName: String? companion object { const val CACHE_FILE_NAME = "locations.txt" @@ -35,9 +35,9 @@ class Location(query: String, context : Context) { * @param context : Context. Context in which this method is called * @return returns the location that has been added to the file, or null if no address was found */ - fun createAndStore(query: String, context: Context): Location?{ + fun createAndStore(query: String, context: Context): Location? { val location = Location(query, context) - if(location.address == null){ + if (location.address == null) { return null } @@ -46,15 +46,16 @@ class Location(query: String, context : Context) { //creates the header if it is a new file if (cacheFile.length() == 0L) cacheFile.appendText("location${CACHE_FILE_SEPARATOR}latitude${CACHE_FILE_SEPARATOR}longitude\n") // stores - val toStore = "${location.addressName}$CACHE_FILE_SEPARATOR${location.address.latitude}$CACHE_FILE_SEPARATOR${location.address.longitude}\n" + val toStore = + "${location.addressName}$CACHE_FILE_SEPARATOR${location.address.latitude}$CACHE_FILE_SEPARATOR${location.address.longitude}\n" //checks if already if file var alreadyExists = false cacheFile.forEachLine { line -> - if(line.contains(location.addressName.toString())) { + if (line.contains(location.addressName.toString())) { alreadyExists = true } } - if(!alreadyExists) cacheFile.appendText(toStore) + if (!alreadyExists) cacheFile.appendText(toStore) return location } @@ -65,24 +66,24 @@ class Location(query: String, context : Context) { * @param context : Context. Context in which this function is called * @return : List
?. Null if no result */ - fun geocoderQuery(query: String, context: Context): List
?{ + fun geocoderQuery(query: String, context: Context): List
? { // must handle differently depending on SDK. // getLocationFromName(String, int) deprecated in SDK 33 val geocoder = Geocoder(context) return if (Build.VERSION.SDK_INT >= TIRAMISU) { tiramisuResultHandler(query, geocoder) - } else{ + } else { resultHandler(query, geocoder) } } @RequiresApi(TIRAMISU) private fun tiramisuResultHandler(query: String, geocoder: Geocoder): List
? { - var result : List
? = listOf() + var result: List
? = listOf() val latch = CountDownLatch(1) // blocking geocoder.getFromLocationName(query, MAX_RESULT) { addresses -> - result = if(addresses.size > 0) addresses else null + result = if (addresses.size > 0) addresses else null latch.countDown() } latch.await() @@ -90,11 +91,12 @@ class Location(query: String, context : Context) { } private fun resultHandler(query: String, geocoder: Geocoder): List
? { - var result : List
? = listOf() + var result: List
? = listOf() try { val geocodeResult = geocoder.getFromLocationName(query, MAX_RESULT) - result = if (geocodeResult == null || geocodeResult.isEmpty()) null else geocodeResult - } catch (e : Exception){ + result = + if (geocodeResult == null || geocodeResult.isEmpty()) null else geocodeResult + } catch (e: Exception) { e.printStackTrace() } return result @@ -107,10 +109,4 @@ class Location(query: String, context : Context) { } - - - - - - } \ No newline at end of file diff --git a/app/src/main/java/com/github/factotum_sdp/factotum/placeholder/Contact.kt b/app/src/main/java/com/github/factotum_sdp/factotum/placeholder/Contact.kt new file mode 100644 index 00000000..ff2711c2 --- /dev/null +++ b/app/src/main/java/com/github/factotum_sdp/factotum/placeholder/Contact.kt @@ -0,0 +1,12 @@ +package com.github.factotum_sdp.factotum.placeholder + +data class Contact( + val id: String = "", + val role: String = "", + val name: String = "", + val surname: String = "", + val profile_pic_id: Int = 0, + val address: String = "", + val phone: String = "", + val details: String? = null +) \ No newline at end of file diff --git a/app/src/main/java/com/github/factotum_sdp/factotum/placeholder/ContactsList.kt b/app/src/main/java/com/github/factotum_sdp/factotum/placeholder/ContactsList.kt deleted file mode 100644 index a71bc223..00000000 --- a/app/src/main/java/com/github/factotum_sdp/factotum/placeholder/ContactsList.kt +++ /dev/null @@ -1,214 +0,0 @@ -package com.github.factotum_sdp.factotum.placeholder - -import android.content.Context -import android.content.SharedPreferences -import android.util.Log -import com.github.factotum_sdp.factotum.R -import com.google.firebase.database.DataSnapshot -import com.google.firebase.database.DatabaseError -import com.google.firebase.database.FirebaseDatabase -import com.google.firebase.database.ValueEventListener -import com.google.gson.Gson -import com.google.gson.reflect.TypeToken -import kotlinx.coroutines.CompletableDeferred - -private const val CONTACTS_PREFS = "contacts_prefs" -private const val CONTACTS_KEY = "contacts_key" - -/** - * Class representing a list of contacts. - */ -object ContactsList { - - /** - * An array of sample contacts. - */ - private val contacts = mutableListOf() - private val randomContacts = mutableListOf() - private lateinit var dataSource: FirebaseDatabase - val size get() = this.contacts.size - - fun init(dataSource: FirebaseDatabase) { - this.dataSource = dataSource - } - - - //fake data --> to be replaced with connection to database - private val randomNames = listOf("John Smith", "Jane Doe", "Bob Builder") - private val roles = listOf("Boss", "Courier", "Client") - private val randomAddresses = listOf("123 Fake Street", "456 Fake Street", "789 Fake Street") - private val randomPhones = listOf("123456789", "987654321", "123987456") - private val randomDetails = listOf("I am a boss", "I am a courier", "I am a client") - - private const val image = R.drawable.contact_image - - private const val COUNT = 5 - - - fun getItems(): List { - return this.contacts - } - - fun setItems(items: List) { - this.contacts.clear() - this.contacts.addAll(items) - } - - //Trivial method for now but will be useful when connecting to database - private fun addItem(item: Contact) { - randomContacts.add(item) - } - - - private fun createContact(position: Int): Contact { - return Contact( - "$position", - roles[position % roles.size], randomNames[position % randomNames.size], image, - randomAddresses[position % randomAddresses.size], - randomPhones[position % randomPhones.size], - randomDetails[position % randomDetails.size]) - } - - private fun createRandomContacts() { - for (i in 0 until COUNT) { - addItem(createContact(i)) - } - } - - /** - * Saves the contacts list to a local storage. - */ - fun saveContactsLocally(context: Context) { - // Get an instance of SharedPreferences with a specific name (CONTACTS_PREFS) and mode (private) - val sharedPreferences: SharedPreferences = context.getSharedPreferences(CONTACTS_PREFS, Context.MODE_PRIVATE) - - // Get an editor for SharedPreferences to make changes - val editor: SharedPreferences.Editor = sharedPreferences.edit() - - // Create a Gson object to convert the contacts list into a JSON string - val gson = Gson() - - // Serialize the contacts list into a JSON string - val jsonContacts = gson.toJson(this.contacts) - - // Save the JSON string into SharedPreferences with the key CONTACTS_KEY - editor.putString(CONTACTS_KEY, jsonContacts) - - // Apply the changes to SharedPreferences - editor.apply() - } - - - /** - * Loads the contacts list from local storage. - */ - fun loadContactsLocally(context: Context) { - // Get an instance of SharedPreferences with the specific name (CONTACTS_PREFS) and mode (private) - val sharedPreferences: SharedPreferences = context.getSharedPreferences(CONTACTS_PREFS, Context.MODE_PRIVATE) - - // Create a Gson object to convert the JSON string back to the contacts list - val gson = Gson() - - // Retrieve the JSON string from SharedPreferences using the key CONTACTS_KEY - val jsonContacts = sharedPreferences.getString(CONTACTS_KEY, null) - - // Check if the JSON string is not null (meaning there are contacts saved in SharedPreferences) - if (jsonContacts != null) { - // Define the type for deserialization (List) - val type = object : TypeToken>() {}.type - - // Clear the current contacts list - this.contacts.clear() - - // Deserialize the JSON string and add the contacts to the list - this.contacts.addAll(gson.fromJson(jsonContacts, type)) - } - } - - /** - * Synchronizes the contacts list with Firebase Realtime Database. - */ - suspend fun syncContactsFromFirebase(context: Context) { - // Get a reference to the database node - val contactsRef = dataSource.getReference("contacts") - - // Create a CompletableDeferred object to wait for the completion of the onDataChange method - val deferred = CompletableDeferred() - - // Add a ValueEventListener for a single read from Firebase Realtime Database - contactsRef.addListenerForSingleValueEvent(createValueEventListener(deferred, context)) - - // Wait for the completion of the onDataChange method - deferred.await() - } - - private fun createValueEventListener(deferred: CompletableDeferred, context: Context): ValueEventListener { - return object : ValueEventListener { - override fun onDataChange(dataSnapshot: DataSnapshot) { - // Clear the current contacts list - this@ContactsList.contacts.clear() - - // Iterate through each child (contact) in dataSnapshot - for (contactSnapshot in dataSnapshot.children) { - // Deserialize the contactSnapshot into a Contact object - val contact = contactSnapshot.getValue(Contact::class.java) - - // Check if the contact is not null and add it to the contacts list - if (contact != null) { - this@ContactsList.contacts.add(contact) - } - } - - // Save the updated contacts list to local storage - saveContactsLocally(context) - - // Mark the CompletableDeferred as complete - deferred.complete(Unit) - } - override fun onCancelled(databaseError: DatabaseError) { - // Mark the CompletableDeferred as failed with an exception - deferred.completeExceptionally(databaseError.toException()) - Log.d("ContactsList", "onCancelled: ${databaseError.toException()}") - } - } - } - - - - /** - * Populates the database with random contacts. - */ - suspend fun populateDatabase() { - val deferred = CompletableDeferred() - - createRandomContacts() - - dataSource.getReference("contacts").setValue(randomContacts) - .addOnSuccessListener { - deferred.complete(Unit) - } - .addOnFailureListener { exception -> - deferred.completeExceptionally(exception) - } - - deferred.await() - } - - - - - /** - * A data class representing a contact. - */ - data class Contact( - val id: String, - val role: String, - val name: String, - val profile_pic_id: Int, - val address: String, - val phone: String, - val details: String? = null) - { - constructor() : this("", "", "", 0, "", "", null) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/github/factotum_sdp/factotum/placeholder/DestinationRecords.kt b/app/src/main/java/com/github/factotum_sdp/factotum/placeholder/DestinationRecords.kt index 69137f48..d44e03b5 100644 --- a/app/src/main/java/com/github/factotum_sdp/factotum/placeholder/DestinationRecords.kt +++ b/app/src/main/java/com/github/factotum_sdp/factotum/placeholder/DestinationRecords.kt @@ -13,13 +13,13 @@ object DestinationRecords { val RECORDS: MutableList = ArrayList() val RECORD_TO_ADD: DestinationRecord = - DestinationRecord("New#1", "New",null, 0, 2, arrayListOf(Action.PICK), "") + DestinationRecord("New#1", "New", null, 0, 2, arrayListOf(Action.PICK), "") init { val cal: Calendar = Calendar.getInstance() RECORDS.addAll( listOf( - DestinationRecord("QG#1", "QG",cal.time, 3, 1, arrayListOf(), ""), + DestinationRecord("QG#1", "QG", cal.time, 3, 1, arrayListOf(), ""), DestinationRecord( "Buhagiat#1", "Buhagiat", @@ -29,7 +29,15 @@ object DestinationRecords { arrayListOf(Action.PICK, Action.PICK, Action.RELAY), "" ), - DestinationRecord("X17#1","X17", null, 0, 1, arrayListOf(Action.DELIVER, Action.CONTACT), "") + DestinationRecord( + "X17#1", + "X17", + null, + 0, + 1, + arrayListOf(Action.DELIVER, Action.CONTACT), + "" + ) ) ) diff --git a/app/src/main/java/com/github/factotum_sdp/factotum/services/LocationService.kt b/app/src/main/java/com/github/factotum_sdp/factotum/services/LocationService.kt index b0325a72..46bafe4e 100644 --- a/app/src/main/java/com/github/factotum_sdp/factotum/services/LocationService.kt +++ b/app/src/main/java/com/github/factotum_sdp/factotum/services/LocationService.kt @@ -29,7 +29,8 @@ private const val CHANNEL_ID = "location_service" private const val CHANNEL_NAME = "My Location Service" private const val SERVICE_ID = 101 private const val UPDATE_INTERVAL = 1000L -class LocationService: Service() { + +class LocationService : Service() { private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) private var onLocationUpdateEvent: ((location: Location) -> Unit)? = null @@ -43,14 +44,14 @@ class LocationService: Service() { override fun onCreate() { super.onCreate() locationClient = FusedLocationClient( - applicationContext, - LocationServices.getFusedLocationProviderClient(applicationContext) - ) + applicationContext, + LocationServices.getFusedLocationProviderClient(applicationContext) + ) } @RequiresApi(Build.VERSION_CODES.Q) override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - when(intent?.action) { + when (intent?.action) { ACTION_START -> start() ACTION_STOP -> stop() } @@ -62,7 +63,7 @@ class LocationService: Service() { } private fun start() { - startForegroundJob(UPDATE_INTERVAL) { service, notification -> + startForegroundJob(UPDATE_INTERVAL) { service, notification -> val updatedNotification = notification.setContentText( getString(R.string.loc_service_notification_message) ).setChannelId(CHANNEL_ID) @@ -70,9 +71,13 @@ class LocationService: Service() { } } - private fun startForegroundJob(interval: Long, - onLocationChanges: (service: NotificationManager, - notification: NotificationCompat.Builder) -> Unit) { + private fun startForegroundJob( + interval: Long, + onLocationChanges: ( + service: NotificationManager, + notification: NotificationCompat.Builder + ) -> Unit + ) { val service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager val channelId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { @@ -103,8 +108,10 @@ class LocationService: Service() { } @RequiresApi(Build.VERSION_CODES.O) - private fun createNotificationChannel(channelId: String, channelName: String, - service: NotificationManager): String{ + private fun createNotificationChannel( + channelId: String, channelName: String, + service: NotificationManager + ): String { val chan = NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH) chan.lightColor = Color.BLUE chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE diff --git a/app/src/main/java/com/github/factotum_sdp/factotum/ui/auth/BaseAuthFragment.kt b/app/src/main/java/com/github/factotum_sdp/factotum/ui/auth/BaseAuthFragment.kt index 28567745..4ff1da44 100644 --- a/app/src/main/java/com/github/factotum_sdp/factotum/ui/auth/BaseAuthFragment.kt +++ b/app/src/main/java/com/github/factotum_sdp/factotum/ui/auth/BaseAuthFragment.kt @@ -43,7 +43,7 @@ abstract class BaseAuthFragment : Fragment() { } abstract fun updateUi(model: Any) - private fun showSignUpFailed(@StringRes errorString: Int){ + private fun showSignUpFailed(@StringRes errorString: Int) { Snackbar.make(requireView(), errorString, Snackbar.LENGTH_SHORT).show() } } \ No newline at end of file diff --git a/app/src/main/java/com/github/factotum_sdp/factotum/ui/auth/BaseAuthState.kt b/app/src/main/java/com/github/factotum_sdp/factotum/ui/auth/BaseAuthState.kt index 76abbf57..3e45b879 100644 --- a/app/src/main/java/com/github/factotum_sdp/factotum/ui/auth/BaseAuthState.kt +++ b/app/src/main/java/com/github/factotum_sdp/factotum/ui/auth/BaseAuthState.kt @@ -2,7 +2,7 @@ package com.github.factotum_sdp.factotum.ui.auth import androidx.core.util.PatternsCompat -abstract class BaseAuthState{ +abstract class BaseAuthState { abstract val emailError: Int? abstract val passwordError: Int? abstract val isDataValid: Boolean diff --git a/app/src/main/java/com/github/factotum_sdp/factotum/ui/auth/BaseAuthViewModel.kt b/app/src/main/java/com/github/factotum_sdp/factotum/ui/auth/BaseAuthViewModel.kt index 118c9305..a289d487 100644 --- a/app/src/main/java/com/github/factotum_sdp/factotum/ui/auth/BaseAuthViewModel.kt +++ b/app/src/main/java/com/github/factotum_sdp/factotum/ui/auth/BaseAuthViewModel.kt @@ -1,10 +1,9 @@ package com.github.factotum_sdp.factotum.ui.auth import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -abstract class BaseAuthViewModel: ViewModel() { +abstract class BaseAuthViewModel : ViewModel() { abstract val authResult: LiveData> diff --git a/app/src/main/java/com/github/factotum_sdp/factotum/ui/directory/ContactCreation.kt b/app/src/main/java/com/github/factotum_sdp/factotum/ui/directory/ContactCreation.kt index 600ed658..10f61b1d 100644 --- a/app/src/main/java/com/github/factotum_sdp/factotum/ui/directory/ContactCreation.kt +++ b/app/src/main/java/com/github/factotum_sdp/factotum/ui/directory/ContactCreation.kt @@ -8,16 +8,18 @@ import android.provider.BaseColumns import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.ArrayAdapter -import android.widget.CursorAdapter -import android.widget.Spinner +import android.widget.* import androidx.appcompat.widget.SearchView import androidx.cursoradapter.widget.SimpleCursorAdapter import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope +import androidx.navigation.findNavController +import com.github.factotum_sdp.factotum.MainActivity import com.github.factotum_sdp.factotum.R import com.github.factotum_sdp.factotum.data.localisation.Location import com.github.factotum_sdp.factotum.databinding.FragmentContactCreationBinding +import com.github.factotum_sdp.factotum.placeholder.Contact import kotlinx.coroutines.launch /** @@ -26,21 +28,30 @@ import kotlinx.coroutines.launch class ContactCreation : Fragment() { // Should not stay like that and instead roles should use roles from future ENUM - val ROLES = listOf("Boss", "Courier", "Client") + private val roles = listOf("Boss", "Courier", "Client") + private var currentContact: Contact? = null + private val isUpdate: Boolean + get() = currentContact != null + private lateinit var viewModel: ContactsViewModel private var _binding: FragmentContactCreationBinding? = null - // This property is only valid between onCreateView and // onDestroyView. private val binding get() = _binding!! - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - } + + + private lateinit var name: EditText + private lateinit var surname: EditText + private lateinit var phoneNumber: EditText + private lateinit var details: EditText + + private lateinit var spinner: Spinner override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, + inflater: LayoutInflater, + container: ViewGroup?, savedInstanceState: Bundle? - ): View? { + ): View { // Inflate the layout for this fragment _binding = FragmentContactCreationBinding.inflate(inflater, container, false) return binding.root @@ -49,21 +60,53 @@ class ContactCreation : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - initializeSpinner(view) + retrievePossibleArguments() val cursor = initializeAddressSearch() setAddressSearchTextListener(cursor) setAddressSearchSuggestions() + initaliseRolesSpinner(view) + + setContactFields(view, currentContact) + + initialiseApproveFormButton(view) + + + } + + private fun retrievePossibleArguments() { + viewModel = ViewModelProvider(requireActivity())[ContactsViewModel::class.java] + viewModel.setDatabase(MainActivity.getDatabase()) + + if (arguments?.getInt("id") != null) { + currentContact = viewModel.contacts.value?.get(arguments?.getInt("id")!!) + } + } + + private fun setContactFields(view: View, contact: Contact?) { + name = view.findViewById(R.id.editTextName) + surname = view.findViewById(R.id.editTextSurname) + phoneNumber = view.findViewById(R.id.contactCreationPhoneNumber) + details = view.findViewById(R.id.contactCreationNotes) + + if (contact != null) { + spinner.setSelection(roles.indexOf(contact.role)) + name.setText(contact.name) + surname.setText(contact.surname) + binding.contactCreationAddress.setQuery(contact.address, false) + phoneNumber.setText(contact.phone) + details.setText(contact.details) + } } - private fun initializeSpinner(view: View){ - val spinner: Spinner = view.findViewById(R.id.roles_spinner) - //Initializes the spinner for the roles + private fun initaliseRolesSpinner(view: View) { + spinner = view.findViewById(R.id.roles_spinner) + // Initializes the spinner for the roles ArrayAdapter( requireContext(), android.R.layout.simple_spinner_item, - ROLES + roles ).also { adapter -> // Specify the layout to use when the list of choices appears adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) @@ -73,10 +116,12 @@ class ContactCreation : Fragment() { } private fun setAddressSearchTextListener(cursorAdapter: SimpleCursorAdapter) { - binding.contactCreationAddress.setOnQueryTextListener(object : SearchView.OnQueryTextListener { + binding.contactCreationAddress.setOnQueryTextListener(object : + SearchView.OnQueryTextListener { override fun onQueryTextSubmit(query: String?): Boolean { return false } + override fun onQueryTextChange(newText: String?): Boolean { if (newText != null && newText.length > 2) { val cursor = @@ -94,10 +139,12 @@ class ContactCreation : Fragment() { }) } - private fun setAddressSearchSuggestions(){ - binding.contactCreationAddress.setOnSuggestionListener(object : SearchView.OnSuggestionListener { + private fun setAddressSearchSuggestions() { + binding.contactCreationAddress.setOnSuggestionListener(object : + SearchView.OnSuggestionListener { override fun onSuggestionClick(position: Int): Boolean { - val cursor = binding.contactCreationAddress.suggestionsAdapter.getItem(position) as Cursor + val cursor = + binding.contactCreationAddress.suggestionsAdapter.getItem(position) as Cursor val index = cursor.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1) if (index == -1) return true val selection = @@ -105,6 +152,7 @@ class ContactCreation : Fragment() { binding.contactCreationAddress.setQuery(selection.toString(), false) return true } + override fun onSuggestionSelect(position: Int): Boolean { return false } @@ -125,4 +173,42 @@ class ContactCreation : Fragment() { binding.contactCreationAddress.suggestionsAdapter = cursorAdapter return cursorAdapter } + + + private fun initialiseApproveFormButton(view: View) { + val approveFormButton = view.findViewById