Skip to content

Fix/navigation/top app bar UI tests #91

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Oct 30, 2024
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.android.periodpals.ui.navigation

import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
Expand All @@ -14,23 +13,58 @@ class TopAppBarTest {
@get:Rule val composeTestRule = createComposeRule()

@Test
fun topAppBar_displaysTitle() {
fun onlyTitleIsDisplayed() {
composeTestRule.setContent { TopAppBar(title = "Tampon Timer") }

composeTestRule.onNodeWithTag("topBar").assertIsDisplayed()
composeTestRule.onNodeWithTag("screenTitle").assertIsDisplayed()
composeTestRule.onNodeWithTag("goBackButton").assertDoesNotExist()
composeTestRule.onNodeWithTag("editButton").assertDoesNotExist()
}

@Test
fun backButtonIsDisplayed() {
composeTestRule.setContent {
TopAppBar(title = "Tampon Timer", backButton = true, onBackButtonClick = { /* Do nothing */})
}

composeTestRule.onNodeWithTag("topBar").assertIsDisplayed()
composeTestRule.onNodeWithTag("screenTitle").assertIsDisplayed()
composeTestRule.onNodeWithTag("goBackButton").assertIsDisplayed()
composeTestRule.onNodeWithTag("editButton").assertDoesNotExist()
}

@Test
fun editButtonIsDisplayed() {
composeTestRule.setContent {
TopAppBar(title = "Tampon Timer", editButton = true, onEditButtonClick = { /* Do nothing */})
}

composeTestRule.onNodeWithTag("topBar").assertIsDisplayed()
composeTestRule.onNodeWithTag("screenTitle").assertIsDisplayed()
composeTestRule.onNodeWithTag("editButton").assertIsDisplayed()
composeTestRule.onNodeWithTag("goBackButton").assertDoesNotExist()
}

@Test
fun topAppBar_displaysBackButton() {
composeTestRule.setContent { TopAppBar(title = "Tampon Timer", backButton = true) }
fun backAndEditButtonsAreDisplayed() {
composeTestRule.setContent {
TopAppBar(
title = "Tampon Timer",
backButton = true,
onBackButtonClick = { /* Do nothing */},
editButton = true,
onEditButtonClick = { /* Do nothing */})
}

composeTestRule.onNodeWithTag("topBar").assertIsDisplayed()
composeTestRule.onNodeWithTag("screenTitle").assertIsDisplayed()
composeTestRule.onNodeWithTag("goBackButton").assertIsDisplayed()
composeTestRule.onNodeWithTag("editButton").assertIsDisplayed()
}

@Test
fun topAppBar_backButtonClick() {
fun backButtonClickWorks() {
var backButtonClicked = false

composeTestRule.setContent {
Expand All @@ -45,48 +79,37 @@ class TopAppBarTest {
}

@Test
fun topAppBar_noBackButton() {
composeTestRule.setContent { TopAppBar(title = "Tampon Timer", backButton = false) }
fun editButtonClickWorks() {
var editButtonClicked = false

composeTestRule.onNodeWithTag("topBar").assertIsDisplayed()
composeTestRule.onNodeWithTag("goBackButton").assertDoesNotExist()
composeTestRule.setContent {
TopAppBar(
title = "Tampon Timer",
editButton = true,
onEditButtonClick = { editButtonClicked = true })
}

composeTestRule.onNodeWithTag("editButton").performClick()
assert(editButtonClicked)
}

@Test
fun topAppBar_backButtonTrue_onBackButtonClickNull_throwsException() {
fun backButtonInvalidFunction() {
val exception =
assertThrows(IllegalArgumentException::class.java) {
composeTestRule.setContent {
TopAppBar(title = "Test Title", backButton = true, onBackButtonClick = null)
}
composeTestRule.setContent { TopAppBar(title = "Test Title", backButton = true) }
}
assert(exception.message == "onBackButtonClick must be provided when backButton is true")
}

@Test
fun topAppBar_backButtonTrue_onBackButtonClickNotNull_doesNotThrowException() {
composeTestRule.setContent {
TopAppBar(title = "Test Title", backButton = true, onBackButtonClick = { /* Do nothing */})
}
composeTestRule.onNodeWithTag("topBar").assertIsDisplayed()
composeTestRule.onNodeWithTag("goBackButton").assertIsDisplayed()
}

@Test
fun topAppBar_backButtonFalse_onBackButtonClickNull_doesNotThrowException() {
composeTestRule.setContent {
TopAppBar(title = "Test Title", backButton = false, onBackButtonClick = null)
}
composeTestRule.onNodeWithTag("topBar").assertIsDisplayed()
composeTestRule.onNodeWithTag("goBackButton").assertIsNotDisplayed()
}

@Test
fun topAppBar_backButtonFalse_onBackButtonClickNotNull_doesNotThrowException() {
composeTestRule.setContent {
TopAppBar(title = "Test Title", backButton = false, onBackButtonClick = { /* Do nothing */})
}
composeTestRule.onNodeWithTag("topBar").assertIsDisplayed()
composeTestRule.onNodeWithTag("goBackButton").assertIsNotDisplayed()
fun editButtonInvalidFunction() {
val exception =
assertThrows(IllegalArgumentException::class.java) {
composeTestRule.setContent {
TopAppBar(title = "Test Title", editButton = true, onEditButtonClick = null)
}
}
assert(exception.message == "onEditButtonClick must be provided when editButton is true")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.ArrowBack
import androidx.compose.material.icons.outlined.Edit
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
Expand All @@ -24,7 +25,9 @@ import com.android.periodpals.ui.theme.PurpleGrey80
*
* @param title The title text to be displayed in the app bar.
* @param backButton Whether to show a back button. Default is false.
* @param onBackButtonClick Called when the back button is clicked.
* @param editButton Whether to show an edit button. Default is false.
* @param onBackButtonClick Called when the back button is clicked. Default is null.
* @param onEditButtonClick Called when the edit button is clicked. Default is null.
*
* ### Usage:
* The top app bar can be displayed with a title:
Expand All @@ -45,14 +48,26 @@ import com.android.periodpals.ui.theme.PurpleGrey80
* - Use the testTag "topBar" to verify the app bar is displayed.
* - If the back button is shown, check for the "goBackButton" tag to confirm its presence and
* functionality.
* - If the edit button is shown, check for the "editButton" tag to confirm its presence and
* functionality.
* - The title can be checked using the "screenTitle" testTag.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TopAppBar(title: String, backButton: Boolean = false, onBackButtonClick: (() -> Unit)? = {}) {
require(!backButton || onBackButtonClick != null) {
fun TopAppBar(
title: String,
backButton: Boolean = false,
onBackButtonClick: (() -> Unit)? = null,
editButton: Boolean = false,
onEditButtonClick: (() -> Unit)? = null
) {
require(!(backButton && onBackButtonClick == null)) {
"onBackButtonClick must be provided when backButton is true"
}
require(!(editButton && onEditButtonClick == null)) {
"onEditButtonClick must be provided when editButton is true"
}

CenterAlignedTopAppBar(
modifier = Modifier.fillMaxWidth().height(48.dp).testTag("topBar"),
title = {
Expand All @@ -69,7 +84,17 @@ fun TopAppBar(title: String, backButton: Boolean = false, onBackButtonClick: (()
contentDescription = "Back",
modifier = Modifier.size(20.dp))
}
} else null
}
},
actions = {
if (editButton) {
IconButton(onClick = onEditButtonClick!!, modifier = Modifier.testTag("editButton")) {
Icon(
imageVector = Icons.Outlined.Edit,
contentDescription = "Edit",
modifier = Modifier.size(20.dp))
}
}
},
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(containerColor = PurpleGrey80))
}
Original file line number Diff line number Diff line change
Expand Up @@ -220,3 +220,8 @@ fun validateDate(date: String): Boolean {
}
return false
}

/** Validates the description is not empty. */
private fun validateDescription(description: String): Boolean {
return description.isNotEmpty()
}
Loading