Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ internal class ConversationsRepositoryActionsTest : BaseConversationsRepositoryT
every { UpdateConversationArchiveStatusAction.archiveConversation(any()) } just runs
every { UpdateConversationArchiveStatusAction.unarchiveConversation(any()) } just runs
every { DeleteConversationAction.deleteConversation(any(), any()) } just runs
every { DeleteConversationAction.deleteConversations(any()) } just runs
}

@After
Expand Down Expand Up @@ -137,4 +138,37 @@ internal class ConversationsRepositoryActionsTest : BaseConversationsRepositoryT
DeleteConversationAction.deleteConversation("conversation-delete", 456L)
}
}

@Test
fun deleteConversations_skipsBlankIdsAndDelegatesTheRestInOneBatch() {
val repository = createRepository()

repository.deleteConversations(
cutoffTimestampsByConversationId = mapOf(
ConversationId("conversation-1") to 123L,
ConversationId(" ") to 456L,
ConversationId("conversation-2") to 789L,
),
)

verify(exactly = 1) {
DeleteConversationAction.deleteConversations(
mapOf(
"conversation-1" to 123L,
"conversation-2" to 789L,
),
)
}
}

@Test
fun deleteConversations_skipsTheActionWhenEveryIdIsBlank() {
createRepository().deleteConversations(
cutoffTimestampsByConversationId = mapOf(ConversationId("") to 123L),
)

verify(exactly = 0) {
DeleteConversationAction.deleteConversations(any())
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package com.android.messaging.datamodel.action

import com.android.messaging.FactoryTestAccess
import com.android.messaging.datamodel.BugleDatabaseOperations
import com.android.messaging.datamodel.BugleNotifications
import com.android.messaging.datamodel.DataModel
import com.android.messaging.datamodel.MessagingContentProvider
import com.android.messaging.sms.MmsUtils
import com.android.messaging.testutil.installTestFactory
import com.android.messaging.util.NotificationChannelUtil
import com.android.messaging.widget.WidgetConversationProvider
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.mockkStatic
import io.mockk.runs
import io.mockk.slot
import io.mockk.unmockkAll
import io.mockk.verify
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment

@RunWith(RobolectricTestRunner::class)
class DeleteConversationActionTest {

private val actionService = mockk<ActionService>()
private val startedAction = slot<Action>()

@Before
fun setUp() {
val dataModel = mockk<DataModel>(relaxed = true)
every { dataModel.actionService } returns actionService
every { actionService.startAction(capture(startedAction)) } just runs
installTestFactory(
context = RuntimeEnvironment.getApplication().applicationContext,
dataModel = dataModel,
)
mockkObject(NotificationChannelUtil)
every { NotificationChannelUtil.deleteChannel(any()) } just runs
mockkStatic(BugleDatabaseOperations::class)
every { BugleDatabaseOperations.getThreadId(any(), CONVERSATION_ID) } returns THREAD_ID
every { BugleDatabaseOperations.deleteConversation(any(), CONVERSATION_ID, any()) } returns
true
mockkStatic(MessagingContentProvider::class)
every { MessagingContentProvider.notifyConversationListChanged() } just runs
mockkStatic(WidgetConversationProvider::class)
every { WidgetConversationProvider.notifyConversationDeleted(any(), any()) } just runs
mockkStatic(BugleNotifications::class)
every { BugleNotifications.update(any(), any()) } just runs
mockkStatic(BugleActionToasts::class)
every { BugleActionToasts.onConversationsDeleted(any()) } just runs
every { BugleActionToasts.onFailedToDeleteConversations(any()) } just runs
mockkStatic(MmsUtils::class)
every { MmsUtils.deleteThread(THREAD_ID, any()) } returns 0
}

@After
fun tearDown() {
unmockkAll()
FactoryTestAccess.reset()
}

@Test
fun draftOnlyConversationWithNoTelephonyMessagesReportsDeleted() {
every { MmsUtils.hasThreadMessages(THREAD_ID, any()) } returns false

deleteConversation()

verify { BugleActionToasts.onConversationsDeleted(1) }
verify(exactly = 0) { BugleActionToasts.onFailedToDeleteConversations(any()) }
}

@Test
fun telephonyMessagesThatSurviveTheDeleteReportFailure() {
every { MmsUtils.hasThreadMessages(THREAD_ID, any()) } returns true

deleteConversation()

verify { BugleActionToasts.onFailedToDeleteConversations(1) }
verify(exactly = 0) { BugleActionToasts.onConversationsDeleted(any()) }
}

private fun deleteConversation() {
DeleteConversationAction.deleteConversation(CONVERSATION_ID, Long.MAX_VALUE)
startedAction.captured.doBackgroundWork()
}

private companion object {
private const val CONVERSATION_ID = "17"
private const val THREAD_ID = 42L
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.android.messaging.sms

import android.content.ContentResolver
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.net.Uri
import com.android.messaging.FactoryTestAccess
import com.android.messaging.testutil.installTestFactory
import io.mockk.every
import io.mockk.mockk
import org.junit.After
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner

/**
* MmsSmsProvider runs thread message queries without bind arguments, so the cutoff only reaches
* SQLite when it is part of the selection itself.
*/
@RunWith(RobolectricTestRunner::class)
class MmsUtilsThreadMessagesTest {

private val database = SQLiteDatabase.create(null)
private val contentResolver = mockk<ContentResolver>()
private val context = mockk<Context>()

@Before
fun setUp() {
database.execSQL("CREATE TABLE sms (_id INTEGER PRIMARY KEY, thread_id, date)")
database.execSQL("INSERT INTO sms (thread_id, date) VALUES ($THREAD_ID, $MESSAGE_DATE)")
every { context.contentResolver } returns contentResolver
every { contentResolver.query(any(), any(), any(), any(), any()) } answers {
val threadId = firstArg<Uri>().lastPathSegment
val selection = thirdArg<String>()
database.rawQuery(
"SELECT _id FROM sms WHERE thread_id = $threadId AND ($selection)",
emptyArray(),
)
}
installTestFactory(context = context)
}

@After
fun tearDown() {
database.close()
FactoryTestAccess.reset()
}

@Test
fun messageUpToTheCutoffIsFound() {
assertTrue(MmsUtils.hasThreadMessages(THREAD_ID, MESSAGE_DATE))
}

@Test
fun messageAfterTheCutoffIsIgnored() {
assertFalse(MmsUtils.hasThreadMessages(THREAD_ID, MESSAGE_DATE - 1))
}

private companion object {
private const val THREAD_ID = 42L
private const val MESSAGE_DATE = 1_790_000_000_000L
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,19 @@ internal class MessageComposeBarInputTypeTest {
assertTrue(inputType and InputType.TYPE_TEXT_FLAG_MULTI_LINE != 0)
}

@Test
fun messageComposeField_withImeSend_offersSendWhileEnterStillAddsANewLine() {
val imeOptions = focusedFieldEditorInfo(onImeSend = {}).imeOptions

assertEquals(EditorInfo.IME_ACTION_SEND, imeOptions and EditorInfo.IME_MASK_ACTION)
assertTrue(imeOptions and EditorInfo.IME_FLAG_NO_ENTER_ACTION != 0)
}

private fun focusedFieldInputType(): Int {
return focusedFieldEditorInfo().inputType
}

private fun focusedFieldEditorInfo(onImeSend: (() -> Unit)? = null): EditorInfo {
var editorInfo: EditorInfo? = null

composeTestRule.setContent {
Expand All @@ -62,6 +74,7 @@ internal class MessageComposeBarInputTypeTest {
fieldStateDescription = null,
fieldTestTag = MESSAGE_COMPOSE_FIELD_TEST_TAG,
sendAction = {},
onImeSend = onImeSend,
)
}
}
Expand All @@ -75,7 +88,7 @@ internal class MessageComposeBarInputTypeTest {
editorInfo != null
}

return requireNotNull(editorInfo).inputType
return requireNotNull(editorInfo)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import com.android.messaging.datamodel.MessageTextStats
import com.android.messaging.datamodel.data.ParticipantData
import com.android.messaging.domain.conversation.usecase.draft.model.ConversationDraftSendProtocol
import com.android.messaging.sms.MmsConfig
import com.android.messaging.ui.conversation.audio.model.ConversationAudioRecordingPhase
import com.android.messaging.ui.conversation.audio.model.ConversationAudioRecordingUiState
import com.android.messaging.ui.conversation.composer.model.ComposerAttachmentUiModel
import com.android.messaging.ui.conversation.composer.model.ConversationDraftState
Expand Down Expand Up @@ -102,6 +103,27 @@ internal class ConversationComposerUiStateMapperImplTest {
assertFalse(uiState.isSendEnabled)
}

@Test
fun map_disablesSendWhileAudioIsRecording() {
val uiState = mapper.map(
audioRecording = ConversationAudioRecordingUiState(
phase = ConversationAudioRecordingPhase.Recording,
),
draftState = ConversationDraftState(
draft = ConversationDraft(
messageText = "Hello",
),
),
attachments = persistentListOf(),
composerAvailability = ConversationComposerAvailability.Editable,
subscriptions = persistentListOf(),
areSubscriptionsLoaded = true,
defaultSmsSubscriptionId = SubId(ParticipantData.DEFAULT_SELF_SUB_ID),
)

assertFalse(uiState.isSendEnabled)
}

@Test
fun map_disablesSendAndAttachmentWhenDraftIsBusy() {
val uiState = mapper.map(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import androidx.compose.ui.test.onAllNodesWithTag
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performImeAction
import androidx.compose.ui.test.performTextInput
import androidx.compose.ui.test.performTouchInput
import androidx.compose.ui.unit.dp
Expand Down Expand Up @@ -258,6 +259,48 @@ class ConversationComposeBarTest {
}
}

@Test
fun keyboardSendAction_sendsTheMessage() {
var sendClicks = 0

setContent(
messageText = "Hello",
isSendActionEnabled = true,
onSendClick = {
sendClicks += 1
},
)

composeTestRule
.onNodeWithTag(CONVERSATION_TEXT_FIELD_TEST_TAG)
.performImeAction()

composeTestRule.runOnIdle {
assertEquals(1, sendClicks)
}
}

@Test
fun keyboardSendAction_doesNothingWhileTheSendButtonIsDisabled() {
var sendClicks = 0

setContent(
messageText = "Hello",
isSendActionEnabled = false,
onSendClick = {
sendClicks += 1
},
)

composeTestRule
.onNodeWithTag(CONVERSATION_TEXT_FIELD_TEST_TAG)
.performImeAction()

composeTestRule.runOnIdle {
assertEquals(0, sendClicks)
}
}

@Test
fun textField_canBeDisabled() {
setContent(
Expand Down
Loading
Loading