[Chat][Refactor] Updates to scroll message repo (#617)

This commit is contained in:
Adam Hammer 2022-11-23 09:14:57 -08:00 коммит произвёл GitHub
Родитель 4d8c044c9c
Коммит 82d05831ff
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
14 изменённых файлов: 290 добавлений и 575 удалений

Просмотреть файл

@ -10,7 +10,6 @@ import com.azure.android.communication.ui.chat.locator.ServiceLocator
import com.azure.android.communication.ui.chat.logger.DefaultLogger
import com.azure.android.communication.ui.chat.logger.Logger
import com.azure.android.communication.ui.chat.models.ChatCompositeRemoteOptions
import com.azure.android.communication.ui.chat.models.MessageInfoModel
import com.azure.android.communication.ui.chat.presentation.manager.NetworkManager
import com.azure.android.communication.ui.chat.redux.AppStore
import com.azure.android.communication.ui.chat.redux.Dispatch
@ -94,7 +93,7 @@ internal class ChatContainer(
val messageRepository = MessageRepository.createSkipListBackedRepository()
addTypedBuilder { chatAdapter }
addTypedBuilder<List<MessageInfoModel>> { messageRepository }
addTypedBuilder { messageRepository }
addTypedBuilder { remoteOptions }

Просмотреть файл

@ -51,8 +51,7 @@ internal fun MessageListView(
dispatchers: Dispatch,
) {
requestPages(scrollState, messages, dispatchers)
scrollToNewestWhenKeyboardOpen(scrollState)
dismissKeyboardWhenScrollUp(scrollState)
// dismissKeyboardWhenScrollUp(scrollState)
if (messages.isNotEmpty()) {
sendReadReceipt(scrollState, messages, dispatchers)
autoScrollToBottom(scrollState, messages)
@ -112,48 +111,18 @@ private fun autoScrollToBottom(
scrollState: LazyListState,
messages: List<MessageViewModel>,
) {
val lastList = remember { mutableStateOf(messages) }
val wasAtEnd = remember { mutableStateOf(scrollState.firstVisibleItemIndex) }
val isAtEnd = scrollState.firstVisibleItemIndex
if (wasAtEnd.value == 0 && wasAtEnd.value != isAtEnd) {
LaunchedEffect(messages.last()) {
if (wasAtEnd.value == 0 &&
messages.last().message.id != lastList.value.last().message.id
) {
LaunchedEffect(messages.last().message.id) {
scrollState.scrollToItem(0)
}
}
wasAtEnd.value = isAtEnd
}
enum class Keyboard {
Opened, Closed
}
@Composable
private fun scrollToNewestWhenKeyboardOpen(scrollState: LazyListState) {
val coroutineScope = rememberCoroutineScope()
val triggered = remember { mutableStateOf(false) }
val view = LocalView.current
DisposableEffect(view) {
val onGlobalListener = ViewTreeObserver.OnGlobalLayoutListener {
val rect = Rect()
view.getWindowVisibleDisplayFrame(rect)
val screenHeight = view.rootView.height
val keypadHeight = screenHeight - rect.bottom
if (keypadHeight > screenHeight * 0.15) {
if (!triggered.value) {
coroutineScope.launch {
scrollState.animateScrollToItem(0)
}
}
triggered.value = true
} else {
triggered.value = false
}
}
view.viewTreeObserver.addOnGlobalLayoutListener(onGlobalListener)
onDispose {
view.viewTreeObserver.removeOnGlobalLayoutListener(onGlobalListener)
}
}
wasAtEnd.value = scrollState.firstVisibleItemIndex
lastList.value = messages
}
@Composable

Просмотреть файл

@ -39,6 +39,7 @@ import com.azure.android.communication.ui.chat.presentation.ui.viewmodel.ChatScr
import com.azure.android.communication.ui.chat.presentation.ui.viewmodel.toViewModelList
import com.azure.android.communication.ui.chat.preview.MOCK_LOCAL_USER_ID
import com.azure.android.communication.ui.chat.preview.MOCK_MESSAGES
import com.azure.android.communication.ui.chat.redux.action.ChatAction
import com.azure.android.communication.ui.chat.redux.action.NavigationAction
import com.azure.android.communication.ui.chat.redux.state.ChatStatus
import com.azure.android.communication.ui.chat.service.sdk.wrapper.CommunicationIdentifier
@ -144,8 +145,10 @@ internal fun ChatScreen(
messageInputTextState = stateViewModel.messageInputTextState,
chatStatus = viewModel.chatStatus,
postAction = {
coroutineScope.launch {
listState.animateScrollToItem(0)
if (it is ChatAction.SendMessage) {
coroutineScope.launch {
listState.animateScrollToItem(0)
}
}
viewModel.postAction(it)
}

Просмотреть файл

@ -24,6 +24,7 @@ import com.azure.android.communication.ui.chat.redux.action.NavigationAction
import com.azure.android.communication.ui.chat.redux.state.AppReduxState
import com.azure.android.communication.ui.chat.redux.state.NavigationStatus
import com.azure.android.communication.ui.chat.redux.state.ReduxState
import com.azure.android.communication.ui.chat.repository.MessageRepository
internal class ChatCompositeViewImpl(
context: Context,
@ -51,7 +52,7 @@ internal class ChatCompositeViewImpl(
buildChatScreenViewModel(
context = context,
store = store,
messages = locator.locate(),
messages = locator.locate<MessageRepository>().getSnapshotList(),
localUserIdentifier = locator.locate<ChatCompositeRemoteOptions>().identity,
dispatch = locator.locate(),
)

Просмотреть файл

@ -15,6 +15,7 @@ import com.azure.android.communication.ui.chat.redux.action.Action
import com.azure.android.communication.ui.chat.redux.state.ChatStatus
import com.azure.android.communication.ui.chat.redux.state.NavigationStatus
import com.azure.android.communication.ui.chat.redux.state.ReduxState
import com.azure.android.communication.ui.chat.utilities.findMessageIdxById
import kotlin.math.max
// View Model for the Chat Screen
@ -78,8 +79,8 @@ private fun getUnReadMessagesCount(
val lastReadId = store.getCurrentState().chatState.lastReadMessageId
val lastSendId = store.getCurrentState().chatState.lastSendMessageId
val internalLastReadIndex = messages.indexOf(MessageInfoModel(id = lastReadId))
val internalLastSendIndex = messages.indexOf(MessageInfoModel(id = lastSendId))
val internalLastReadIndex = messages.findMessageIdxById(lastReadId)
val internalLastSendIndex = messages.findMessageIdxById(lastSendId)
val internalLastIndex = max(internalLastReadIndex, internalLastSendIndex)

Просмотреть файл

@ -7,6 +7,7 @@ import android.content.Context
import com.azure.android.communication.ui.chat.R
import com.azure.android.communication.ui.chat.models.EMPTY_MESSAGE_INFO_MODEL
import com.azure.android.communication.ui.chat.models.MessageInfoModel
import com.azure.android.communication.ui.chat.utilities.findMessageIdxById
import com.azure.android.core.rest.annotation.Immutable
import org.threeten.bp.OffsetDateTime
import org.threeten.bp.format.DateTimeFormatter
@ -107,7 +108,7 @@ private class InfoModelToViewModelAdapter(
override fun containsAll(elements: Collection<MessageViewModel>) =
messages.containsAll(elements.map { it.message })
override fun indexOf(element: MessageViewModel) = messages.indexOf(element.message)
override fun indexOf(element: MessageViewModel) = messages.findMessageIdxById(element.message.id ?: "")
override fun isEmpty() = messages.isEmpty()

Просмотреть файл

@ -14,10 +14,15 @@ import com.azure.android.communication.ui.chat.repository.storage.MessageReposit
internal class MessageRepository private constructor(
val readerDelegate: MessageRepositoryReader,
val writerDelegate: MessageRepositoryWriter,
) : MessageRepositoryReader(), MessageRepositoryWriter {
) : MessageRepositoryWriter {
override val size: Int get() = readerDelegate.size
override fun get(index: Int): MessageInfoModel = readerDelegate[index]
// override val size: Int get() = readerDelegate.size
// override fun get(index: Int): MessageInfoModel = readerDelegate[index]
// override fun indexOf(element: MessageInfoModel) = readerDelegate.indexOf(element)
fun getSnapshotList(): List<MessageInfoModel> {
return readerDelegate.getSnapshotList()
}
override fun addLocalMessage(messageInfoModel: MessageInfoModel) =
writerDelegate.addLocalMessage(messageInfoModel)
@ -31,8 +36,6 @@ internal class MessageRepository private constructor(
override fun editMessage(message: MessageInfoModel) =
writerDelegate.editMessage(message = message)
override fun indexOf(element: MessageInfoModel) = readerDelegate.indexOf(element)
// TODO: We should be using read interface to get last message in list
// This isn't a write message
override fun getLastMessage(): MessageInfoModel? = writerDelegate.getLastMessage()

Просмотреть файл

@ -8,12 +8,15 @@ import java.lang.RuntimeException
internal abstract class MessageRepositoryReader : List<MessageInfoModel> {
// Note:
// While we use List<MessageInfoModel> on the MessageRepository
// List methods we do not need will be stubbed out
// They are defined here so we do not need them
//
// Methods not defined here from List will pass through to the implementor
fun getSnapshotList(): List<MessageInfoModel> {
// This is a inefficient implementation
// but is generic and will work with any backing data
val result = ArrayList<MessageInfoModel>()
for (i in 0 until size) {
result.add(get(i))
}
return result
}
override fun isEmpty(): Boolean {
return size == 0

Просмотреть файл

@ -0,0 +1,30 @@
package com.azure.android.communication.ui.chat.utilities
import com.azure.android.communication.ui.chat.models.MessageInfoModel
// Returns an index of -1 if item can't be found
internal fun List<MessageInfoModel>.findMessageIdxById(messageId: String): Int {
if (messageId.trim() == "") return -1
var first = 0
var last = size - 1
var mid: Int = size / 2
while (first <= last) {
mid = (first + last) / 2
while (this[mid].id == null) {
mid--
if (mid < first) {
throw IllegalArgumentException("Message with id $messageId not found")
}
}
val midVal = this[mid].id!!.toLong()
if (midVal < messageId.toLong()) {
first = mid + 1
} else if (midVal > messageId.toLong()) {
last = mid - 1
} else {
return mid
}
}
return -1
}

Просмотреть файл

@ -12,6 +12,9 @@ import com.azure.android.communication.ui.chat.repository.storage.MessageReposit
import org.junit.Assert
import org.junit.Test
// Todo: Move these 3 tests to the 3 files in Storage package
// or add "verifyImplementation(messageRepo, readerClass, writerClass)" to main test file
// and delegate in the 3 files to verify concrete implementation is correctly chosen
class MessageRepositoryUnitTest {
@Test

Просмотреть файл

@ -3,190 +3,39 @@
package com.azure.android.communication.ui.chat.repository.storage
import com.azure.android.communication.ui.chat.models.MessageInfoModel
import com.azure.android.communication.ui.chat.repository.MessageRepository
import com.azure.android.communication.ui.chat.service.sdk.wrapper.ChatMessageType
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.junit.MockitoJUnitRunner
import org.threeten.bp.OffsetDateTime
import org.threeten.bp.ZoneOffset
import java.util.Collections
@RunWith(MockitoJUnitRunner::class)
internal class MessageRepositoryListStorageUnitTest {
@Test
fun messageRepositoryListStorage_addPage_test() {
val messageRepository = MessageRepository.createListBackedRepository()
val messages = Collections.synchronizedList(mutableListOf<MessageInfoModel>())
val numberOfTestMessages = 51
for (i in 0..50) {
messages.add(
MessageInfoModel(
id = i.toString(),
content = "Message $i",
messageType = ChatMessageType.TEXT
)
)
}
messageRepository.addPage(messages)
Assert.assertEquals(numberOfTestMessages, messageRepository.size)
for (i in 0..50) {
Assert.assertEquals("Message $i", messageRepository[i].content)
}
private fun getMessageRepo(): MessageRepository {
return MessageRepository.createListBackedRepository()
}
@Test
fun messageRepositoryListStorage_removeMessage_test() {
val messageRepository = MessageRepository.createListBackedRepository()
val numberOfTestMessages = 51
for (i in 0..numberOfTestMessages) {
messageRepository.addLocalMessage(
MessageInfoModel(
id = i.toString(),
content = "Message $i",
messageType = ChatMessageType.TEXT
)
)
}
messageRepository.removeMessage(messageRepository.get(0))
Assert.assertEquals(numberOfTestMessages, messageRepository.size)
}
fun messageRepositoryListStorage_addPage_test() =
MessageRepositoryUnitTest.addPageTest(getMessageRepo())
@Test
fun messageRepositoryListStorage_editMessage_test() {
val messageRepository = MessageRepository.createListBackedRepository()
val numberOfTestMessages = 51
for (i in 0..numberOfTestMessages) {
messageRepository.addLocalMessage(
MessageInfoModel(
id = i.toString(),
content = "Message $i",
messageType = ChatMessageType.TEXT
)
)
}
val newMessage = MessageInfoModel(
id = messageRepository.get(0).id,
content = "Edited Message 0",
messageType = messageRepository.get(0).messageType
)
messageRepository.editMessage(newMessage)
Assert.assertEquals("Edited Message 0", messageRepository.get(0).content)
}
fun messageRepositoryListStorage_removeMessage_test() =
MessageRepositoryUnitTest.removeMessageTest(getMessageRepo())
@Test
fun messageRepositoryListStorage_removeMessageTest() {
val storage = MessageRepository.createListBackedRepository()
val numberOfTestMessages = 50
for (i in 1..numberOfTestMessages) {
storage.addLocalMessage(
MessageInfoModel(
id = i.toString(),
content = "Message $i",
messageType = ChatMessageType.TEXT
)
)
}
storage.removeMessage(
MessageInfoModel(
id = "5",
content = "Message $5",
messageType = ChatMessageType.TEXT
)
)
Assert.assertEquals(numberOfTestMessages - 1, storage.size)
}
fun messageRepositoryListStorage_editMessage_test() =
MessageRepositoryUnitTest.editMessageTest(getMessageRepo())
@Test
fun messageRepositoryListStorage_OutOfOrderTest() {
val repository = MessageRepository.createListBackedRepository()
// Add ID 4..7
for (i in 4..7) {
repository.addServerMessage(
MessageInfoModel(
id = "$i",
content = "Message $i",
messageType = ChatMessageType.TEXT,
createdOn = OffsetDateTime.of(2001, 3, 26, i, 0, i, 0, ZoneOffset.ofHours(2))
)
)
}
// Add ID 0 out of Order in middle
repository.addServerMessage(
MessageInfoModel(
id = "0",
content = "Message 0",
messageType = ChatMessageType.TEXT,
createdOn = OffsetDateTime.of(1980, 3, 26, 0, 0, 0, 0, ZoneOffset.ofHours(2))
)
)
// Add IDs [1-3]
for (i in 1..3) {
repository.addServerMessage(
MessageInfoModel(
id = "$i",
content = "Message $i",
messageType = ChatMessageType.TEXT,
createdOn = OffsetDateTime.of(2000, 3, 26, i, 0, 0, 0, ZoneOffset.ofHours(2))
)
)
}
// Expect that first message is ID 0
Assert.assertEquals("0", repository[0].id)
Assert.assertEquals("1", repository[1].id)
Assert.assertEquals("2", repository[2].id)
Assert.assertEquals("3", repository[3].id)
Assert.assertEquals("4", repository[4].id)
Assert.assertEquals("5", repository[5].id)
Assert.assertEquals("6", repository[6].id)
Assert.assertEquals("7", repository[7].id)
}
fun messageRepositoryListStorage_removeMessageTest() =
MessageRepositoryUnitTest.removeMessageTest(getMessageRepo())
@Test
fun messageRepositoryListStorage_indexOfTest() {
val storage = MessageRepository.createListBackedRepository()
fun messageRepositoryListStorage_OutOfOrderTest() =
MessageRepositoryUnitTest.outOfOrderTest(getMessageRepo())
val numberOfTestMessages = 50
for (i in 1..numberOfTestMessages) {
storage.addLocalMessage(
MessageInfoModel(
id = i.toString(),
content = "Message $i",
messageType = ChatMessageType.TEXT
)
)
}
Assert.assertEquals(
1,
storage.indexOf(
MessageInfoModel(
id = "2",
content = "",
messageType = ChatMessageType.TEXT
)
)
)
}
@Test
fun messageRepositoryListStorage_indexOfTest() =
MessageRepositoryUnitTest.indexOfTest(getMessageRepo())
}

Просмотреть файл

@ -3,197 +3,36 @@
package com.azure.android.communication.ui.chat.repository.storage
import com.azure.android.communication.ui.chat.models.MessageInfoModel
import com.azure.android.communication.ui.chat.repository.MessageRepository
import com.azure.android.communication.ui.chat.service.sdk.wrapper.ChatMessageType
import org.junit.Assert
import org.junit.Test
internal class MessageRepositorySkipListStorageUnitTest {
@Test
fun messageRepositorySkipListStorage_addMessage_test() {
val storage = MessageRepository.createSkipListBackedRepository()
val numberOfTestMessages = 170
for (i in 1..numberOfTestMessages) {
storage.addLocalMessage(
MessageInfoModel(
id = i.toString(),
content = "Message $i",
messageType = ChatMessageType.TEXT
)
)
}
Assert.assertEquals(numberOfTestMessages, storage.size)
for (i in 1..numberOfTestMessages) {
Assert.assertEquals("Message $i", storage[i - 1].content)
}
private fun getMessageRepo(): MessageRepository {
return MessageRepository.createSkipListBackedRepository()
}
@Test
fun messageRepositorySkipListStorage_removeMessage_test() {
val storage = MessageRepository.createSkipListBackedRepository()
val numberOfTestMessages = 17
for (i in 1..numberOfTestMessages) {
storage.addLocalMessage(
MessageInfoModel(
id = i.toString(),
content = "Message $i",
messageType = ChatMessageType.TEXT
)
)
}
storage.removeMessage(
MessageInfoModel(
id = "17",
content = "Message 17",
messageType = ChatMessageType.TEXT
)
)
Assert.assertEquals(numberOfTestMessages - 1, storage.size)
Assert.assertEquals(16, storage.getLastMessage()?.id?.toLong() ?: 0)
}
fun messageRepositoryListStorage_addPage_test() =
MessageRepositoryUnitTest.addPageTest(getMessageRepo())
@Test
fun messageRepositorySkipListStorage_getLastMessage_test() {
val storage = MessageRepository.createSkipListBackedRepository()
val numberOfTestMessages = 17
for (i in 1..numberOfTestMessages) {
storage.addLocalMessage(
MessageInfoModel(
id = i.toString(),
content = "Message $i",
messageType = ChatMessageType.TEXT
)
)
}
storage.getLastMessage()?.id?.let { Assert.assertEquals(17, it.toLong()) }
}
fun messageRepositoryListStorage_removeMessage_test() =
MessageRepositoryUnitTest.removeMessageTest(getMessageRepo())
@Test
fun messageRepositorySkipListStorage_editMessage_test() {
val storage = MessageRepository.createSkipListBackedRepository()
val numberOfTestMessages = 17
for (i in 1..numberOfTestMessages) {
storage.addLocalMessage(
MessageInfoModel(
id = i.toString(),
content = "Message $i",
messageType = ChatMessageType.TEXT
)
)
}
storage.editMessage(
MessageInfoModel(
id = "5",
content = "Message 55",
messageType = ChatMessageType.TEXT
)
)
Assert.assertEquals("Message 55", storage[4].content)
}
fun messageRepositoryListStorage_editMessage_test() =
MessageRepositoryUnitTest.editMessageTest(getMessageRepo())
@Test
fun messageRepositorySkipListStorage_addPage_test() {
val storage = MessageRepository.createSkipListBackedRepository()
val numberOfTestMessages = 50
val messageList = mutableListOf<MessageInfoModel>()
for (i in 1..numberOfTestMessages) {
messageList.add(
MessageInfoModel(
id = i.toString(),
content = "Message $i",
messageType = ChatMessageType.TEXT
)
)
}
storage.addPage(messageList)
Assert.assertEquals(numberOfTestMessages, storage.size)
}
fun messageRepositoryListStorage_removeMessageTest() =
MessageRepositoryUnitTest.removeMessageTest(getMessageRepo())
@Test
fun messageRepositorySkipListStorage_PerformanceTest() {
val storage = MessageRepository.createSkipListBackedRepository()
val startTime = System.nanoTime()
// Increase decrease the number of messages to find out the execution time
var numberOfTestMessages = 20000
for (i in 1..numberOfTestMessages) {
storage.addServerMessage(
MessageInfoModel(
id = i.toString(),
content = "Message $i",
messageType = ChatMessageType.TEXT,
topic = "chat",
participants = emptyList(),
senderDisplayName = "display name of sender"
)
)
}
// Edit Messages
for (i in 1..numberOfTestMessages) {
storage.editMessage(
MessageInfoModel(
id = i.toString(),
content = "Message ${i * 2}",
messageType = ChatMessageType.TEXT,
topic = "chat",
participants = emptyList(),
senderDisplayName = "display name of sender"
)
)
}
// delete messages
for (i in 1..numberOfTestMessages) {
storage.removeMessage(storage.get(storage.size - 1))
}
var endTime = System.nanoTime()
var executionTime: Double = (endTime - startTime).toDouble().div(1000000000.0)
println("---------- ExecutionTime ------------")
println("Time: $executionTime")
println("Time: ${endTime - startTime}")
println("---------- ExecutionTime ------------")
Assert.assertEquals(true, startTime < endTime)
}
fun messageRepositoryListStorage_OutOfOrderTest() =
MessageRepositoryUnitTest.outOfOrderTest(getMessageRepo())
@Test
fun messageRepositorySkipListStorage_indexOfTest() {
val storage = MessageRepository.createSkipListBackedRepository()
val numberOfTestMessages = 50
for (i in 1..numberOfTestMessages) {
storage.addLocalMessage(
MessageInfoModel(
id = i.toString(),
content = "Message $i",
messageType = ChatMessageType.TEXT
)
)
}
Assert.assertEquals(
1,
storage.indexOf(
MessageInfoModel(
id = "2",
content = "",
messageType = ChatMessageType.TEXT
)
)
)
}
fun messageRepositoryListStorage_indexOfTest() =
MessageRepositoryUnitTest.indexOfTest(getMessageRepo())
}

Просмотреть файл

@ -3,10 +3,7 @@
package com.azure.android.communication.ui.chat.repository.storage
import com.azure.android.communication.ui.chat.models.MessageInfoModel
import com.azure.android.communication.ui.chat.repository.MessageRepository
import com.azure.android.communication.ui.chat.service.sdk.wrapper.ChatMessageType
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.junit.MockitoJUnitRunner
@ -14,189 +11,31 @@ import org.mockito.junit.MockitoJUnitRunner
@RunWith(MockitoJUnitRunner::class)
class MessageRepositoryTreeStorageUnitTest {
@Test
fun messageRepositoryTreeStorage_addMessage_test() {
val storage = MessageRepository.createTreeBackedRepository()
val numberOfTestMessages = 170
for (i in 1..numberOfTestMessages) {
storage.addLocalMessage(
MessageInfoModel(
id = i.toString(),
content = "Message $i",
messageType = ChatMessageType.TEXT
)
)
}
Assert.assertEquals(numberOfTestMessages, storage.size)
for (i in 1..numberOfTestMessages) {
Assert.assertEquals("Message $i", storage[i - 1].content)
}
private fun getMessageRepo(): MessageRepository {
return MessageRepository.createTreeBackedRepository()
}
@Test
fun messageRepositoryTreeStorage_removeMessage_test() {
val storage = MessageRepository.createTreeBackedRepository()
val numberOfTestMessages = 17
for (i in 1..numberOfTestMessages) {
storage.addLocalMessage(
MessageInfoModel(
id = i.toString(),
content = "Message $i",
messageType = ChatMessageType.TEXT
)
)
}
storage.removeMessage(
MessageInfoModel(
id = "17",
content = "Message 17",
messageType = ChatMessageType.TEXT
)
)
Assert.assertEquals(numberOfTestMessages - 1, storage.size)
Assert.assertEquals(16, storage.getLastMessage()?.id?.toLong() ?: 0)
}
fun messageRepositoryListStorage_addPage_test() =
MessageRepositoryUnitTest.addPageTest(getMessageRepo())
@Test
fun messageRepositoryTreeStorage_getLastMessage_test() {
val storage = MessageRepository.createTreeBackedRepository()
val numberOfTestMessages = 17
for (i in 1..numberOfTestMessages) {
storage.addLocalMessage(
MessageInfoModel(
id = i.toString(),
content = "Message $i",
messageType = ChatMessageType.TEXT
)
)
}
storage.getLastMessage()?.id?.let { Assert.assertEquals(17, it.toLong()) }
}
fun messageRepositoryListStorage_removeMessage_test() =
MessageRepositoryUnitTest.removeMessageTest(getMessageRepo())
@Test
fun messageRepositoryTreeStorage_editMessage_test() {
val storage = MessageRepository.createTreeBackedRepository()
val numberOfTestMessages = 17
for (i in 1..numberOfTestMessages) {
storage.addLocalMessage(
MessageInfoModel(
id = i.toString(),
content = "Message $i",
messageType = ChatMessageType.TEXT
)
)
}
storage.editMessage(
MessageInfoModel(
id = "5",
content = "Message 55",
messageType = ChatMessageType.TEXT
)
)
Assert.assertEquals("Message 55", storage[4].content)
}
fun messageRepositoryListStorage_editMessage_test() =
MessageRepositoryUnitTest.editMessageTest(getMessageRepo())
@Test
fun messageRepositoryTreeStorage_addPage_test() {
val storage = MessageRepository.createTreeBackedRepository()
val numberOfTestMessages = 50
val messageList = mutableListOf<MessageInfoModel>()
for (i in 1..numberOfTestMessages) {
messageList.add(
MessageInfoModel(
id = i.toString(),
content = "Message $i",
messageType = ChatMessageType.TEXT
)
)
}
storage.addPage(messageList)
Assert.assertEquals(numberOfTestMessages, storage.size)
}
fun messageRepositoryListStorage_removeMessageTest() =
MessageRepositoryUnitTest.removeMessageTest(getMessageRepo())
@Test
fun messageRepositoryTreeStorage_PerformanceTest() {
val storage = MessageRepository.createTreeBackedRepository()
val startTime = System.nanoTime()
// Increase decrease the number of messages to find out the execution time
var numberOfTestMessages = 20000
for (i in 1..numberOfTestMessages) {
storage.addServerMessage(
MessageInfoModel(
id = i.toString(),
content = "Message $i",
messageType = ChatMessageType.TEXT,
topic = "chat",
participants = emptyList(),
senderDisplayName = "display name of sender"
)
)
}
// Edit Messages
for (i in 1..numberOfTestMessages) {
storage.editMessage(
MessageInfoModel(
id = i.toString(),
content = "Message ${i * 2}",
messageType = ChatMessageType.TEXT,
topic = "chat",
participants = emptyList(),
senderDisplayName = "display name of sender"
)
)
}
// delete messages
for (i in 1..numberOfTestMessages) {
storage.removeMessage(storage.get(storage.size - 1))
}
var endTime = System.nanoTime()
var executionTime: Double = (endTime - startTime).toDouble().div(1000000000.0)
println("---------- ExecutionTime ------------")
println("Time: $executionTime")
println("Time: ${endTime - startTime}")
println("---------- ExecutionTime ------------")
Assert.assertEquals(true, startTime < endTime)
}
fun messageRepositoryListStorage_OutOfOrderTest() =
MessageRepositoryUnitTest.outOfOrderTest(getMessageRepo())
@Test
fun messageRepositoryTreeStorage_indexOfTest() {
val storage = MessageRepository.createTreeBackedRepository()
val numberOfTestMessages = 50
for (i in 1..numberOfTestMessages) {
storage.addLocalMessage(
MessageInfoModel(
id = i.toString(),
content = "Message $i",
messageType = ChatMessageType.TEXT
)
)
}
Assert.assertEquals(
1,
storage.indexOf(
MessageInfoModel(
id = "2",
content = "",
messageType = ChatMessageType.TEXT
)
)
)
}
fun messageRepositoryListStorage_indexOfTest() =
MessageRepositoryUnitTest.indexOfTest(getMessageRepo())
}

Просмотреть файл

@ -0,0 +1,175 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.android.communication.ui.chat.repository.storage
import com.azure.android.communication.ui.chat.models.MessageInfoModel
import com.azure.android.communication.ui.chat.repository.MessageRepository
import com.azure.android.communication.ui.chat.service.sdk.wrapper.ChatMessageType
import com.azure.android.communication.ui.chat.utilities.findMessageIdxById
import org.junit.Assert
import org.threeten.bp.OffsetDateTime
import org.threeten.bp.ZoneOffset
import java.util.Collections
// Helper to share tests across implementations
internal class MessageRepositoryUnitTest {
companion object {
fun addPageTest(messageRepository: MessageRepository) {
val messages = Collections.synchronizedList(mutableListOf<MessageInfoModel>())
val numberOfTestMessages = 51
for (i in 0..50) {
messages.add(
MessageInfoModel(
id = i.toString(),
content = "Message $i",
messageType = ChatMessageType.TEXT
)
)
}
messageRepository.addPage(messages)
val resultList = messageRepository.getSnapshotList()
Assert.assertEquals(numberOfTestMessages, resultList.size)
for (i in 0..50) {
Assert.assertEquals("Message $i", resultList[i].content)
}
}
fun removeMessageTest(messageRepository: MessageRepository) {
val numberOfTestMessages = 51
for (i in 0..numberOfTestMessages) {
messageRepository.addLocalMessage(
MessageInfoModel(
id = i.toString(),
content = "Message $i",
messageType = ChatMessageType.TEXT
)
)
}
val resultList = messageRepository.getSnapshotList()
messageRepository.removeMessage(resultList[0])
val updatedList = messageRepository.getSnapshotList()
Assert.assertEquals(numberOfTestMessages, updatedList.size)
}
fun editMessageTest(messageRepository: MessageRepository) {
val numberOfTestMessages = 51
for (i in 0..numberOfTestMessages) {
messageRepository.addLocalMessage(
MessageInfoModel(
id = i.toString(),
content = "Message $i",
messageType = ChatMessageType.TEXT
)
)
}
val resultList = messageRepository.getSnapshotList()
val newMessage = MessageInfoModel(
id = resultList[0].id,
content = "Edited Message 0",
messageType = resultList[0].messageType
)
messageRepository.editMessage(newMessage)
val updatedResultList = messageRepository.getSnapshotList()
Assert.assertEquals("Edited Message 0", updatedResultList[0].content)
}
fun messageRepositoryListStorage_removeMessageTest(storage: MessageRepository) {
val numberOfTestMessages = 50
for (i in 1..numberOfTestMessages) {
storage.addLocalMessage(
MessageInfoModel(
id = i.toString(),
content = "Message $i",
messageType = ChatMessageType.TEXT
)
)
}
storage.removeMessage(
MessageInfoModel(
id = "5",
content = "Message $5",
messageType = ChatMessageType.TEXT
)
)
val resultList = storage.getSnapshotList()
Assert.assertEquals(numberOfTestMessages - 1, resultList.size)
}
fun outOfOrderTest(repository: MessageRepository) {
// Add ID 4..7
for (i in 4..7) {
repository.addServerMessage(
MessageInfoModel(
id = "$i",
content = "Message $i",
messageType = ChatMessageType.TEXT,
createdOn = OffsetDateTime.of(2001, 3, 26, i, 0, i, 0, ZoneOffset.ofHours(2))
)
)
}
// Add ID 0 out of Order in middle
repository.addServerMessage(
MessageInfoModel(
id = "0",
content = "Message 0",
messageType = ChatMessageType.TEXT,
createdOn = OffsetDateTime.of(1980, 3, 26, 0, 0, 0, 0, ZoneOffset.ofHours(2))
)
)
// Add IDs [1-3]
for (i in 1..3) {
repository.addServerMessage(
MessageInfoModel(
id = "$i",
content = "Message $i",
messageType = ChatMessageType.TEXT,
createdOn = OffsetDateTime.of(2000, 3, 26, i, 0, 0, 0, ZoneOffset.ofHours(2))
)
)
}
val resultList = repository.getSnapshotList()
// Expect that first message is ID 0
Assert.assertEquals("0", resultList[0].id)
Assert.assertEquals("1", resultList[1].id)
Assert.assertEquals("2", resultList[2].id)
Assert.assertEquals("3", resultList[3].id)
Assert.assertEquals("4", resultList[4].id)
Assert.assertEquals("5", resultList[5].id)
Assert.assertEquals("6", resultList[6].id)
Assert.assertEquals("7", resultList[7].id)
}
fun indexOfTest(storage: MessageRepository) {
val numberOfTestMessages = 50
for (i in 1..numberOfTestMessages) {
storage.addLocalMessage(
MessageInfoModel(
id = i.toString(),
content = "Message $i",
messageType = ChatMessageType.TEXT
)
)
}
val resultList = storage.getSnapshotList()
Assert.assertEquals(
1,
resultList.findMessageIdxById("2")
)
}
}
}