Skip to content

Commit

Permalink
Merge pull request #12 from azrael8576/feat/contact-me
Browse files Browse the repository at this point in the history
feat: Implement functionality of the contactme module
  • Loading branch information
azrael8576 authored Dec 2, 2023
2 parents 20ba7ff + 71b6182 commit 94733e3
Show file tree
Hide file tree
Showing 20 changed files with 987 additions and 6 deletions.
5 changes: 2 additions & 3 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,13 @@ android {
dependencies {
implementation(project(":feature:home"))
implementation(project(":feature:photo"))

// TODO Wei
// implementation(project(":feature:contactme"))
implementation(project(":feature:contactme"))

implementation(project(":core:designsystem"))
implementation(project(":core:common"))
implementation(project(":core:data"))
implementation(project(":core:model"))
// TODO Wei
// implementation(project(":core:datastore"))

androidTestImplementation(project(":core:designsystem"))
Expand Down
8 changes: 8 additions & 0 deletions app/src/main/java/com/wei/picquest/navigation/PqNavHost.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import androidx.compose.ui.Modifier
import androidx.navigation.compose.NavHost
import androidx.window.layout.DisplayFeature
import com.wei.picquest.core.designsystem.ui.DeviceOrientation
import com.wei.picquest.feature.contactme.contactme.navigation.contactMeGraph
import com.wei.picquest.feature.home.home.navigation.homeGraph
import com.wei.picquest.feature.home.home.navigation.homeRoute
import com.wei.picquest.feature.photo.photolibrary.navigation.photoLibraryGraph
Expand Down Expand Up @@ -44,5 +45,12 @@ fun PqNavHost(
photoLibraryGraph(navController = navController)
},
)
contactMeGraph(
navController = navController,
contentType = contentType,
displayFeatures = displayFeatures,
navigationType = navigationType,
nestedGraphs = { },
)
}
}
9 changes: 6 additions & 3 deletions app/src/main/java/com/wei/picquest/ui/PqAppState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import com.wei.picquest.core.designsystem.ui.PqNavigationType
import com.wei.picquest.core.designsystem.ui.currentDeviceOrientation
import com.wei.picquest.core.designsystem.ui.isBookPosture
import com.wei.picquest.core.designsystem.ui.isSeparating
import com.wei.picquest.feature.contactme.contactme.navigation.contactMeRoute
import com.wei.picquest.feature.contactme.contactme.navigation.navigateToContactMe
import com.wei.picquest.feature.home.home.navigation.homeRoute
import com.wei.picquest.feature.home.home.navigation.navigateToHome
import com.wei.picquest.feature.photo.photosearch.navigation.navigateToPhotoSearch
Expand Down Expand Up @@ -152,6 +154,7 @@ class PqAppState(
@Composable get() = when (currentDestination?.route) {
homeRoute -> TopLevelDestination.HOME
photoSearchRoute -> TopLevelDestination.PHOTO
contactMeRoute -> TopLevelDestination.CONTACT_ME
else -> null
}

Expand Down Expand Up @@ -203,9 +206,9 @@ class PqAppState(
topLevelNavOptions,
)

// TopLevelDestination.CONTACT_ME -> navController.navigateToContactMe(
// topLevelNavOptions,
// )
TopLevelDestination.CONTACT_ME -> navController.navigateToContactMe(
topLevelNavOptions,
)

else -> showFunctionalityNotAvailablePopup.value = true
}
Expand Down
1 change: 1 addition & 0 deletions feature/contactme/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
12 changes: 12 additions & 0 deletions feature/contactme/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
plugins {
alias(libs.plugins.pq.android.feature)
alias(libs.plugins.pq.android.library.compose)
alias(libs.plugins.pq.android.hilt)
}

android {
namespace = "com.wei.picquest.feature.contactme"
}

dependencies {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
package com.wei.picquest.feature.contactme.contactme

import androidx.activity.ComponentActivity
import androidx.annotation.StringRes
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.window.layout.DisplayFeature
import com.google.common.truth.Truth
import com.wei.picquest.core.designsystem.theme.PqTheme
import com.wei.picquest.core.designsystem.ui.PqContentType
import com.wei.picquest.feature.contactme.R
import com.wei.picquest.feature.contactme.contactme.utilities.EMAIL
import com.wei.picquest.feature.contactme.contactme.utilities.LINKEDIN_URL
import com.wei.picquest.feature.contactme.contactme.utilities.NAME_ENG
import com.wei.picquest.feature.contactme.contactme.utilities.NAME_TW
import com.wei.picquest.feature.contactme.contactme.utilities.PHONE
import com.wei.picquest.feature.contactme.contactme.utilities.POSITION
import com.wei.picquest.feature.contactme.contactme.utilities.TIME_ZONE
import kotlin.properties.ReadOnlyProperty

/**
* Screen Robot for [ContactMeScreenTest].
*
* 遵循此模型,找到測試使用者介面元素、檢查其屬性、和透過測試規則執行動作:
* composeTestRule{.finder}{.assertion}{.action}
*
* Testing cheatsheet:
* https://developer.android.com/jetpack/compose/testing-cheatsheet
*/
internal fun contactMeScreenRobot(
composeTestRule: AndroidComposeTestRule<ActivityScenarioRule<ComponentActivity>, ComponentActivity>,
func: ContactMeScreenRobot.() -> Unit,
) = ContactMeScreenRobot(composeTestRule).apply(func)

internal open class ContactMeScreenRobot(
private val composeTestRule: AndroidComposeTestRule<ActivityScenarioRule<ComponentActivity>, ComponentActivity>,
) {
private fun AndroidComposeTestRule<*, *>.stringResource(@StringRes resId: Int) =
ReadOnlyProperty<Any?, String> { _, _ -> activity.getString(resId) }

private val profilePictureDescription by composeTestRule.stringResource(R.string.profile_picture)
private val linkedinString by composeTestRule.stringResource(R.string.linkedin)
private val emailString by composeTestRule.stringResource(R.string.email)
private val timezoneString by composeTestRule.stringResource(R.string.timezone)
private val callDescription by composeTestRule.stringResource(R.string.call)

private val profilePicture by lazy {
composeTestRule.onNodeWithContentDescription(
profilePictureDescription.format(testUiState.nameEng),
useUnmergedTree = true,
)
}
private val name by lazy {
composeTestRule.onNodeWithContentDescription(
testUiState.nameEng,
useUnmergedTree = true,
)
}
private val position by lazy {
composeTestRule.onNodeWithContentDescription(
testUiState.position,
useUnmergedTree = true,
)
}
private val linkedin by lazy {
composeTestRule.onNodeWithContentDescription(
linkedinString,
useUnmergedTree = true,
)
}
private val linkedinValue by lazy {
composeTestRule.onNodeWithContentDescription(
testUiState.linkedinUrl,
useUnmergedTree = true,
)
}
private val email by lazy {
composeTestRule.onNodeWithContentDescription(
emailString,
useUnmergedTree = true,
)
}
private val emailValue by lazy {
composeTestRule.onNodeWithContentDescription(
testUiState.email,
useUnmergedTree = true,
)
}
private val timeZone by lazy {
composeTestRule.onNodeWithContentDescription(
timezoneString,
useUnmergedTree = true,
)
}
private val timeZoneValue by lazy {
composeTestRule.onNodeWithContentDescription(
testUiState.timeZone,
useUnmergedTree = true,
)
}
private val call by lazy {
composeTestRule.onNodeWithContentDescription(
callDescription.format(testUiState.nameEng),
useUnmergedTree = true,
)
}

private var callClicked: Boolean = false

fun setContactMeScreenContent(
contentType: PqContentType,
displayFeature: List<DisplayFeature>,
) {
composeTestRule.setContent {
PqTheme {
ContactMeScreen(
uiStates = testUiState,
contentType = contentType,
displayFeatures = displayFeature,
onPhoneClick = { callClicked = true },
)
}
}
}

fun verifyProfilePictureDisplayed() {
profilePicture.assertExists().assertIsDisplayed()
}

fun verifyNameDisplayed() {
name.assertExists().assertIsDisplayed()
}

fun verifyPositionDisplayed() {
position.assertExists().assertIsDisplayed()
}

fun verifyCallDisplayed() {
call.assertExists().assertIsDisplayed()
}

fun verifyLinkedinExists() {
linkedin.assertExists()
}

fun verifyLinkedinValueExists() {
linkedinValue.assertExists()
}

fun verifyEmailExists() {
email.assertExists()
}

fun verifyEmailValueExists() {
emailValue.assertExists()
}

fun verifyTimeZoneExists() {
timeZone.assertExists()
}

fun verifyTimeZoneValueExists() {
timeZoneValue.assertExists()
}

infix fun call(func: ContactMeScreenCallRobot.() -> Unit): ContactMeScreenCallRobot {
call.assertExists().performClick()
return contactMeScreenCallRobot(composeTestRule) {
setIsCallClicked(callClicked)
func()
}
}
}

internal fun contactMeScreenCallRobot(
composeTestRule: AndroidComposeTestRule<ActivityScenarioRule<ComponentActivity>, ComponentActivity>,
func: ContactMeScreenCallRobot.() -> Unit,
) = ContactMeScreenCallRobot(composeTestRule).apply(func)

internal open class ContactMeScreenCallRobot(
private val composeTestRule: AndroidComposeTestRule<ActivityScenarioRule<ComponentActivity>, ComponentActivity>,
) {

private var isCallClicked: Boolean = false

fun setIsCallClicked(backClicked: Boolean) {
isCallClicked = backClicked
}

fun isCall() {
Truth.assertThat(isCallClicked).isTrue()
}
}

val testUiState = ContactMeViewState(
nameTw = NAME_TW,
nameEng = NAME_ENG,
position = POSITION,
phone = PHONE,
linkedinUrl = LINKEDIN_URL,
email = EMAIL,
timeZone = TIME_ZONE,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.wei.picquest.feature.contactme.contactme

import androidx.activity.ComponentActivity
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.window.layout.DisplayFeature
import com.wei.picquest.core.designsystem.ui.PqContentType
import org.junit.Rule
import org.junit.Test

/**
* UI tests for [ContactMeScreen] composable.
*/
class ContactMeScreenTest {

/**
* 通常我們使用 createComposeRule(),作為 composeTestRule
*
* 但若測試案例需查找資源檔 e.g. R.string.welcome。
* 使用 createAndroidComposeRule<ComponentActivity>(),作為 composeTestRule
*/
@get:Rule
val composeTestRule = createAndroidComposeRule<ComponentActivity>()

@Test
fun checkElementsVisibility_afterOpeningTheScreen() {
contactMeScreenRobot(composeTestRule) {
setContactMeScreenContent(
contentType = PqContentType.SINGLE_PANE,
displayFeature = emptyList<DisplayFeature>(),
)

verifyProfilePictureDisplayed()
verifyNameDisplayed()
verifyPositionDisplayed()
verifyCallDisplayed()
verifyLinkedinExists()
verifyLinkedinValueExists()
verifyEmailExists()
verifyEmailValueExists()
verifyTimeZoneExists()
verifyTimeZoneValueExists()
}
}

@Test
fun checkCallButtonAction_afterPress() {
contactMeScreenRobot(composeTestRule) {
setContactMeScreenContent(
contentType = PqContentType.SINGLE_PANE,
displayFeature = emptyList<DisplayFeature>(),
)
} call {
isCall()
}
}
}
4 changes: 4 additions & 0 deletions feature/contactme/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

</manifest>
Loading

0 comments on commit 94733e3

Please sign in to comment.