[Chat][Refactor] Updates to scroll message repo (#617)
This commit is contained in:
Родитель
4d8c044c9c
Коммит
82d05831ff
|
@ -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")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче