aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/test_config.cpp56
-rw-r--r--tests/test_keymap.cpp20
-rw-r--r--tests/test_mainwindow.cpp3
-rw-r--r--tests/test_messageview.cpp55
4 files changed, 134 insertions, 0 deletions
diff --git a/tests/test_config.cpp b/tests/test_config.cpp
index e492480..367643c 100644
--- a/tests/test_config.cpp
+++ b/tests/test_config.cpp
@@ -34,6 +34,8 @@ private slots:
void brokenSyncCommandIsAProblem();
void malformedAccountIsAProblem();
void validConfigHasNoProblems();
+ void generalSectionKeysAreActuallyRead();
+ void messageZoomDefaultsAndValidates();
};
static QString writeIni(const QTemporaryDir &dir, const QString &body)
@@ -222,5 +224,59 @@ void TestConfig::validConfigHasNoProblems()
QVERIFY(config.warnings().isEmpty());
}
+void TestConfig::generalSectionKeysAreActuallyRead()
+{
+ // QSettings' INI backend treats a section literally named [general] as its
+ // own fallback section and strips the prefix, so a "general/<key>" lookup
+ // matches nothing. notmuch_config was read that way and had never worked.
+ QTemporaryDir dir;
+ Config config;
+ config.load(writeIni(dir, QStringLiteral(
+ "[general]\n"
+ "notmuch_config=/somewhere/notmuch-config\n"
+ "\n"
+ "[sync]\n"
+ "command=/bin/true\n")));
+
+ QCOMPARE(config.notmuchConfig(),
+ QStringLiteral("/somewhere/notmuch-config"));
+}
+
+void TestConfig::messageZoomDefaultsAndValidates()
+{
+ // A QTemporaryDir per case, not one shared: writeIni() always uses the
+ // same file name, and QSettings caches by path, so a second load of the
+ // same path would return the first case's contents.
+
+ // Absent: 1.0, silently. Nothing the user asked for is being ignored.
+ {
+ QTemporaryDir dir;
+ Config config;
+ config.load(writeIni(dir, QStringLiteral("[general]\n")));
+ QCOMPARE(config.messageZoom(), 1.0);
+ QVERIFY(config.problems().isEmpty());
+ }
+
+ {
+ QTemporaryDir dir;
+ Config config;
+ config.load(writeIni(dir, QStringLiteral("[general]\n"
+ "message_zoom=1.25\n")));
+ QCOMPARE(config.messageZoom(), 1.25);
+ QVERIFY(config.problems().isEmpty());
+ }
+
+ // Present but unparseable is a problem: the user asked for something and
+ // is not getting it, which is the line addProblem() draws.
+ {
+ QTemporaryDir dir;
+ Config config;
+ config.load(writeIni(dir, QStringLiteral("[general]\n"
+ "message_zoom=huge\n")));
+ QCOMPARE(config.messageZoom(), 1.0);
+ QCOMPARE(config.problems().size(), 1);
+ }
+}
+
QTEST_MAIN(TestConfig)
#include "test_config.moc"
diff --git a/tests/test_keymap.cpp b/tests/test_keymap.cpp
index c81eeb0..0fb4f57 100644
--- a/tests/test_keymap.cpp
+++ b/tests/test_keymap.cpp
@@ -36,8 +36,28 @@ private slots:
void userBindingWinsOverDefaultInMenus();
void defaultsDoNotCollide();
void everyDefaultIsAKnownAction();
+ void everyDefaultParses();
};
+void TestKeyMap::everyDefaultParses()
+{
+ // A default that does not parse is a dead binding, the failure mode
+ // bareCapitalMatchesShiftedPress() covers for user-written keys.
+ //
+ // This deliberately does NOT try to decide which keys a keyboard can
+ // deliver. Whether a symbol needs Shift is a layout property, not a Qt
+ // one: Ctrl++ is exactly what the '+' key emits on an Italian layout and
+ // is unreachable on a US one, and QTest::keyClick() cannot reproduce
+ // either faithfully. A test asserting reachability from synthetic input
+ // would encode one layout's habits as a rule for all of them.
+ for (const auto &binding : KeyMap::defaultBindings()) {
+ const QKeySequence sequence = KeyMap::normalizeSequence(binding.first);
+ QVERIFY2(!sequence.isEmpty(),
+ qPrintable(QStringLiteral("default '%1' for %2 does not parse")
+ .arg(binding.first, binding.second)));
+ }
+}
+
void TestKeyMap::defaultsAreLoaded()
{
KeyMap map;
diff --git a/tests/test_mainwindow.cpp b/tests/test_mainwindow.cpp
index 628472b..b4d9e4a 100644
--- a/tests/test_mainwindow.cpp
+++ b/tests/test_mainwindow.cpp
@@ -28,6 +28,7 @@
#include "config.h"
#include "keymap.h"
#include "mainwindow.h"
+#include "messageview.h"
/// MainWindow is mostly wiring, and the parts that need a real database are
/// still verified manually. What is checked here is the action registry: the
@@ -190,6 +191,7 @@ void TestMainWindow::uiStateSurvivesARestart()
const Config config;
MainWindow window(config);
window.resize(resized);
+ window.findChild<MessageView *>()->setZoomFactor(1.4);
window.close(); // closeEvent() is what persists the state
}
@@ -200,6 +202,7 @@ void TestMainWindow::uiStateSurvivesARestart()
const Config config;
MainWindow reopened(config);
QCOMPARE(reopened.size(), resized);
+ QCOMPARE(reopened.findChild<MessageView *>()->zoomFactor(), 1.4);
QFile::remove(MainWindow::uiStatePath());
QStandardPaths::setTestModeEnabled(false);
diff --git a/tests/test_messageview.cpp b/tests/test_messageview.cpp
index 0c3353d..4b6bc4c 100644
--- a/tests/test_messageview.cpp
+++ b/tests/test_messageview.cpp
@@ -36,6 +36,8 @@ private slots:
void documentActuallyLoads();
void threadContentReachesThePage();
void dataUrlSubResourceStillBlocked();
+ void zoomIsClampedToARenderableRange();
+ void zoomSurvivesANewDocument();
private:
QWebEngineView *webViewOf(MessageView *view) const
@@ -175,5 +177,58 @@ void TestMessageView::dataUrlSubResourceStillBlocked()
QVERIFY(text.contains(QStringLiteral("visible-text")));
}
+void TestMessageView::zoomIsClampedToARenderableRange()
+{
+ // A factor outside the range leaves the pane unreadable, and the only way
+ // back is a menu entry the user can no longer read. A corrupt state file
+ // reaching setZoomFactor() must not be able to do that.
+ QCOMPARE(MessageView::clampZoom(100.0), MessageView::kMaxZoom);
+ QCOMPARE(MessageView::clampZoom(0.01), MessageView::kMinZoom);
+
+ // A missing or non-numeric state value converts to 0.0, and a hand-edited
+ // one can hold NaN or an infinity. None of those may reach the web view.
+ QCOMPARE(MessageView::clampZoom(0.0), MessageView::kDefaultZoom);
+ QCOMPARE(MessageView::clampZoom(-2.0), MessageView::kDefaultZoom);
+ QCOMPARE(MessageView::clampZoom(qQNaN()), MessageView::kDefaultZoom);
+ QCOMPARE(MessageView::clampZoom(qInf()), MessageView::kDefaultZoom);
+
+ // In-range values pass through untouched.
+ QCOMPARE(MessageView::clampZoom(1.4), 1.4);
+
+ MessageView view;
+ view.setZoomFactor(50.0);
+ QCOMPARE(view.zoomFactor(), MessageView::kMaxZoom);
+}
+
+void TestMessageView::zoomSurvivesANewDocument()
+{
+ // MainWindow persists whatever zoomFactor() reports and never reapplies it
+ // per render, which is only correct if the web view keeps the factor
+ // across setHtml(). Verified rather than assumed.
+ MessageView view;
+ QWebEngineView *web = webViewOf(&view);
+ QVERIFY(web);
+
+ view.setZoomFactor(1.5);
+
+ QSignalSpy loaded(web, &QWebEngineView::loadFinished);
+
+ ParsedMessage message;
+ message.ok = true;
+ message.from = QStringLiteral("Sender <sender@example.org>");
+ message.subject = QStringLiteral("Zoom");
+ message.plainBody = QStringLiteral("body text");
+
+ ThreadRenderItem item;
+ item.message = message;
+ item.cidPrefix = QStringLiteral("m0");
+ item.expanded = true;
+
+ view.showThread({ item });
+ QVERIFY(loaded.wait(15000));
+
+ QCOMPARE(view.zoomFactor(), 1.5);
+}
+
QTEST_MAIN(TestMessageView)
#include "test_messageview.moc"