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 @@ -49,6 +49,7 @@ internal abstract class BaseConversationScreenTest {
screenModel: ConversationScreenModel,
conversationId: () -> ConversationId? = { CONVERSATION_ID },
cancelIncomingNotification: Boolean = true,
offersArchiveUndo: Boolean = false,
lifecycleOwner: LifecycleOwner? = null,
isScreenShown: () -> Boolean = { true },
onAddPeopleClick: () -> Unit = {},
Expand All @@ -60,6 +61,7 @@ internal abstract class BaseConversationScreenTest {
onPendingStartupAttachmentConsumed: () -> Unit = {},
pendingScrollMessageId: MessageId? = null,
onPendingScrollMessageIdConsumed: () -> Unit = {},
onCloseConversation: () -> Unit = {},
) {
composeTestRule.setContent {
val content: @Composable () -> Unit = content@{
Expand All @@ -70,6 +72,7 @@ internal abstract class BaseConversationScreenTest {
ConversationScreen(
conversationId = conversationId(),
cancelIncomingNotification = cancelIncomingNotification,
offersArchiveUndo = offersArchiveUndo,
onAddPeopleClick = onAddPeopleClick,
onConversationDetailsClick = {},
onNavigateToMessageDetails = {},
Expand All @@ -78,7 +81,7 @@ internal abstract class BaseConversationScreenTest {
onNavigateToAddContact = {},
onNavigateToForward = {},
onNavigateBack = {},
onCloseConversation = {},
onCloseConversation = onCloseConversation,
pendingLaunchPayload = ConversationPendingLaunchPayload(
draft = pendingDraft,
scrollMessageId = pendingScrollMessageId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,9 @@ internal abstract class BaseConversationScreenEffectsActionTest {
onNavigateToMessageDetails(event.messageId)
}

is ConversationScreenNavEvent.ForwardMessage -> Unit
ConversationScreenNavEvent.CloseAfterArchive,
is ConversationScreenNavEvent.ForwardMessage,
-> Unit
}
}
ConversationScreenEffects(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ import com.android.messaging.data.conversation.store.ConversationArchiveStore
import com.android.messaging.data.conversation.store.ConversationPinStore
import com.android.messaging.data.conversation.store.ConversationReadStore
import com.android.messaging.data.conversation.store.ConversationSelfIdStore
import com.android.messaging.data.conversationsettings.repository.ConversationNotificationRepository
import com.android.messaging.testutil.MainDispatcherRule
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.runs
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import org.junit.Before
import org.junit.Rule

Expand All @@ -30,13 +32,19 @@ internal abstract class BaseConversationsRepositoryTest {
protected lateinit var messageDetailsMapper: ConversationMessageDetailsMapper
protected lateinit var messageDetailsPlatformSource: MessageDetailsPlatformSource
protected lateinit var conversationArchiveStore: ConversationArchiveStore
protected lateinit var notificationRepository: ConversationNotificationRepository
protected val isSnoozedFlow = MutableStateFlow(value = false)

@Before
fun setUp() {
contentResolver = mockk()
messageDetailsMapper = mockk(relaxed = true)
messageDetailsPlatformSource = mockk(relaxed = true)
conversationArchiveStore = mockk(relaxed = true)
notificationRepository = mockk {
every { observeIsSnoozed(conversationId = any()) } returns isSnoozedFlow
every { isSnoozed(conversationId = any()) } answers { isSnoozedFlow.value }
}
}

protected fun createRepository(): ConversationsRepositoryImpl {
Expand All @@ -48,6 +56,7 @@ internal abstract class BaseConversationsRepositoryTest {
conversationReadStore = mockk<ConversationReadStore>(relaxed = true),
conversationPinStore = mockk<ConversationPinStore>(relaxed = true),
conversationArchiveStore = conversationArchiveStore,
notificationRepository = notificationRepository,
defaultDispatcher = mainDispatcherRule.testDispatcher,
messagingDbDispatcher = mainDispatcherRule.testDispatcher,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.android.messaging.data.conversation.repository.conversations
import android.database.ContentObserver
import android.database.Cursor
import android.database.MatrixCursor
import android.net.Uri
import app.cash.turbine.test
import com.android.messaging.data.conversation.model.ParticipantId
import com.android.messaging.datamodel.DatabaseHelper.ConversationColumns
Expand All @@ -16,6 +17,7 @@ import com.android.messaging.testutil.TEST_CONVERSATION_ID as CONVERSATION_ID
import com.android.messaging.testutil.assertThat
import com.android.messaging.testutil.createParticipantsCursor
import com.android.messaging.testutil.participantRow
import io.mockk.every
import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
Expand Down Expand Up @@ -284,6 +286,64 @@ internal class ConversationsRepositoryMetadataTest : BaseConversationsRepository
}
}

@Test
fun getConversationMetadata_reEmitsWithSnoozeStateWhenSnoozeChanges() {
runTest(context = mainDispatcherRule.testDispatcher) {
val repository = createRepository()
val expectedUri = MessagingContentProvider.buildConversationMetadataUri(
CONVERSATION_ID.value
)

stubObserverRegistration(
registeredObservers = mutableListOf(),
expectedUri = expectedUri,
)
stubFreshMetadataCursorPerQuery(expectedUri = expectedUri)

repository.getConversationMetadata(conversationId = CONVERSATION_ID).test {
assertEquals(false, awaitItem()?.isSnoozed)

isSnoozedFlow.value = true

assertEquals(true, awaitItem()?.isSnoozed)
cancelAndIgnoreRemainingEvents()
}
}
}

@Test
fun getConversationMetadataSnapshot_includesSnoozeState() {
runTest(context = mainDispatcherRule.testDispatcher) {
val repository = createRepository()
val expectedUri = MessagingContentProvider.buildConversationMetadataUri(
CONVERSATION_ID.value
)

stubFreshMetadataCursorPerQuery(expectedUri = expectedUri)
isSnoozedFlow.value = true

val metadata = repository.getConversationMetadataSnapshot(
conversationId = CONVERSATION_ID,
)

assertEquals(true, metadata?.isSnoozed)
}
}

private fun stubFreshMetadataCursorPerQuery(expectedUri: Uri) {
every {
contentResolver.query(expectedUri, any(), null, null, null)
} answers {
createConversationMetadataCursor(
row = conversationMetadataRow(
conversationName = "Carol, Dave",
selfParticipantId = "self-2",
participantCount = 2,
),
)
}
}

private fun createConversationMetadataCursor(row: TestConversationMetadataRow?): Cursor {
val cursor = MatrixCursor(ConversationListItemData.PROJECTION)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
@file:OptIn(ExperimentalCoroutinesApi::class)

package com.android.messaging.data.conversationsettings.repository

import app.cash.turbine.test
import com.android.messaging.FactoryTestAccess
import com.android.messaging.data.conversation.model.ConversationId
import com.android.messaging.testutil.FakeBuglePrefs
import com.android.messaging.testutil.installTestFactory
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
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
import org.robolectric.RuntimeEnvironment

@RunWith(RobolectricTestRunner::class)
class ConversationNotificationRepositoryImplTest {

private val prefs = FakeBuglePrefs()
private val context = RuntimeEnvironment.getApplication()
private val repository = ConversationNotificationRepositoryImpl(context = context)

@Before
fun setUp() {
installTestFactory(context = context, prefs = prefs)
}

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

@Test
fun observeIsSnoozed_turnsFalseWhenTheSnoozeExpires() = runTest {
prefs.putLong(
"${ConversationNotificationRepositoryImpl.SNOOZE_KEY_PREFIX}${CONVERSATION_ID.value}",
System.currentTimeMillis() + SNOOZE_MILLIS,
)

repository.observeIsSnoozed(conversationId = CONVERSATION_ID).test {
assertTrue(awaitItem())

advanceTimeBy(delayTimeMillis = SNOOZE_MILLIS / 2)
expectNoEvents()

advanceTimeBy(delayTimeMillis = SNOOZE_MILLIS)
assertFalse(awaitItem())
}
}

private companion object {
val CONVERSATION_ID = ConversationId("conversation-1")
const val SNOOZE_MILLIS = 60 * 60 * 1000L
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.android.messaging.domain.conversation.usecase.action

import app.cash.turbine.test
import com.android.messaging.data.conversation.event.ConversationArchiveEventsImpl
import com.android.messaging.data.conversation.model.ConversationId
import com.android.messaging.data.conversation.repository.ConversationsRepository
import io.mockk.coVerify
import io.mockk.mockk
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Test

class ArchiveConversationImplTest {

@Test
fun invoke_notifiesConversationListAndArchives() {
runTest {
val conversationsRepository = mockk<ConversationsRepository>(relaxed = true)
val conversationArchiveEvents = ConversationArchiveEventsImpl()
val archiveConversation = ArchiveConversationImpl(
conversationsRepository = conversationsRepository,
conversationArchiveEvents = conversationArchiveEvents,
)

conversationArchiveEvents.archivedConversationIds.test {
archiveConversation(conversationId = CONVERSATION_ID)

assertEquals(CONVERSATION_ID, awaitItem())
}
coVerify(exactly = 1) {
conversationsRepository.archiveConversation(conversationId = CONVERSATION_ID)
}
}
}

private companion object {
val CONVERSATION_ID = ConversationId("conversation-42")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ internal fun createConversationMetadata(
otherParticipantPhotoUri = null,
isArchived = false,
isBlocked = false,
isSnoozed = false,
composerAvailability = ConversationComposerAvailability.Editable,
sortTimestamp = 0L,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,63 @@ class ConversationComposeBarTest {
}
}

@Test
fun longPressAndDragLeftDriftingUp_cancelsRecordingWithoutLocking() {
var audioRecording by mutableStateOf(ConversationAudioRecordingUiState())
var lockRequests = 0
var finishRequests = 0
var cancelRequests = 0
val cancelDragDistancePx = with(composeTestRule.density) {
(AUDIO_RECORD_CANCEL_THRESHOLD + 24.dp).toPx()
}
val lockDragDistancePx = with(composeTestRule.density) {
(AUDIO_RECORD_LOCK_THRESHOLD + 8.dp).toPx()
}

setContent(
audioRecording = { audioRecording },
messageText = { "" },
isSendActionEnabled = false,
shouldShowRecordAction = { true },
onAudioRecordingStartRequest = {
audioRecording = recordingAudioState()
},
onAudioRecordingFinish = {
finishRequests += 1
audioRecording = ConversationAudioRecordingUiState()
},
onAudioRecordingLock = {
lockRequests += 1
audioRecording = recordingAudioState(isLocked = true)
true
},
onAudioRecordingCancel = {
cancelRequests += 1
audioRecording = ConversationAudioRecordingUiState()
},
)

composeTestRule
.onNodeWithTag(CONVERSATION_SEND_BUTTON_TEST_TAG)
.performTouchInput {
down(center)
advanceEventTime(durationMillis = 700L)
moveBy(
Offset(
x = -cancelDragDistancePx,
y = -lockDragDistancePx,
),
)
up()
}

composeTestRule.runOnIdle {
assertEquals(0, lockRequests)
assertEquals(0, finishRequests)
assertEquals(1, cancelRequests)
}
}

@Test
fun lockGesture_emitsConfirmHapticAndKeepsRecordingActiveUntilStopTap() {
var audioRecording by mutableStateOf(ConversationAudioRecordingUiState())
Expand Down
Loading
Loading