aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/calendarstore.cpp14
-rw-r--r--tests/test_calendarstore.cpp38
2 files changed, 50 insertions, 2 deletions
diff --git a/src/calendarstore.cpp b/src/calendarstore.cpp
index 92a7b74..c388cd6 100644
--- a/src/calendarstore.cpp
+++ b/src/calendarstore.cpp
@@ -604,10 +604,20 @@ bool sameMeaning(const QByteArray &a, const QByteArray &b)
const CalEvent y = parseEvent(b, {}, {}, &ignored);
if (x.uid.isEmpty() || y.uid.isEmpty())
return x.uid.isEmpty() && y.uid.isEmpty();
+ if (x.uid != y.uid)
+ return false;
if (x.overrides.size() != y.overrides.size())
return false;
- for (int i = 0; i < x.overrides.size(); ++i) {
- const CalOverride &p = x.overrides[i], &q = y.overrides[i];
+ // Overrides are separate VEVENTs whose file order is not semantic, and a
+ // server may normalise it, so pair them by recurrence id, not by index.
+ QList<CalOverride> xo = x.overrides, yo = y.overrides;
+ const auto byRecurrenceId = [](const CalOverride &a, const CalOverride &b) {
+ return a.recurrenceId < b.recurrenceId;
+ };
+ std::stable_sort(xo.begin(), xo.end(), byRecurrenceId);
+ std::stable_sort(yo.begin(), yo.end(), byRecurrenceId);
+ for (int i = 0; i < xo.size(); ++i) {
+ const CalOverride &p = xo[i], &q = yo[i];
if (p.recurrenceId != q.recurrenceId || p.start != q.start
|| p.end != q.end || p.summary != q.summary || p.cancelled != q.cancelled)
return false;
diff --git a/tests/test_calendarstore.cpp b/tests/test_calendarstore.cpp
index 977f96b..ba2d5c7 100644
--- a/tests/test_calendarstore.cpp
+++ b/tests/test_calendarstore.cpp
@@ -139,6 +139,8 @@ private slots:
void anEditCanTurnAnEventAllDayAndBack();
void aNewEventIsCompleteAndEmbedsItsZone();
void sameMeaningIgnoresFormatting();
+ void sameMeaningMatchesOverridesRegardlessOfOrder();
+ void sameMeaningRejectsDifferentUids();
};
void TestCalendarStore::parsesAUtcEvent()
@@ -549,5 +551,41 @@ void TestCalendarStore::sameMeaningIgnoresFormatting()
QVERIFY(!CalendarStore::sameMeaning(a, moved));
}
+void TestCalendarStore::sameMeaningMatchesOverridesRegardlessOfOrder()
+{
+ // Two SERVER-normalised texts: the same master and the same two overrides,
+ // but the overrides are separate VEVENTs and their file order is not
+ // semantic, so a reordered pair still describes one event.
+ const QString master = vevent(QStringLiteral(
+ "UID:ord@example.org\r\nDTSTART:20260921T080000Z\r\nDTEND:20260921T090000Z\r\n"
+ "RRULE:FREQ=DAILY;COUNT=5\r\nSUMMARY:Series\r\n"));
+ const QString first = vevent(QStringLiteral(
+ "UID:ord@example.org\r\nRECURRENCE-ID:20260922T080000Z\r\n"
+ "DTSTART:20260922T080000Z\r\nDTEND:20260922T090000Z\r\nSUMMARY:Moved A\r\n"));
+ const QString second = vevent(QStringLiteral(
+ "UID:ord@example.org\r\nRECURRENCE-ID:20260923T080000Z\r\n"
+ "DTSTART:20260923T100000Z\r\nDTEND:20260923T110000Z\r\nSUMMARY:Moved B\r\n"));
+ const QByteArray a = ics(master + first + second);
+ const QByteArray b = ics(master + second + first);
+ QVERIFY(CalendarStore::sameMeaning(a, b));
+
+ // The order is only ignored, not the contents.
+ const QString changed = vevent(QStringLiteral(
+ "UID:ord@example.org\r\nRECURRENCE-ID:20260923T080000Z\r\n"
+ "DTSTART:20260923T100000Z\r\nDTEND:20260923T110000Z\r\nSUMMARY:Moved C\r\n"));
+ QVERIFY(!CalendarStore::sameMeaning(a, ics(master + second + changed)));
+}
+
+void TestCalendarStore::sameMeaningRejectsDifferentUids()
+{
+ // Identical fields, different events: the UID is the identity.
+ const QByteArray a = ics(vevent(QStringLiteral(
+ "UID:one@example.org\r\nDTSTART:20260922T080000Z\r\nSUMMARY:Standup\r\n")));
+ const QByteArray b = ics(vevent(QStringLiteral(
+ "UID:two@example.org\r\nDTSTART:20260922T080000Z\r\nSUMMARY:Standup\r\n")));
+ QVERIFY(!CalendarStore::sameMeaning(a, b));
+ QVERIFY(CalendarStore::sameMeaning(a, a));
+}
+
QTEST_MAIN(TestCalendarStore)
#include "test_calendarstore.moc"