From df5f64f16359c9e34d258758f174187ffc618101 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Mon, 24 Aug 2026 10:40:23 +0200 Subject: fix(compose): put the reply cursor on blank space, not on the quote quote_position names where the QUOTE goes, so the reply belongs on the other side of it and the cursor has to follow the reply rather than the buffer. seedBody() moved the cursor to Start under both positions, which is correct only for Below: under Above, the shipped default, it landed on the "On ... wrote:" attribution line, so every reply had to have room made before it could be typed. End under Above, Start under Below. The existing theQuotePositionDecidesWhereTheQuoteLands passed throughout the defect and still does, because the quote was in the right place all along; only the cursor was not. The new test asserts the cursor's block is blank, and that typing lands on the correct side of the quote, so a fix that freed the cursor by inverting the position would not satisfy it. Also re-measures item 136 in the backlog, which is not the intermittent race it was filed as: undoMovesTheMessageBack fails 6 runs in 6 when named alone and passes in the full suite, on a clean tree, so it depends on the tests before it. --- tests/test_mainwindow.cpp | 55 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) (limited to 'tests') diff --git a/tests/test_mainwindow.cpp b/tests/test_mainwindow.cpp index 98dae12..91fbd44 100644 --- a/tests/test_mainwindow.cpp +++ b/tests/test_mainwindow.cpp @@ -56,6 +56,7 @@ #include "messagesender.h" #include #include +#include #include #include #include "cardlayout.h" @@ -484,6 +485,7 @@ private slots: void theAttachmentWarningRespectsTheConfiguredThreshold(); void aDisabledAttachmentWarningWarnsAboutNothing(); void theQuotePositionDecidesWhereTheQuoteLands(); + void theCursorStartsOnBlankSpaceNotOnTheQuote(); void theSeededQuoteIsNotAnUndoStep(); void aReplySeedsTheHtmlToggleFromTheOriginal(); void aNewMessageSeedsTheHtmlToggleFromConfig(); @@ -12374,6 +12376,59 @@ void TestMainWindow::theQuotePositionDecidesWhereTheQuoteLands() } } +void TestMainWindow::theCursorStartsOnBlankSpaceNotOnTheQuote() +{ + // The user types their reply where the cursor lands, so that line must be + // blank under BOTH quote positions. Asserting on the buffer's shape is not + // enough: theQuotePositionDecidesWhereTheQuoteLands() already does that and + // passed throughout the defect, because the quote was in the right place + // and the cursor was on top of it. + const QString quote = QStringLiteral("> the original"); + + const struct { + const char *position; + const char *label; + } cases[] = { + { "above", "quote_position=above" }, + { "below", "quote_position=below" }, + }; + + for (const auto &testCase : cases) { + ComposeFixture fixture; + QVERIFY(fixture.build(QStringLiteral("Drafts"), QStringLiteral("Sent"), + QStringLiteral("quote_position=%1") + .arg(QLatin1String(testCase.position)))); + ComposeContext context = newContext(); + context.kind = ComposeContext::Kind::Reply; + context.quotedBody = quote; + + ComposeWindow window(context, fixture.config(), fixture.mailRoot()); + auto *body = window.findChild(QStringLiteral("body")); + QVERIFY(body); + + const QTextCursor cursor = body->textCursor(); + QVERIFY2(cursor.block().text().isEmpty(), + qPrintable(QStringLiteral("%1: the cursor starts on \"%2\", " + "not on a blank line") + .arg(QLatin1String(testCase.label), + cursor.block().text()))); + + // Typing must not land inside the quote either. A blank line that is + // still BELOW the quote would satisfy the check above while leaving the + // reply underneath what it answers, which is what quote_position + // decides and must not be silently inverted. + QTextCursor probe = cursor; + probe.insertText(QStringLiteral("typed")); + const QString text = body->toPlainText(); + const bool typedFirst = text.indexOf(QStringLiteral("typed")) + < text.indexOf(quote); + QCOMPARE(typedFirst, + QLatin1String(testCase.position) == QLatin1String("above") + ? false + : true); + } +} + void TestMainWindow::theSeededQuoteIsNotAnUndoStep() { ComposeFixture fixture; -- cgit v1.2.3