- UI tests updated

This commit is contained in:
2023-09-15 13:34:06 +01:00
parent 5c0ddcae5d
commit 7366fba0ad
14 changed files with 407 additions and 51 deletions

View File

@@ -15,6 +15,8 @@ import androidx.test.espresso.matcher.RootMatchers.withDecorView
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.platform.app.InstrumentationRegistry
import com.appttude.h_mal.farmr.application.TestAppClass
import com.appttude.h_mal.farmr.ui.utils.EspressoHelper.waitFor
import com.appttude.h_mal.farmr.ui.utils.generateShifts
import com.appttude.h_mal.farmr.ui.utils.getShifts
import kotlinx.coroutines.runBlocking
import org.hamcrest.Matcher
@@ -73,16 +75,6 @@ open class BaseTest<A : Activity>(
open fun afterLaunch() {}
open fun testFinished() {}
fun waitFor(delay: Long) {
Espresso.onView(ViewMatchers.isRoot()).perform(object : ViewAction {
override fun getConstraints(): Matcher<View> = ViewMatchers.isRoot()
override fun getDescription(): String = "wait for $delay milliseconds"
override fun perform(uiController: UiController, v: View?) {
uiController.loopMainThreadForAtLeast(delay)
}
})
}
@Suppress("DEPRECATION")
fun checkToastMessage(message: String) {
Espresso.onView(ViewMatchers.withText(message)).inRoot(withDecorView(Matchers.not(decorView)))
@@ -95,7 +87,7 @@ open class BaseTest<A : Activity>(
fun navigateBack() = Espresso.pressBack()
fun addRandomShifts() {
testApp.addShiftsToDatabase(getShifts())
testApp.addShiftsToDatabase(generateShifts())
}
fun clearDataBase() = testApp.clearDatabase()

View File

@@ -0,0 +1,55 @@
package com.appttude.h_mal.farmr.ui.robots
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.matcher.ViewMatchers.hasTextColor
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import com.appttude.h_mal.farmr.R
import com.appttude.h_mal.farmr.base.BaseRecyclerAdapter.CurrentViewHolder
import com.appttude.h_mal.farmr.ui.BaseTestRobot
import org.hamcrest.core.AllOf.allOf
import org.hamcrest.core.IsNot.not
fun calendarScreen(func: CalendarScreenRobot.() -> Unit) = CalendarScreenRobot().apply { func() }
class CalendarScreenRobot : BaseTestRobot() {
fun clickOnListItemWithText(text: String) =
clickOnRecyclerItemWithText<CurrentViewHolder>(R.id.shifts_available_recycler, text)
fun clickOnListItemAtPosition(position: Int) =
clickRecyclerAtPosition<CurrentViewHolder>(R.id.shifts_available_recycler, position)
fun clickOnEditForItem(position: Int) {
clickViewInRecyclerAtPosition<CurrentViewHolder>(
R.id.shifts_available_recycler,
position,
R.id.imageView
)
onView(withId(R.id.update)).perform(click())
}
fun clickOnDeleteForItem(position: Int) {
clickViewInRecyclerAtPosition<CurrentViewHolder>(
R.id.shifts_available_recycler,
position,
R.id.imageView
)
onView(withId(R.id.delete)).perform(click())
}
fun clickOnCalendarDay(day: Int) {
onView(
allOf(
withId(R.id.dayLabel),
not(hasTextColor(com.applandeo.materialcalendarview.R.color.nextMonthDayColor)),
withText("$day"),
isDisplayed()
)
).perform(click())
}
fun clickNextMonth() = clickButton(com.applandeo.materialcalendarview.R.id.forwardButton)
fun clickPreviousMonth() = clickButton(com.applandeo.materialcalendarview.R.id.previousButton)
}

View File

@@ -0,0 +1,33 @@
package com.appttude.h_mal.farmr.ui.robots
import androidx.test.espresso.action.ViewActions.scrollTo
import com.appttude.h_mal.farmr.R
import com.appttude.h_mal.farmr.model.ShiftType
import com.appttude.h_mal.farmr.ui.BaseTestRobot
fun furtherInfoScreen(func: FurtherInfoScreenRobot.() -> Unit) = FurtherInfoScreenRobot().apply { func() }
class FurtherInfoScreenRobot : BaseTestRobot() {
fun assertShiftType(type: ShiftType) {
matchText(R.id.details_shift, type.type)
}
fun assertDescription(details: String) = matchText(R.id.details_desc, details)
fun assertDate(date: String) = matchText(R.id.details_date, date)
fun assertTime(time: String) = matchText(R.id.details_time, time)
fun assertBreak(breakSummary: String) = matchText(R.id.details_breaks, breakSummary)
fun assertDuration(duration: String) = matchText(R.id.details_duration, duration)
fun assertUnits(units: String) = fillEditText(R.id.details_units, units)
fun assertRateOfPay(rateOfPay: String) = matchText(R.id.details_pay_rate, rateOfPay)
fun assertTotalPay(text: String?) = fillEditText(R.id.details_totalpay, text)
fun update() {
matchView(R.id.details_edit).perform(scrollTo())
clickButton(R.id.details_edit)
}
}

View File

@@ -1,7 +1,9 @@
package com.appttude.h_mal.farmr.ui.robots
import androidx.test.espresso.Espresso
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.matcher.ViewMatchers
import com.appttude.h_mal.farmr.R
import com.appttude.h_mal.farmr.base.BaseRecyclerAdapter.CurrentViewHolder
import com.appttude.h_mal.farmr.model.Order
import com.appttude.h_mal.farmr.model.Sortable
import com.appttude.h_mal.farmr.ui.BaseTestRobot
@@ -9,9 +11,6 @@ import com.appttude.h_mal.farmr.ui.BaseTestRobot
fun homeScreen(func: HomeScreenRobot.() -> Unit) = HomeScreenRobot().apply { func() }
class HomeScreenRobot : BaseTestRobot() {
fun clickOnItemWithText(text: String) = clickOnRecyclerItemWithText<CurrentViewHolder>(R.id.list_item_view, text)
fun clickOnItemAtPosition(position: Int) = clickRecyclerAtPosition<CurrentViewHolder>(R.id.list_item_view, position)
fun clickOnEdit(position: Int) = clickViewInRecyclerAtPosition<CurrentViewHolder>(R.id.list_item_view, position, R.id.imageView)
fun clickFab() = clickButton(R.id.fab1)
fun clickOnInfoIcon() = clickButton(R.id.action_favorite)
fun clickFilterInMenu() = clickOnMenuItem(R.string.filter)
@@ -25,4 +24,16 @@ class HomeScreenRobot : BaseTestRobot() {
val orderLabel = order.label
clickDialogButton(orderLabel)
}
fun clickTab(tab: Tab) {
val id = when (tab) {
Tab.LIST -> R.id.nav_list
Tab.CALENDAR -> R.id.nav_calendar
}
Espresso.onView(ViewMatchers.withId(id)).perform(ViewActions.click())
}
enum class Tab {
LIST, CALENDAR
}
}

View File

@@ -0,0 +1,54 @@
package com.appttude.h_mal.farmr.ui.robots
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.RootMatchers.isDialog
import androidx.test.espresso.matcher.ViewMatchers.hasChildCount
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withText
import com.appttude.h_mal.farmr.R
import com.appttude.h_mal.farmr.base.BaseRecyclerAdapter.CurrentViewHolder
import com.appttude.h_mal.farmr.ui.BaseTestRobot
import com.appttude.h_mal.farmr.ui.utils.EspressoHelper
import com.appttude.h_mal.farmr.ui.utils.EspressoHelper.waitFor
fun listScreen(func: ListScreenRobot.() -> Unit) = ListScreenRobot().apply { func() }
class ListScreenRobot : BaseTestRobot() {
fun clickOnItemWithText(text: String) =
clickOnRecyclerItemWithText<CurrentViewHolder>(R.id.list_item_view, text)
fun clickOnItemAtPosition(position: Int) =
clickRecyclerAtPosition<CurrentViewHolder>(R.id.list_item_view, position)
fun clickOnEditForItem(position: Int) {
clickViewInRecyclerAtPosition<CurrentViewHolder>(
R.id.list_item_view,
position,
R.id.imageView
)
waitFor(800)
EspressoHelper.waitForView(withText("Update Shift")).perform(click())
}
fun clickOnDeleteForItem(position: Int) {
clickViewInRecyclerAtPosition<CurrentViewHolder>(
R.id.list_item_view,
position,
R.id.imageView
)
waitFor(800)
EspressoHelper.waitForView(withText("Delete Shift")).perform(click())
}
fun confirmDeleteItemOnDialog() {
onView(withText("delete"))
.inRoot(isDialog())
.check(matches(isDisplayed()))
.perform(click())
}
fun assertListCount(count: Int) =
matchView(R.id.list_item_view).check(matches(hasChildCount(count)))
}

View File

@@ -23,9 +23,9 @@ class ViewItemScreenRobot : BaseTestRobot() {
matchText(R.id.details_time, "$timeIn-$timeOut")
}
fun matchBreakTime(mins: Int) = matchText(R.id.details_breaks, mins.toString())
fun matchUnits(units: Float) = fillEditText(R.id.details_units, units.toString())
fun matchRateOfPay(rateOfPay: Float) = fillEditText(R.id.details_pay_rate, rateOfPay.toString())
fun matchBreakTime(mins: Int) = matchText(R.id.details_breaks, "$mins mins")
fun matchUnits(units: Float) = matchText(R.id.details_units, units.toString())
fun matchRateOfPay(rateOfPay: Float) = matchText(R.id.details_pay_rate, rateOfPay.toString())
fun matchTotalPay(pay: String) = matchText(R.id.details_totalpay, pay)
fun matchDuration(duration: String) = matchText(R.id.details_duration, duration)

View File

@@ -0,0 +1,31 @@
package com.appttude.h_mal.farmr.ui.tests
import com.appttude.h_mal.farmr.ui.BaseTest
import com.appttude.h_mal.farmr.ui.MainActivity
import com.appttude.h_mal.farmr.ui.robots.homeScreen
import org.junit.Ignore
import org.junit.Test
@Ignore
class DummyShiftTests : BaseTest<MainActivity>(MainActivity::class.java) {
override fun afterLaunch() {
super.afterLaunch()
addRandomShifts()
// Content resolver hard to mock
// Dirty technique to have a populated list
homeScreen {
clickFab()
navigateBack()
}
}
// Add a shift successfully
@Test
fun openAddScreen_addNewHourlyShift_assertShiftDetail() {
homeScreen {
clickFab()
}
}
}

View File

@@ -5,11 +5,17 @@ import com.appttude.h_mal.farmr.model.ShiftType
import com.appttude.h_mal.farmr.model.Sortable
import com.appttude.h_mal.farmr.ui.BaseTest
import com.appttude.h_mal.farmr.ui.MainActivity
import com.appttude.h_mal.farmr.ui.robots.HomeScreenRobot
import com.appttude.h_mal.farmr.ui.robots.addScreen
import com.appttude.h_mal.farmr.ui.robots.calendarScreen
import com.appttude.h_mal.farmr.ui.robots.filterScreen
import com.appttude.h_mal.farmr.ui.robots.homeScreen
import com.appttude.h_mal.farmr.ui.robots.listScreen
import com.appttude.h_mal.farmr.ui.robots.viewScreen
import org.junit.Test
import java.util.Calendar
import java.util.Calendar.MONTH
import java.util.Calendar.YEAR
class ShiftTests : BaseTest<MainActivity>(MainActivity::class.java) {
@@ -33,7 +39,7 @@ class ShiftTests : BaseTest<MainActivity>(MainActivity::class.java) {
// Add a shift successfully
@Test
fun openAddScreen_addNewShift_newShiftCreated() {
fun openAddScreen_addNewHourlyShift_assertShiftDetail() {
homeScreen {
clickFab()
}
@@ -49,16 +55,25 @@ class ShiftTests : BaseTest<MainActivity>(MainActivity::class.java) {
assertTotalPay("£20.00")
submit()
}
homeScreen {
listScreen {
clickOnItemWithText("This is a description")
}
viewScreen {
matchDescription("This is a description")
matchDate("2023-02-11")
matchShiftType(ShiftType.HOURLY)
matchTime("12:00", "14:30")
matchBreakTime(30)
matchRateOfPay(10.0f)
matchDuration("2 Hours 0 Minutes (+ 30 minutes break)")
matchTotalPay("2.0 Hours @ £10.00 per Hour\nEquals: £20.00")
}
}
// Edit a shift successfully
@Test
fun test2() {
homeScreen {
clickOnEdit(0)
fun editShift_newDetailsAdded_assertShiftDetail() {
listScreen {
clickOnEditForItem(0)
}
addScreen {
setDescription("Edited this shift")
@@ -70,7 +85,7 @@ class ShiftTests : BaseTest<MainActivity>(MainActivity::class.java) {
assertTotalPay("£40.00")
submit()
}
homeScreen {
listScreen {
clickOnItemWithText("Edited this shift")
}
viewScreen {
@@ -80,12 +95,13 @@ class ShiftTests : BaseTest<MainActivity>(MainActivity::class.java) {
}
}
// filter the list with date from
@Test
fun test3() {
fun applySort_listIsSorted_assertShiftsSortedCorrectly() {
homeScreen {
applySort(Sortable.TYPE, Order.DESCENDING)
clickOnItemAtPosition(0)
listScreen {
clickOnItemAtPosition(0)
}
viewScreen {
matchDescription("Day five")
matchShiftType(ShiftType.PIECE)
@@ -93,25 +109,41 @@ class ShiftTests : BaseTest<MainActivity>(MainActivity::class.java) {
}
}
// filter the list with date to
@Test
fun test4() {
fun applyDateBetweenFilterAndClear_listIsFilteredByDate_assertFilteredResultsCorrectly() {
homeScreen {
clickFilterInMenu()
}
filterScreen {
setDateIn(2023,8,3)
setDateOut(2023,8,6)
val calendar = Calendar.getInstance()
val year = calendar.get(YEAR)
val month = calendar.get(MONTH) + 1
setDateIn(year, month, 3)
setDateOut(year, month, 6)
submit()
}
homeScreen {
clickOnItemAtPosition(0)
listScreen {
assertListCount(4)
homeScreen {
clickClearFilterInMenu()
assertListCount(8)
clickFilterInMenu()
}
}
filterScreen {
val calendar = Calendar.getInstance()
val year = calendar.get(YEAR)
val month = calendar.get(MONTH) + 1
setDateOut(year, month, 6)
submit()
}
listScreen {
assertListCount(5)
}
}
// Add a shift as piece rate
@Test
fun test5() {
fun openAddScreen_addNewPieceShift_assertShiftDetail() {
homeScreen {
clickFab()
}
@@ -124,18 +156,42 @@ class ShiftTests : BaseTest<MainActivity>(MainActivity::class.java) {
assertTotalPay("£10.00")
submit()
}
homeScreen {
listScreen {
clickOnItemWithText("This is a description")
}
viewScreen {
matchDescription("This is a description")
matchDate("2023-02-11")
matchShiftType(ShiftType.PIECE)
matchUnits(1f)
matchRateOfPay(10.0f)
matchTotalPay("1.0 Units @ £10.00 per Unit\nEquals: £10.00")
}
}
// Validate the details screen
@Test
fun test6() {
fun openCalendarTab_clickOnFirstActiveDay_assertShiftDetails() {
homeScreen {
clickTab(HomeScreenRobot.Tab.CALENDAR)
}
calendarScreen {
clickOnCalendarDay(1)
clickOnListItemAtPosition(0)
}
viewScreen {
matchDate("2023-09-01")
}
}
// filter, sort, order and then reset
@Test
fun test7() {
fun deleteShift_confirmDelete_assertShiftDeleted() {
listScreen {
clickOnDeleteForItem(0)
confirmDeleteItemOnDialog()
clickOnItemAtPosition(0)
}
viewScreen {
matchDescription("Day two")
}
}
}

View File

@@ -2,6 +2,13 @@ package com.appttude.h_mal.farmr.ui.utils
import com.appttude.h_mal.farmr.model.Shift
import com.appttude.h_mal.farmr.model.ShiftType
import com.appttude.h_mal.farmr.utils.DATE_FORMAT
import com.appttude.h_mal.farmr.utils.TIME_FORMAT
import com.appttude.h_mal.farmr.utils.getTimeString
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Calendar.DAY_OF_MONTH
import java.util.Locale
fun getShifts() = listOf(
Shift(
@@ -100,4 +107,113 @@ fun getShifts() = listOf(
10f,
10f
)
)
)
fun Calendar.setDayAndGetDateString(day: Int): String {
set(Calendar.DAY_OF_MONTH, day)
val format = SimpleDateFormat(DATE_FORMAT, Locale.getDefault())
return format.format(time)
}
fun generateShifts(): List<Shift> {
val calendar: Calendar = Calendar.getInstance()
return listOf(
Shift(
ShiftType.HOURLY,
"Day one",
calendar.setDayAndGetDateString(1),
"12:00",
"13:00",
1f,
0,
0f,
10f,
10f
),
Shift(
ShiftType.HOURLY,
"Day two",
calendar.setDayAndGetDateString(2),
"12:00",
"13:00",
1f,
0,
0f,
10f,
10f
),
Shift(
ShiftType.HOURLY,
"Day three",
calendar.setDayAndGetDateString(3),
"12:00",
"13:00",
1f,
30,
0f,
10f,
5f
),
Shift(
ShiftType.HOURLY,
"Day four",
calendar.setDayAndGetDateString(4),
"12:00",
"13:00",
1f,
30,
0f,
10f,
5f
),
Shift(
ShiftType.PIECE,
"Day five",
calendar.setDayAndGetDateString(5),
"",
"",
0f,
0,
1f,
10f,
10f
),
Shift(
ShiftType.PIECE,
"Day six",
calendar.setDayAndGetDateString(6),
"",
"",
0f,
0,
1f,
10f,
10f
),
Shift(
ShiftType.PIECE,
"Day seven",
calendar.setDayAndGetDateString(7),
"",
"",
0f,
0,
1f,
10f,
10f
),
Shift(
ShiftType.PIECE,
"Day eight",
calendar.setDayAndGetDateString(8),
"",
"",
0f,
0,
1f,
10f,
10f
)
)
}

View File

@@ -4,11 +4,13 @@ import android.os.SystemClock.sleep
import android.view.View
import android.widget.CheckBox
import android.widget.Checkable
import androidx.test.espresso.Espresso
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.NoMatchingViewException
import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction
import androidx.test.espresso.ViewInteraction
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.isRoot
import androidx.test.espresso.util.TreeIterables
import org.hamcrest.BaseMatcher
@@ -120,4 +122,14 @@ object EspressoHelper {
throw Exception("Error finding a view matching $viewMatcher")
}
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)
}
})
}
}

View File

@@ -12,6 +12,7 @@ import com.appttude.h_mal.farmr.data.legacydb.ShiftObject
import com.appttude.h_mal.farmr.model.ShiftType
import com.appttude.h_mal.farmr.utils.CURRENCY
import com.appttude.h_mal.farmr.utils.formatAsCurrencyString
import com.appttude.h_mal.farmr.utils.formatToTwoDp
import com.appttude.h_mal.farmr.utils.hide
import com.appttude.h_mal.farmr.utils.navigateTo
import com.appttude.h_mal.farmr.utils.navigateToFragment
@@ -104,7 +105,7 @@ class FurtherInfoFragment : BaseFragment<InfoViewModel>(R.layout.fragment_futher
unitsTV.text = units.toString()
val paymentSummary =
StringBuilder().append(units.formatAsCurrencyString()).append(" Units @ ")
StringBuilder().append(units.formatToTwoDp()).append(" Units @ ")
.append(rateOfPay.formatAsCurrencyString()).append(" per Unit").append("\n")
.append("Equals: ").append(totalPay.formatAsCurrencyString())
totalPayTV.text = paymentSummary

View File

@@ -101,7 +101,7 @@ class MainViewModel(
if (second == null) return compareDate.after(first)
if (first == null) return compareDate.before(second)
return compareDate.after(first) && compareDate.before(second)
return compareDate.compareTo(first) * second.compareTo(compareDate) >= 0
}

View File

@@ -87,13 +87,8 @@
<string name="export">Export Data</string>
<string name="sort">Sort</string>
<string name="admob_unit_id">ca-app-pub-3406791512187471/7557456476</string>
<string name="banner_home_footer">ca-app-pub-3406791512187471~9541579845</string>
<string name="help">Help &amp; Support</string>
<!-- TODO: Remove or change this placeholder text -->
<string name="hello_blank_fragment">Hello blank fragment</string>
<string name="further_info_title">Shift Details</string>
<string name="insert_break_in_minutes">insert break in minutes</string>
<string name="break_res">Break</string>
@@ -101,7 +96,7 @@
<string name="minutes_symbol">m</string>
<string name="piece_symbol">pcs</string>
<string name="pound_sign">£</string>
<string name="delete_shift">Delete shift</string>
<string name="delete_shift">Delete Shift</string>
<string name="update_shift">Update Shift</string>
<string name="fab">Floating action button</string>
<string name="text_label_1">Shifts</string>

View File

@@ -90,7 +90,7 @@ class MainViewModelTest {
val retrievedShifts = retrieveCurrentData()
val description = viewModel.getInformation()
every { repository.setFilteringDetailsInPrefs(null, null, null, null) }.returns(Unit)
every { repository.setFilteringDetailsInPrefs(null, null, null, null) }.returns(true)
every { repository.retrieveFilteringDetailsInPrefs() }.returns(getFilter())
viewModel.clearFilters()
val descriptionAfterClearedFilter = viewModel.getInformation()