Add tests on MessagesView
This commit is contained in:
parent
4e0f308cac
commit
fa52ff54c8
8 changed files with 376 additions and 23 deletions
|
|
@ -62,6 +62,7 @@ dependencies {
|
|||
implementation(projects.libraries.voicerecorder.api)
|
||||
implementation(projects.libraries.mediaplayer.api)
|
||||
implementation(projects.libraries.uiUtils)
|
||||
implementation(projects.libraries.testtags)
|
||||
implementation(projects.features.networkmonitor.api)
|
||||
implementation(projects.services.analytics.api)
|
||||
implementation(libs.coil.compose)
|
||||
|
|
|
|||
|
|
@ -17,13 +17,16 @@
|
|||
package io.element.android.features.messages.impl
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.features.messages.impl.actionlist.ActionListState
|
||||
import io.element.android.features.messages.impl.actionlist.anActionListState
|
||||
import io.element.android.features.messages.impl.messagecomposer.AttachmentsState
|
||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerState
|
||||
import io.element.android.features.messages.impl.messagecomposer.aMessageComposerState
|
||||
import io.element.android.features.messages.impl.timeline.aTimelineItemList
|
||||
import io.element.android.features.messages.impl.timeline.aTimelineState
|
||||
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionEvents
|
||||
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionState
|
||||
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryEvents
|
||||
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryState
|
||||
import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetState
|
||||
import io.element.android.features.messages.impl.timeline.components.retrysendmenu.RetrySendMenuState
|
||||
|
|
@ -93,10 +96,14 @@ fun aMessagesState(
|
|||
mode = MessageComposerMode.Normal,
|
||||
),
|
||||
voiceMessageComposerState: VoiceMessageComposerState = aVoiceMessageComposerState(),
|
||||
actionListState: ActionListState = anActionListState(),
|
||||
customReactionState: CustomReactionState = aCustomReactionState(),
|
||||
reactionSummaryState: ReactionSummaryState = aReactionSummaryState(),
|
||||
hasNetworkConnection: Boolean = true,
|
||||
showReinvitePrompt: Boolean = false,
|
||||
enableVoiceMessages: Boolean = true,
|
||||
callState: RoomCallState = RoomCallState.ENABLED,
|
||||
eventSink: (MessagesEvents) -> Unit = {},
|
||||
) = MessagesState(
|
||||
roomId = RoomId("!id:domain"),
|
||||
roomName = roomName,
|
||||
|
|
@ -118,16 +125,9 @@ fun aMessagesState(
|
|||
selectedEvent = null,
|
||||
eventSink = {},
|
||||
),
|
||||
actionListState = anActionListState(),
|
||||
customReactionState = CustomReactionState(
|
||||
target = CustomReactionState.Target.None,
|
||||
eventSink = {},
|
||||
selectedEmoji = persistentSetOf(),
|
||||
),
|
||||
reactionSummaryState = ReactionSummaryState(
|
||||
target = null,
|
||||
eventSink = {},
|
||||
),
|
||||
actionListState = actionListState,
|
||||
customReactionState = customReactionState,
|
||||
reactionSummaryState = reactionSummaryState,
|
||||
hasNetworkConnection = hasNetworkConnection,
|
||||
snackbarMessage = null,
|
||||
inviteProgress = AsyncData.Uninitialized,
|
||||
|
|
@ -136,5 +136,21 @@ fun aMessagesState(
|
|||
enableVoiceMessages = enableVoiceMessages,
|
||||
callState = callState,
|
||||
appName = "Element",
|
||||
eventSink = {}
|
||||
eventSink = eventSink,
|
||||
)
|
||||
|
||||
fun aReactionSummaryState(
|
||||
target: ReactionSummaryState.Summary? = null,
|
||||
eventSink: (ReactionSummaryEvents) -> Unit = {}
|
||||
) = ReactionSummaryState(
|
||||
target = target,
|
||||
eventSink = eventSink,
|
||||
)
|
||||
|
||||
fun aCustomReactionState(
|
||||
eventSink: (CustomReactionEvents) -> Unit = {},
|
||||
) = CustomReactionState(
|
||||
target = CustomReactionState.Target.None,
|
||||
selectedEmoji = persistentSetOf(),
|
||||
eventSink = eventSink,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -122,9 +122,12 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
|||
}
|
||||
}
|
||||
|
||||
fun anActionListState() = ActionListState(
|
||||
target = ActionListState.Target.None,
|
||||
eventSink = {}
|
||||
fun anActionListState(
|
||||
target: ActionListState.Target = ActionListState.Target.None,
|
||||
eventSink: (ActionListEvents) -> Unit = {},
|
||||
) = ActionListState(
|
||||
target = target,
|
||||
eventSink = eventSink
|
||||
)
|
||||
|
||||
fun aTimelineItemActionList(): ImmutableList<TimelineItemAction> {
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalInspectionMode
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.clearAndSetSemantics
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
|
|
@ -86,6 +87,7 @@ import io.element.android.libraries.designsystem.theme.components.hide
|
|||
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
|
|
@ -101,28 +103,52 @@ fun ActionListView(
|
|||
val targetItem = (state.target as? ActionListState.Target.Success)?.event
|
||||
|
||||
fun onItemActionClicked(
|
||||
itemAction: TimelineItemAction
|
||||
itemAction: TimelineItemAction,
|
||||
immediate: Boolean,
|
||||
) {
|
||||
if (targetItem == null) return
|
||||
sheetState.hide(coroutineScope) {
|
||||
if (immediate) {
|
||||
coroutineScope.launch { sheetState.hide() }
|
||||
state.eventSink(ActionListEvents.Clear)
|
||||
onActionSelected(itemAction, targetItem)
|
||||
} else {
|
||||
sheetState.hide(coroutineScope) {
|
||||
state.eventSink(ActionListEvents.Clear)
|
||||
onActionSelected(itemAction, targetItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onEmojiReactionClicked(emoji: String) {
|
||||
fun onEmojiReactionClicked(
|
||||
emoji: String,
|
||||
immediate: Boolean,
|
||||
) {
|
||||
if (targetItem == null) return
|
||||
sheetState.hide(coroutineScope) {
|
||||
if (immediate) {
|
||||
coroutineScope.launch { sheetState.hide() }
|
||||
state.eventSink(ActionListEvents.Clear)
|
||||
onEmojiReactionClicked(emoji, targetItem)
|
||||
} else {
|
||||
sheetState.hide(coroutineScope) {
|
||||
state.eventSink(ActionListEvents.Clear)
|
||||
onEmojiReactionClicked(emoji, targetItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onCustomReactionClicked() {
|
||||
fun onCustomReactionClicked(
|
||||
immediate: Boolean,
|
||||
) {
|
||||
if (targetItem == null) return
|
||||
sheetState.hide(coroutineScope) {
|
||||
if (immediate) {
|
||||
coroutineScope.launch { sheetState.hide() }
|
||||
state.eventSink(ActionListEvents.Clear)
|
||||
onCustomReactionClicked(targetItem)
|
||||
} else {
|
||||
sheetState.hide(coroutineScope) {
|
||||
state.eventSink(ActionListEvents.Clear)
|
||||
onCustomReactionClicked(targetItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -136,11 +162,18 @@ fun ActionListView(
|
|||
onDismissRequest = ::onDismiss,
|
||||
modifier = modifier,
|
||||
) {
|
||||
val immediate = LocalInspectionMode.current
|
||||
SheetContent(
|
||||
state = state,
|
||||
onActionClicked = ::onItemActionClicked,
|
||||
onEmojiReactionClicked = ::onEmojiReactionClicked,
|
||||
onCustomReactionClicked = ::onCustomReactionClicked,
|
||||
onActionClicked = {
|
||||
onItemActionClicked(it, immediate)
|
||||
},
|
||||
onEmojiReactionClicked = {
|
||||
onEmojiReactionClicked(it, immediate)
|
||||
},
|
||||
onCustomReactionClicked = {
|
||||
onCustomReactionClicked(immediate)
|
||||
},
|
||||
modifier = Modifier
|
||||
.navigationBarsPadding()
|
||||
.imePadding()
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@ import io.element.android.libraries.designsystem.theme.components.Surface
|
|||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.messageFromMeBackground
|
||||
import io.element.android.libraries.designsystem.theme.messageFromOtherBackground
|
||||
import io.element.android.libraries.testtags.TestTags
|
||||
import io.element.android.libraries.testtags.testTag
|
||||
|
||||
private val BUBBLE_RADIUS = 12.dp
|
||||
internal val BUBBLE_INCOMING_OFFSET = 16.dp
|
||||
|
|
@ -115,6 +117,7 @@ fun MessageEventBubble(
|
|||
) {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.testTag(TestTags.messageBubble)
|
||||
.widthIn(min = 80.dp)
|
||||
.clip(bubbleShape)
|
||||
.combinedClickable(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,286 @@
|
|||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.messages.impl
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.platform.LocalInspectionMode
|
||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.longClick
|
||||
import androidx.compose.ui.test.onAllNodesWithContentDescription
|
||||
import androidx.compose.ui.test.onAllNodesWithTag
|
||||
import androidx.compose.ui.test.onAllNodesWithText
|
||||
import androidx.compose.ui.test.onFirst
|
||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.performTouchInput
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import io.element.android.features.messages.impl.actionlist.ActionListState
|
||||
import io.element.android.features.messages.impl.actionlist.anActionListState
|
||||
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
|
||||
import io.element.android.features.messages.impl.attachments.Attachment
|
||||
import io.element.android.features.messages.impl.messagecomposer.aMessageComposerState
|
||||
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionEvents
|
||||
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryEvents
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.testtags.TestTags
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.element.android.tests.testutils.EnsureCalledOnceWithParam
|
||||
import io.element.android.tests.testutils.EnsureNeverCalled
|
||||
import io.element.android.tests.testutils.EnsureNeverCalledWithParam
|
||||
import io.element.android.tests.testutils.EnsureNeverCalledWithParamAndResult
|
||||
import io.element.android.tests.testutils.EventsRecorder
|
||||
import io.element.android.tests.testutils.clickOn
|
||||
import io.element.android.tests.testutils.ensureCalledOnce
|
||||
import io.element.android.tests.testutils.ensureCalledOnceWithParam
|
||||
import io.element.android.tests.testutils.pressBack
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TestRule
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class MessagesViewTest {
|
||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
||||
|
||||
@Test
|
||||
fun `clicking on back invoke expected callback`() {
|
||||
val eventsRecorder = EventsRecorder<MessagesEvents>(expectEvents = false)
|
||||
val state = aMessagesState(
|
||||
eventSink = eventsRecorder
|
||||
)
|
||||
ensureCalledOnce { callback ->
|
||||
rule.setMessagesView(
|
||||
state = state,
|
||||
onBackPressed = callback,
|
||||
)
|
||||
rule.pressBack()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on room name invoke expected callback`() {
|
||||
val eventsRecorder = EventsRecorder<MessagesEvents>(expectEvents = false)
|
||||
val state = aMessagesState(
|
||||
eventSink = eventsRecorder
|
||||
)
|
||||
ensureCalledOnce { callback ->
|
||||
rule.setMessagesView(
|
||||
state = state,
|
||||
onRoomDetailsClicked = callback,
|
||||
)
|
||||
rule.onNodeWithText(state.roomName.dataOrNull().orEmpty()).performClick()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on join call invoke expected callback`() {
|
||||
val eventsRecorder = EventsRecorder<MessagesEvents>(expectEvents = false)
|
||||
val state = aMessagesState(
|
||||
eventSink = eventsRecorder
|
||||
)
|
||||
ensureCalledOnce { callback ->
|
||||
rule.setMessagesView(
|
||||
state = state,
|
||||
onJoinCallClicked = callback,
|
||||
)
|
||||
val joinCallContentDescription = rule.activity.getString(CommonStrings.a11y_start_call)
|
||||
rule.onNodeWithContentDescription(joinCallContentDescription).performClick()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on an Event invoke expected callback`() {
|
||||
val eventsRecorder = EventsRecorder<MessagesEvents>(expectEvents = false)
|
||||
val state = aMessagesState(
|
||||
eventSink = eventsRecorder
|
||||
)
|
||||
val timelineItem = state.timelineState.timelineItems.first()
|
||||
val callback = EnsureCalledOnceWithParam(
|
||||
expectedParam = timelineItem,
|
||||
result = true,
|
||||
)
|
||||
rule.setMessagesView(
|
||||
state = state,
|
||||
onEventClicked = callback,
|
||||
)
|
||||
// Cannot perform click on "Text", it's not detected. Use tag instead
|
||||
rule.onAllNodesWithTag(TestTags.messageBubble.value).onFirst().performClick()
|
||||
callback.assertSuccess()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on send location invoke expected callback`() {
|
||||
val eventsRecorder = EventsRecorder<MessagesEvents>(expectEvents = false)
|
||||
val state = aMessagesState(
|
||||
composerState = aMessageComposerState(
|
||||
showAttachmentSourcePicker = true
|
||||
),
|
||||
eventSink = eventsRecorder
|
||||
)
|
||||
ensureCalledOnce { callback ->
|
||||
rule.setMessagesView(
|
||||
state = state,
|
||||
onSendLocationClicked = callback,
|
||||
)
|
||||
rule.clickOn(R.string.screen_room_attachment_source_location)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on create poll invoke expected callback`() {
|
||||
val eventsRecorder = EventsRecorder<MessagesEvents>(expectEvents = false)
|
||||
val state = aMessagesState(
|
||||
composerState = aMessageComposerState(
|
||||
showAttachmentSourcePicker = true
|
||||
),
|
||||
eventSink = eventsRecorder
|
||||
)
|
||||
ensureCalledOnce { callback ->
|
||||
rule.setMessagesView(
|
||||
state = state,
|
||||
onCreatePollClicked = callback,
|
||||
)
|
||||
// Then click on the poll action
|
||||
rule.clickOn(R.string.screen_room_attachment_source_poll)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on the sender of an Event invoke expected callback`() {
|
||||
val eventsRecorder = EventsRecorder<MessagesEvents>(expectEvents = false)
|
||||
val state = aMessagesState(
|
||||
eventSink = eventsRecorder
|
||||
)
|
||||
val timelineItem = state.timelineState.timelineItems.first()
|
||||
ensureCalledOnceWithParam(
|
||||
param = (timelineItem as TimelineItem.Event).senderId
|
||||
) { callback ->
|
||||
rule.setMessagesView(
|
||||
state = state,
|
||||
onUserDataClicked = callback,
|
||||
)
|
||||
val senderName = (timelineItem as? TimelineItem.Event)?.senderDisplayName.orEmpty()
|
||||
rule.onNodeWithText(senderName).performClick()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `selecting a action on a message emits the expected Event`() {
|
||||
val eventsRecorder = EventsRecorder<MessagesEvents>()
|
||||
val state = aMessagesState(
|
||||
eventSink = eventsRecorder
|
||||
)
|
||||
val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event
|
||||
val stateWithMessageAction = state.copy(
|
||||
actionListState = anActionListState(
|
||||
target = ActionListState.Target.Success(
|
||||
event = timelineItem,
|
||||
displayEmojiReactions = true,
|
||||
actions = persistentListOf(TimelineItemAction.Edit),
|
||||
)
|
||||
),
|
||||
)
|
||||
rule.setMessagesView(
|
||||
state = stateWithMessageAction,
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_edit)
|
||||
eventsRecorder.assertSingle(MessagesEvents.HandleAction(TimelineItemAction.Edit, timelineItem))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on a reaction emits the expected Event`() {
|
||||
val eventsRecorder = EventsRecorder<MessagesEvents>()
|
||||
val state = aMessagesState(
|
||||
eventSink = eventsRecorder
|
||||
)
|
||||
val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event
|
||||
rule.setMessagesView(
|
||||
state = state,
|
||||
)
|
||||
rule.onAllNodesWithText("👍️").onFirst().performClick()
|
||||
eventsRecorder.assertSingle(MessagesEvents.ToggleReaction("👍️", timelineItem.eventId!!))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `long clicking on a reaction emits the expected Event`() {
|
||||
val eventsRecorder = EventsRecorder<ReactionSummaryEvents>()
|
||||
val state = aMessagesState(
|
||||
reactionSummaryState = aReactionSummaryState(
|
||||
target = null,
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event
|
||||
rule.setMessagesView(
|
||||
state = state,
|
||||
)
|
||||
rule.onAllNodesWithText("👍️").onFirst().performTouchInput { longClick() }
|
||||
eventsRecorder.assertSingle(ReactionSummaryEvents.ShowReactionSummary(timelineItem.eventId!!, timelineItem.reactionsState.reactions, "👍️"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on more reaction emits the expected Event`() {
|
||||
val eventsRecorder = EventsRecorder<CustomReactionEvents>()
|
||||
val state = aMessagesState(
|
||||
customReactionState = aCustomReactionState(
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event
|
||||
rule.setMessagesView(
|
||||
state = state,
|
||||
)
|
||||
val moreReactionContentDescription = rule.activity.getString(R.string.screen_room_timeline_add_reaction)
|
||||
rule.onAllNodesWithContentDescription(moreReactionContentDescription).onFirst().performClick()
|
||||
eventsRecorder.assertSingle(CustomReactionEvents.ShowCustomReactionSheet(timelineItem))
|
||||
}
|
||||
}
|
||||
|
||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setMessagesView(
|
||||
state: MessagesState,
|
||||
onBackPressed: () -> Unit = EnsureNeverCalled(),
|
||||
onRoomDetailsClicked: () -> Unit = EnsureNeverCalled(),
|
||||
onEventClicked: (event: TimelineItem.Event) -> Boolean = EnsureNeverCalledWithParamAndResult(),
|
||||
onUserDataClicked: (UserId) -> Unit = EnsureNeverCalledWithParam(),
|
||||
onPreviewAttachments: (ImmutableList<Attachment>) -> Unit = EnsureNeverCalledWithParam(),
|
||||
onSendLocationClicked: () -> Unit = EnsureNeverCalled(),
|
||||
onCreatePollClicked: () -> Unit = EnsureNeverCalled(),
|
||||
onJoinCallClicked: () -> Unit = EnsureNeverCalled(),
|
||||
) {
|
||||
setContent {
|
||||
// Cannot use the RichTextEditor, so simulate a LocalInspectionMode
|
||||
CompositionLocalProvider(LocalInspectionMode provides true) {
|
||||
MessagesView(
|
||||
state = state,
|
||||
onBackPressed = onBackPressed,
|
||||
onRoomDetailsClicked = onRoomDetailsClicked,
|
||||
onEventClicked = onEventClicked,
|
||||
onUserDataClicked = onUserDataClicked,
|
||||
onPreviewAttachments = onPreviewAttachments,
|
||||
onSendLocationClicked = onSendLocationClicked,
|
||||
onCreatePollClicked = onCreatePollClicked,
|
||||
onJoinCallClicked = onJoinCallClicked,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -58,6 +58,11 @@ object TestTags {
|
|||
*/
|
||||
val richTextEditor = TestTag("rich_text_editor")
|
||||
|
||||
/**
|
||||
* Message bubble.
|
||||
*/
|
||||
val messageBubble = TestTag("message_bubble")
|
||||
|
||||
/**
|
||||
* Dialogs.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -28,6 +28,12 @@ class EnsureNeverCalledWithParam<T> : (T) -> Unit {
|
|||
}
|
||||
}
|
||||
|
||||
class EnsureNeverCalledWithParamAndResult<T, R> : (T) -> R {
|
||||
override fun invoke(p1: T): R {
|
||||
throw AssertionError("Should not be called and is called with $p1")
|
||||
}
|
||||
}
|
||||
|
||||
class EnsureNeverCalledWithTwoParams<T, U> : (T, U) -> Unit {
|
||||
override fun invoke(p1: T, p2: U) {
|
||||
throw AssertionError("Should not be called and is called with $p1 and $p2")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue