aboutsummaryrefslogtreecommitdiffstats
path: root/tests/test_composewindow.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test_composewindow.cpp')
-rw-r--r--tests/test_composewindow.cpp165
1 files changed, 165 insertions, 0 deletions
diff --git a/tests/test_composewindow.cpp b/tests/test_composewindow.cpp
index 68ec4d4..779d48c 100644
--- a/tests/test_composewindow.cpp
+++ b/tests/test_composewindow.cpp
@@ -24,6 +24,10 @@
#include <QPlainTextEdit>
#include <QSignalSpy>
#include <QLabel>
+#include <QMenuBar>
+#include <QToolBar>
+#include <QSet>
+#include <functional>
#include <QRegularExpression>
#include <QTemporaryDir>
#include <QTimer>
@@ -56,6 +60,10 @@ private slots:
void aSavedDraftReportsItAndClearsTheCue();
void aSentMessageLeavesNoUnsavedCue();
void onlyTheSetterWritesTheDirtyFlag();
+ void theMenuBarReachesEveryComposerAction();
+ void saveDraftWritesAndReports();
+ void theMenusReuseTheToolbarActions();
+ void theHtmlMenuItemTracksTheToolbarButton();
void theAgeLineFollowsTheClock();
private:
@@ -643,5 +651,162 @@ void TestComposeWindow::theAgeLineFollowsTheClock()
"the age line must move as the clock does");
}
+/// Item 132's rule for the main window, applied to the composer: an action
+/// nobody can find in a menu is reachable only by a chord the user has to
+/// know. Walks the real menu bar rather than a list, so an action added to
+/// the toolbar and forgotten in the menus fails here.
+void TestComposeWindow::theMenuBarReachesEveryComposerAction()
+{
+ const Config config = configWithDrafts();
+
+ ComposeContext context;
+ context.kind = ComposeContext::Kind::New;
+ context.accountKey = QStringLiteral("work");
+
+ ComposeWindow window(context, config, m_dir->path());
+
+ QMenuBar *bar = window.menuBar();
+ QVERIFY2(bar, "the composer has no menu bar");
+
+ // Every action the menus reach, submenus included.
+ QSet<QString> reachable;
+ std::function<void(QMenu *)> walk = [&](QMenu *menu) {
+ const QList<QAction *> actions = menu->actions();
+ for (QAction *action : actions) {
+ if (QMenu *sub = action->menu()) {
+ // An action owning a submenu is not itself reachable: Qt
+ // emits no triggered for it, as CLAUDE.md records.
+ walk(sub);
+ continue;
+ }
+ if (action->isSeparator())
+ continue;
+ if (!action->objectName().isEmpty())
+ reachable.insert(action->objectName());
+ }
+ };
+ const QList<QAction *> top = bar->actions();
+ for (QAction *action : top) {
+ QVERIFY2(action->menu(), "a top-level menu bar entry with no menu");
+ walk(action->menu());
+ }
+
+ // Every named action the composer owns. findChildren, so an action added
+ // later is picked up without touching this list.
+ const QList<QAction *> owned = window.findChildren<QAction *>();
+ QStringList missing;
+ for (QAction *action : owned) {
+ const QString name = action->objectName();
+ if (name.isEmpty())
+ continue;
+ // The signature entries are built from the files on disk and named
+ // per signature; the switch itself is what a menu offers.
+ if (name.startsWith(QStringLiteral("signature_choice")))
+ continue;
+ if (!reachable.contains(name))
+ missing.append(name);
+ }
+
+ QVERIFY2(missing.isEmpty(),
+ qPrintable(QStringLiteral("not reachable from any menu: %1")
+ .arg(missing.join(QStringLiteral(", ")))));
+}
+
+/// The action item 161 adds. Save draft did not exist at all: saveDraftNow()
+/// was reachable only from the timer, the send path and closeEvent, so the
+/// user could not ask for a save.
+void TestComposeWindow::saveDraftWritesAndReports()
+{
+ const Config config = configWithDrafts();
+
+ ComposeContext context;
+ context.kind = ComposeContext::Kind::New;
+ context.accountKey = QStringLiteral("work");
+
+ ComposeWindow window(context, config, m_dir->path());
+ auto *body = window.findChild<QPlainTextEdit *>(QStringLiteral("body"));
+ QVERIFY(body);
+ body->setPlainText(QStringLiteral("Saved by hand."));
+
+ auto *save = window.findChild<QAction *>(QStringLiteral("compose_save"));
+ QVERIFY2(save, "there is no Save draft action");
+ QCOMPARE(save->shortcut(), QKeySequence(QStringLiteral("Ctrl+S")));
+
+ QSignalSpy saved(&window, &ComposeWindow::draftSaved);
+ save->trigger();
+
+ QCOMPARE(saved.size(), 1);
+
+ // Routed through saveDraftNow(), so item 160's reporting comes free. A
+ // second write path would have to repeat it, and would be the ghost-file
+ // bug item 158 fixed.
+ auto *age = window.findChild<QLabel *>(QStringLiteral("draftAge"));
+ QVERIFY(age);
+ QVERIFY2(!age->text().isEmpty(), "a manual save must report like an autosave");
+ QVERIFY2(!window.isWindowModified(), "a manual save must clear the marker");
+}
+
+/// The same QAction objects, shown twice over, exactly as item 140 required
+/// for the message pane's bar. A copy would drift: an enablement change or a
+/// new shortcut would reach one surface and not the other.
+void TestComposeWindow::theMenusReuseTheToolbarActions()
+{
+ const Config config = configWithDrafts();
+
+ ComposeContext context;
+ context.kind = ComposeContext::Kind::New;
+ context.accountKey = QStringLiteral("work");
+
+ ComposeWindow window(context, config, m_dir->path());
+
+ auto *bold = window.findChild<QAction *>(QStringLiteral("format_bold"));
+ auto *toolbar = window.findChild<QToolBar *>(QStringLiteral("formatToolbar"));
+ QVERIFY(bold);
+ QVERIFY(toolbar);
+ QVERIFY2(toolbar->actions().contains(bold),
+ "Bold left the formatting toolbar");
+
+ QMenu *format = nullptr;
+ const QList<QAction *> top = window.menuBar()->actions();
+ for (QAction *action : top) {
+ if (action->menu() && action->menu()->actions().contains(bold))
+ format = action->menu();
+ }
+ QVERIFY2(format, "Bold is not in any menu");
+
+ // The pointer itself, not a namesake.
+ QVERIFY2(format->actions().contains(bold),
+ "the menu holds a copy of Bold rather than the action itself");
+}
+
+/// The HTML toggle is a QToolButton, not a QAction, so a menu entry for it
+/// has to be built and kept in step by hand. Both directions: a menu that
+/// only follows the button is half a control.
+void TestComposeWindow::theHtmlMenuItemTracksTheToolbarButton()
+{
+ const Config config = configWithDrafts();
+
+ ComposeContext context;
+ context.kind = ComposeContext::Kind::New;
+ context.accountKey = QStringLiteral("work");
+
+ ComposeWindow window(context, config, m_dir->path());
+
+ auto *button = window.findChild<QToolButton *>(QStringLiteral("sendHtml"));
+ auto *item = window.findChild<QAction *>(QStringLiteral("compose_send_html"));
+ QVERIFY(button);
+ QVERIFY2(item, "there is no menu entry for the HTML toggle");
+ QVERIFY(item->isCheckable());
+
+ const bool initial = button->isChecked();
+ QCOMPARE(item->isChecked(), initial);
+
+ button->setChecked(!initial);
+ QCOMPARE(item->isChecked(), !initial);
+
+ item->setChecked(initial);
+ QCOMPARE(button->isChecked(), initial);
+}
+
QTEST_MAIN(TestComposeWindow)
#include "test_composewindow.moc"