diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test_mainwindow.cpp | 52 | ||||
| -rw-r--r-- | tests/test_threadlistmodel.cpp | 188 |
2 files changed, 186 insertions, 54 deletions
diff --git a/tests/test_mainwindow.cpp b/tests/test_mainwindow.cpp index cba4c96..aecefb7 100644 --- a/tests/test_mainwindow.cpp +++ b/tests/test_mainwindow.cpp @@ -1062,7 +1062,10 @@ void TestMainWindow::childRowsAreIndentedUnderTheirThread() // visualRect reported an indent the text did not have; here it reports // none while the text is indented. const QModelIndex rootCell = model->index(0, 0, QModelIndex()); - const QModelIndex child = model->index(0, 0, root); + // Child 1, not child 0: since item 177 a conversation lists its FIRST + // message as a child too, so child 0 is m0 (depth 0) and the reply whose + // nesting is being measured is the one after it. + const QModelIndex child = model->index(1, 0, root); QVERIFY(child.isValid()); // Guards before the claim: a probe that cannot see both rows can report @@ -1635,7 +1638,10 @@ void TestMainWindow::anActionOnAMessageRowTagsThatMessageNotTheThread() view->expand(threadRow); QApplication::processEvents(); - const QModelIndex messageRow = model->index(0, 0, threadRow); + // Child 1, not child 0: since item 177 a conversation lists its first + // message as a child too, so child 0 is m0 and the reply this test acts on + // is the one after the card's own message. + const QModelIndex messageRow = model->index(1, 0, threadRow); QVERIFY(model->isMessageRow(messageRow)); view->selectionModel()->select( @@ -3227,7 +3233,10 @@ void TestMainWindow::theStaleNoticeCarriesTheMessageBeingRead() model->setThreadMessages(QStringLiteral("T1"), { root, reply }); const QModelIndex threadIndex = model->index(0, 0, QModelIndex()); - const QModelIndex replyIndex = model->index(0, 0, threadIndex); + // Child 1, not child 0: since item 177 a conversation lists its first + // message as a child too, so the reply being read is the one after the + // card's own message. + const QModelIndex replyIndex = model->index(1, 0, threadIndex); QVERIFY(replyIndex.isValid()); view->setCurrentIndex(replyIndex); @@ -4008,9 +4017,12 @@ void TestMainWindow::recoveryFromAnExpandedThreadRestoresTheReply() const QModelIndex threadIndex = model->index(0, 0, QModelIndex()); view->expand(threadIndex); - QCOMPARE(model->rowCount(threadIndex), 3); + // All four messages are children since item 177: a conversation lists its + // first message under itself like every other. + QCOMPARE(model->rowCount(threadIndex), 4); - const QModelIndex fourth = model->index(2, 0, threadIndex); + // The fourth message, now at child index 3 behind the first three. + const QModelIndex fourth = model->index(3, 0, threadIndex); QVERIFY(fourth.isValid()); QCOMPARE(model->messageAt(fourth).messageId, QStringLiteral("m3@example.org")); @@ -4045,7 +4057,7 @@ void TestMainWindow::recoveryFromAnExpandedThreadRestoresTheReply() QVERIFY2(view->isExpanded(back), "the thread collapsed again once its replies arrived"); - QCOMPARE(model->rowCount(back), 3); + QCOMPARE(model->rowCount(back), 4); const QModelIndex current = view->currentIndex(); QVERIFY2(current.isValid(), "recovery left nothing selected"); @@ -4917,13 +4929,20 @@ static QModelIndex expandSecondThreadAndSelectItsReply( const QModelIndex threadRow = model->index(1, 0, QModelIndex()); view->expand(threadRow); - const QModelIndex replyRow = model->index(0, 0, threadRow); + // Child 1, not child 0. Since item 177 a conversation lists its FIRST + // message as a child too, so child 0 is m0 and the reply this helper + // promises is the one after it. Every caller is about a reply + // specifically, and handing them the root message instead makes each one + // assert about the wrong message while still looking correct. + const QModelIndex replyRow = model->index(1, 0, threadRow); if (!replyRow.isValid() || !model->isMessageRow(replyRow)) return {}; - // Row 0 under its parent, which is the trap: the number is a plausible - // top-level row and threadAt() cannot tell the difference. - if (replyRow.row() != 0) + // A child row number that is also a plausible top-level one, which is the + // trap: threadAt() cannot tell the difference. Child 1 sits under the + // thread at top-level row 1, so threadAt() reading it still lands on the + // wrong object, which is what these tests exist to catch. + if (replyRow.row() != 1) return {}; view->selectionModel()->select( @@ -5831,8 +5850,10 @@ void TestMainWindow::taggingAnUnrelatedReplyLeavesTheStripAlone() const QModelIndex threadRow = model->index(0, 0, QModelIndex()); view->expand(threadRow); - // The FIRST reply is the one on display. - const QModelIndex displayed = model->index(0, 0, threadRow); + // The FIRST reply is the one on display. Child 1, not child 0: since item + // 177 a conversation lists its first message as a child too, so child 0 is + // m0 (the card's own message) and the reply the strip describes is m1. + const QModelIndex displayed = model->index(1, 0, threadRow); view->selectionModel()->select( displayed, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); @@ -12268,9 +12289,12 @@ void TestMainWindow::deleteOnAReplyMovesThatReplyOnly() QVERIFY2(conversation.isValid(), "no multi-message thread in the list"); view->expand(conversation); - QTRY_VERIFY_WITH_TIMEOUT(model->rowCount(conversation) == 1, 15000); + // Both messages are children since item 177: the conversation lists its + // first message under itself too. + QTRY_VERIFY_WITH_TIMEOUT(model->rowCount(conversation) == 2, 15000); - const QModelIndex replyIndex = model->index(0, 0, conversation); + // The reply is child 1, behind the thread's first message. + const QModelIndex replyIndex = model->index(1, 0, conversation); QVERIFY(model->isMessageRow(replyIndex)); QCOMPARE(model->messageAt(replyIndex).messageId, QStringLiteral("reply@example.org")); diff --git a/tests/test_threadlistmodel.cpp b/tests/test_threadlistmodel.cpp index dbff6b7..fba2d2d 100644 --- a/tests/test_threadlistmodel.cpp +++ b/tests/test_threadlistmodel.cpp @@ -106,6 +106,8 @@ private slots: void aRowNamingNoMessageIsSkippedNotEscalated(); void aLoneMessageRowResolvesToItsMessage(); void aReplyRowResolvesToItsMessage(); + void aConversationListsItsFirstMessageAsAChild(); + void aMessageRowDoesNotListItselfBeneathItself(); void aMixedSelectionCarriesBothScopes(); }; @@ -142,19 +144,20 @@ void TestThreadListModel::repliesBecomeChildRowsUnderTheirThread() model.appendBatch({ makeThread(QStringLiteral("t1"), QStringLiteral("A subject")) }); - // Depth 0 is the thread's FIRST message and belongs on the root row, not in - // the children: the user's model is "N replies", so a thread of three shows - // one root and two children. + // EVERY message sits under a conversation row, the first included. This + // read "one root and two children" until item 177, when the root stopped + // meaning the first message and started meaning the conversation; the + // first message then had no row anywhere and the user could not reach it. model.setThreadMessages(QStringLiteral("t1"), { makeNode(QStringLiteral("m0@example.org"), 0), makeNode(QStringLiteral("m1@example.org"), 1), makeNode(QStringLiteral("m2@example.org"), 2) }); const QModelIndex root = model.index(0, 0, QModelIndex()); - QCOMPARE(model.rowCount(root), 2); + QCOMPARE(model.rowCount(root), 3); const QModelIndex child = - model.index(0, 0, root); + model.index(1, 0, root); QVERIFY(child.isValid()); QCOMPARE(model.parent(child), model.index(0, 0, QModelIndex())); @@ -196,7 +199,7 @@ void TestThreadListModel::messageRowsShowTheirOwnSenderAndSubject() QStringLiteral("Re: A subject")) }); const QModelIndex root = model.index(0, 0, QModelIndex()); - const QModelIndex reply = model.index(0, 0, root); + const QModelIndex reply = model.index(1, 0, root); QCOMPARE(model.data(reply, ThreadListModel::SendersRole).toString(), QStringLiteral("Bob <bob@example.org>")); @@ -222,12 +225,15 @@ void TestThreadListModel::reloadingAThreadReplacesItsRepliesRatherThanRepeatingT makeNode(QStringLiteral("m1@example.org"), 1) }; + // Two messages, two rows, both times. The number is what item 177 changed; + // the property under test is that a reload REPLACES rather than appends, so + // what matters is that the second call does not double it. model.setThreadMessages(QStringLiteral("t1"), nodes); const QModelIndex root = model.index(0, 0, QModelIndex()); - QCOMPARE(model.rowCount(root), 1); + QCOMPARE(model.rowCount(root), 2); model.setThreadMessages(QStringLiteral("t1"), nodes); - QCOMPARE(model.rowCount(root), 1); + QCOMPARE(model.rowCount(root), 2); QAbstractItemModelTester tester( &model, QAbstractItemModelTester::FailureReportingMode::Warning); @@ -474,7 +480,7 @@ void TestThreadListModel::aMessageRowCarriesItsOwnSenderAndAddress() model.setThreadMessages(QStringLiteral("t1"), { root, reply }); const QModelIndex replyIndex = - model.index(0, 0, model.index(0, 0, QModelIndex())); + model.index(1, 0, model.index(0, 0, QModelIndex())); QVERIFY(model.isMessageRow(replyIndex)); QCOMPARE(replyIndex.data(ThreadListModel::SenderAddressRole).toString(), @@ -882,7 +888,7 @@ void TestThreadListModel::invalidIndexesReturnNothing() QVERIFY(!model.index(-1, 0).isValid()); // A child index must yield nothing: this is a table, not a tree. - const QModelIndex child = model.index(0, 0, model.index(0, 0)); + const QModelIndex child = model.index(1, 0, model.index(0, 0)); QVERIFY(!child.isValid()); QVERIFY(!model.data(child, Qt::DisplayRole).isValid()); } @@ -900,18 +906,25 @@ void TestThreadListModel::threadAtOutOfRangeIsSafe() void TestThreadListModel::threadForResolvesAReplyThroughItsParent() { // Item 88. threadAt() takes a top-level row and a tree numbers rows per - // parent, so the first reply of ANY thread has row() == 0 and threadAt(0) - // answers "t1" for a reply of t2. threadFor() resolves through the parent - // instead, which is what every caller holding an index needs. + // parent, so a child's row() indexes its siblings and threadAt() reads it + // as a position in the top-level list. threadFor() resolves through the + // parent instead, which is what every caller holding an index needs. + // + // THREE threads, and the message taken from the LAST one. The child row + // and the thread's own row have to differ, or threadAt() returns the right + // answer by coincidence and the test proves nothing. With the conversation + // at top-level row 2 and its message at child row 0, threadAt(0) names the + // first thread while the message belongs to the third. ThreadListModel model; model.appendBatch({ makeThread(QStringLiteral("t1"), QStringLiteral("one")), - makeThread(QStringLiteral("t2"), QStringLiteral("two")) }); - model.setThreadMessages(QStringLiteral("t2"), + makeThread(QStringLiteral("t2"), QStringLiteral("two")), + makeThread(QStringLiteral("t3"), QStringLiteral("three")) }); + model.setThreadMessages(QStringLiteral("t3"), { makeNode(QStringLiteral("m0@example.org"), 0), makeNode(QStringLiteral("m1@example.org"), 1) }); - const QModelIndex second = model.index(1, 0, QModelIndex()); - QCOMPARE(model.threadFor(second).threadId, QStringLiteral("t2")); + const QModelIndex second = model.index(2, 0, QModelIndex()); + QCOMPARE(model.threadFor(second).threadId, QStringLiteral("t3")); const QModelIndex reply = model.index(0, 0, second); QVERIFY(reply.isValid()); @@ -919,11 +932,16 @@ void TestThreadListModel::threadForResolvesAReplyThroughItsParent() "the fixture did not produce a message row"); QCOMPARE(reply.row(), 0); // The trap: a plausible top-level row number. - QCOMPARE(model.threadFor(reply).threadId, QStringLiteral("t2")); + QCOMPARE(model.threadFor(reply).threadId, QStringLiteral("t3")); // And the row-taking overload still does the wrong thing for that index, // which is why it is documented as unsafe rather than merely deprecated. + // threadAt() reads a top-level row number and this index carries a child's. QCOMPARE(model.threadAt(reply.row()).threadId, QStringLiteral("t1")); + QVERIFY2(model.threadAt(reply.row()).threadId + != model.threadFor(reply).threadId, + "threadAt() and threadFor() agree for this index, so the test no " + "longer demonstrates why the row-taking overload is unsafe"); // An invalid index gives an empty summary, which every caller treats as // "nothing to do" rather than acting on row 0. @@ -946,7 +964,7 @@ void TestThreadListModel::applyMessageTagChangeRepaintsThatReplyAlone() model.setThreadMessages(QStringLiteral("t1"), { root, reply }); const QModelIndex threadIndex = model.index(0, 0, QModelIndex()); - const QModelIndex replyIndex = model.index(0, 0, threadIndex); + const QModelIndex replyIndex = model.index(1, 0, threadIndex); QVERIFY(model.isMessageRow(replyIndex)); const QStringList threadTagsBefore = @@ -971,7 +989,7 @@ void TestThreadListModel::applyMessageTagChangeRepaintsThatReplyAlone() // the missing update was avoiding, and it must stay avoided. QCOMPARE(model.data(threadIndex, ThreadListModel::TagsRole).toStringList(), threadTagsBefore); - QCOMPARE(model.messageAt(model.index(0, 0, threadIndex)).messageId, + QCOMPARE(model.messageAt(model.index(1, 0, threadIndex)).messageId, QStringLiteral("m1@example.org")); // An unknown message is a no-op rather than a wrong row repainted. @@ -995,7 +1013,7 @@ void TestThreadListModel::aDeletedReplyIsPaintedAsDoomed() model.setThreadMessages(QStringLiteral("t1"), { root, reply }); const QModelIndex threadIndex = model.index(0, 0, QModelIndex()); - const QModelIndex replyIndex = model.index(0, 0, threadIndex); + const QModelIndex replyIndex = model.index(1, 0, threadIndex); const QVariant plainBackground = model.data(replyIndex, Qt::BackgroundRole); @@ -1033,7 +1051,7 @@ void TestThreadListModel::aDeletedReplyIsStruckThrough() makeNode(QStringLiteral("m1@example.org"), 1) }); const QModelIndex replyIndex = - model.index(0, 0, model.index(0, 0, QModelIndex())); + model.index(1, 0, model.index(0, 0, QModelIndex())); QVERIFY(!model.data(replyIndex, Qt::FontRole).value<QFont>().strikeOut()); @@ -1069,7 +1087,7 @@ void TestThreadListModel::markingAReplyReadChangesItsForeground() model.setThreadMessages(QStringLiteral("t1"), { root, reply }); const QModelIndex replyIndex = - model.index(0, 0, model.index(0, 0, QModelIndex())); + model.index(1, 0, model.index(0, 0, QModelIndex())); const QVariant unreadForeground = model.data(replyIndex, Qt::ForegroundRole); @@ -1109,7 +1127,7 @@ void TestThreadListModel::anUnreadReplyIsBoldAndStillSmallerThanItsThread() model.setThreadMessages(QStringLiteral("t1"), { root, reply }); const QModelIndex threadIndex = model.index(0, 0, QModelIndex()); - const QModelIndex replyIndex = model.index(0, 0, threadIndex); + const QModelIndex replyIndex = model.index(1, 0, threadIndex); const QFont unreadFont = model.data(replyIndex, Qt::FontRole).value<QFont>(); @@ -1152,7 +1170,7 @@ void TestThreadListModel::aThreadTagChangeReachesItsLoadedReplies() model.setThreadMessages(QStringLiteral("t1"), { root, reply }); const QModelIndex threadIndex = model.index(0, 0, QModelIndex()); - const QModelIndex replyIndex = model.index(0, 0, threadIndex); + const QModelIndex replyIndex = model.index(1, 0, threadIndex); QVERIFY(model.messageAt(replyIndex).isUnread()); QSignalSpy spy(&model, &QAbstractItemModel::dataChanged); @@ -1491,17 +1509,23 @@ void TestThreadListModel::aFlatThreadStillListsItsReplies() const QModelIndex root = model.index(0, 0); - // Two children, not zero: the FIRST message is the root card itself, and - // the rest are its replies however flat the thread is. - QCOMPARE(model.rowCount(root), 2); + // Three children, not zero: a flat thread's messages all arrive at depth 0, + // and selecting on depth would list none of them. Position is the rule, and + // since item 177 it starts at the first message rather than after it. + QCOMPARE(model.rowCount(root), 3); QCOMPARE(model.index(0, 0, root).data(ThreadListModel::MessageIdRole) .toString(), + QStringLiteral("m0@example.org")); + QCOMPARE(model.index(1, 0, root).data(ThreadListModel::MessageIdRole) + .toString(), QStringLiteral("m1@example.org")); - // And the count the card advertises must agree with the rows beneath it, - // or the expander opens onto nothing. + // The pill counts REPLIES while the rows are MESSAGES, so since item 177 + // the rows are one more than the pill: the conversation lists its first + // message too. They must still move together, or the expander opens onto a + // number the card never promised. QCOMPARE(root.data(ThreadListModel::ReplyCountRole).toInt(), - model.rowCount(root)); + model.rowCount(root) - 1); } void TestThreadListModel::theRootCardKnowsItsOwnMessage() @@ -1529,10 +1553,15 @@ void TestThreadListModel::theRootCardKnowsItsOwnMessage() QCOMPARE(root.data(ThreadListModel::MessageIdRole).toString(), QStringLiteral("m0@example.org")); - // And it is the FIRST message, not just any of them: the reply must still - // report its own. + // The card still knows its own message, which the dashboard's heading and + // the thread-scoped actions read. Child 0 is now that same first message + // rather than the first reply: since item 177 the conversation lists every + // message, so the row under the card is m0 and m1 follows it. QCOMPARE(model.index(0, 0, root).data(ThreadListModel::MessageIdRole) .toString(), + QStringLiteral("m0@example.org")); + QCOMPARE(model.index(1, 0, root).data(ThreadListModel::MessageIdRole) + .toString(), QStringLiteral("m1@example.org")); } @@ -1565,7 +1594,7 @@ void TestThreadListModel::replyShowsOnlyItsOwnTags() const QModelIndex threadIndex = model.index(0, 0); QVERIFY(model.hasChildren(threadIndex)); - const QModelIndex replyIndex = model.index(0, 0, threadIndex); + const QModelIndex replyIndex = model.index(1, 0, threadIndex); QVERIFY(replyIndex.isValid()); const QStringList own = @@ -1601,7 +1630,7 @@ void TestThreadListModel::replySharingEveryThreadTagShowsNone() root.depth = 0; model.setThreadMessages(QStringLiteral("T1"), { root, reply }); - const QModelIndex replyIndex = model.index(0, 0, model.index(0, 0)); + const QModelIndex replyIndex = model.index(1, 0, model.index(0, 0)); QVERIFY(replyIndex.isValid()); QVERIFY(replyIndex.data(ThreadListModel::MessageOwnTagsRole) .toStringList() @@ -1677,7 +1706,7 @@ void TestThreadListModel::reconcileKeepsSurvivingRowsAndTheirExpansion() MessageNode root = makeNode(QStringLiteral("m1"), 0); MessageNode reply = makeNode(QStringLiteral("m2"), 1); model.setThreadMessages(QStringLiteral("t1"), { root, reply }); - QCOMPARE(model.rowCount(model.index(0, 0)), 1); + QCOMPARE(model.rowCount(model.index(0, 0)), 2); const QPersistentModelIndex survivor(model.index(0, 0)); QVERIFY(survivor.isValid()); @@ -1693,8 +1722,10 @@ void TestThreadListModel::reconcileKeepsSurvivingRowsAndTheirExpansion() "the open thread would be lost exactly as a reset loses them"); QCOMPARE(model.threadAt(survivor.row()).threadId, QStringLiteral("t1")); - // Its loaded replies survive too, or the thread collapses under the reader. - QCOMPARE(model.rowCount(model.index(survivor.row(), 0)), 1); + // Its loaded messages survive too, or the thread collapses under the + // reader. Two of them since item 177: a conversation lists its first + // message as well as its replies. + QCOMPARE(model.rowCount(model.index(survivor.row(), 0)), 2); } void TestThreadListModel::reconcileUpdatesTagsOnASurvivingThread() @@ -2099,6 +2130,81 @@ void TestThreadListModel::aLoadedThreadTrustsItsChildrenOverItsCount() "conversation after loading no replies at all"); } +void TestThreadListModel::aConversationListsItsFirstMessageAsAChild() +{ + // The user's report: "I can't reach the first message in a thread. All + // messages were supposed to sit below the new thread row. They start + // from #2." + // + // setThreadMessages() dropped nodes.first() until item 177, and that was + // correct while a thread row MEANT its first message: listing the message + // under itself would have shown it twice. Item 177 made the row stand for + // the conversation and render a dashboard instead of any one message, so + // the drop left the first message with no row anywhere in the interface. + // Unreachable, not merely unlabelled: expanding was the only route to it. + ThreadListModel model; + ThreadSummary conversation = + makeThread(QStringLiteral("t1"), QStringLiteral("A subject")); + conversation.totalCount = 3; + conversation.firstMessageId = QStringLiteral("m0@example.org"); + model.appendBatch({ conversation }); + + model.setThreadMessages( + QStringLiteral("t1"), + { makeNode(QStringLiteral("m0@example.org"), 0), + makeNode(QStringLiteral("m1@example.org"), 1), + makeNode(QStringLiteral("m2@example.org"), 2) }); + + const QModelIndex root = model.index(0, 0, QModelIndex()); + QVERIFY2(model.isConversationRow(root), + "the fixture is not a conversation, so this test says nothing " + "about the case the defect lives in"); + + // Three messages, three rows. Counting is not enough on its own: a row + // could be added anywhere, so the first child is named explicitly. + QCOMPARE(model.rowCount(root), 3); + QCOMPARE(model.data(model.index(0, 0, root), + ThreadListModel::MessageIdRole).toString(), + QStringLiteral("m0@example.org")); + QCOMPARE(model.data(model.index(2, 0, root), + ThreadListModel::MessageIdRole).toString(), + QStringLiteral("m2@example.org")); + + // And it is reachable as a MESSAGE row, which is what makes it selectable + // and actionable rather than merely drawn. + const QModelIndex first = model.index(0, 0, root); + QVERIFY(model.isMessageRow(first)); + QCOMPARE(model.messageAt(first).messageId, QStringLiteral("m0@example.org")); +} + +void TestThreadListModel::aMessageRowDoesNotListItselfBeneathItself() +{ + // The other half, and the reason the drop existed. A thread of one is + // still a MESSAGE row under item 177: its card renders that message, so a + // child carrying the same message would show it twice, once as the card + // and once beneath it. + // + // Decided on the messages that ARRIVED rather than on summary.totalCount, + // which counts duplicates: a "thread of 2" that loads one message must + // take this branch, or it gains a child and then claims through + // isConversationRow() to be a conversation it cannot open. + ThreadListModel model; + ThreadSummary lone = + makeThread(QStringLiteral("t1"), QStringLiteral("Alone")); + lone.totalCount = 2; // notmuch counted a duplicate; one message arrives. + lone.firstMessageId = QStringLiteral("m0@example.org"); + model.appendBatch({ lone }); + + model.setThreadMessages(QStringLiteral("t1"), + { makeNode(QStringLiteral("m0@example.org"), 0) }); + + const QModelIndex root = model.index(0, 0, QModelIndex()); + QCOMPARE(model.rowCount(root), 0); + QVERIFY2(!model.isConversationRow(root), + "a thread that loaded one message claims to be a conversation, so " + "it offers an expander onto nothing"); +} + void TestThreadListModel::aMessageRowIsNeverAConversationRow() { ThreadListModel model; @@ -2118,7 +2224,7 @@ void TestThreadListModel::aMessageRowIsNeverAConversationRow() model.setThreadMessages(QStringLiteral("t1"), { root, reply }); const QModelIndex thread = model.index(0, 0, QModelIndex()); - const QModelIndex replyRow = model.index(0, 0, thread); + const QModelIndex replyRow = model.index(1, 0, thread); QVERIFY(model.isMessageRow(replyRow)); QVERIFY2(!model.isConversationRow(replyRow), "a reply row answered yes, so an action on it would scope to the " @@ -2220,8 +2326,10 @@ void TestThreadListModel::aReplyRowResolvesToItsMessage() model.setThreadMessages(QStringLiteral("t2"), { root, reply }); const QModelIndex thread = model.index(1, 0, QModelIndex()); + // Child 1, not child 0: since item 177 a conversation lists its first + // message too, so child 0 is m1 and the REPLY this test is about is m2. const ActionScope scope = - model.scopeForSelection({ model.index(0, 0, thread) }); + model.scopeForSelection({ model.index(1, 0, thread) }); QCOMPARE(scope.messageIds, QStringList{ QStringLiteral("m2") }); QVERIFY(scope.threadIds.isEmpty()); |
