- flavours compartmentalised

Took 10 hours 11 minutes
This commit is contained in:
2023-03-28 21:38:33 +01:00
parent f87d986849
commit 16433d0852
130 changed files with 814 additions and 706 deletions

View File

@@ -0,0 +1,45 @@
package h_mal.appttude.com.driver
import android.content.res.Resources
import androidx.annotation.StringRes
import androidx.test.espresso.Espresso.onData
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.ViewInteraction
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.withId
import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.anything
open class BaseTestRobot {
fun fillEditText(resId: Int, text: String?): ViewInteraction =
onView(withId(resId)).perform(
ViewActions.replaceText(text),
ViewActions.closeSoftKeyboard()
)
fun clickButton(resId: Int): ViewInteraction =
onView((withId(resId))).perform(ViewActions.click())
fun textView(resId: Int): ViewInteraction = onView(withId(resId))
fun matchText(viewInteraction: ViewInteraction, text: String): ViewInteraction = viewInteraction
.check(matches(ViewMatchers.withText(text)))
fun matchText(resId: Int, text: String): ViewInteraction = matchText(textView(resId), text)
fun clickListItem(listRes: Int, position: Int) {
onData(anything())
.inAdapterView(allOf(withId(listRes)))
.atPosition(position).perform(ViewActions.click())
}
fun checkErrorOnTextEntry(resId: Int, errorMessage: String): ViewInteraction =
onView(withId(resId)).check(matches(checkErrorMessage(errorMessage)))
fun getStringFromResource(@StringRes resId: Int): String =
Resources.getSystem().getString(resId)
}

View File

@@ -0,0 +1,60 @@
package h_mal.appttude.com.driver
import android.view.View
import androidx.annotation.StringRes
import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.IdlingRegistry
import androidx.test.espresso.IdlingResource
import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction
import androidx.test.espresso.matcher.ViewMatchers.isRoot
import androidx.test.platform.app.InstrumentationRegistry
import h_mal.appttude.com.driver.base.BaseActivity
import org.hamcrest.Matcher
import org.junit.After
import org.junit.Before
open class BaseUiTest<T : BaseActivity<*,*>>(
private val activity: Class<T>
) {
private lateinit var mActivityScenarioRule: ActivityScenario<T>
private var mIdlingResource: IdlingResource? = null
@Before
fun setup() {
beforeLaunch()
mActivityScenarioRule = ActivityScenario.launch(activity)
mActivityScenarioRule.onActivity {
mIdlingResource = it.getIdlingResource()!!
IdlingRegistry.getInstance().register(mIdlingResource)
}
}
@After
fun tearDown() {
mIdlingResource?.let {
IdlingRegistry.getInstance().unregister(it)
}
}
fun getResourceString(@StringRes stringRes: Int): String {
return InstrumentationRegistry.getInstrumentation().targetContext.resources.getString(
stringRes
)
}
fun waitFor(delay: Long) {
onView(isRoot()).perform(object : ViewAction {
override fun getConstraints(): Matcher<View> = isRoot()
override fun getDescription(): String? = "wait for $delay milliseconds"
override fun perform(uiController: UiController, v: View?) {
uiController.loopMainThreadForAtLeast(delay)
}
})
}
open fun beforeLaunch() {}
}

View File

@@ -0,0 +1,10 @@
package h_mal.appttude.com.driver
private const val apiKey = "test_key"
const val signUpFirebase = "http://identitytoolkit.googleapis.com/v1/accounts:signUp?key=$apiKey"
const val deleteAccountFirebase =
"http://10.0.2.2:9099/identitytoolkit.googleapis.com/v1/accounts:delete?key=$apiKey"
const val USER_PASSWORD = "LetMeIn123!"

View File

@@ -0,0 +1,30 @@
package h_mal.appttude.com.driver
import android.view.View
import android.widget.EditText
import com.google.android.material.textfield.TextInputLayout
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.hamcrest.TypeSafeMatcher
/**
* Matcher for testing error of TextInputLayout
*/
fun checkErrorMessage(expectedErrorText: String): Matcher<View?>? {
return object : TypeSafeMatcher<View?>() {
override fun matchesSafely(view: View?): Boolean {
if (view is EditText) {
return view.error.toString() == expectedErrorText
}
if (view !is TextInputLayout) return false
val error = view.error ?: return false
return expectedErrorText == error.toString()
}
override fun describeTo(d: Description?) {}
}
}

View File

@@ -0,0 +1,90 @@
package h_mal.appttude.com.driver
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.database.FirebaseDatabase
import com.google.firebase.storage.FirebaseStorage
import h_mal.appttude.com.driver.base.BaseActivity
import h_mal.appttude.com.driver.data.FirebaseAuthSource
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.tasks.await
import org.junit.After
import org.junit.BeforeClass
open class FirebaseTest<T : BaseActivity<*,*>>(
activity: Class<T>,
private val registered: Boolean = false,
private val signedIn: Boolean = false
) : BaseUiTest<T>(activity) {
private val firebaseAuthSource by lazy { FirebaseAuthSource() }
private var email: String? = null
companion object {
/**
* Setup firebase emulators before all tests
*/
@JvmStatic
@BeforeClass
fun setupFirebase() {
val localHost = "10.0.2.2"
FirebaseAuth.getInstance().useEmulator(localHost, 9099)
FirebaseDatabase.getInstance().useEmulator(localHost, 9000)
FirebaseStorage.getInstance().useEmulator(localHost, 9199)
}
}
override fun beforeLaunch() {
if (registered) {
runBlocking {
setupUser()
}
if (!signedIn) firebaseAuthSource.logOut()
}
}
@After
fun tearDownFirebase() = runBlocking {
removeUser()
firebaseAuthSource.logOut()
}
suspend fun setupUser(
signInEmail: String = generateEmailAddress(),
password: String = USER_PASSWORD
) {
email = signInEmail
firebaseAuthSource.registerUser(signInEmail, password).await().user
}
// remove the user we created for testing
suspend fun removeUser() {
try {
getEmail()?.let {
if (firebaseAuthSource.getUser() == null) firebaseAuthSource.signIn(
email = it,
password = USER_PASSWORD
).await()
firebaseAuthSource.reauthenticate(it, USER_PASSWORD).await()
firebaseAuthSource.deleteProfile().await()
}
} catch (e: Exception) {
e.printStackTrace()
}
}
fun generateEmailAddress(): String {
val suffix = (1000..50000).random()
email = "test-${suffix}@test-account.com"
return email!!
}
fun getEmail(): String? {
firebaseAuthSource.getUser()?.email?.let {
return it
}
return email
}
}

View File

@@ -0,0 +1,62 @@
package h_mal.appttude.com.driver
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import kotlinx.coroutines.suspendCancellableCoroutine
import okhttp3.*
import okhttp3.RequestBody.Companion.toRequestBody
import java.io.IOException
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
class WebUtils {
private val okHttpClient by lazy { OkHttpClient() }
suspend fun <T : Any> post(url: String, body: String): T? {
val requestBody = body.toRequestBody()
val request = Request.Builder()
.post(requestBody)
.url(url)
.build()
return okHttpClient.newCall(request).await()
}
suspend fun get(url: String): String? {
val request: Request = Request.Builder()
.url(url)
.build()
return okHttpClient.newCall(request).await()
}
private suspend fun <T> Call.await(): T? {
val objectMapper = Gson()
val typeToken: TypeToken<T> = object : TypeToken<T>() {}
return suspendCancellableCoroutine { continuation ->
enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
continuation.resume(
objectMapper.fromJson(
response.body?.string(),
typeToken.type
)
)
}
override fun onFailure(call: Call, e: IOException) {
// Don't bother with resuming the continuation if it is already cancelled.
if (continuation.isCancelled) return
continuation.resumeWithException(e)
}
})
continuation.invokeOnCancellation {
try {
cancel()
} catch (ex: Throwable) {
//Ignore cancel exception
}
}
}
}
}

View File

@@ -0,0 +1,10 @@
package h_mal.appttude.com.driver.firebase
data class SignUpResponse(
val expiresIn: String? = null,
val kind: String? = null,
val idToken: String? = null,
val localId: String? = null,
val email: String? = null,
val refreshToken: String? = null
)