summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/test_calendarstore.cpp49
-rw-r--r--tests/test_calendarwindow.cpp53
-rw-r--r--tests/test_config.cpp15
-rw-r--r--tests/test_mainwindow.cpp3
4 files changed, 120 insertions, 0 deletions
diff --git a/tests/test_calendarstore.cpp b/tests/test_calendarstore.cpp
index 524304e..8aa526e 100644
--- a/tests/test_calendarstore.cpp
+++ b/tests/test_calendarstore.cpp
@@ -152,10 +152,12 @@ private slots:
void aNewEventIsCompleteAndEmbedsItsZone();
void sameMeaningIgnoresFormatting();
void sameMeaningMatchesOverridesRegardlessOfOrder();
+ void sameMeaningTreatsExdatesAsASet();
void sameMeaningRejectsDifferentUids();
void editingOneOccurrenceWritesAnOverride();
void editingTheSameOccurrenceAgainReplacesItsOverride();
+ void stoppingASeriesDropsItsOverrides();
void deletingOneOccurrenceAddsAnExdateAndDropsItsOverride();
void deletingOneOccurrenceKeepsExistingExdates();
@@ -595,6 +597,25 @@ void TestCalendarStore::sameMeaningMatchesOverridesRegardlessOfOrder()
QVERIFY(!CalendarStore::sameMeaning(a, ics(master + second + changed)));
}
+void TestCalendarStore::sameMeaningTreatsExdatesAsASet()
+{
+ // EXDATE properties are a set; a server may reorder them on the round
+ // trip, and a list comparison would then warn about a version it kept.
+ const QByteArray a = ics(vevent(QStringLiteral(
+ "UID:ex@example.org\r\nDTSTART:20260922T080000Z\r\n"
+ "EXDATE:20260923T080000Z\r\nEXDATE:20260924T080000Z\r\n")));
+ const QByteArray b = ics(vevent(QStringLiteral(
+ "UID:ex@example.org\r\nDTSTART:20260922T080000Z\r\n"
+ "EXDATE:20260924T080000Z\r\nEXDATE:20260923T080000Z\r\n")));
+ QVERIFY(CalendarStore::sameMeaning(a, b));
+
+ // Only the order is ignored, not the contents.
+ const QByteArray changed = ics(vevent(QStringLiteral(
+ "UID:ex@example.org\r\nDTSTART:20260922T080000Z\r\n"
+ "EXDATE:20260923T080000Z\r\nEXDATE:20260925T080000Z\r\n")));
+ QVERIFY(!CalendarStore::sameMeaning(a, changed));
+}
+
void TestCalendarStore::sameMeaningRejectsDifferentUids()
{
// Identical fields, different events: the UID is the identity.
@@ -647,6 +668,34 @@ void TestCalendarStore::editingTheSameOccurrenceAgainReplacesItsOverride()
QCOMPARE(e.overrides[0].start, rome(22, 17));
}
+void TestCalendarStore::stoppingASeriesDropsItsOverrides()
+{
+ // A series with one overridden occurrence, then the repeat is stopped
+ // (Scope::All with RepeatRule() = Freq::None). The override is not a real
+ // occurrence any more: occurrences() never reads it for a non-repeating
+ // event, so leaving it in the file is invalid iCalendar that other clients
+ // may render as a phantom, and it would spring back if a repeat were
+ // re-enabled. EXDATEs are left inert.
+ const CalEvent series = parse(vevent(kDailySeries));
+ EventEdit edit = editOf(series);
+ edit.summary = QStringLiteral("Just this one");
+ edit.start = rome(22, 15);
+ edit.end = rome(22, 16);
+ const QByteArray withOverride = CalendarStore::applyEdit(
+ series.rawText, edit, CalendarStore::Scope::ThisOccurrence, rome(22, 10));
+ QCOMPARE(reparse(withOverride).overrides.size(), 1);
+
+ EventEdit stop = editOf(reparse(withOverride));
+ stop.repeat = RepeatRule(); // does not repeat
+ const QByteArray after = CalendarStore::applyEdit(
+ withOverride, stop, CalendarStore::Scope::All, {});
+
+ const CalEvent e = reparse(after);
+ QVERIFY(e.overrides.isEmpty());
+ QVERIFY2(!after.contains("RECURRENCE-ID"), after.constData());
+ QVERIFY2(!after.contains("RRULE"), after.constData());
+}
+
void TestCalendarStore::deletingOneOccurrenceAddsAnExdateAndDropsItsOverride()
{
const CalEvent series = parse(vevent(kDailySeries));
diff --git a/tests/test_calendarwindow.cpp b/tests/test_calendarwindow.cpp
index c52f245..5591b2b 100644
--- a/tests/test_calendarwindow.cpp
+++ b/tests/test_calendarwindow.cpp
@@ -27,6 +27,7 @@
#include <QMessageBox>
#include <QPushButton>
#include <QSpinBox>
+#include <QStatusBar>
#include <QTemporaryDir>
#include <QTimer>
#include <QToolBar>
@@ -126,6 +127,7 @@ private slots:
void theToolbarCarriesTheNewEventAction();
void aFreshWindowShowsTheCurrentMonth();
void aStaleSaveCanBeRetried();
+ void aRolledBackWriteIsNotReportedLostAfterSync();
};
void TestCalendarWindow::loadsAndSelects()
@@ -267,5 +269,56 @@ void TestCalendarWindow::aStaleSaveCanBeRetried()
QVERIFY(!w->isEditing());
}
+void TestCalendarWindow::aRolledBackWriteIsNotReportedLostAfterSync()
+{
+ // applyChanges records every successful write in m_written for the next
+ // sync to verify. A later change in the same call fails, rolling the
+ // earlier one back, so that write never reached disk and must be
+ // forgotten. Keeping it makes the next sync compare the file against
+ // bytes that were reverted and warn that the server kept a different
+ // version, a difference that never happened.
+ QTemporaryDir dir;
+ const QString cal = dir.filePath(QStringLiteral("cal"));
+ const QString one = cal + QStringLiteral("/a/one.ics");
+ const QString two = cal + QStringLiteral("/a/two.ics");
+ const QByteArray original =
+ eventText(QStringLiteral("one@example.org"), QStringLiteral("One"), 22);
+ writeFile(one, original);
+ writeFile(two, eventText(QStringLiteral("two@example.org"), QStringLiteral("Two"), 23));
+
+ // A real sync command, so the post-sync check actually runs.
+ const QString ini = dir.filePath(QStringLiteral("qtmaildir.conf"));
+ writeFile(ini, QStringLiteral("[general]\ncalendars_dir = %1\n"
+ "calendar_sync_command = /bin/true\n"
+ "calendar_sync_delay_ms = 0\n").arg(cal).toUtf8());
+ Config config;
+ config.load(ini);
+ std::unique_ptr<CalendarWindow> w(
+ new CalendarWindow(config, { QStringLiteral("me@example.org") },
+ dir.filePath(QStringLiteral("uistate.conf"))));
+
+ // Change 1 succeeds; change 2 is stale (its expected bytes do not match a
+ // missing file), so change 1 is rolled back.
+ const QByteArray rolled =
+ eventText(QStringLiteral("one@example.org"), QStringLiteral("Rolled"), 22);
+ const QList<CalendarWindow::Change> changes = {
+ { one, original, rolled },
+ { cal + QStringLiteral("/a/ghost.ics"), QByteArray("gone"), QByteArray("x") },
+ };
+ QVERIFY(!w->applyChanges(changes, false));
+ QCOMPARE(read(one), original); // the first change was reverted
+
+ // A later successful write triggers the sync. The rolled-back path must
+ // not reappear in the post-sync check: with the fix the sync reports
+ // clean, without it the stale entry produces a false "different version".
+ const QByteArray twoAfter =
+ eventText(QStringLiteral("two@example.org"), QStringLiteral("Two, edited"), 23);
+ QVERIFY(w->applyChanges({ { two, read(two), twoAfter } }, false));
+
+ QTRY_VERIFY_WITH_TIMEOUT(
+ w->statusBar()->currentMessage().contains(QStringLiteral("Calendars synced.")),
+ 5000);
+}
+
QTEST_MAIN(TestCalendarWindow)
#include "test_calendarwindow.moc"
diff --git a/tests/test_config.cpp b/tests/test_config.cpp
index be343d1..b0597de 100644
--- a/tests/test_config.cpp
+++ b/tests/test_config.cpp
@@ -154,6 +154,7 @@ private slots:
void calendarSyncCommandSurvivesACommaAndDefaults();
void calendarsDirThatDoesNotExistIsReported();
void calendarSyncDelayRejectsGarbage();
+ void aNegativeCalendarSyncDelayIsOutOfRange();
};
static QString writeIni(const QTemporaryDir &dir, const QString &body)
@@ -2963,5 +2964,19 @@ void TestConfig::calendarSyncDelayRejectsGarbage()
QVERIFY(!config.problems().isEmpty());
}
+void TestConfig::aNegativeCalendarSyncDelayIsOutOfRange()
+{
+ // "-5" parses, so "is not a number" would be a lie; the value is simply
+ // outside what the key accepts. It falls back to the default and says so.
+ QTemporaryDir dir;
+ Config config;
+ config.load(writeIni(dir, QStringLiteral("[general]\n"
+ "calendar_sync_delay_ms=-5\n")));
+ QCOMPARE(config.calendarSyncDelayMs(), 2000);
+ const QString joined = config.problems().join(QLatin1Char('\n'));
+ QVERIFY2(joined.contains(QStringLiteral("out of range")), qPrintable(joined));
+ QVERIFY2(!joined.contains(QStringLiteral("not a number")), qPrintable(joined));
+}
+
QTEST_MAIN(TestConfig)
#include "test_config.moc"
diff --git a/tests/test_mainwindow.cpp b/tests/test_mainwindow.cpp
index 5446780..f6f75aa 100644
--- a/tests/test_mainwindow.cpp
+++ b/tests/test_mainwindow.cpp
@@ -8833,6 +8833,9 @@ void TestMainWindow::theCalendarActionWithNoCalendarOpensNothing()
MainWindow window(config);
auto *action = window.findChild<QAction *>(QStringLiteral("calendar"));
QVERIFY2(action, "no action named calendar");
+ // Enabled, not disabled: a future edit that greys it out would otherwise
+ // make the trigger below a no-op and leave this test passing vacuously.
+ QVERIFY2(action->isEnabled(), "the calendar action is disabled");
action->trigger();