diff options
| author | Danilo M. <danix@danix.xyz> | 2026-09-24 16:01:07 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-09-24 16:01:07 +0200 |
| commit | e10498e1e721ce67475e3d239412b0396ea2acde (patch) | |
| tree | 380b03ce4947c5cb3532b31232ada7ae46924b0d /tests | |
| parent | 0a26a0b3968486b3769ab42b2966fff6d2db1fb0 (diff) | |
| download | qtmaildir-e10498e1e721ce67475e3d239412b0396ea2acde.tar.gz qtmaildir-e10498e1e721ce67475e3d239412b0396ea2acde.zip | |
feat: read every time form and load a calendar vdir
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test_calendarstore.cpp | 123 |
1 files changed, 123 insertions, 0 deletions
diff --git a/tests/test_calendarstore.cpp b/tests/test_calendarstore.cpp index e7e3a3f..5c177fb 100644 --- a/tests/test_calendarstore.cpp +++ b/tests/test_calendarstore.cpp @@ -19,6 +19,7 @@ #include <QtTest> #include <QDir> #include <QFile> +#include <QFileInfo> #include <QTemporaryDir> #include <QTimeZone> @@ -39,6 +40,30 @@ QString vevent(const QString &lines) return QStringLiteral("BEGIN:VEVENT\r\n%1END:VEVENT\r\n").arg(lines); } +const char *kRomeVtimezone = + "BEGIN:VTIMEZONE\r\nTZID:Europe/Rome\r\n" + "BEGIN:DAYLIGHT\r\nTZOFFSETFROM:+0100\r\nTZOFFSETTO:+0200\r\nTZNAME:CEST\r\n" + "DTSTART:19700329T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU\r\nEND:DAYLIGHT\r\n" + "BEGIN:STANDARD\r\nTZOFFSETFROM:+0200\r\nTZOFFSETTO:+0100\r\nTZNAME:CET\r\n" + "DTSTART:19701025T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU\r\nEND:STANDARD\r\n" + "END:VTIMEZONE\r\n"; + +CalEvent parse(const QString &body, bool *unknown = nullptr) +{ + bool ignored = false; + return CalendarStore::parseEvent(ics(body), QStringLiteral("/x/e.ics"), + QStringLiteral("x"), unknown ? unknown : &ignored); +} + +void writeFile(const QString &path, const QByteArray &content) +{ + QDir().mkpath(QFileInfo(path).absolutePath()); + QFile file(path); + QVERIFY2(file.open(QIODevice::WriteOnly | QIODevice::Truncate), + qPrintable(file.errorString())); + file.write(content); +} + } // namespace class TestCalendarStore : public QObject @@ -47,6 +72,13 @@ class TestCalendarStore : public QObject private slots: void parsesAUtcEvent(); + void readsATzidWithItsVtimezone(); + void readsATzidWithoutAVtimezoneAsIana(); + void readsAnUnknownTzidAsLocalAndSaysSo(); + void readsFloatingTimeAsLocal(); + void readsAnAllDayEventWithAnExclusiveEnd(); + void loadReadsCollectionsAndFallsBack(); + void loadSkipsAndCountsABrokenFile(); }; void TestCalendarStore::parsesAUtcEvent() @@ -72,5 +104,96 @@ void TestCalendarStore::parsesAUtcEvent() QVERIFY(!event.rawText.isEmpty()); } +void TestCalendarStore::readsATzidWithItsVtimezone() +{ + const CalEvent e = parse(QString::fromLatin1(kRomeVtimezone) + vevent(QStringLiteral( + "UID:a@example.org\r\nDTSTART;TZID=Europe/Rome:20260922T100000\r\n" + "DTEND;TZID=Europe/Rome:20260922T110000\r\n"))); + // 10:00 in Rome in September is 08:00 UTC (CEST, +2). + QCOMPARE(e.start.toUTC(), QDateTime(QDate(2026, 9, 22), QTime(8, 0), QTimeZone::utc())); + QCOMPARE(e.end.toUTC(), QDateTime(QDate(2026, 9, 22), QTime(9, 0), QTimeZone::utc())); +} + +void TestCalendarStore::readsATzidWithoutAVtimezoneAsIana() +{ + // 120 of the user's 312 files are shaped like this: RFC 5545 requires the + // VTIMEZONE, and real servers omit it. + bool unknown = true; + const CalEvent e = parse(vevent(QStringLiteral( + "UID:b@example.org\r\nDTSTART;TZID=Europe/Rome:20260922T100000\r\n" + "DTEND;TZID=Europe/Rome:20260922T110000\r\n")), &unknown); + QCOMPARE(e.start.toUTC(), QDateTime(QDate(2026, 9, 22), QTime(8, 0), QTimeZone::utc())); + QVERIFY(!unknown); +} + +void TestCalendarStore::readsAnUnknownTzidAsLocalAndSaysSo() +{ + bool unknown = false; + const CalEvent e = parse(vevent(QStringLiteral( + "UID:c@example.org\r\nDTSTART;TZID=Not A Zone:20260922T100000\r\n" + "DTEND;TZID=Not A Zone:20260922T110000\r\n")), &unknown); + QVERIFY(unknown); + QCOMPARE(e.start, QDateTime(QDate(2026, 9, 22), QTime(10, 0))); +} + +void TestCalendarStore::readsFloatingTimeAsLocal() +{ + const CalEvent e = parse(vevent(QStringLiteral( + "UID:d@example.org\r\nDTSTART:20260922T100000\r\nDTEND:20260922T110000\r\n"))); + QCOMPARE(e.start, QDateTime(QDate(2026, 9, 22), QTime(10, 0))); + QCOMPARE(e.start.timeSpec(), Qt::LocalTime); +} + +void TestCalendarStore::readsAnAllDayEventWithAnExclusiveEnd() +{ + const CalEvent e = parse(vevent(QStringLiteral( + "UID:e@example.org\r\nDTSTART;VALUE=DATE:20260924\r\nDTEND;VALUE=DATE:20260925\r\n"))); + QVERIFY(e.allDay); + QCOMPARE(e.start.date(), QDate(2026, 9, 24)); + // Exclusive: a one-day event on the 24th ends at the START of the 25th. + QCOMPARE(e.end.date(), QDate(2026, 9, 25)); + QCOMPARE(e.end.time(), QTime(0, 0)); +} + +void TestCalendarStore::loadReadsCollectionsAndFallsBack() +{ + QTemporaryDir dir; + const QString root = dir.path(); + writeFile(root + QStringLiteral("/52/displayname"), "Work"); + writeFile(root + QStringLiteral("/52/color"), "#60a5fa\n"); + writeFile(root + QStringLiteral("/52/one.ics"), ics(vevent(QStringLiteral( + "UID:one@example.org\r\nDTSTART:20260922T080000Z\r\n")))); + writeFile(root + QStringLiteral("/31/two.ics"), ics(vevent(QStringLiteral( + "UID:two@example.org\r\nDTSTART:20260923T080000Z\r\n")))); + writeFile(root + QStringLiteral("/31/notes.txt"), "not a calendar file"); + + const LoadResult r = CalendarStore::load(root); + QCOMPARE(r.collections.size(), 2); + QCOMPARE(r.events.size(), 2); + QCOMPARE(r.unparsable, 0); + + // Sorted by display name, so "31" (no displayname) sorts before "Work". + QCOMPARE(r.collections[0].dir, QStringLiteral("31")); + QCOMPARE(r.collections[0].displayName, QStringLiteral("31")); + QVERIFY(r.collections[0].color.isValid()); + QCOMPARE(r.collections[1].displayName, QStringLiteral("Work")); + QCOMPARE(r.collections[1].color, QColor(QStringLiteral("#60a5fa"))); + QVERIFY(!r.collections[1].readOnly); + + // The hashed fallback is stable across loads, or colours would shuffle. + QCOMPARE(CalendarStore::load(root).collections[0].color, r.collections[0].color); +} + +void TestCalendarStore::loadSkipsAndCountsABrokenFile() +{ + QTemporaryDir dir; + writeFile(dir.path() + QStringLiteral("/c/good.ics"), ics(vevent(QStringLiteral( + "UID:good@example.org\r\nDTSTART:20260922T080000Z\r\n")))); + writeFile(dir.path() + QStringLiteral("/c/bad.ics"), "this is not iCalendar"); + const LoadResult r = CalendarStore::load(dir.path()); + QCOMPARE(r.events.size(), 1); + QCOMPARE(r.unparsable, 1); +} + QTEST_MAIN(TestCalendarStore) #include "test_calendarstore.moc" |
