Skip to content

Commit eb4bcc7

Browse files
committed
Add unit tests for MessageComposerScreen
1 parent b2ee08d commit eb4bcc7

File tree

3 files changed

+153
-1
lines changed

3 files changed

+153
-1
lines changed

stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/DefaultMessageComposerRecordingContent.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ import androidx.compose.ui.platform.testTag
6363
import androidx.compose.ui.res.stringResource
6464
import androidx.compose.ui.semantics.contentDescription
6565
import androidx.compose.ui.semantics.semantics
66+
import androidx.compose.ui.semantics.testTag
6667
import androidx.compose.ui.tooling.preview.Preview
6768
import androidx.compose.ui.unit.IntOffset
6869
import androidx.compose.ui.unit.IntSize
@@ -712,6 +713,9 @@ private fun RecordingControlButtons(
712713
IconButton(
713714
onClick = onDeleteRecording,
714715
modifier = Modifier
716+
.semantics {
717+
testTag = "Stream_ComposerDeleteAudioRecordingButton"
718+
}
715719
.size(deleteStyle.size)
716720
.padding(deleteStyle.padding)
717721
.focusable(true),
@@ -730,6 +734,9 @@ private fun RecordingControlButtons(
730734
IconButton(
731735
onClick = onStopRecording,
732736
modifier = Modifier
737+
.semantics {
738+
testTag = "Stream_ComposerStopAudioRecordingButton"
739+
}
733740
.size(stopStyle.size)
734741
.padding(stopStyle.padding)
735742
.focusable(true),
@@ -748,6 +755,9 @@ private fun RecordingControlButtons(
748755
IconButton(
749756
onClick = { onCompleteRecording(sendOnComplete) },
750757
modifier = Modifier
758+
.semantics {
759+
testTag = "Stream_ComposerCompleteAudioRecordingButton"
760+
}
751761
.size(completeStyle.size)
752762
.padding(completeStyle.padding)
753763
.focusable(true),

stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/ComposeTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,6 @@ internal interface ComposeTest {
5757

5858
@After
5959
fun tearDown() {
60-
reset(MockChatClient)
60+
reset(MockChatClient, MockClientState)
6161
}
6262
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/*
2+
* Copyright (c) 2014-2025 Stream.io Inc. All rights reserved.
3+
*
4+
* Licensed under the Stream License;
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://github.com/GetStream/stream-chat-android/blob/main/LICENSE
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.getstream.chat.android.compose.ui.messages.composer
18+
19+
import androidx.annotation.UiThread
20+
import androidx.compose.ui.test.hasText
21+
import androidx.compose.ui.test.junit4.createComposeRule
22+
import androidx.compose.ui.test.onNodeWithTag
23+
import androidx.compose.ui.test.onNodeWithText
24+
import io.getstream.chat.android.compose.ui.ComposeTest
25+
import io.getstream.chat.android.compose.ui.theme.ChatTheme
26+
import io.getstream.chat.android.compose.viewmodel.messages.MessageComposerViewModel
27+
import io.getstream.chat.android.previewdata.PreviewUserData
28+
import io.getstream.chat.android.randomCommand
29+
import io.getstream.chat.android.randomFloat
30+
import io.getstream.chat.android.randomMessage
31+
import io.getstream.chat.android.ui.common.state.messages.MessageMode.MessageThread
32+
import io.getstream.chat.android.ui.common.state.messages.composer.MessageComposerState
33+
import io.getstream.chat.android.ui.common.state.messages.composer.RecordingState
34+
import kotlinx.coroutines.flow.MutableStateFlow
35+
import org.junit.Rule
36+
import org.junit.Test
37+
import org.junit.runner.RunWith
38+
import org.mockito.kotlin.doReturn
39+
import org.mockito.kotlin.mock
40+
import org.mockito.kotlin.whenever
41+
import org.robolectric.RobolectricTestRunner
42+
import org.robolectric.annotation.Config
43+
44+
@RunWith(RobolectricTestRunner::class)
45+
@Config(sdk = [33])
46+
internal class MessageComposerScreenTest : ComposeTest {
47+
48+
@get:Rule
49+
val composeTestRule = createComposeRule()
50+
51+
val mockViewModel: MessageComposerViewModel = mock()
52+
53+
@Test
54+
@UiThread
55+
fun `instant commands`() {
56+
val command = randomCommand()
57+
whenever(mockViewModel.messageComposerState) doReturn
58+
MutableStateFlow(MessageComposerState(hasCommands = true, commandSuggestions = listOf(command)))
59+
60+
composeTestRule.setContent {
61+
ChatTheme {
62+
MessageComposer(viewModel = mockViewModel)
63+
}
64+
}
65+
66+
composeTestRule.onNodeWithText("Instant Commands").assertExists()
67+
composeTestRule.onNodeWithText("/${command.name} ${command.args}").assertExists()
68+
}
69+
70+
@Test
71+
@UiThread
72+
fun `mention suggestions`() {
73+
val user = PreviewUserData.user7
74+
whenever(mockViewModel.messageComposerState) doReturn
75+
MutableStateFlow(MessageComposerState(mentionSuggestions = listOf(user)))
76+
77+
composeTestRule.setContent {
78+
ChatTheme {
79+
MessageComposer(viewModel = mockViewModel)
80+
}
81+
}
82+
83+
composeTestRule.onNode(hasText(user.name)).assertExists()
84+
}
85+
86+
@Test
87+
@UiThread
88+
fun `thread mode`() {
89+
whenever(mockViewModel.messageComposerState) doReturn
90+
MutableStateFlow(MessageComposerState(messageMode = MessageThread(parentMessage = randomMessage())))
91+
92+
composeTestRule.setContent {
93+
ChatTheme {
94+
MessageComposer(viewModel = mockViewModel)
95+
}
96+
}
97+
98+
composeTestRule.onNodeWithText("Also send as direct message").assertExists()
99+
}
100+
101+
@Test
102+
@UiThread
103+
fun `audio recording hold`() {
104+
val recording = RecordingState.Hold(
105+
durationInMs = 120_000,
106+
waveform = emptyList(),
107+
)
108+
whenever(mockViewModel.messageComposerState) doReturn
109+
MutableStateFlow(MessageComposerState(recording = recording))
110+
111+
composeTestRule.setContent {
112+
ChatTheme {
113+
MessageComposer(viewModel = mockViewModel)
114+
}
115+
}
116+
117+
composeTestRule.onNodeWithText("02:00").assertExists()
118+
composeTestRule.onNodeWithText("Slide to cancel").assertExists()
119+
}
120+
121+
@Test
122+
@UiThread
123+
fun `audio recording locked`() {
124+
val recording = RecordingState.Locked(
125+
durationInMs = 120_000,
126+
waveform = listOf(randomFloat()),
127+
)
128+
whenever(mockViewModel.messageComposerState) doReturn
129+
MutableStateFlow(MessageComposerState(recording = recording))
130+
131+
composeTestRule.setContent {
132+
ChatTheme {
133+
MessageComposer(viewModel = mockViewModel)
134+
}
135+
}
136+
137+
composeTestRule.onNodeWithText("02:00").assertExists()
138+
composeTestRule.onNodeWithTag("Stream_ComposerDeleteAudioRecordingButton").assertExists()
139+
composeTestRule.onNodeWithTag("Stream_ComposerStopAudioRecordingButton").assertExists()
140+
composeTestRule.onNodeWithTag("Stream_ComposerCompleteAudioRecordingButton").assertExists()
141+
}
142+
}

0 commit comments

Comments
 (0)