Skip to content

Commit

Permalink
Merge pull request philipplackner#1 from philipplackner/part13.1/writ…
Browse files Browse the repository at this point in the history
…ing_your_first_end_to_end_test_final

Part13.1/writing your first end to end test final
  • Loading branch information
philipplackner authored Aug 21, 2023
2 parents 2a3f657 + 3c1723b commit 0f31d79
Show file tree
Hide file tree
Showing 44 changed files with 1,790 additions and 13 deletions.
52 changes: 52 additions & 0 deletions app/src/androidTest/java/com/ivy/CreateTransactionE2E.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.ivy

import androidx.compose.ui.test.junit4.createAndroidComposeRule
import com.ivy.common.androidtest.IvyAndroidTest
import com.ivy.core.data.CategoryType
import com.ivy.home.HomeScreenRobot
import com.ivy.navigation.Navigator
import com.ivy.transaction.NewTransactionRobot
import com.ivy.wallet.ui.RootActivity
import dagger.hilt.android.testing.HiltAndroidTest
import org.junit.Rule
import org.junit.Test
import javax.inject.Inject

@HiltAndroidTest
class CreateTransactionE2E: IvyAndroidTest() {

@get:Rule
val composeRule = createAndroidComposeRule<RootActivity>()

@Inject
lateinit var navigator: Navigator

@Test
fun testCreatingExpenseWithCategoriesAndAccount() {
val homeScreenRobot = HomeScreenRobot(composeRule)

homeScreenRobot
.navigateTo(navigator)
.clickNewTransaction()
.clickExpense()

NewTransactionRobot(composeRule)
.addAccount("PayPal")
.selectAccount("PayPal")
.enterTransactionAmount(65)
.addCategory("Transport", CategoryType.Expense, null)
.addCategory("Car", CategoryType.Expense, "Transport")
.chooseSubCategory("Transport", "Car")
.enterTransactionTitle("Fuel")
.enterTransactionDescription("For my Ford")
.clickAddTransaction()

homeScreenRobot
.assertTotalExpensesIs(65)
.assertTransactionIsDisplayed(
transactionTitle = "Fuel",
accountName = "PayPal",
categoryName = "Car"
)
}
}
7 changes: 7 additions & 0 deletions app/src/androidTest/java/com/ivy/IvyComposeRule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.ivy

import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.test.ext.junit.rules.ActivityScenarioRule
import com.ivy.wallet.ui.RootActivity

typealias IvyComposeRule = AndroidComposeTestRule<ActivityScenarioRule<RootActivity>, RootActivity>
140 changes: 140 additions & 0 deletions app/src/androidTest/java/com/ivy/home/HomeScreenRobot.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package com.ivy.home

import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertTextEquals
import androidx.compose.ui.test.hasAnySibling
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.compose.ui.test.onAllNodesWithTag
import androidx.compose.ui.test.onFirst
import androidx.compose.ui.test.onLast
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollTo
import androidx.test.ext.junit.rules.ActivityScenarioRule
import com.ivy.IvyComposeRule
import com.ivy.common.time.provider.TimeProvider
import com.ivy.data.CurrencyCode
import com.ivy.navigation.Navigator
import com.ivy.navigation.destinations.main.Home
import com.ivy.wallet.ui.RootActivity
import kotlinx.coroutines.runBlocking

class HomeScreenRobot(
private val composeRule: IvyComposeRule
) {
fun navigateTo(navigator: Navigator): HomeScreenRobot {
runBlocking {
composeRule.awaitIdle()
composeRule.runOnUiThread {
navigator.navigate(Home.route) {
popUpTo(Home.route) {
inclusive = false
}
}
}
}
return this
}

fun openDateRangeSheet(timeProvider: TimeProvider): HomeScreenRobot {
composeRule
.onNodeWithText(timeProvider.dateNow().month.name, ignoreCase = true)
.performClick()
return this
}

fun selectMonth(monthName: String): HomeScreenRobot {
composeRule
.onNodeWithText(monthName)
.performClick()
return this
}

fun assertDateIsDisplayed(day: Int, month: String): HomeScreenRobot {
val paddedDay = day.toString().padStart(2, '0')
composeRule
.onNodeWithText("${month.take(3)}. $paddedDay")
.assertIsDisplayed()
return this
}

fun clickDone(): HomeScreenRobot {
composeRule.onNodeWithText("Done").performClick()
return this
}

fun clickUpcoming(): HomeScreenRobot {
composeRule.onNodeWithText("Upcoming").performClick()
return this
}

fun assertTransactionDoesNotExist(transactionTitle: String): HomeScreenRobot {
composeRule.onNodeWithText(transactionTitle).assertDoesNotExist()
return this
}

fun assertTransactionIsDisplayed(transactionTitle: String): HomeScreenRobot {
composeRule.onNodeWithText(transactionTitle).assertIsDisplayed()
return this
}

fun assertTransactionIsDisplayed(
transactionTitle: String,
accountName: String,
categoryName: String
): HomeScreenRobot {
composeRule.onNodeWithText(transactionTitle).assertIsDisplayed()
composeRule.onNodeWithText(accountName).assertIsDisplayed()
composeRule.onNodeWithText(categoryName).assertIsDisplayed()
return this
}

fun openOverdue(): HomeScreenRobot {
composeRule
.onNodeWithText("Overdue")
.performClick()
return this
}

fun assertBalanceIsDisplayed(amount: Double, currency: CurrencyCode): HomeScreenRobot {
val formattedAmount = if(amount % 1.0 == 0.0) {
amount.toInt().toString()
} else amount.toString()

composeRule
.onAllNodes(
hasText(formattedAmount) and hasAnySibling(hasText(currency)),
useUnmergedTree = true
)
.onFirst()
.assertIsDisplayed()

return this
}

fun clickGet(): HomeScreenRobot {
composeRule.onNodeWithText("Get").performClick()
return this
}

fun clickNewTransaction(): HomeScreenRobot {
composeRule.onNodeWithContentDescription("Add new transaction").performClick()
return this
}

fun clickExpense(): HomeScreenRobot {
composeRule.onNodeWithContentDescription("Create new expense").performClick()
return this
}

fun assertTotalExpensesIs(amount: Int): HomeScreenRobot {
composeRule
.onAllNodesWithTag("amount", useUnmergedTree = true)
.onLast()
.assertTextEquals(amount.toString())
return this
}

}
90 changes: 90 additions & 0 deletions app/src/androidTest/java/com/ivy/home/HomeScreenTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package com.ivy.home

import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import com.ivy.common.androidtest.IvyAndroidTest
import com.ivy.common.androidtest.test_data.saveAccountWithTransactions
import com.ivy.common.androidtest.test_data.transactionWithTime
import com.ivy.core.persistence.entity.trn.data.TrnTimeType
import com.ivy.data.transaction.TransactionType
import com.ivy.navigation.Navigator
import com.ivy.navigation.destinations.main.Home
import com.ivy.wallet.ui.RootActivity
import dagger.hilt.android.testing.HiltAndroidTest
import kotlinx.coroutines.runBlocking
import org.junit.Assert.*
import org.junit.Rule
import org.junit.Test
import java.time.Instant
import java.time.LocalDate
import java.time.ZoneId
import javax.inject.Inject

@HiltAndroidTest
class HomeScreenTest: IvyAndroidTest() {

@get:Rule
val composeRule = createAndroidComposeRule<RootActivity>()

@Inject
lateinit var navigator: Navigator

@Test
fun testSelectingDateRange() = runBlocking<Unit> {
val date = LocalDate.of(2023, 7, 23)
setDate(date)

val transaction1 = transactionWithTime(Instant.parse("2023-07-24T09:00:00Z")).copy(
title = "Transaction1"
)
val transaction2 = transactionWithTime(Instant.parse("2023-08-01T09:00:00Z")).copy(
title = "Transaction2"
)
val transaction3 = transactionWithTime(Instant.parse("2023-08-31T09:00:00Z")).copy(
title = "Transaction3"
)
db.saveAccountWithTransactions(
transactions = listOf(transaction1, transaction2, transaction3)
)

HomeScreenRobot(composeRule)
.navigateTo(navigator)
.openDateRangeSheet(timeProvider)
.selectMonth("August")
.assertDateIsDisplayed(1, "August")
.assertDateIsDisplayed(31, "August")
.clickDone()
.clickUpcoming()
.assertTransactionDoesNotExist("Transaction1")
.assertTransactionIsDisplayed("Transaction2")
.assertTransactionIsDisplayed("Transaction3")
}

@Test
fun testGetOverdueTransaction_turnsIntoNormalTransaction() = runBlocking<Unit> {
val date = LocalDate.of(2023, 7, 15)
setDate(date)
val dueTransaction = transactionWithTime(
time = date
.minusDays(1) // Make due
.atStartOfDay()
.atZone(ZoneId.systemDefault())
.toInstant()
).copy(
type = TransactionType.Income,
timeType = TrnTimeType.Due,
amount = 5.5
)
db.saveAccountWithTransactions(transactions = listOf(dueTransaction))

HomeScreenRobot(composeRule)
.navigateTo(navigator)
.openOverdue()
.clickGet()
.assertTransactionIsDisplayed(dueTransaction.title!!)
.assertBalanceIsDisplayed(dueTransaction.amount, dueTransaction.currency)
}

}
101 changes: 101 additions & 0 deletions app/src/androidTest/java/com/ivy/transaction/NewTransactionRobot.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package com.ivy.transaction

import androidx.compose.ui.test.hasClickAction
import androidx.compose.ui.test.hasTestTag
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.onAllNodesWithText
import androidx.compose.ui.test.onLast
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTextInput
import com.ivy.IvyComposeRule
import com.ivy.core.data.CategoryType

class NewTransactionRobot(
private val composeRule: IvyComposeRule
) {
fun addAccount(name: String): NewTransactionRobot {
composeRule.onNodeWithText("Add account").performClick()
composeRule.onNodeWithContentDescription("New account").performTextInput(name)
composeRule.onAllNodesWithText("Add account").onLast().performClick()
return this
}

fun selectAccount(name: String): NewTransactionRobot {
composeRule.onNodeWithText(name).performClick()
return this
}

fun enterTransactionAmount(amount: Int): NewTransactionRobot {
val digits = amount.toString().map { it.digitToInt() }
digits.forEach { digit ->
composeRule.onNode(
hasText(digit.toString()) and hasClickAction()
).performClick()
}
composeRule.onNodeWithText("Enter").performClick()
return this
}

fun addCategory(name: String, type: CategoryType, parentName: String?): NewTransactionRobot {
clickAddCategoryOnNewCategoryModal()
.enterCategoryName(name)
.selectCategoryType(type)
.apply {
if(parentName != null) {
chooseParent(parentName)
}
}
.clickAddCategoryOnNewCategoryModal()
return this
}

private fun clickAddCategoryOnNewCategoryModal(): NewTransactionRobot {
composeRule.onAllNodesWithText("Add category").onLast().performClick()
return this
}

private fun enterCategoryName(name: String): NewTransactionRobot {
composeRule.onNodeWithContentDescription("New Category").performTextInput(name)
return this
}

private fun selectCategoryType(type: CategoryType): NewTransactionRobot {
composeRule.onNode(
hasText(type.toString()) and hasTestTag("category_type_button")
).performClick()
return this
}

private fun chooseParent(parentName: String): NewTransactionRobot {
composeRule.onNodeWithText("Choose parent").performClick()
composeRule.onAllNodesWithText(parentName).onLast().performClick()
return this
}

fun chooseSubCategory(parentName: String, subName: String): NewTransactionRobot {
composeRule.onNodeWithText(parentName).performClick()
composeRule.onNodeWithText(subName).performClick()
return this
}

fun enterTransactionTitle(title: String): NewTransactionRobot {
composeRule.onNodeWithContentDescription("Title").performTextInput(title)
return this
}

fun enterTransactionDescription(description: String): NewTransactionRobot {
composeRule.onNodeWithText("Add description").performClick()
composeRule
.onNodeWithContentDescription("Enter any details here")
.performTextInput(description)
composeRule.onAllNodesWithText("Add").onLast().performClick()
return this
}

fun clickAddTransaction(): NewTransactionRobot {
composeRule.onNodeWithText("Add").performClick()
return this
}
}
Loading

0 comments on commit 0f31d79

Please sign in to comment.