- storage permissions request updated

- test suite expanded
This commit is contained in:
2023-08-18 21:14:41 +01:00
parent 27c778e8ca
commit 9153c21648
47 changed files with 722 additions and 354 deletions

View File

@@ -20,16 +20,10 @@ commands:
command: | command: |
echo "$GOOGLE_SERVICES_KEY" > "app/google-services.json" echo "$GOOGLE_SERVICES_KEY" > "app/google-services.json"
- android/restore-gradle-cache - android/restore-gradle-cache
build_gradle:
description: Build the gradle
steps:
- android/restore-gradle-cache
- run: - run:
name: Download Dependencies name: allow gradle
command: | command: |
sudo chmod +x ./gradlew sudo chmod +x ./gradlew
./gradlew androidDependencies
- android/save-gradle-cache
run_tests: run_tests:
description: run non-instrumentation tests for flavour specified description: run non-instrumentation tests for flavour specified
parameters: parameters:
@@ -38,11 +32,11 @@ commands:
default: "Driver" default: "Driver"
steps: steps:
# The next step will run the unit tests # The next step will run the unit tests
- build_gradle
- run: - run:
name: Run non-instrumentation unit tests name: Run non-instrumentation unit tests
command: | command: |
./gradlew test<< parameters.flavour >>DebugUnitTest --continue ./gradlew test<< parameters.flavour >>DebugUnitTest
- android/save-gradle-cache
- store_artifacts: - store_artifacts:
path: app/build/reports path: app/build/reports
destination: reports destination: reports
@@ -53,10 +47,9 @@ commands:
parameters: parameters:
flavour: flavour:
type: string type: string
default: "AtlasWeather" default: "Driver"
steps: steps:
# Download and cache dependencies # Download and cache dependencies
- build_gradle
- run: - run:
name: Setup subtree for test data name: Setup subtree for test data
command: | command: |
@@ -78,9 +71,7 @@ commands:
post-emulator-launch-assemble-command: ./gradlew assemble<< parameters.flavour >>DebugAndroidTest post-emulator-launch-assemble-command: ./gradlew assemble<< parameters.flavour >>DebugAndroidTest
test-command: ./gradlew connected<< parameters.flavour >>DebugAndroidTest test-command: ./gradlew connected<< parameters.flavour >>DebugAndroidTest
system-image: system-images;android-25;google_apis;x86 system-image: system-images;android-25;google_apis;x86
pull-data: true pre-test-command: adb push driver_app_data/images /sdcard/Camera
pull-data-path: /storage/emulated/0/Android/data/
pull-data-target: ~/app-data
pre-emulator-wait-steps: pre-emulator-wait-steps:
# Start firebase emulator in the background while waiting to start testing # Start firebase emulator in the background while waiting to start testing
- run: - run:
@@ -94,6 +85,13 @@ commands:
paths: paths:
- ~/.cache/firebase/emulators/ - ~/.cache/firebase/emulators/
key: emulator-cache-v1-{{ epoch }} key: emulator-cache-v1-{{ epoch }}
# store screenshots for failed ui tests
- when:
condition: on_fail
steps:
- store_artifacts:
path: app/build/outputs/connected_android_test_additional_output/
destination: connected_android_test
# store test reports # store test reports
- store_artifacts: - store_artifacts:
path: app/build/reports/androidTests/connected path: app/build/reports/androidTests/connected
@@ -128,7 +126,6 @@ commands:
name: Setup playstore key name: Setup playstore key
command: | command: |
echo "$GOOGLE_PLAY_KEY" > "google-play-key.json" echo "$GOOGLE_PLAY_KEY" > "google-play-key.json"
- build_gradle
- run: - run:
name: Run fastlane command to deploy to playstore name: Run fastlane command to deploy to playstore
command: | command: |
@@ -156,8 +153,23 @@ jobs:
steps: steps:
# Checkout the code and its submodule as the first step. # Checkout the code and its submodule as the first step.
- setup_repo - setup_repo
# - run_tests: - run_tests:
# flavour: << parameters.flavour >> flavour: << parameters.flavour >>
run_instrumentation_test:
# Parameters used for determining
parameters:
flavour:
type: string
default: "Driver"
# These next lines define the Android machine image executor.
# See: https://circleci.com/docs/2.0/executor-types/
executor:
name: android/android-machine
tag: 2023.05.1
# Add steps to the job
# See: https://circleci.com/docs/2.0/configuration-reference/#steps
steps:
- setup_repo
- run_ui_tests: - run_ui_tests:
flavour: << parameters.flavour >> flavour: << parameters.flavour >>
deploy-to-playstore: deploy-to-playstore:
@@ -187,6 +199,14 @@ workflows:
branches: branches:
ignore: ignore:
- main_admin - main_admin
- run_instrumentation_test:
context: appttude
flavour: "Driver"
filters:
branches:
only:
- master
- main_driver
- deploy-to-playstore: - deploy-to-playstore:
context: appttude context: appttude
flavour: "Driver" flavour: "Driver"
@@ -195,7 +215,7 @@ workflows:
only: only:
- main_driver - main_driver
requires: requires:
- build-and-test - run_instrumentation_test
build-release-admin: build-release-admin:
jobs: jobs:
- build-and-test: - build-and-test:
@@ -205,12 +225,20 @@ workflows:
branches: branches:
ignore: ignore:
- main_driver - main_driver
- deploy-to-playstore: - run_instrumentation_test:
context: appttude context: appttude
flavour: "Admin" flavour: "Admin"
filters:
branches:
only:
- master
- main_admin
- deploy-to-playstore:
context: appttude
flavour: "Driver"
filters: filters:
branches: branches:
only: only:
- main_admin - main_admin
requires: requires:
- build-and-test - run_instrumentation_test

View File

@@ -170,4 +170,6 @@ dependencies {
def dispatcher_ver = "4.9.2" def dispatcher_ver = "4.9.2"
implementation "com.github.permissions-dispatcher:permissionsdispatcher:${dispatcher_ver}" implementation "com.github.permissions-dispatcher:permissionsdispatcher:${dispatcher_ver}"
kapt "com.github.permissions-dispatcher:permissionsdispatcher-processor:${dispatcher_ver}" kapt "com.github.permissions-dispatcher:permissionsdispatcher-processor:${dispatcher_ver}"
/ * Date utils * /
implementation 'net.danlew:android.joda:2.12.5'
} }

View File

@@ -0,0 +1,9 @@
{
"address": "123 test street update",
"dateFirst": "26/01/2019",
"dob": "26/01/1979",
"forenames": "Alex Smith",
"driverPic": "driver_profile_pic.jpg",
"ni": "NI 12 34 56 A",
"postcode": "EC1V 2AL"
}

View File

@@ -0,0 +1,5 @@
{
"licenseExpiry": "27/04/2019",
"licenseImageString": "driver_license_driver.jpg",
"licenseNumber": "FARME100165AB5EW"
}

View File

@@ -0,0 +1,7 @@
{
"expiryDate": "03/02/2019",
"insurerName": "Insurer",
"photoStrings": [
"driver_insurance.jpg"
]
}

View File

@@ -0,0 +1,4 @@
{
"photoString": "driver_logbook.jpg",
"v5cnumber": "NJ59NTV"
}

View File

@@ -0,0 +1,4 @@
{
"motExpiry": "11/06/2019",
"motImageString": "driver_mot.jpg"
}

View File

@@ -0,0 +1,5 @@
{
"phExpiry": "27/04/2019",
"phImageString": "driver_license_private_hire.jpg",
"phNumber": "987651"
}

View File

@@ -0,0 +1,5 @@
{
"phCarExpiry": "28/01/2019",
"phCarImageString": "driver_license_private_hire_car.jpg",
"phCarNumber": "4602060501"
}

View File

@@ -0,0 +1,11 @@
{
"colour": "Black",
"keeperAddress": "483 Green lanes London",
"keeperName": "Adam Cars Ltd",
"keeperPostCode": "N13 4BS",
"make": "Toyota",
"model": "Prius",
"reg": "NG59ERY",
"seized": false,
"startDate": "04/02/2019"
}

View File

@@ -7,7 +7,7 @@ import android.content.res.Resources
import android.net.Uri import android.net.Uri
import android.view.View import android.view.View
import android.widget.DatePicker import android.widget.DatePicker
import android.widget.ListView import androidx.annotation.IdRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.recyclerview.widget.RecyclerView.ViewHolder import androidx.recyclerview.widget.RecyclerView.ViewHolder
import androidx.test.espresso.Espresso.onData import androidx.test.espresso.Espresso.onData
@@ -17,13 +17,13 @@ import androidx.test.espresso.ViewAction
import androidx.test.espresso.ViewInteraction import androidx.test.espresso.ViewInteraction
import androidx.test.espresso.action.ViewActions import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.scrollTo
import androidx.test.espresso.action.ViewActions.swipeDown import androidx.test.espresso.action.ViewActions.swipeDown
import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.PickerActions import androidx.test.espresso.contrib.PickerActions
import androidx.test.espresso.contrib.RecyclerViewActions import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.intent.Intents import androidx.test.espresso.intent.Intents
import androidx.test.espresso.intent.Intents.intending import androidx.test.espresso.intent.Intents.intending
import androidx.test.espresso.intent.matcher.IntentMatchers
import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction
import androidx.test.espresso.matcher.ViewMatchers.hasDescendant import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
import androidx.test.espresso.matcher.ViewMatchers.isRoot import androidx.test.espresso.matcher.ViewMatchers.isRoot
@@ -41,34 +41,40 @@ import java.io.File
@SuppressWarnings("unused") @SuppressWarnings("unused")
open class BaseTestRobot { open class BaseTestRobot {
fun fillEditText(resId: Int, text: String?): ViewInteraction = fun fillEditText(@IdRes resId: Int, text: String?): ViewInteraction =
onView(withId(resId)).perform( onView(withId(resId)).perform(
ViewActions.replaceText(text), ViewActions.replaceText(text),
ViewActions.closeSoftKeyboard() ViewActions.closeSoftKeyboard()
) )
fun clickButton(resId: Int): ViewInteraction = fun scrollAndFillEditText(@IdRes resId: Int, text: String?): ViewInteraction =
onView(withId(resId)).perform(
scrollTo(),
ViewActions.replaceText(text),
ViewActions.closeSoftKeyboard()
)
fun clickButton(@IdRes resId: Int): ViewInteraction =
onView((withId(resId))).perform(click()) onView((withId(resId))).perform(click())
fun matchView(resId: Int): ViewInteraction = onView(withId(resId)) fun matchView(@IdRes resId: Int): ViewInteraction = onView(withId(resId))
fun matchViewWaitFor(resId: Int): ViewInteraction = waitForView(withId(resId)) fun matchViewWaitFor(@IdRes resId: Int): ViewInteraction = waitForView(withId(resId))
fun matchText(viewInteraction: ViewInteraction, text: String): ViewInteraction = viewInteraction fun matchText(viewInteraction: ViewInteraction, text: String): ViewInteraction = viewInteraction
.check(matches(withText(text))) .check(matches(withText(text)))
fun matchText(viewId: Int, textId: Int): ViewInteraction = onView(withId(viewId)) fun matchText(@StringRes stringId:Int): ViewInteraction = onView(withText(stringId))
.check(matches(withText(textId)))
fun matchText(resId: Int, text: String): ViewInteraction = matchText(matchView(resId), text) fun matchText(@IdRes resId: Int, text: String): ViewInteraction = matchText(matchView(resId), text)
fun clickListItem(listRes: Int, position: Int) { fun clickListItem(@IdRes listRes: Int, position: Int) {
onData(anything()) onData(anything())
.inAdapterView(allOf(withId(listRes))) .inAdapterView(allOf(withId(listRes)))
.atPosition(position).perform(click()) .atPosition(position).perform(click())
} }
fun <VH : ViewHolder> scrollToRecyclerItem(recyclerId: Int, text: String): ViewInteraction? { fun <VH : ViewHolder> scrollToRecyclerItem(@IdRes recyclerId: Int, text: String): ViewInteraction? {
return matchView(recyclerId) return matchView(recyclerId)
.perform( .perform(
// scrollTo will fail the test if no item matches. // scrollTo will fail the test if no item matches.
@@ -78,7 +84,7 @@ open class BaseTestRobot {
) )
} }
fun <VH : ViewHolder> scrollToRecyclerItem(recyclerId: Int, resIdForString: Int): ViewInteraction? { fun <VH : ViewHolder> scrollToRecyclerItem(@IdRes recyclerId: Int, resIdForString: Int): ViewInteraction? {
return matchView(recyclerId) return matchView(recyclerId)
.perform( .perform(
// scrollTo will fail the test if no item matches. // scrollTo will fail the test if no item matches.
@@ -88,7 +94,7 @@ open class BaseTestRobot {
) )
} }
fun <VH : ViewHolder> scrollToRecyclerItemByPosition(recyclerId: Int, position: Int): ViewInteraction? { fun <VH : ViewHolder> scrollToRecyclerItemByPosition(@IdRes recyclerId: Int, position: Int): ViewInteraction? {
return matchView(recyclerId) return matchView(recyclerId)
.perform( .perform(
// scrollTo will fail the test if no item matches. // scrollTo will fail the test if no item matches.
@@ -96,7 +102,7 @@ open class BaseTestRobot {
) )
} }
fun <VH : ViewHolder> clickViewInRecycler(recyclerId: Int, text: String) { fun <VH : ViewHolder> clickViewInRecycler(@IdRes recyclerId: Int, text: String) {
matchView(recyclerId) matchView(recyclerId)
.perform( .perform(
// scrollTo will fail the test if no item matches. // scrollTo will fail the test if no item matches.
@@ -104,7 +110,7 @@ open class BaseTestRobot {
) )
} }
fun <VH : ViewHolder> clickViewInRecycler(recyclerId: Int, resIdForString: Int) { fun <VH : ViewHolder> clickViewInRecycler(@IdRes recyclerId: Int, resIdForString: Int) {
matchView(recyclerId) matchView(recyclerId)
.perform( .perform(
// scrollTo will fail the test if no item matches. // scrollTo will fail the test if no item matches.
@@ -112,7 +118,7 @@ open class BaseTestRobot {
) )
} }
fun <VH : ViewHolder> clickSubViewInRecycler(recyclerId: Int, text: String, subView: Int) { fun <VH : ViewHolder> clickSubViewInRecycler(@IdRes recyclerId: Int, text: String, subView: Int) {
scrollToRecyclerItem<VH>(recyclerId, text) scrollToRecyclerItem<VH>(recyclerId, text)
?.perform( ?.perform(
// scrollTo will fail the test if no item matches. // scrollTo will fail the test if no item matches.
@@ -128,13 +134,13 @@ open class BaseTestRobot {
) )
} }
fun checkErrorOnTextEntry(resId: Int, errorMessage: String): ViewInteraction = fun checkErrorOnTextEntry(@IdRes resId: Int, errorMessage: String): ViewInteraction =
onView(withId(resId)).check(matches(checkErrorMessage(errorMessage))) onView(withId(resId)).check(matches(checkErrorMessage(errorMessage)))
fun checkImageViewHasImage(resId: Int): ViewInteraction = fun checkImageViewDoesNotHaveDefaultImage(@IdRes resId: Int): ViewInteraction =
onView(withId(resId)).check(matches(checkImage())) onView(withId(resId)).check(matches(checkImage()))
fun swipeDown(resId: Int): ViewInteraction = fun swipeDown(@IdRes resId: Int): ViewInteraction =
onView(withId(resId)).perform(swipeDown()) onView(withId(resId)).perform(swipeDown())
fun getStringFromResource(@StringRes resId: Int): String = fun getStringFromResource(@StringRes resId: Int): String =
@@ -150,12 +156,12 @@ open class BaseTestRobot {
) )
} }
fun selectSingleImageFromGallery(filePath: FormRobot.FilePath, openSelector: () -> Unit) { fun selectSingleImageFromGallery(filePath: String, openSelector: () -> Unit) {
Intents.init() Intents.init()
// Build the result to return when the activity is launched. // Build the result to return when the activity is launched.
val resultData = Intent() val resultData = Intent()
resultData.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION resultData.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
resultData.data = Uri.fromFile(File(FormRobot.FilePath.getFilePath(filePath))) resultData.data = Uri.fromFile(File("/sdcard/Camera/", filePath))
val result = Instrumentation.ActivityResult(Activity.RESULT_OK, resultData) val result = Instrumentation.ActivityResult(Activity.RESULT_OK, resultData)
// Set up result stubbing when an intent sent to image picker is seen. // Set up result stubbing when an intent sent to image picker is seen.
intending(hasAction(Intent.ACTION_GET_CONTENT)).respondWith(result) intending(hasAction(Intent.ACTION_GET_CONTENT)).respondWith(result)
@@ -164,7 +170,7 @@ open class BaseTestRobot {
Intents.release() Intents.release()
} }
fun selectMultipleImageFromGallery(filePaths: Array<String>, openSelector: () -> Unit) { fun selectMultipleImageFromGallery(filePaths: List<String>, openSelector: () -> Unit) {
Intents.init() Intents.init()
// Build the result to return when the activity is launched. // Build the result to return when the activity is launched.
val resultData = Intent() val resultData = Intent()
@@ -172,7 +178,7 @@ open class BaseTestRobot {
resultData.clipData = clipData resultData.clipData = clipData
val result = Instrumentation.ActivityResult(Activity.RESULT_OK, resultData) val result = Instrumentation.ActivityResult(Activity.RESULT_OK, resultData)
// Set up result stubbing when an intent sent to "contacts" is seen. // Set up result stubbing when an intent sent to "contacts" is seen.
intending(IntentMatchers.toPackage("android.intent.action.PICK")).respondWith(result) intending(hasAction(Intent.ACTION_GET_CONTENT)).respondWith(result)
openSelector() openSelector()
Intents.release() Intents.release()

View File

@@ -3,25 +3,27 @@ package h_mal.appttude.com.driver
import android.Manifest import android.Manifest
import android.R import android.R
import android.app.Activity import android.app.Activity
import android.content.Context
import android.os.Build
import android.view.View import android.view.View
import android.view.WindowManager
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.test.core.app.ActivityScenario import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.*
import androidx.test.espresso.Espresso.onView 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.assertion.ViewAssertions.matches import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.* import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isRoot
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import androidx.test.rule.GrantPermissionRule import androidx.test.rule.GrantPermissionRule
import com.google.gson.Gson
import h_mal.appttude.com.driver.base.BaseActivity import h_mal.appttude.com.driver.base.BaseActivity
import h_mal.appttude.com.driver.helpers.BaseViewAction import h_mal.appttude.com.driver.helpers.BaseViewAction
import h_mal.appttude.com.driver.helpers.SnapshotRule import h_mal.appttude.com.driver.helpers.SnapshotRule
import org.hamcrest.CoreMatchers import org.hamcrest.CoreMatchers
import org.hamcrest.Description
import org.hamcrest.Matcher import org.hamcrest.Matcher
import org.hamcrest.TypeSafeMatcher
import org.hamcrest.core.AllOf import org.hamcrest.core.AllOf
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
@@ -29,11 +31,13 @@ import org.junit.Rule
import tools.fastlane.screengrab.Screengrab import tools.fastlane.screengrab.Screengrab
import tools.fastlane.screengrab.UiAutomatorScreenshotStrategy import tools.fastlane.screengrab.UiAutomatorScreenshotStrategy
import tools.fastlane.screengrab.locale.LocaleTestRule import tools.fastlane.screengrab.locale.LocaleTestRule
import java.io.BufferedReader
open class BaseUiTest<T : BaseActivity<*, *>>( open class BaseUiTest<T : BaseActivity<*, *>>(
private val activity: Class<T> private val activity: Class<T>
) { ) {
val gson by lazy { Gson() }
private lateinit var mActivityScenarioRule: ActivityScenario<T> private lateinit var mActivityScenarioRule: ActivityScenario<T>
private var mIdlingResource: IdlingResource? = null private var mIdlingResource: IdlingResource? = null
@@ -41,7 +45,7 @@ open class BaseUiTest<T : BaseActivity<*, *>>(
private lateinit var currentActivity: Activity private lateinit var currentActivity: Activity
@get:Rule @get:Rule
var permissionRule = GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE) var permissionRule = GrantPermissionRule.grant(Manifest.permission.READ_EXTERNAL_STORAGE)
@get:Rule @get:Rule
var snapshotRule: SnapshotRule = SnapshotRule() var snapshotRule: SnapshotRule = SnapshotRule()
@@ -58,8 +62,8 @@ open class BaseUiTest<T : BaseActivity<*, *>>(
mActivityScenarioRule.onActivity { mActivityScenarioRule.onActivity {
mIdlingResource = it.getIdlingResource()!! mIdlingResource = it.getIdlingResource()!!
IdlingRegistry.getInstance().register(mIdlingResource) IdlingRegistry.getInstance().register(mIdlingResource)
afterLaunch(it)
} }
afterLaunch()
} }
@After @After
@@ -86,7 +90,7 @@ open class BaseUiTest<T : BaseActivity<*, *>>(
} }
open fun beforeLaunch() {} open fun beforeLaunch() {}
open fun afterLaunch(context: Context) {} open fun afterLaunch() {}
fun checkSnackBarDisplayedByMessage(message: String) { fun checkSnackBarDisplayedByMessage(message: String) {
onView( onView(
@@ -108,4 +112,22 @@ open class BaseUiTest<T : BaseActivity<*, *>>(
}) })
return currentActivity return currentActivity
} }
fun <T: Any> readDataFromAsset(fileName: String, clazz: Class<T>): T {
val iStream =
getInstrumentation().context.assets.open("$fileName.json")
val data = iStream.bufferedReader().use(BufferedReader::readText)
return gson.fromJson(data, clazz)
}
inline fun <reified M: Any> readDataFromAsset(fileName: String): M {
val iStream =
getInstrumentation().context.assets.open("$fileName.json")
val data = iStream.bufferedReader().use(BufferedReader::readText)
return fromJson<M>(data)
}
inline fun <reified M> fromJson(json: String)
= gson.fromJson<M>(json, M::class.java)
} }

View File

@@ -5,41 +5,32 @@ import android.view.View
import android.widget.EditText import android.widget.EditText
import android.widget.ImageView import android.widget.ImageView
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import org.hamcrest.Description import h_mal.appttude.com.driver.helpers.BaseMatcher
import org.hamcrest.Matcher import org.hamcrest.Matcher
import org.hamcrest.TypeSafeMatcher
/** /**
* Matcher for testing error of TextInputLayout * Matcher for testing error of TextInputLayout
*/ */
fun checkErrorMessage(expectedErrorText: String): Matcher<View?> { fun checkErrorMessage(expectedErrorText: String): Matcher<View> {
return object : TypeSafeMatcher<View?>() { return object : BaseMatcher<View>() {
override fun matchesSafely(view: View?): Boolean { override fun match(item: View): Boolean {
if (view is EditText) { if (item is EditText) {
return view.error.toString() == expectedErrorText return item.error.toString() == expectedErrorText
} }
if (view !is TextInputLayout) return false if (item !is TextInputLayout) return false
val error = view.error ?: return false val error = item.error ?: return false
return expectedErrorText == error.toString() return expectedErrorText == error.toString()
} }
override fun describeTo(d: Description?) {}
} }
} }
fun checkImage(): Matcher<View?> { @Suppress("UNCHECKED_CAST")
return object : TypeSafeMatcher<View?>() { fun checkImage(): Matcher<View> {
override fun matchesSafely(view: View?): Boolean { return object: BaseMatcher<ImageView>() {
if (view is ImageView) { override fun match(item: ImageView): Boolean = hasImage(item)
return hasImage(view)
}
return false
}
override fun describeTo(d: Description?) {}
private fun hasImage(view: ImageView): Boolean { private fun hasImage(view: ImageView): Boolean {
val drawable = view.drawable val drawable = view.drawable
@@ -49,6 +40,6 @@ fun checkImage(): Matcher<View?> {
} }
return hasImage return hasImage
} }
} } as Matcher<View>
} }

View File

@@ -1,13 +1,27 @@
package h_mal.appttude.com.driver package h_mal.appttude.com.driver
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.ViewInteraction
import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.scrollTo
import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom
import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withId
import h_mal.appttude.com.driver.helpers.getImagePath import h_mal.appttude.com.driver.helpers.EspressoHelper.trying
import h_mal.appttude.com.driver.model.Model
import org.hamcrest.CoreMatchers.allOf
import java.time.LocalDate
import java.time.format.DateTimeFormatter
open class FormRobot : BaseTestRobot() { open class FormRobot<T : Model> : BaseTestRobot() {
fun submit() = clickButton(R.id.submit)
fun submit() = onView(
allOf(
withId(R.id.submit),
isAssignableFrom(com.google.android.material.button.MaterialButton::class.java)
)
).perform(click())
fun setDate(datePickerLaunchViewId: Int, year: Int, monthOfYear: Int, dayOfMonth: Int) { fun setDate(datePickerLaunchViewId: Int, year: Int, monthOfYear: Int, dayOfMonth: Int) {
onView(withId(datePickerLaunchViewId)).perform(click()) onView(withId(datePickerLaunchViewId)).perform(click())
selectDateInPicker(year, monthOfYear, dayOfMonth) selectDateInPicker(year, monthOfYear, dayOfMonth)
@@ -15,32 +29,50 @@ open class FormRobot : BaseTestRobot() {
onView(withId(android.R.id.button1)).perform(click()) onView(withId(android.R.id.button1)).perform(click())
} }
fun selectSingleImage(imagePickerLauncherViewId: Int, filePath: FilePath) { fun setDate(datePickerLaunchViewId: Int, dateString: String) {
selectSingleImageFromGallery(filePath) { onView(withId(datePickerLaunchViewId)).perform(click())
onView(withId(imagePickerLauncherViewId)).perform(click()) val date = LocalDate.parse(dateString, DateTimeFormatter.ofPattern("dd/MM/yyyy"))
} selectDateInPicker(date.year, date.monthValue, date.dayOfMonth)
// click ok in date picker // click ok in date picker
onView(withId(android.R.id.button1)).perform(click())
} }
fun selectMultipleImage(imagePickerLauncherViewId: Int, filePaths: Array<String>) { fun scrollAndSetDate(datePickerLaunchViewId: Int, dateString: String) {
selectMultipleImageFromGallery(filePaths) { onView(withId(datePickerLaunchViewId)).perform(scrollTo(), click())
val date = LocalDate.parse(dateString, DateTimeFormatter.ofPattern("dd/MM/yyyy"))
selectDateInPicker(date.year, date.monthValue, date.dayOfMonth)
// click ok in date picker
onView(withId(android.R.id.button1)).perform(click())
}
fun selectSingleImage(imagePickerLauncherViewId: Int, fileName: String) {
selectSingleImageFromGallery(fileName) {
onView(withId(imagePickerLauncherViewId)).perform(click()) onView(withId(imagePickerLauncherViewId)).perform(click())
} }
} }
enum class FilePath(val path: String) { fun selectSingleImage(imagePickerViewInteraction: ViewInteraction, fileName: String) {
PROFILE_PIC("driver_profile_pic.jpg"), selectSingleImageFromGallery(fileName) {
INSURANCE("driver_insurance.jpg"), imagePickerViewInteraction.perform(click())
PRIVATE_HIRE("driver_license_private_hire.jpg"), }
PRIVATE_HIRE_CAR("driver_license_private_hire_car.jpg"), }
LOGBOOK("driver_logbook.jpg"),
MOT("driver_mot.jpg"),
LICENSE("driver_license_driver.jpg");
companion object { fun selectMultipleImage(imagePickerLauncherViewId: Int, filePaths: List<String>) {
fun getFilePath(filePath: FilePath): String { selectMultipleImageFromGallery(filePaths.map { "/sdcard/Camera/$it" }) {
return getImagePath(filePath.path) onView(withId(imagePickerLauncherViewId)).perform(click())
} }
} }
open fun submitForm(data: T) {
(trying {
onView(withId(R.id.submit)).perform(scrollTo())
} ?: onView(withId(R.id.submit))).perform(click())
}
open fun validateSubmission(data: T) {}
open fun submitAndValidate(data: T) {
submitForm(data)
validateSubmission(data)
} }
} }

View File

@@ -1,17 +1,22 @@
package h_mal.appttude.com.driver.helpers package h_mal.appttude.com.driver.helpers
import android.view.View
import org.hamcrest.BaseMatcher
import org.hamcrest.Description import org.hamcrest.Description
import org.hamcrest.TypeSafeMatcher
class BaseMatcher: BaseMatcher<View>() { open class BaseMatcher<T: Any>: TypeSafeMatcher<T>() {
override fun describeTo(description: Description?) { override fun describeTo(description: Description?) { }
TODO("Not yet implemented")
}
override fun matches(actual: Any?): Boolean { override fun describeMismatchSafely(item: T, mismatchDescription: Description?) {
TODO("Not yet implemented") describe(item, mismatchDescription)
} }
override fun matchesSafely(item: T): Boolean = match(item)
open fun match(item: T): Boolean { return false }
open fun describe(item: T, mismatchDescription: Description?) {
super.describeMismatchSafely(item, mismatchDescription)
}
} }

View File

@@ -1,14 +1 @@
package h_mal.appttude.com.driver.helpers package h_mal.appttude.com.driver.helpers
import android.os.Environment
import java.io.File
/**
* File paths for images on device
*/
fun getImagePath(imageConst: String): String {
return File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM),
"/Camera/images/$imageConst"
).absolutePath
}

View File

@@ -18,10 +18,9 @@ object DataHelper {
fun createClipData(filePath: String) = createClipData(createClipItem(filePath)) fun createClipData(filePath: String) = createClipData(createClipItem(filePath))
fun createClipData(filePaths: Array<String>): ClipData { fun createClipData(filePaths: List<String>): ClipData {
val clipData = createClipData(filePaths[0]) val clipData = createClipData(filePaths[0])
val remainingFiles = filePaths.copyOfRange(1, filePaths.size - 1) filePaths.filterIndexed { i, _ -> i > 0 }.let { clipData.addFilePaths(it.toTypedArray()) }
clipData.addFilePaths(remainingFiles)
return clipData return clipData
} }

View File

@@ -120,4 +120,50 @@ object EspressoHelper {
throw Exception("Error finding a view matching $viewMatcher") throw Exception("Error finding a view matching $viewMatcher")
} }
/**
* try and perform a view interaction for
* @param waitMillis at intervals of
* @param waitMillisPerTry,
* upon failure to locate an element, it will return null
*
*/
fun ViewInteraction.tryPerform(
vararg viewActions: ViewAction,
waitMillis: Int = 1000,
waitMillisPerTry: Long = 200,
): ViewInteraction? {
// Derive the max tries
val maxTries = waitMillis / waitMillisPerTry.toInt()
var tries = 0
for (i in 0..maxTries)
try {
// Track the amount of times we've tried
tries++
// Search the root for the view
return perform(*viewActions)
} catch (e: Exception) {
if (tries == maxTries) {
throw e
}
sleep(waitMillisPerTry)
}
return null
}
fun <T: Any> trying(action: () -> T): T? {
return try {
val result = action.invoke()
result
}catch (_: Exception) {
null
}
}
} }

View File

@@ -1,11 +1,24 @@
package h_mal.appttude.com.driver.utils package h_mal.appttude.com.driver.untiTests
import androidx.startup.AppInitializer
import androidx.test.platform.app.InstrumentationRegistry
import h_mal.appttude.com.driver.utils.DateUtils
import h_mal.appttude.com.driver.utils.DateUtils.convertDateStringDatePattern import h_mal.appttude.com.driver.utils.DateUtils.convertDateStringDatePattern
import org.junit.Assert.* import net.danlew.android.joda.JodaTimeInitializer
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test import org.junit.Test
class DateUtilsTest { class DateUtilsTest {
@Before
fun setup() {
AppInitializer.getInstance(InstrumentationRegistry.getInstrumentation().context.applicationContext)
.initializeComponent(JodaTimeInitializer::class.java)
}
@Test @Test
fun test_getDateTimeStamp() { fun test_getDateTimeStamp() {
val regex1 = "[0-9]{8}_[0-9]{6}".toRegex() val regex1 = "[0-9]{8}_[0-9]{6}".toRegex()
@@ -31,6 +44,8 @@ class DateUtilsTest {
} }
@Test @Test
fun test_parseCalenderIntoDateString() { fun test_getDateString() {
val date = DateUtils.getDateString(2019, 8, 1)
assertEquals(date, "01/08/2019")
} }
} }

View File

@@ -1,11 +1,12 @@
package h_mal.appttude.com.driver.robots package h_mal.appttude.com.driver.robots
import h_mal.appttude.com.driver.FormRobot import h_mal.appttude.com.driver.BaseTestRobot
import h_mal.appttude.com.driver.R import h_mal.appttude.com.driver.R
fun delete(func: DeleteRobot.() -> Unit) = DeleteRobot().apply { func() } fun delete(func: DeleteRobot.() -> Unit) = DeleteRobot().apply { func() }
class DeleteRobot : FormRobot() { class DeleteRobot : BaseTestRobot() {
fun submit() = clickButton(R.id.submit)
fun enterEmail(email: String) = fillEditText(R.id.email_update, email) fun enterEmail(email: String) = fillEditText(R.id.email_update, email)
fun enterPassword(password: String) = fillEditText(R.id.password_top, password) fun enterPassword(password: String) = fillEditText(R.id.password_top, password)

View File

@@ -1,11 +1,12 @@
package h_mal.appttude.com.driver.robots package h_mal.appttude.com.driver.robots
import h_mal.appttude.com.driver.FormRobot import h_mal.appttude.com.driver.BaseTestRobot
import h_mal.appttude.com.driver.R import h_mal.appttude.com.driver.R
fun updateEmail(func: UpdateEmailRobot.() -> Unit) = UpdateEmailRobot().apply { func() } fun updateEmail(func: UpdateEmailRobot.() -> Unit) = UpdateEmailRobot().apply { func() }
class UpdateEmailRobot : FormRobot() { class UpdateEmailRobot : BaseTestRobot() {
fun submit() = clickButton(R.id.submit)
fun enterEmail(email: String) = fillEditText(R.id.email_update, email) fun enterEmail(email: String) = fillEditText(R.id.email_update, email)
fun enterPassword(password: String) = fillEditText(R.id.password_top, password) fun enterPassword(password: String) = fillEditText(R.id.password_top, password)
fun enterNewEmail(email: String) = fillEditText(R.id.new_email, email) fun enterNewEmail(email: String) = fillEditText(R.id.new_email, email)

View File

@@ -1,11 +1,12 @@
package h_mal.appttude.com.driver.robots package h_mal.appttude.com.driver.robots
import h_mal.appttude.com.driver.FormRobot import h_mal.appttude.com.driver.BaseTestRobot
import h_mal.appttude.com.driver.R import h_mal.appttude.com.driver.R
fun updatePassword(func: UpdatePasswordRobot.() -> Unit) = UpdatePasswordRobot().apply { func() } fun updatePassword(func: UpdatePasswordRobot.() -> Unit) = UpdatePasswordRobot().apply { func() }
class UpdatePasswordRobot : FormRobot() { class UpdatePasswordRobot : BaseTestRobot() {
fun submit() = clickButton(R.id.submit)
fun enterEmail(email: String) = fillEditText(R.id.email_update, email) fun enterEmail(email: String) = fillEditText(R.id.email_update, email)
fun enterPassword(password: String) = fillEditText(R.id.password_top, password) fun enterPassword(password: String) = fillEditText(R.id.password_top, password)
fun enterNewPassword(email: String) = fillEditText(R.id.password_bottom, email) fun enterNewPassword(email: String) = fillEditText(R.id.password_bottom, email)

View File

@@ -1,16 +1,18 @@
package h_mal.appttude.com.driver.robots package h_mal.appttude.com.driver.robots
import h_mal.appttude.com.driver.FormRobot import h_mal.appttude.com.driver.BaseTestRobot
import h_mal.appttude.com.driver.R import h_mal.appttude.com.driver.R
fun updateProfile(func: UpdateProfileRobot.() -> Unit) = UpdateProfileRobot().apply { func() } fun updateProfile(func: UpdateProfileRobot.() -> Unit) = UpdateProfileRobot().apply { func() }
class UpdateProfileRobot : FormRobot() { class UpdateProfileRobot : BaseTestRobot() {
fun submit() = clickButton(R.id.submit)
fun enterName(name: String) = fillEditText(R.id.update_name, name) fun enterName(name: String) = fillEditText(R.id.update_name, name)
fun selectImage() = selectSingleImage(R.id.profile_img, FilePath.PROFILE_PIC)
fun submitForm(name: String) { fun submitForm(name: String) {
// selectImage() selectSingleImageFromGallery("driver_profile_pic") {
clickButton(R.id.profile_img)
}
enterName(name) enterName(name)
submit() submit()
} }

View File

@@ -2,26 +2,23 @@ package h_mal.appttude.com.driver.robots.driver
import h_mal.appttude.com.driver.FormRobot import h_mal.appttude.com.driver.FormRobot
import h_mal.appttude.com.driver.R import h_mal.appttude.com.driver.R
import h_mal.appttude.com.driver.model.DriversLicense
fun driversLicense(func: DriversLicenseRobot.() -> Unit) = DriversLicenseRobot().apply { func() } fun driversLicense(func: DriversLicenseRobot.() -> Unit) = DriversLicenseRobot().apply { func() }
class DriversLicenseRobot : FormRobot() { class DriversLicenseRobot : FormRobot<DriversLicense>() {
fun enterLicenseNumber(text: String) = fillEditText(R.id.lic_no, text) fun enterLicenseNumber(text: String) = fillEditText(R.id.lic_no, text)
fun enterLicenseExpiry(year: Int, monthOfYear: Int, dayOfMonth: Int) = fun enterLicenseExpiry(data: String) = setDate(R.id.lic_expiry, data)
setDate(R.id.lic_expiry, year, monthOfYear, dayOfMonth)
fun selectImage() = selectSingleImage(R.id.search_image, FilePath.LICENSE) override fun submitForm(data: DriversLicense) {
selectSingleImage(R.id.search_image, data.licenseImageString!!)
fun submitForm(licenseNumber: String, year: Int, monthOfYear: Int, dayOfMonth: Int) { enterLicenseExpiry(data.licenseExpiry!!)
selectImage() enterLicenseNumber(data.licenseNumber!!)
enterLicenseNumber(licenseNumber) super.submitForm(data)
enterLicenseExpiry(year, monthOfYear, dayOfMonth)
submit()
} }
fun validate() { override fun validateSubmission(data: DriversLicense) {
checkImageViewHasImage(R.id.driversli_img) checkImageViewDoesNotHaveDefaultImage(R.id.driversli_img)
} }
} }

View File

@@ -1,38 +1,39 @@
package h_mal.appttude.com.driver.robots.driver package h_mal.appttude.com.driver.robots.driver
import androidx.test.espresso.action.ViewActions.closeSoftKeyboard
import androidx.test.espresso.action.ViewActions.scrollTo
import h_mal.appttude.com.driver.FormRobot import h_mal.appttude.com.driver.FormRobot
import h_mal.appttude.com.driver.R import h_mal.appttude.com.driver.R
import h_mal.appttude.com.driver.model.DriverProfile
fun driversProfile(func: DriversProfileRobot.() -> Unit) = DriversProfileRobot().apply { func() } fun driversProfile(func: DriversProfileRobot.() -> Unit) = DriversProfileRobot().apply { func() }
class DriversProfileRobot : FormRobot() { class DriversProfileRobot : FormRobot<DriverProfile>() {
fun enterName(name: String) = fillEditText(R.id.names_input, name) fun enterName(name: String) = fillEditText(R.id.names_input, name)
fun enterAddress(address: String) = fillEditText(R.id.address_input, address) fun enterAddress(address: String) = fillEditText(R.id.address_input, address)
fun enterPostcode(postcode: String) = fillEditText(R.id.postcode_input, postcode) fun enterPostcode(postcode: String) = fillEditText(R.id.postcode_input, postcode)
fun enterDateOfBirth(dob: String) = fillEditText(R.id.dob_input, dob) fun enterDateOfBirth(date: String) = setDate(R.id.dob_input, date)
fun enterNINumber(niNumber: String) = fillEditText(R.id.ni_number, niNumber) fun enterNINumber(niNumber: String) = fillEditText(R.id.ni_number, niNumber)
fun enterDateFirstAvailable(year: Int, monthOfYear: Int, dayOfMonth: Int) = fun enterDateFirstAvailable(date: String) {
setDate(R.id.date_first, year, monthOfYear, dayOfMonth) closeSoftKeyboard()
matchView(R.id.date_first).perform(scrollTo())
setDate(R.id.date_first, date)
}
fun selectImage() = selectSingleImage(R.id.add_photo, FilePath.PROFILE_PIC) override fun validateSubmission(data: DriverProfile) {
checkImageViewDoesNotHaveDefaultImage(R.id.driver_pic)
matchText(R.id.names_input, data.forenames!!)
}
fun submitForm( override fun submitForm(data: DriverProfile) = data.run {
name: String, selectSingleImage(R.id.add_photo, driverPic!!)
address: String, enterName(forenames!!)
postcode: String, enterAddress(address!!)
dob: String, enterPostcode(postcode!!)
niNumber: String, enterDateOfBirth(dob!!)
year: Int, enterNINumber(ni!!)
monthOfYear: Int, enterDateFirstAvailable(dateFirst!!)
dayOfMonth: Int super.submitForm(data)
) {
selectImage()
enterName(name)
enterAddress(address)
enterPostcode(postcode)
enterDateOfBirth(dob)
enterNINumber(niNumber)
enterDateFirstAvailable(year, monthOfYear, dayOfMonth)
submit()
} }
} }

View File

@@ -2,22 +2,29 @@ package h_mal.appttude.com.driver.robots.driver
import h_mal.appttude.com.driver.FormRobot import h_mal.appttude.com.driver.FormRobot
import h_mal.appttude.com.driver.R import h_mal.appttude.com.driver.R
import h_mal.appttude.com.driver.model.PrivateHireLicense
fun privateHireLicenseRobot(func: PrivateHireLicenseRobot.() -> Unit) = fun privateHireLicenseRobot(func: PrivateHireLicenseRobot.() -> Unit) =
PrivateHireLicenseRobot().apply { func() } PrivateHireLicenseRobot().apply { func() }
class PrivateHireLicenseRobot : FormRobot() { class PrivateHireLicenseRobot : FormRobot<PrivateHireLicense>() {
fun enterLicenseNumber(text: String) = fillEditText(R.id.ph_no, text) fun enterLicenseNumber(text: String) = fillEditText(R.id.ph_no, text)
fun enterLicenseExpiry(year: Int, monthOfYear: Int, dayOfMonth: Int) = fun enterLicenseExpiry(date: String) = setDate(R.id.ph_expiry, date)
setDate(R.id.ph_expiry, year, monthOfYear, dayOfMonth)
fun selectImage() = selectSingleImage(R.id.uploadphlic, FilePath.PRIVATE_HIRE) fun selectImage(fileName: String) = selectSingleImage(R.id.uploadphlic, fileName)
fun submitForm(licenseNumber: String, year: Int, monthOfYear: Int, dayOfMonth: Int) { override fun submitForm(data: PrivateHireLicense) {
selectImage() selectImage(data.phImageString!!)
enterLicenseNumber(licenseNumber) enterLicenseNumber(data.phNumber!!)
enterLicenseExpiry(year, monthOfYear, dayOfMonth) enterLicenseExpiry(data.phExpiry!!)
submit() super.submitForm(data)
} }
fun validate(data: PrivateHireLicense) {
checkImageViewDoesNotHaveDefaultImage(R.id.imageView2)
matchText(R.id.ph_expiry, data.phExpiry!!)
matchText(R.id.ph_no, data.phNumber!!)
}
} }

View File

@@ -1,23 +1,24 @@
package h_mal.appttude.com.driver.robots.vehicle package h_mal.appttude.com.driver.robots.vehicle
import h_mal.appttude.com.driver.FormRobot import h_mal.appttude.com.driver.FormRobot
import h_mal.appttude.com.driver.FormRobot.FilePath.Companion.getFilePath
import h_mal.appttude.com.driver.R import h_mal.appttude.com.driver.R
import h_mal.appttude.com.driver.model.Insurance
fun insurance(func: InsuranceRobot.() -> Unit) = InsuranceRobot().apply { func() } fun insurance(func: InsuranceRobot.() -> Unit) = InsuranceRobot().apply { func() }
class InsuranceRobot : FormRobot() { class InsuranceRobot : FormRobot<Insurance>() {
fun enterInsurance(text: String) = fillEditText(R.id.insurer, text) fun enterInsurance(text: String) = fillEditText(R.id.insurer, text)
fun enterInsuranceExpiry(year: Int, monthOfYear: Int, dayOfMonth: Int) = fun enterInsuranceExpiry(date: String) = setDate(R.id.insurance_exp, date)
setDate(R.id.insurance_exp, year, monthOfYear, dayOfMonth)
fun selectImages() = override fun submitForm(data: Insurance) {
selectMultipleImage(R.id.uploadInsurance, arrayOf(getFilePath(FilePath.INSURANCE))) selectMultipleImage(R.id.uploadInsurance, data.photoStrings!!.map { it!! })
enterInsurance(data.insurerName!!)
enterInsuranceExpiry(data.expiryDate!!)
super.submitForm(data)
}
fun submitForm(insurer: String, year: Int, monthOfYear: Int, dayOfMonth: Int) { override fun validateSubmission(data: Insurance) {
selectImages() matchText(R.id.insurer, data.insurerName!!)
enterInsurance(insurer) matchText(R.id.insurance_exp, data.expiryDate!!)
enterInsuranceExpiry(year, monthOfYear, dayOfMonth)
submit()
} }
} }

View File

@@ -2,17 +2,21 @@ package h_mal.appttude.com.driver.robots.vehicle
import h_mal.appttude.com.driver.FormRobot import h_mal.appttude.com.driver.FormRobot
import h_mal.appttude.com.driver.R import h_mal.appttude.com.driver.R
import h_mal.appttude.com.driver.model.Logbook
fun logbook(func: LogbookRobot.() -> Unit) = LogbookRobot().apply { func() } fun logbook(func: LogbookRobot.() -> Unit) = LogbookRobot().apply { func() }
class LogbookRobot : FormRobot() { class LogbookRobot : FormRobot<Logbook>() {
fun selectImages() = selectSingleImage(R.id.uploadmot, FilePath.MOT) fun enterV5c(v5c: String) = fillEditText(R.id.v5c_no, v5c)
fun enterExpiryDate(year: Int, monthOfYear: Int, dayOfMonth: Int) =
setDate(R.id.mot_expiry, year, monthOfYear, dayOfMonth)
fun submitForm(year: Int, monthOfYear: Int, dayOfMonth: Int) { override fun submitForm(data: Logbook) {
selectImages() selectSingleImage(R.id.upload_lb, data.photoString!!)
enterExpiryDate(year, monthOfYear, dayOfMonth) enterV5c(data.v5cnumber!!)
submit() super.submitForm(data)
}
override fun validateSubmission(data: Logbook) {
checkImageViewDoesNotHaveDefaultImage(R.id.log_book_img)
matchText(R.id.v5c_no, data.v5cnumber!!)
} }
} }

View File

@@ -2,16 +2,21 @@ package h_mal.appttude.com.driver.robots.vehicle
import h_mal.appttude.com.driver.FormRobot import h_mal.appttude.com.driver.FormRobot
import h_mal.appttude.com.driver.R import h_mal.appttude.com.driver.R
import h_mal.appttude.com.driver.model.Mot
fun mot(func: MOTRobot.() -> Unit) = MOTRobot().apply { func() } fun mot(func: MOTRobot.() -> Unit) = MOTRobot().apply { func() }
class MOTRobot : FormRobot() { class MOTRobot : FormRobot<Mot>() {
fun enterV5cNumber(v5c: String) = fillEditText(R.id.mot_expiry, v5c) fun enterMotExpiry(expiry: String) = setDate(R.id.mot_expiry, expiry)
fun selectImages() = selectSingleImage(R.id.mot_expiry, FilePath.LOGBOOK)
fun submitForm(v5c: String) { override fun submitForm(data: Mot) {
selectImages() selectSingleImage(R.id.uploadmot, data.motImageString!!)
enterV5cNumber(v5c) enterMotExpiry(data.motExpiry!!)
submit() super.submitForm(data)
}
override fun validateSubmission(data: Mot) {
checkImageViewDoesNotHaveDefaultImage(R.id.mot_img)
matchText(R.id.mot_expiry, data.motExpiry!!)
} }
} }

View File

@@ -2,22 +2,28 @@ package h_mal.appttude.com.driver.robots.vehicle
import h_mal.appttude.com.driver.FormRobot import h_mal.appttude.com.driver.FormRobot
import h_mal.appttude.com.driver.R import h_mal.appttude.com.driver.R
import h_mal.appttude.com.driver.model.PrivateHireVehicle
fun privateHireVehicleLicense(func: PrivateHireVehicleLicenseRobot.() -> Unit) = fun privateHireVehicleLicense(func: PrivateHireVehicleLicenseRobot.() -> Unit) =
PrivateHireVehicleLicenseRobot().apply { func() } PrivateHireVehicleLicenseRobot().apply { func() }
class PrivateHireVehicleLicenseRobot : FormRobot<PrivateHireVehicle>() {
class PrivateHireVehicleLicenseRobot : FormRobot() {
fun enterLicenseNumber(text: String) = fillEditText(R.id.ph_no, text) fun enterLicenseNumber(text: String) = fillEditText(R.id.ph_no, text)
fun enterLicenseExpiry(year: Int, monthOfYear: Int, dayOfMonth: Int) = fun enterLicenseExpiry(date: String) = setDate(R.id.ph_expiry, date)
setDate(R.id.ph_expiry, year, monthOfYear, dayOfMonth)
fun selectImage() = selectSingleImage(R.id.uploadphlic, FilePath.PRIVATE_HIRE) override fun submitForm(data: PrivateHireVehicle) {
selectSingleImage(
matchText(R.string.upload_private_hire_photo),
data.phCarImageString!!
)
enterLicenseNumber(data.phCarNumber!!)
enterLicenseExpiry(data.phCarExpiry!!)
super.submitForm(data)
}
fun submitForm(licenseNumber: String, year: Int, monthOfYear: Int, dayOfMonth: Int) { override fun validateSubmission(data: PrivateHireVehicle) {
selectImage() checkImageViewDoesNotHaveDefaultImage(R.id.imageView2)
enterLicenseNumber(licenseNumber) matchText(R.id.ph_no, data.phCarNumber!!)
enterLicenseExpiry(year, monthOfYear, dayOfMonth) matchText(R.id.ph_expiry, data.phCarExpiry!!)
submit()
} }
} }

View File

@@ -1,46 +1,51 @@
package h_mal.appttude.com.driver.robots.vehicle package h_mal.appttude.com.driver.robots.vehicle
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isChecked
import androidx.test.espresso.matcher.ViewMatchers.isNotChecked
import h_mal.appttude.com.driver.FormRobot import h_mal.appttude.com.driver.FormRobot
import h_mal.appttude.com.driver.R import h_mal.appttude.com.driver.R
import h_mal.appttude.com.driver.helpers.EspressoHelper.setChecked import h_mal.appttude.com.driver.helpers.EspressoHelper.setChecked
import h_mal.appttude.com.driver.model.VehicleProfile
fun vehicleProfile(func: VehicleProfileRobot.() -> Unit) = VehicleProfileRobot().apply { func() } fun vehicleProfile(func: VehicleProfileRobot.() -> Unit) = VehicleProfileRobot().apply { func() }
class VehicleProfileRobot : FormRobot() { class VehicleProfileRobot : FormRobot<VehicleProfile>() {
fun enterRegistration(reg: String) = fillEditText(R.id.reg, reg)
fun enterMake(make: String) = fillEditText(R.id.make, make)
fun enterModel(model: String) = fillEditText(R.id.car_model, model)
fun enterColour(colour: String) = fillEditText(R.id.colour, colour)
fun enterAddress(address: String) = fillEditText(R.id.address, address)
fun enterPostcode(postCode: String) = fillEditText(R.id.postcode, postCode)
fun enterKeeperName(name: String) = fillEditText(R.id.keeper_name, name)
fun enterDateFirstAvailable(year: Int, monthOfYear: Int, dayOfMonth: Int) =
setDate(R.id.start_date, year, monthOfYear, dayOfMonth)
fun enterRegistration(reg: String) = scrollAndFillEditText(R.id.reg, reg)
fun enterMake(make: String) = scrollAndFillEditText(R.id.make, make)
fun enterModel(model: String) = scrollAndFillEditText(R.id.car_model, model)
fun enterColour(colour: String) = scrollAndFillEditText(R.id.colour, colour)
fun enterAddress(address: String) = scrollAndFillEditText(R.id.address, address)
fun enterPostcode(postCode: String) = scrollAndFillEditText(R.id.postcode, postCode)
fun enterKeeperName(name: String) = scrollAndFillEditText(R.id.keeper_name, name)
fun enterDateFirstAvailable(date: String) = scrollAndSetDate(R.id.start_date, date)
fun isSeized(seized: Boolean) = matchView(R.id.seized_checkbox).perform(setChecked(seized)) fun isSeized(seized: Boolean) = matchView(R.id.seized_checkbox).perform(setChecked(seized))
fun submitForm(
reg: String, override fun submitForm(data: VehicleProfile) {
make: String, enterRegistration(data.reg!!)
model: String, enterMake(data.make!!)
colour: String, enterModel(data.model!!)
address: String, enterColour(data.colour!!)
postCode: String, enterAddress(data.keeperAddress!!)
name: String, enterPostcode(data.keeperPostCode!!)
year: Int, enterKeeperName(data.keeperName!!)
monthOfYear: Int, enterDateFirstAvailable(data.startDate!!)
dayOfMonth: Int, isSeized(data.isSeized)
seized: Boolean = false super.submitForm(data)
) { }
enterRegistration(reg)
enterMake(make) override fun validateSubmission(data: VehicleProfile) {
enterModel(model) matchText(R.id.reg, data.reg!!)
enterColour(colour) matchText(R.id.make, data.make!!)
enterAddress(address) matchText(R.id.car_model, data.model!!)
enterPostcode(postCode) matchText(R.id.colour, data.colour!!)
enterKeeperName(name) matchText(R.id.address, data.keeperAddress!!)
enterDateFirstAvailable(year, monthOfYear, dayOfMonth) matchText(R.id.postcode, data.keeperPostCode!!)
isSeized(seized) matchText(R.id.keeper_name, data.keeperName!!)
submit() matchText(R.id.start_date, data.startDate!!)
val checking = if (data.isSeized) isChecked() else isNotChecked()
matchView(R.id.seized_checkbox).check(matches(checking))
super.validateSubmission(data)
} }
} }

View File

@@ -0,0 +1,52 @@
package h_mal.appttude.com.driver.tests.newUser
import androidx.test.espresso.matcher.ViewMatchers.withText
import h_mal.appttude.com.driver.FirebaseTest
import h_mal.appttude.com.driver.R
import h_mal.appttude.com.driver.helpers.EspressoHelper.trying
import h_mal.appttude.com.driver.helpers.EspressoHelper.waitForView
import h_mal.appttude.com.driver.model.DriverProfile
import h_mal.appttude.com.driver.model.DriversLicense
import h_mal.appttude.com.driver.model.Insurance
import h_mal.appttude.com.driver.model.Logbook
import h_mal.appttude.com.driver.model.Model
import h_mal.appttude.com.driver.model.Mot
import h_mal.appttude.com.driver.model.PrivateHireLicense
import h_mal.appttude.com.driver.model.PrivateHireVehicle
import h_mal.appttude.com.driver.model.VehicleProfile
import h_mal.appttude.com.driver.robots.home
import h_mal.appttude.com.driver.ui.MainActivity
import java.io.IOException
open class DataSubmissionTest :
FirebaseTest<MainActivity>(MainActivity::class.java, registered = true, signedIn = true) {
override fun afterLaunch() {
super.afterLaunch()
home {
waitForView(withText(getResourceString(R.string.welcome_title)), waitMillis = 10000)
trying {
requestProfile()
}
}
}
inline fun <reified T : Model> getAssetData(): T {
val file = when (T::class) {
DriverProfile::class -> "driver_details"
DriversLicense::class -> "drivers_license"
Insurance::class -> "insurance_details"
Logbook::class -> "log_book"
Mot::class -> "mot_details"
PrivateHireLicense::class -> "private_hire_license"
PrivateHireVehicle::class -> "private_hire_vehicle"
VehicleProfile::class -> "vehicle_details"
else -> {
throw IOException("No file for ${T::class}")
}
}
return readDataFromAsset(file)
}
}

View File

@@ -0,0 +1,13 @@
package h_mal.appttude.com.driver.tests.newUser
import h_mal.appttude.com.driver.robots.home
open class DriverProfileTest : DataSubmissionTest() {
override fun afterLaunch() {
super.afterLaunch()
home {
openDriverProfile()
}
}
}

View File

@@ -1,37 +0,0 @@
package h_mal.appttude.com.driver.tests.newUser
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.rule.GrantPermissionRule
import h_mal.appttude.com.driver.FirebaseTest
import h_mal.appttude.com.driver.R
import h_mal.appttude.com.driver.robots.*
import h_mal.appttude.com.driver.robots.driver.driversLicense
import h_mal.appttude.com.driver.ui.MainActivity
import org.junit.*
import org.junit.runner.RunWith
@LargeTest
@RunWith(AndroidJUnit4::class)
class SubmitNewDataActivityTest :
FirebaseTest<MainActivity>(MainActivity::class.java, registered = true, signedIn = true) {
@Test
fun verifyUserRegistration_validUsernameAndPassword_loggedIn() {
home {
waitFor(2500)
checkTitleExists(getResourceString(R.string.welcome_title))
requestProfile()
openDriverProfile()
}
driverScreen {
driverLicense()
}
driversLicense {
submitForm("SAMPLE8456310LTU", 2022, 10, 2)
}
}
}

View File

@@ -0,0 +1,54 @@
package h_mal.appttude.com.driver.tests.newUser
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import h_mal.appttude.com.driver.model.DriverProfile
import h_mal.appttude.com.driver.model.DriversLicense
import h_mal.appttude.com.driver.model.PrivateHireLicense
import h_mal.appttude.com.driver.robots.*
import h_mal.appttude.com.driver.robots.driver.driversLicense
import h_mal.appttude.com.driver.robots.driver.driversProfile
import h_mal.appttude.com.driver.robots.driver.privateHireLicenseRobot
import org.junit.*
import org.junit.runner.RunWith
@LargeTest
@RunWith(AndroidJUnit4::class)
class SubmitNewDriverDataTest : DriverProfileTest() {
@Test
fun signedInUser_uploadsValidLicenseDetails_uploadSuccessful() {
driverScreen {
driverLicense()
}
driversLicense {
val data = getAssetData<DriversLicense>()
submitAndValidate(data)
}
}
@Test
fun signedInUser_uploadsValidDriverDetails_uploadSuccessful() {
driverScreen {
driverProfile()
}
driversProfile {
val data = getAssetData<DriverProfile>()
submitAndValidate(data)
}
}
@Test
fun signedInUser_uploadsValidPrivateHireDetails_uploadSuccessful() {
driverScreen {
privateHireLicense()
}
privateHireLicenseRobot {
val data = getAssetData<PrivateHireLicense>()
submitAndValidate(data)
}
}
}

View File

@@ -0,0 +1,80 @@
package h_mal.appttude.com.driver.tests.newUser
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import h_mal.appttude.com.driver.model.Insurance
import h_mal.appttude.com.driver.model.Logbook
import h_mal.appttude.com.driver.model.Mot
import h_mal.appttude.com.driver.model.PrivateHireVehicle
import h_mal.appttude.com.driver.model.VehicleProfile
import h_mal.appttude.com.driver.robots.*
import h_mal.appttude.com.driver.robots.vehicle.insurance
import h_mal.appttude.com.driver.robots.vehicle.logbook
import h_mal.appttude.com.driver.robots.vehicle.mot
import h_mal.appttude.com.driver.robots.vehicle.privateHireVehicleLicense
import h_mal.appttude.com.driver.robots.vehicle.vehicleProfile
import org.junit.*
import org.junit.runner.RunWith
@LargeTest
@RunWith(AndroidJUnit4::class)
class SubmitNewVehicleDataTest : VehicleProfileTest() {
@Test
fun signedInUser_uploadsValidVehicleProfile_uploadSuccessful() {
vehicleScreen {
vehicleProfile()
}
vehicleProfile {
val data = getAssetData<VehicleProfile>()
submitAndValidate(data)
}
}
@Test
fun signedInUser_uploadsValidInsurance_uploadSuccessful() {
vehicleScreen {
insurance()
}
insurance {
val data = getAssetData<Insurance>()
submitAndValidate(data)
}
}
@Test
fun signedInUser_uploadsValidMot_uploadSuccessful() {
vehicleScreen {
mot()
}
mot {
val data = getAssetData<Mot>()
submitAndValidate(data)
}
}
@Test
fun signedInUser_uploadsValidLogbook_uploadSuccessful() {
vehicleScreen {
logbook()
}
logbook {
val data = getAssetData<Logbook>()
submitAndValidate(data)
}
}
@Test
fun signedInUser_uploadsValidPrivateHireVehicleLicense_uploadSuccessful() {
vehicleScreen {
privateHireVehicleLicense()
}
privateHireVehicleLicense {
val data = getAssetData<PrivateHireVehicle>()
submitAndValidate(data)
}
}
}

View File

@@ -0,0 +1,13 @@
package h_mal.appttude.com.driver.tests.newUser
import h_mal.appttude.com.driver.robots.home
open class VehicleProfileTest : DataSubmissionTest() {
override fun afterLaunch() {
super.afterLaunch()
home {
openVehicleProfile()
}
}
}

View File

@@ -5,7 +5,6 @@ import androidx.lifecycle.ViewModelProvider
import h_mal.appttude.com.driver.data.FirebaseAuthSource import h_mal.appttude.com.driver.data.FirebaseAuthSource
import h_mal.appttude.com.driver.data.FirebaseDatabaseSource import h_mal.appttude.com.driver.data.FirebaseDatabaseSource
import h_mal.appttude.com.driver.data.FirebaseStorageSource import h_mal.appttude.com.driver.data.FirebaseStorageSource
import h_mal.appttude.com.driver.data.prefs.PreferenceProvider
import h_mal.appttude.com.driver.viewmodels.* import h_mal.appttude.com.driver.viewmodels.*
class ApplicationViewModelFactory( class ApplicationViewModelFactory(

View File

@@ -1,17 +1,25 @@
package h_mal.appttude.com.driver.application package h_mal.appttude.com.driver.application
import android.app.Application import android.app.Application
import androidx.startup.AppInitializer
import h_mal.appttude.com.driver.data.FirebaseAuthSource import h_mal.appttude.com.driver.data.FirebaseAuthSource
import h_mal.appttude.com.driver.data.FirebaseDatabaseSource import h_mal.appttude.com.driver.data.FirebaseDatabaseSource
import h_mal.appttude.com.driver.data.FirebaseStorageSource import h_mal.appttude.com.driver.data.FirebaseStorageSource
import net.danlew.android.joda.JodaTimeInitializer
import org.kodein.di.Kodein import org.kodein.di.Kodein
import org.kodein.di.KodeinAware import org.kodein.di.KodeinAware
import org.kodein.di.android.x.androidXModule import org.kodein.di.android.x.androidXModule
import org.kodein.di.generic.bind import org.kodein.di.generic.bind
import org.kodein.di.generic.singleton import org.kodein.di.generic.singleton
const val GLOBAL_FORMAT = "dd/MM/yyyy"
open class BaseApplication : Application(), KodeinAware { open class BaseApplication : Application(), KodeinAware {
override fun onCreate() {
super.onCreate()
AppInitializer.getInstance(this).initializeComponent(JodaTimeInitializer::class.java)
}
// Kodein aware to initialise the classes used for DI // Kodein aware to initialise the classes used for DI
override val kodein = Kodein.lazy { override val kodein = Kodein.lazy {
import(parentModule) import(parentModule)

View File

@@ -161,10 +161,12 @@ abstract class BaseActivity<V : BaseViewModel, VB : ViewBinding> : AppCompatActi
mIdlingResource?.setIdleState(false) mIdlingResource?.setIdleState(false)
} }
}) })
} else {
}
toast.show() toast.show()
} else {
mIdlingResource?.setIdleState(true)
toast.show()
mIdlingResource?.setIdleState(false)
}
} }
fun showSnackBar(message: String) { fun showSnackBar(message: String) {

View File

@@ -3,14 +3,11 @@ package h_mal.appttude.com.driver.dialogs
import android.app.DatePickerDialog import android.app.DatePickerDialog
import android.app.DatePickerDialog.OnDateSetListener import android.app.DatePickerDialog.OnDateSetListener
import android.icu.util.Calendar
import android.widget.EditText import android.widget.EditText
import h_mal.appttude.com.driver.R import h_mal.appttude.com.driver.R
import h_mal.appttude.com.driver.utils.DateUtils import h_mal.appttude.com.driver.utils.DateUtils
private const val DATE_FORMAT = "dd/MM/yyyy"
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
class DateDialog( class DateDialog(
private val editText: EditText, private val editText: EditText,
@@ -19,10 +16,7 @@ class DateDialog(
private val dateSetListener: OnDateSetListener = private val dateSetListener: OnDateSetListener =
OnDateSetListener { _, year, month, dayOfMonth -> OnDateSetListener { _, year, month, dayOfMonth ->
val cal = Calendar.getInstance() val date = DateUtils.getDateString(year, month, dayOfMonth)
cal.set(year, month + 1, dayOfMonth)
val date = DateUtils.parseCalenderIntoDateString(cal, DATE_FORMAT)
dateSelected(date) dateSelected(date)
editText.setText(date) editText.setText(date)
editText.error = null editText.error = null
@@ -33,27 +27,17 @@ class DateDialog(
spinnersShown = true spinnersShown = true
calendarViewShown = false calendarViewShown = false
} }
val dateString = editText.text?.toString() val dateString = editText.text.toString()
val date = if (dateString.isNullOrBlank()) { val date = DateUtils.parseDateStringIntoCalender(dateString)
// Set time to now
Calendar.getInstance()
} else {
// Parse current edit text string and set value
DateUtils.parseDateStringIntoCalender(dateString, DATE_FORMAT)
?: Calendar.getInstance()
}
setDateFromCalender(date) setDateFromCalender(date)
setOnDateSetListener(dateSetListener) setOnDateSetListener(dateSetListener)
setTitle(context.getString(R.string.set_date)) setTitle(context.getString(R.string.set_date))
show() show()
} }
private fun setDateFromCalender(calendar: Calendar) { private fun setDateFromCalender(calendar: org.joda.time.LocalDate) {
val mYear = calendar.get(Calendar.YEAR) updateDate(calendar.year, calendar.monthOfYear, calendar.dayOfMonth)
val mMonth = calendar.get(Calendar.MONTH)
val mDay = calendar.get(Calendar.DAY_OF_MONTH)
updateDate(mYear, mMonth, mDay)
} }
} }

View File

@@ -1,7 +1,8 @@
package h_mal.appttude.com.driver.ui.update package h_mal.appttude.com.driver.ui.update
import h_mal.appttude.com.driver.base.BaseActivity import h_mal.appttude.com.driver.base.BaseActivity
import h_mal.appttude.com.driver.data.FirebaseCompletion import h_mal.appttude.com.driver.data.FirebaseCompletion.Changed
import h_mal.appttude.com.driver.data.FirebaseCompletion.ProfileDeleted
import h_mal.appttude.com.driver.databinding.UpdateActivityBinding import h_mal.appttude.com.driver.databinding.UpdateActivityBinding
import h_mal.appttude.com.driver.viewmodels.UpdateUserViewModel import h_mal.appttude.com.driver.viewmodels.UpdateUserViewModel
@@ -10,7 +11,8 @@ class UpdateActivity : BaseActivity<UpdateUserViewModel, UpdateActivityBinding>(
override fun onSuccess(data: Any?) { override fun onSuccess(data: Any?) {
super.onSuccess(data) super.onSuccess(data)
when (data) { when (data) {
is FirebaseCompletion.Changed -> showToast(data.message) is Changed -> showSnackBar(data.message)
is ProfileDeleted -> showToast(data.message)
} }
} }
} }

View File

@@ -1,10 +1,13 @@
package h_mal.appttude.com.driver.utils package h_mal.appttude.com.driver.utils
import android.icu.util.Calendar import h_mal.appttude.com.driver.application.GLOBAL_FORMAT
import org.joda.time.LocalDate
import org.joda.time.format.DateTimeFormat
import java.text.ParseException import java.text.ParseException
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
object DateUtils { object DateUtils {
fun getDateTimeStamp(): String { fun getDateTimeStamp(): String {
@@ -27,26 +30,19 @@ object DateUtils {
private fun getSimpleDateFormat(format: String) = SimpleDateFormat(format, Locale.getDefault()) private fun getSimpleDateFormat(format: String) = SimpleDateFormat(format, Locale.getDefault())
fun parseDateStringIntoCalender(dateString: String, format: String): Calendar? { fun parseDateStringIntoCalender(dateString: String, format: String = GLOBAL_FORMAT): LocalDate {
val dateFormat = getSimpleDateFormat(format) if (dateString.isBlank()) {
val calendar = Calendar.getInstance() return LocalDate.now()
return try {
calendar.time = dateFormat.parse(dateString)
calendar
} catch (e: Exception) {
null
} }
val dtf = DateTimeFormat.forPattern(format)
return dtf.parseLocalDate(dateString)
} }
fun parseCalenderIntoDateString(calendar: Calendar, format: String): String? { fun getDateString(year: Int, month: Int, dayOfMonth: Int): String {
val date = calendar.time val date = LocalDate.now()
val dateFormat = getSimpleDateFormat(format) .withYear(year)
.withMonthOfYear(month + 1)
return try { .withDayOfMonth(dayOfMonth)
dateFormat.format(date) return date.toString(GLOBAL_FORMAT)
} catch (e: ParseException) {
e.printStackTrace()
null
}
} }
} }

View File

@@ -22,8 +22,7 @@
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:contentDescription="@string/image_description" android:contentDescription="@string/image_description"
android:scaleType="centerCrop" android:scaleType="centerCrop" />
tools:src="@drawable/choice_img_round" />
<com.mikhaellopez.circularimageview.CircularImageView <com.mikhaellopez.circularimageview.CircularImageView
android:id="@+id/search_image" android:id="@+id/search_image"

View File

@@ -2,13 +2,19 @@
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
style="@style/parent_constraint_layout" android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginEnd="@dimen/activity_vertical_margin"
android:layout_marginStart="@dimen/activity_vertical_margin"
android:layout_marginRight="@dimen/activity_horizontal_margin"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:orientation="vertical" android:orientation="vertical"
tools:context=".ui.driverprofile.DriverProfileFragment"> tools:context=".ui.driverprofile.DriverProfileFragment">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="vertical"> android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout

View File

@@ -1,17 +0,0 @@
package driver;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}

View File

@@ -2,7 +2,7 @@ rules_version = '2';
service firebase.storage { service firebase.storage {
match /b/{bucket}/o { match /b/{bucket}/o {
match /{allPaths=**} { match /{allPaths=**} {
allow read, write: if false; allow read, write: if request.auth != null;
} }
} }
} }