diff options
| author | Danilo M. <danix@danix.xyz> | 2026-08-23 20:39:17 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-08-23 20:39:17 +0200 |
| commit | 0c6d43d62cae7815096919239b64891843864896 (patch) | |
| tree | 7f93d3ffde71aba2296de2ecdb15a0d77b519baf /tests/test_mainwindow.cpp | |
| parent | f7948d98fe09c551856b04ba8a473dbd40649dc3 (diff) | |
| download | qtmaildir-0c6d43d62cae7815096919239b64891843864896.tar.gz qtmaildir-0c6d43d62cae7815096919239b64891843864896.zip | |
fix(compose): close every composer when the main window quits
A composer is deliberately parentless, so that it appears in the task
switcher and stays usable while the main window is. Qt therefore does not
take it down with that window, and being a live top-level it kept the process
alive: the main window vanished, the composer stayed on screen with nothing
behind it, and closing it then raised the unsaved-edits dialog for a session
the user had already ended.
The quit path already ASKED about those edits and saved them. What it never
did was close the windows afterwards.
Closing rather than deleting: WA_DeleteOnClose is set on every composer, so
close() is what frees them, and it lets ComposeWindow::closeEvent() run its
own draft handling on the way out. Iterating a copy of the list, since
closing runs the `closed` handler and that mutates m_composers.
Placed last, after every route that turns back has returned: reaching it means
the application really is quitting.
The test opens TWO composers, so a fix that closed only the last one cannot
pass it. Mutation-checked by removing the loop.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q2koFevoSxTLhfexJTZWQd
Diffstat (limited to 'tests/test_mainwindow.cpp')
| -rw-r--r-- | tests/test_mainwindow.cpp | 38 |
1 files changed, 38 insertions, 0 deletions
diff --git a/tests/test_mainwindow.cpp b/tests/test_mainwindow.cpp index ecaab2f..98dae12 100644 --- a/tests/test_mainwindow.cpp +++ b/tests/test_mainwindow.cpp @@ -267,6 +267,7 @@ private slots: void savingAMessageWithAHostileSubjectStaysInTheDirectory(); void aStuckComposeRequestDoesNotHijackTheNextPaneLoad(); void theSaveLoopToleratesAComposerClosedUnderTheDialog(); + void quittingClosesEveryComposerRatherThanOrphaningIt(); void forwardingCarriesTheOriginalsAttachments(); void forwardSeedsHtmlFromTheConfigNotTheOriginal(); void aStartupAccountScopesTheStartupQuery(); @@ -8833,6 +8834,43 @@ void TestMainWindow::aStuckComposeRequestDoesNotHijackTheNextPaneLoad() QCOMPARE(window.openComposerCount(), 0); } +void TestMainWindow::quittingClosesEveryComposerRatherThanOrphaningIt() +{ + // A composer is a parentless top-level window, deliberately: it must appear + // in the task switcher and be usable while the main window is. The cost is + // that closing the main window does NOT take it down, so quitting left a + // composer on screen with no application behind it, and Qt kept the process + // alive for it. Reported from a hand test: the main window closed, the + // orphan stayed, and its own close then raised the unsaved-edits dialog for + // a session the user had already ended. + // + // The quit path already ASKS about those edits and saves them; what it + // never did was close the windows afterwards. + WorkerComposeFixture fixture; + QVERIFY2(fixture.seed({ { QStringLiteral("work"), QStringLiteral("work"), + QString(), QStringLiteral("/bin/true"), + QStringLiteral("you@example.org") } }, + QStringLiteral("work/inbox")), + qPrintable(fixture.backed.error())); + + MainWindow window(fixture.backed.config()); + QTRY_VERIFY_WITH_TIMEOUT(!window.mailRootForTesting().isEmpty(), 15000); + + // Two, so the fix cannot be "close the last one" and pass. + QVERIFY2(window.openComposerForTest(), "no composer opened"); + QVERIFY2(window.openComposerForTest(), "no second composer opened"); + QCOMPARE(window.openComposerCount(), 2); + + // Clean composers: the point here is the CLOSE, not the unsaved-edits + // dialog, which has its own tests and would block this one on a modal. + window.show(); + window.close(); + + // deleteLater() is how a composer goes away, so the count settles on the + // next event-loop pass rather than synchronously. + QTRY_COMPARE_WITH_TIMEOUT(window.openComposerCount(), 0, 5000); +} + void TestMainWindow::theSaveLoopToleratesAComposerClosedUnderTheDialog() { // The regression for a measured use-after-free. composersBlockingQuit() |
