Skip to content

Commit 71b6182

Browse files
committed
feat: Implement functionality of the contactme module
1 parent 20ba7ff commit 71b6182

File tree

20 files changed

+987
-6
lines changed

20 files changed

+987
-6
lines changed

app/build.gradle.kts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,14 +68,13 @@ android {
6868
dependencies {
6969
implementation(project(":feature:home"))
7070
implementation(project(":feature:photo"))
71-
72-
// TODO Wei
73-
// implementation(project(":feature:contactme"))
71+
implementation(project(":feature:contactme"))
7472

7573
implementation(project(":core:designsystem"))
7674
implementation(project(":core:common"))
7775
implementation(project(":core:data"))
7876
implementation(project(":core:model"))
77+
// TODO Wei
7978
// implementation(project(":core:datastore"))
8079

8180
androidTestImplementation(project(":core:designsystem"))

app/src/main/java/com/wei/picquest/navigation/PqNavHost.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import androidx.compose.ui.Modifier
55
import androidx.navigation.compose.NavHost
66
import androidx.window.layout.DisplayFeature
77
import com.wei.picquest.core.designsystem.ui.DeviceOrientation
8+
import com.wei.picquest.feature.contactme.contactme.navigation.contactMeGraph
89
import com.wei.picquest.feature.home.home.navigation.homeGraph
910
import com.wei.picquest.feature.home.home.navigation.homeRoute
1011
import com.wei.picquest.feature.photo.photolibrary.navigation.photoLibraryGraph
@@ -44,5 +45,12 @@ fun PqNavHost(
4445
photoLibraryGraph(navController = navController)
4546
},
4647
)
48+
contactMeGraph(
49+
navController = navController,
50+
contentType = contentType,
51+
displayFeatures = displayFeatures,
52+
navigationType = navigationType,
53+
nestedGraphs = { },
54+
)
4755
}
4856
}

app/src/main/java/com/wei/picquest/ui/PqAppState.kt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import com.wei.picquest.core.designsystem.ui.PqNavigationType
2525
import com.wei.picquest.core.designsystem.ui.currentDeviceOrientation
2626
import com.wei.picquest.core.designsystem.ui.isBookPosture
2727
import com.wei.picquest.core.designsystem.ui.isSeparating
28+
import com.wei.picquest.feature.contactme.contactme.navigation.contactMeRoute
29+
import com.wei.picquest.feature.contactme.contactme.navigation.navigateToContactMe
2830
import com.wei.picquest.feature.home.home.navigation.homeRoute
2931
import com.wei.picquest.feature.home.home.navigation.navigateToHome
3032
import com.wei.picquest.feature.photo.photosearch.navigation.navigateToPhotoSearch
@@ -152,6 +154,7 @@ class PqAppState(
152154
@Composable get() = when (currentDestination?.route) {
153155
homeRoute -> TopLevelDestination.HOME
154156
photoSearchRoute -> TopLevelDestination.PHOTO
157+
contactMeRoute -> TopLevelDestination.CONTACT_ME
155158
else -> null
156159
}
157160

@@ -203,9 +206,9 @@ class PqAppState(
203206
topLevelNavOptions,
204207
)
205208

206-
// TopLevelDestination.CONTACT_ME -> navController.navigateToContactMe(
207-
// topLevelNavOptions,
208-
// )
209+
TopLevelDestination.CONTACT_ME -> navController.navigateToContactMe(
210+
topLevelNavOptions,
211+
)
209212

210213
else -> showFunctionalityNotAvailablePopup.value = true
211214
}

feature/contactme/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/build

feature/contactme/build.gradle.kts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
plugins {
2+
alias(libs.plugins.pq.android.feature)
3+
alias(libs.plugins.pq.android.library.compose)
4+
alias(libs.plugins.pq.android.hilt)
5+
}
6+
7+
android {
8+
namespace = "com.wei.picquest.feature.contactme"
9+
}
10+
11+
dependencies {
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
package com.wei.picquest.feature.contactme.contactme
2+
3+
import androidx.activity.ComponentActivity
4+
import androidx.annotation.StringRes
5+
import androidx.compose.ui.test.assertIsDisplayed
6+
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
7+
import androidx.compose.ui.test.onNodeWithContentDescription
8+
import androidx.compose.ui.test.performClick
9+
import androidx.test.ext.junit.rules.ActivityScenarioRule
10+
import androidx.window.layout.DisplayFeature
11+
import com.google.common.truth.Truth
12+
import com.wei.picquest.core.designsystem.theme.PqTheme
13+
import com.wei.picquest.core.designsystem.ui.PqContentType
14+
import com.wei.picquest.feature.contactme.R
15+
import com.wei.picquest.feature.contactme.contactme.utilities.EMAIL
16+
import com.wei.picquest.feature.contactme.contactme.utilities.LINKEDIN_URL
17+
import com.wei.picquest.feature.contactme.contactme.utilities.NAME_ENG
18+
import com.wei.picquest.feature.contactme.contactme.utilities.NAME_TW
19+
import com.wei.picquest.feature.contactme.contactme.utilities.PHONE
20+
import com.wei.picquest.feature.contactme.contactme.utilities.POSITION
21+
import com.wei.picquest.feature.contactme.contactme.utilities.TIME_ZONE
22+
import kotlin.properties.ReadOnlyProperty
23+
24+
/**
25+
* Screen Robot for [ContactMeScreenTest].
26+
*
27+
* 遵循此模型,找到測試使用者介面元素、檢查其屬性、和透過測試規則執行動作:
28+
* composeTestRule{.finder}{.assertion}{.action}
29+
*
30+
* Testing cheatsheet:
31+
* https://developer.android.com/jetpack/compose/testing-cheatsheet
32+
*/
33+
internal fun contactMeScreenRobot(
34+
composeTestRule: AndroidComposeTestRule<ActivityScenarioRule<ComponentActivity>, ComponentActivity>,
35+
func: ContactMeScreenRobot.() -> Unit,
36+
) = ContactMeScreenRobot(composeTestRule).apply(func)
37+
38+
internal open class ContactMeScreenRobot(
39+
private val composeTestRule: AndroidComposeTestRule<ActivityScenarioRule<ComponentActivity>, ComponentActivity>,
40+
) {
41+
private fun AndroidComposeTestRule<*, *>.stringResource(@StringRes resId: Int) =
42+
ReadOnlyProperty<Any?, String> { _, _ -> activity.getString(resId) }
43+
44+
private val profilePictureDescription by composeTestRule.stringResource(R.string.profile_picture)
45+
private val linkedinString by composeTestRule.stringResource(R.string.linkedin)
46+
private val emailString by composeTestRule.stringResource(R.string.email)
47+
private val timezoneString by composeTestRule.stringResource(R.string.timezone)
48+
private val callDescription by composeTestRule.stringResource(R.string.call)
49+
50+
private val profilePicture by lazy {
51+
composeTestRule.onNodeWithContentDescription(
52+
profilePictureDescription.format(testUiState.nameEng),
53+
useUnmergedTree = true,
54+
)
55+
}
56+
private val name by lazy {
57+
composeTestRule.onNodeWithContentDescription(
58+
testUiState.nameEng,
59+
useUnmergedTree = true,
60+
)
61+
}
62+
private val position by lazy {
63+
composeTestRule.onNodeWithContentDescription(
64+
testUiState.position,
65+
useUnmergedTree = true,
66+
)
67+
}
68+
private val linkedin by lazy {
69+
composeTestRule.onNodeWithContentDescription(
70+
linkedinString,
71+
useUnmergedTree = true,
72+
)
73+
}
74+
private val linkedinValue by lazy {
75+
composeTestRule.onNodeWithContentDescription(
76+
testUiState.linkedinUrl,
77+
useUnmergedTree = true,
78+
)
79+
}
80+
private val email by lazy {
81+
composeTestRule.onNodeWithContentDescription(
82+
emailString,
83+
useUnmergedTree = true,
84+
)
85+
}
86+
private val emailValue by lazy {
87+
composeTestRule.onNodeWithContentDescription(
88+
testUiState.email,
89+
useUnmergedTree = true,
90+
)
91+
}
92+
private val timeZone by lazy {
93+
composeTestRule.onNodeWithContentDescription(
94+
timezoneString,
95+
useUnmergedTree = true,
96+
)
97+
}
98+
private val timeZoneValue by lazy {
99+
composeTestRule.onNodeWithContentDescription(
100+
testUiState.timeZone,
101+
useUnmergedTree = true,
102+
)
103+
}
104+
private val call by lazy {
105+
composeTestRule.onNodeWithContentDescription(
106+
callDescription.format(testUiState.nameEng),
107+
useUnmergedTree = true,
108+
)
109+
}
110+
111+
private var callClicked: Boolean = false
112+
113+
fun setContactMeScreenContent(
114+
contentType: PqContentType,
115+
displayFeature: List<DisplayFeature>,
116+
) {
117+
composeTestRule.setContent {
118+
PqTheme {
119+
ContactMeScreen(
120+
uiStates = testUiState,
121+
contentType = contentType,
122+
displayFeatures = displayFeature,
123+
onPhoneClick = { callClicked = true },
124+
)
125+
}
126+
}
127+
}
128+
129+
fun verifyProfilePictureDisplayed() {
130+
profilePicture.assertExists().assertIsDisplayed()
131+
}
132+
133+
fun verifyNameDisplayed() {
134+
name.assertExists().assertIsDisplayed()
135+
}
136+
137+
fun verifyPositionDisplayed() {
138+
position.assertExists().assertIsDisplayed()
139+
}
140+
141+
fun verifyCallDisplayed() {
142+
call.assertExists().assertIsDisplayed()
143+
}
144+
145+
fun verifyLinkedinExists() {
146+
linkedin.assertExists()
147+
}
148+
149+
fun verifyLinkedinValueExists() {
150+
linkedinValue.assertExists()
151+
}
152+
153+
fun verifyEmailExists() {
154+
email.assertExists()
155+
}
156+
157+
fun verifyEmailValueExists() {
158+
emailValue.assertExists()
159+
}
160+
161+
fun verifyTimeZoneExists() {
162+
timeZone.assertExists()
163+
}
164+
165+
fun verifyTimeZoneValueExists() {
166+
timeZoneValue.assertExists()
167+
}
168+
169+
infix fun call(func: ContactMeScreenCallRobot.() -> Unit): ContactMeScreenCallRobot {
170+
call.assertExists().performClick()
171+
return contactMeScreenCallRobot(composeTestRule) {
172+
setIsCallClicked(callClicked)
173+
func()
174+
}
175+
}
176+
}
177+
178+
internal fun contactMeScreenCallRobot(
179+
composeTestRule: AndroidComposeTestRule<ActivityScenarioRule<ComponentActivity>, ComponentActivity>,
180+
func: ContactMeScreenCallRobot.() -> Unit,
181+
) = ContactMeScreenCallRobot(composeTestRule).apply(func)
182+
183+
internal open class ContactMeScreenCallRobot(
184+
private val composeTestRule: AndroidComposeTestRule<ActivityScenarioRule<ComponentActivity>, ComponentActivity>,
185+
) {
186+
187+
private var isCallClicked: Boolean = false
188+
189+
fun setIsCallClicked(backClicked: Boolean) {
190+
isCallClicked = backClicked
191+
}
192+
193+
fun isCall() {
194+
Truth.assertThat(isCallClicked).isTrue()
195+
}
196+
}
197+
198+
val testUiState = ContactMeViewState(
199+
nameTw = NAME_TW,
200+
nameEng = NAME_ENG,
201+
position = POSITION,
202+
phone = PHONE,
203+
linkedinUrl = LINKEDIN_URL,
204+
email = EMAIL,
205+
timeZone = TIME_ZONE,
206+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package com.wei.picquest.feature.contactme.contactme
2+
3+
import androidx.activity.ComponentActivity
4+
import androidx.compose.ui.test.junit4.createAndroidComposeRule
5+
import androidx.window.layout.DisplayFeature
6+
import com.wei.picquest.core.designsystem.ui.PqContentType
7+
import org.junit.Rule
8+
import org.junit.Test
9+
10+
/**
11+
* UI tests for [ContactMeScreen] composable.
12+
*/
13+
class ContactMeScreenTest {
14+
15+
/**
16+
* 通常我們使用 createComposeRule(),作為 composeTestRule
17+
*
18+
* 但若測試案例需查找資源檔 e.g. R.string.welcome。
19+
* 使用 createAndroidComposeRule<ComponentActivity>(),作為 composeTestRule
20+
*/
21+
@get:Rule
22+
val composeTestRule = createAndroidComposeRule<ComponentActivity>()
23+
24+
@Test
25+
fun checkElementsVisibility_afterOpeningTheScreen() {
26+
contactMeScreenRobot(composeTestRule) {
27+
setContactMeScreenContent(
28+
contentType = PqContentType.SINGLE_PANE,
29+
displayFeature = emptyList<DisplayFeature>(),
30+
)
31+
32+
verifyProfilePictureDisplayed()
33+
verifyNameDisplayed()
34+
verifyPositionDisplayed()
35+
verifyCallDisplayed()
36+
verifyLinkedinExists()
37+
verifyLinkedinValueExists()
38+
verifyEmailExists()
39+
verifyEmailValueExists()
40+
verifyTimeZoneExists()
41+
verifyTimeZoneValueExists()
42+
}
43+
}
44+
45+
@Test
46+
fun checkCallButtonAction_afterPress() {
47+
contactMeScreenRobot(composeTestRule) {
48+
setContactMeScreenContent(
49+
contentType = PqContentType.SINGLE_PANE,
50+
displayFeature = emptyList<DisplayFeature>(),
51+
)
52+
} call {
53+
isCall()
54+
}
55+
}
56+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
3+
4+
</manifest>

0 commit comments

Comments
 (0)