fix(chat): ensure messages ordering on model append/prepend

fixes: #8466
This commit is contained in:
Patryk Osmaczko 2022-12-19 19:23:46 +01:00 committed by osmaczko
parent 3acf7dbebf
commit 7ede3389ff
2 changed files with 171 additions and 40 deletions

View File

@ -84,7 +84,7 @@ QtObject:
read = getCount read = getCount
notify = countChanged notify = countChanged
method rowCount(self: Model, index: QModelIndex = nil): int = method rowCount*(self: Model, index: QModelIndex = nil): int =
return self.items.len return self.items.len
method roleNames(self: Model): Table[int, string] = method roleNames(self: Model): Table[int, string] =
@ -252,45 +252,47 @@ QtObject:
elif clock == self.items[i].clock: # break ties by message id elif clock == self.items[i].clock: # break ties by message id
if id > self.items[i].id: if id > self.items[i].id:
return i return i
return 0 return self.items.len
proc filterExistingItems(self: Model, items: seq[Item]): seq[Item] = proc filterExistingItems(self: Model, items: seq[Item]): seq[Item] =
for item in items: for item in items:
if(self.findIndexForMessageId(item.id) < 0): if(self.findIndexForMessageId(item.id) < 0):
result &= item result &= item
proc prependItems*(self: Model, items: seq[Item]) = proc insertItemBasedOnClock*(self: Model, item: Item) =
let itemsToAppend = self.filterExistingItems(items) if(self.findIndexForMessageId(item.id) != -1):
if(itemsToAppend.len == 0):
return return
let parentModelIndex = newQModelIndex() let parentModelIndex = newQModelIndex()
defer: parentModelIndex.delete defer: parentModelIndex.delete
let first = 0 let position = self.findIndexBasedOnClockToInsertTo(item.clock, item.id)
let last = itemsToAppend.len - 1
self.beginInsertRows(parentModelIndex, first, last) self.beginInsertRows(parentModelIndex, position, position)
self.items = itemsToAppend & self.items self.items.insert(item, position)
self.endInsertRows() self.endInsertRows()
if position > 0:
self.updateItemAtIndex(position - 1)
if position + 1 < self.items.len:
self.updateItemAtIndex(position + 1)
self.countChanged() self.countChanged()
proc prependItems*(self: Model, items: seq[Item]) =
let itemsToAppend = self.filterExistingItems(items)
if(itemsToAppend.len == 0):
return
for item in items:
self.insertItemBasedOnClock(item)
proc appendItems*(self: Model, items: seq[Item]) = proc appendItems*(self: Model, items: seq[Item]) =
let itemsToAppend = self.filterExistingItems(items) let itemsToAppend = self.filterExistingItems(items)
if(itemsToAppend.len == 0): if(itemsToAppend.len == 0):
return return
let parentModelIndex = newQModelIndex() for item in items:
defer: parentModelIndex.delete self.insertItemBasedOnClock(item)
let first = self.items.len
let last = first + itemsToAppend.len - 1
self.beginInsertRows(parentModelIndex, first, last)
self.items.add(itemsToAppend)
self.endInsertRows()
if first > 0:
self.updateItemAtIndex(first - 1)
self.countChanged()
proc appendItem*(self: Model, item: Item) = proc appendItem*(self: Model, item: Item) =
if(self.findIndexForMessageId(item.id) != -1): if(self.findIndexForMessageId(item.id) != -1):
@ -324,25 +326,6 @@ QtObject:
self.updateItemAtIndex(1) self.updateItemAtIndex(1)
self.countChanged() self.countChanged()
proc insertItemBasedOnClock*(self: Model, item: Item) =
if(self.findIndexForMessageId(item.id) != -1):
return
let parentModelIndex = newQModelIndex()
defer: parentModelIndex.delete
let position = self.findIndexBasedOnClockToInsertTo(item.clock, item.id)
self.beginInsertRows(parentModelIndex, position, position)
self.items.insert(item, position)
self.endInsertRows()
if position > 0:
self.updateItemAtIndex(position - 1)
if position + 1 < self.items.len:
self.updateItemAtIndex(position + 1)
self.countChanged()
proc replyDeleted*(self: Model, messageIndex: int) {.signal.} proc replyDeleted*(self: Model, messageIndex: int) {.signal.}
proc updateMessagesWithResponseTo(self: Model, messageId: string) = proc updateMessagesWithResponseTo(self: Model, messageId: string) =

View File

@ -0,0 +1,148 @@
import unittest
import ../../../src/app_service/common/types
import ../../../src/app_service/service/contacts/dto/contacts
import ../../../src/app_service/service/message/dto/message
import ../../../src/app/modules/shared_models/message_model
import ../../../src/app/modules/shared_models/message_item
import ../../../src/app/modules/shared_models/message_transaction_parameters_item
proc createTestMessageItem(id: string, clock: int64): Item =
return initItem(
id = id,
communityId = "",
responseToMessageWithId = "",
senderId = "",
senderDisplayName = "",
senderOptionalName = "",
senderIcon = "",
amISender = false,
senderIsAdded = false,
outgoingStatus = "",
text = "",
image = "",
messageContainsMentions = false,
seen = true,
timestamp = 0,
clock = clock,
ContentType.NewMessagesMarker,
messageType = -1,
contactRequestState = 0,
sticker = "",
stickerPack = -1,
links = @[],
transactionParameters = newTransactionParametersItem("","","","","","",-1,""),
mentionedUsersPks = @[],
senderTrustStatus = TrustStatus.Unknown,
senderEnsVerified = false,
discordMessage = DiscordMessage(),
resendError = ""
)
let message1 = createTestMessageItem("0xa", 1)
let message2 = createTestMessageItem("0xb", 2)
let message3 = createTestMessageItem("0xc", 3)
let message4 = createTestMessageItem("0xd", 3)
let message5 = createTestMessageItem("0xe", 4)
template checkOrder(model: Model) =
require(model.items.len == 5)
check(model.items[0].id == message5.id)
check(model.items[1].id == message4.id)
check(model.items[2].id == message3.id)
check(model.items[3].id == message2.id)
check(model.items[4].id == message1.id)
suite "empty model":
let model = newModel()
test "initial size":
require(model.rowCount() == 0)
# newest messages should be first, break ties by message id
suite "inserting new messages":
setup:
let model = newModel()
test "insert same message twice":
model.insertItemBasedOnClock(message1)
check(model.rowCount() == 1)
model.insertItemBasedOnClock(message1)
check(model.rowCount() == 1)
test "insert in order":
model.insertItemBasedOnClock(message1)
check(model.rowCount() == 1)
model.insertItemBasedOnClock(message2)
check(model.rowCount() == 2)
model.insertItemBasedOnClock(message3)
check(model.rowCount() == 3)
model.insertItemBasedOnClock(message4)
check(model.rowCount() == 4)
model.insertItemBasedOnClock(message5)
check(model.rowCount() == 5)
checkOrder(model)
test "insert out of order":
model.insertItemBasedOnClock(message5)
check(model.rowCount() == 1)
model.insertItemBasedOnClock(message4)
check(model.rowCount() == 2)
model.insertItemBasedOnClock(message3)
check(model.rowCount() == 3)
model.insertItemBasedOnClock(message2)
check(model.rowCount() == 4)
model.insertItemBasedOnClock(message1)
check(model.rowCount() == 5)
checkOrder(model)
test "insert out of order (randomly)":
model.insertItemBasedOnClock(message3)
check(model.rowCount() == 1)
model.insertItemBasedOnClock(message1)
check(model.rowCount() == 2)
model.insertItemBasedOnClock(message4)
check(model.rowCount() == 3)
model.insertItemBasedOnClock(message2)
check(model.rowCount() == 4)
model.insertItemBasedOnClock(message5)
check(model.rowCount() == 5)
checkOrder(model)
# assumption: each append sequence is already sorted
suite "appending new messages":
setup:
let model = newModel()
test "append empty model":
model.appendItems(@[message5,
message4,
message3,
message2,
message1])
checkOrder(model)
test "append to model with only newer messages":
model.insertItemBasedOnClock(message5)
model.insertItemBasedOnClock(message4)
model.appendItems(@[message3,
message2,
message1])
checkOrder(model)
test "append to model with newer and older messages":
model.insertItemBasedOnClock(message5)
model.insertItemBasedOnClock(message1)
model.appendItems(@[message4,
message3,
message2])
checkOrder(model)
test "append to model with newer and older messages and some in between":
model.insertItemBasedOnClock(message5)
model.insertItemBasedOnClock(message1)
model.insertItemBasedOnClock(message3) # in between
model.appendItems(@[message4,
message2])
checkOrder(model)