diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test_calendarstore.cpp | 49 | ||||
| -rw-r--r-- | tests/test_calendarwindow.cpp | 53 | ||||
| -rw-r--r-- | tests/test_config.cpp | 15 | ||||
| -rw-r--r-- | tests/test_mainwindow.cpp | 3 |
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(); |
