diff --git a/resources.qrc b/resources.qrc
index fdd4bcf570e63..7a53e12e65106 100644
--- a/resources.qrc
+++ b/resources.qrc
@@ -59,5 +59,6 @@
src/gui/ResolveConflictsDialog.qml
src/gui/ConflictDelegate.qml
src/gui/ConflictItemFileInfo.qml
+ src/gui/filedetails/NCInputDateField.qml
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt
index 0a89c6d307afc..fdcde5d6f32bd 100644
--- a/src/gui/CMakeLists.txt
+++ b/src/gui/CMakeLists.txt
@@ -190,6 +190,8 @@ set(client_SRCS
syncconflictsmodel.cpp
fileactivitylistmodel.h
fileactivitylistmodel.cpp
+ filedetails/datefieldbackend.h
+ filedetails/datefieldbackend.cpp
filedetails/filedetails.h
filedetails/filedetails.cpp
filedetails/sharemodel.h
diff --git a/src/gui/filedetails/NCInputDateField.qml b/src/gui/filedetails/NCInputDateField.qml
new file mode 100644
index 0000000000000..4c0ea491c7fe4
--- /dev/null
+++ b/src/gui/filedetails/NCInputDateField.qml
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import com.nextcloud.desktopclient 1.0
+
+NCInputTextField {
+ id: root
+
+ signal userAcceptedDate
+
+ function updateText() {
+ text = backend.dateString;
+ }
+
+ DateFieldBackend {
+ id: backend
+ onDateStringChanged: if (!root.activeFocus) root.updateText()
+ }
+
+ property alias date: backend.date
+ property alias dateInMs: backend.dateMsecs
+ property alias minimumDate: backend.minimumDate
+ property alias minimumDateMs: backend.minimumDateMsecs
+ property alias maximumDate: backend.maximumDate
+ property alias maximumDateMs: backend.maximumDateMsecs
+
+ inputMethodHints: Qt.ImhDate
+ validInput: backend.validDate
+ text: backend.dateString
+ onTextChanged: backend.dateString = text
+
+ onAccepted: {
+ backend.dateString = text;
+ root.userAcceptedDate();
+ }
+}
\ No newline at end of file
diff --git a/src/gui/filedetails/ShareDetailsPage.qml b/src/gui/filedetails/ShareDetailsPage.qml
index 527f48de8402e..c09f42abd6dc2 100644
--- a/src/gui/filedetails/ShareDetailsPage.qml
+++ b/src/gui/filedetails/ShareDetailsPage.qml
@@ -64,7 +64,7 @@ Page {
readonly property string passwordPlaceholder: "●●●●●●●●●●"
readonly property var expireDate: shareModelData.expireDate // Don't use int as we are limited
- readonly property var maximumExpireDate: shareModelData.enforcedMaximumExpireDate
+ readonly property var maximumExpireDate: shareModelData.enforcedMaximumExpireDate // msecs epoch
readonly property string linkShareLabel: shareModelData.linkShareLabel ?? ""
@@ -127,7 +127,7 @@ Page {
//
// So to ensure that the text of the spin box is correctly updated, force an update of the
// contents of the expire date text field.
- expireDateSpinBox.updateText();
+ expireDateField.updateText();
waitingForExpireDateChange = false;
}
@@ -731,149 +731,29 @@ Page {
sourceSize.height: scrollContentsColumn.rowIconWidth
}
- // QML dates are essentially JavaScript dates, which makes them very finicky and unreliable.
- // Instead, we exclusively deal with msecs from epoch time to make things less painful when editing.
- // We only use the QML Date when showing the nice string to the user.
- SpinBox {
- id: expireDateSpinBox
-
- function updateText() {
- expireDateSpinBoxTextField.text = textFromValue(value, locale);
- }
-
- // Work arounds the limitations of QML's 32 bit integer when handling msecs from epoch
- // Instead, we handle everything as days since epoch
- readonly property int dayInMSecs: 24 * 60 * 60 * 1000
- readonly property int expireDateReduced: Math.floor(root.expireDate / dayInMSecs)
- // Reset the model data after binding broken on user interact
- onExpireDateReducedChanged: {
- value = expireDateReduced;
- updateText();
- }
-
- // We can't use JS's convenient Infinity or Number.MAX_VALUE as
- // JS Number type is 64 bits, whereas QML's int type is only 32 bits
- readonly property IntValidator intValidator: IntValidator {}
- readonly property int maximumExpireDateReduced: root.expireDateEnforced ?
- Math.floor(root.maximumExpireDate / dayInMSecs) :
- intValidator.top
- readonly property int minimumExpireDateReduced: {
- const currentDate = new Date();
- const minDateUTC = new Date(Date.UTC(currentDate.getFullYear(),
- currentDate.getMonth(),
- currentDate.getDate() + 1));
- return Math.floor(minDateUTC / dayInMSecs) // Start of day at 00:00:0000 UTC
- }
-
- // Taken from Kalendar 22.08
- // https://invent.kde.org/pim/kalendar/-/blob/release/22.08/src/contents/ui/KalendarUtils/dateutils.js
- function parseDateString(dateString) {
- function defaultParse() {
- const defaultParsedDate = Date.fromLocaleDateString(Qt.locale(), dateString, Locale.NarrowFormat);
- // JS always generates date in system locale, eliminate timezone difference to UTC
- const msecsSinceEpoch = defaultParsedDate.getTime() - (defaultParsedDate.getTimezoneOffset() * 60 * 1000);
- return new Date(msecsSinceEpoch);
- }
-
- const dateStringDelimiterMatches = dateString.match(/\D/);
- if(dateStringDelimiterMatches.length === 0) {
- // Let the date method figure out this weirdness
- return defaultParse();
- }
-
- const dateStringDelimiter = dateStringDelimiterMatches[0];
-
- const localisedDateFormatSplit = Qt.locale().dateFormat(Locale.NarrowFormat).split(dateStringDelimiter);
- const localisedDateDayPosition = localisedDateFormatSplit.findIndex((x) => /d/gi.test(x));
- const localisedDateMonthPosition = localisedDateFormatSplit.findIndex((x) => /m/gi.test(x));
- const localisedDateYearPosition = localisedDateFormatSplit.findIndex((x) => /y/gi.test(x));
-
- let splitDateString = dateString.split(dateStringDelimiter);
- let userProvidedYear = splitDateString[localisedDateYearPosition]
-
- const dateNow = new Date();
- const stringifiedCurrentYear = dateNow.getFullYear().toString();
-
- // If we have any input weirdness, or if we have a fully-written year
- // (e.g. 2022 instead of 22) then use default parse
- if(splitDateString.length === 0 ||
- splitDateString.length > 3 ||
- userProvidedYear.length >= stringifiedCurrentYear.length) {
-
- return defaultParse();
- }
-
- let fullyWrittenYear = userProvidedYear.split("");
- const digitsToAdd = stringifiedCurrentYear.length - fullyWrittenYear.length;
- for(let i = 0; i < digitsToAdd; i++) {
- fullyWrittenYear.splice(i, 0, stringifiedCurrentYear[i])
- }
- fullyWrittenYear = fullyWrittenYear.join("");
-
- const fixedYearNum = Number(fullyWrittenYear);
- const monthIndexNum = Number(splitDateString[localisedDateMonthPosition]) - 1;
- const dayNum = Number(splitDateString[localisedDateDayPosition]);
-
- console.log(dayNum, monthIndexNum, fixedYearNum);
-
- // Modification: return date in UTC
- return new Date(Date.UTC(fixedYearNum, monthIndexNum, dayNum));
- }
+ NCInputDateField {
+ id: expireDateField
Layout.fillWidth: true
height: visible ? implicitHeight : 0
- // We want all the internal benefits of the spinbox but don't actually want the
- // buttons, so set an empty item as a dummy
- up.indicator: Item {}
- down.indicator: Item {}
-
- padding: 0
- background: null
- contentItem: NCInputTextField {
- id: expireDateSpinBoxTextField
-
- validInput: {
- const value = expireDateSpinBox.valueFromText(text);
- return value >= expireDateSpinBox.from && value <= expireDateSpinBox.to;
- }
-
- text: expireDateSpinBox.textFromValue(expireDateSpinBox.value, expireDateSpinBox.locale)
- readOnly: !expireDateSpinBox.editable
- validator: expireDateSpinBox.validator
- inputMethodHints: Qt.ImhFormattedNumbersOnly
- onAccepted: {
- expireDateSpinBox.value = expireDateSpinBox.valueFromText(text, expireDateSpinBox.locale);
- expireDateSpinBox.valueModified();
- }
- }
-
- value: expireDateReduced
- from: minimumExpireDateReduced
- to: maximumExpireDateReduced
-
- textFromValue: (value, locale) => {
- const dateFromValue = new Date(value * dayInMSecs);
- return dateFromValue.toLocaleDateString(Qt.locale(), Locale.NarrowFormat);
- }
- valueFromText: (text, locale) => {
- const dateFromText = parseDateString(text);
- return Math.floor(dateFromText.getTime() / dayInMSecs);
+ dateInMs: root.expireDate
+ maximumDateMs: root.maximumExpireDate
+ minimumDateMs: {
+ const currentDate = new Date();
+ const currentYear = currentDate.getFullYear();
+ const currentMonth = currentDate.getMonth();
+ const currentMonthDay = currentDate.getDate();
+ // Start of day at 00:00:0000 UTC
+ return Date.UTC(currentYear, currentMonth, currentMonthDay + 1);
}
- editable: true
- inputMethodHints: Qt.ImhDate | Qt.ImhFormattedNumbersOnly
-
enabled: root.expireDateEnabled &&
!root.waitingForExpireDateChange &&
!root.waitingForExpireDateEnabledChange
- onValueModified: {
- if (!enabled || !activeFocus) {
- return;
- }
-
- root.setExpireDate(value * dayInMSecs);
+ onUserAcceptedDate: {
+ root.setExpireDate(dateInMs);
root.waitingForExpireDateChange = true;
}
diff --git a/src/gui/filedetails/datefieldbackend.cpp b/src/gui/filedetails/datefieldbackend.cpp
new file mode 100644
index 0000000000000..036ee7ee8302d
--- /dev/null
+++ b/src/gui/filedetails/datefieldbackend.cpp
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2023 by Claudio Cambra
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "datefieldbackend.h"
+
+#include
+#include
+#include
+
+namespace OCC
+{
+namespace Quick
+{
+
+DateFieldBackend::DateFieldBackend(QObject *const parent)
+ : QObject(parent)
+{
+ _dateFormat = QLocale::system().dateFormat(QLocale::ShortFormat);
+
+ // Ensure the date format is for a full year. QLocale::ShortFormat often
+ // provides a short year format that is only two years, which is an absolute
+ // pain to work with -- ensure instead we have the full, unambiguous year.
+ // Check for specifically two y's, no more and no fewer, within format date
+ const QRegularExpression yearRe("(? 0) {
+ valid &= _date >= _minimumDate;
+ }
+
+ if (_maximumDate.isValid() && maximumDateMsecs() > 0) {
+ valid &= _date <= _maximumDate;
+ }
+
+ return valid;
+}
+}
+}
\ No newline at end of file
diff --git a/src/gui/filedetails/datefieldbackend.h b/src/gui/filedetails/datefieldbackend.h
new file mode 100644
index 0000000000000..c9e275dd04243
--- /dev/null
+++ b/src/gui/filedetails/datefieldbackend.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2023 by Claudio Cambra
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#pragma once
+
+#include
+#include
+
+class TestDateFieldBackend;
+
+namespace OCC
+{
+namespace Quick
+{
+
+class DateFieldBackend : public QObject
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QDate date READ date WRITE setDate NOTIFY dateChanged)
+ Q_PROPERTY(qint64 dateMsecs READ dateMsecs WRITE setDateMsecs NOTIFY dateMsecsChanged)
+ Q_PROPERTY(QString dateString READ dateString WRITE setDateString NOTIFY dateStringChanged)
+
+ Q_PROPERTY(QDate minimumDate READ minimumDate WRITE setMinimumDate NOTIFY minimumDateChanged)
+ Q_PROPERTY(qint64 minimumDateMsecs READ minimumDateMsecs WRITE setMinimumDateMsecs NOTIFY minimumDateMsecsChanged)
+
+ Q_PROPERTY(QDate maximumDate READ maximumDate WRITE setMaximumDate NOTIFY maximumDateChanged)
+ Q_PROPERTY(qint64 maximumDateMsecs READ maximumDateMsecs WRITE setMaximumDateMsecs NOTIFY maximumDateMsecsChanged)
+
+ Q_PROPERTY(bool validDate READ validDate NOTIFY validDateChanged)
+
+public:
+ explicit DateFieldBackend(QObject *const parent = nullptr);
+
+ [[nodiscard]] QDate date() const;
+ [[nodiscard]] qint64 dateMsecs() const;
+ [[nodiscard]] QString dateString() const;
+
+ [[nodiscard]] QDate minimumDate() const;
+ [[nodiscard]] qint64 minimumDateMsecs() const;
+
+ [[nodiscard]] QDate maximumDate() const;
+ [[nodiscard]] qint64 maximumDateMsecs() const;
+
+ [[nodiscard]] bool validDate() const;
+
+public slots:
+ void setDate(const QDate &date);
+ void setDateMsecs(const qint64 dateMsecs);
+ void setDateString(const QString &dateString);
+
+ void setMinimumDate(const QDate &minimumDate);
+ void setMinimumDateMsecs(const qint64 minimumDateMsecs);
+
+ void setMaximumDate(const QDate &maximumDate);
+ void setMaximumDateMsecs(const qint64 maximumDateMsecs);
+
+signals:
+ void dateChanged();
+ void dateMsecsChanged();
+ void dateStringChanged();
+
+ void minimumDateChanged();
+ void minimumDateMsecsChanged();
+
+ void maximumDateChanged();
+ void maximumDateMsecsChanged();
+
+ void validDateChanged();
+
+private:
+ friend class ::TestDateFieldBackend;
+
+ QDate _date = QDate::currentDate();
+ QDate _minimumDate;
+ QDate _maximumDate;
+
+ QString _dateFormat;
+ QString _leadingZeroMonthDateFormat;
+};
+
+} // namespace Quick
+} // namespace OCC
diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp
index 9d1d5856f3405..6749df8a6e91b 100644
--- a/src/gui/owncloudgui.cpp
+++ b/src/gui/owncloudgui.cpp
@@ -32,6 +32,7 @@
#include "theme.h"
#include "wheelhandler.h"
#include "syncconflictsmodel.h"
+#include "filedetails/datefieldbackend.h"
#include "filedetails/filedetails.h"
#include "filedetails/shareemodel.h"
#include "filedetails/sharemodel.h"
@@ -118,6 +119,7 @@ ownCloudGui::ownCloudGui(Application *parent)
qmlRegisterType("com.nextcloud.desktopclient", 1, 0, "SortedActivityListModel");
qmlRegisterType("com.nextcloud.desktopclient", 1, 0, "WheelHandler");
qmlRegisterType("com.nextcloud.desktopclient", 1, 0, "CallStateChecker");
+ qmlRegisterType("com.nextcloud.desktopclient", 1, 0, "DateFieldBackend");
qmlRegisterType("com.nextcloud.desktopclient", 1, 0, "FileDetails");
qmlRegisterType("com.nextcloud.desktopclient", 1, 0, "ShareModel");
qmlRegisterType("com.nextcloud.desktopclient", 1, 0, "ShareeModel");
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 69ebc08d518b4..e1ea80801ded0 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -74,6 +74,7 @@ nextcloud_add_test(SortedShareModel)
nextcloud_add_test(SecureFileDrop)
nextcloud_add_test(FileTagModel)
nextcloud_add_test(SyncConflictsModel)
+nextcloud_add_test(DateFieldBackend)
target_link_libraries(SecureFileDropTest PRIVATE Nextcloud::sync)
configure_file(fake2eelocksucceeded.json "${PROJECT_BINARY_DIR}/bin/fake2eelocksucceeded.json" COPYONLY)
diff --git a/test/testdatefieldbackend.cpp b/test/testdatefieldbackend.cpp
new file mode 100644
index 0000000000000..c80aa3a2db2e4
--- /dev/null
+++ b/test/testdatefieldbackend.cpp
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) by Claudio Cambra
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "gui/filedetails/datefieldbackend.h"
+
+#include
+#include
+
+using namespace OCC;
+
+class TestDateFieldBackend : public QObject
+{
+ Q_OBJECT
+
+private:
+ static constexpr auto dateStringFormat = "dd/MM/yyyy";
+
+private slots:
+ void testDefaultBehaviour()
+ {
+ Quick::DateFieldBackend backend;
+ backend._dateFormat = dateStringFormat;
+
+ const auto currentDate = QDate::currentDate();
+ const auto currentDateMSecs = currentDate.startOfDay(Qt::UTC).toMSecsSinceEpoch();
+ const auto currentDateString = currentDate.toString(dateStringFormat);
+
+ QCOMPARE(backend.date(), currentDate);
+ QCOMPARE(backend.dateMsecs(), currentDateMSecs);
+ QCOMPARE(backend.dateString(), currentDateString);
+ }
+
+ void testDateBoundaries()
+ {
+ Quick::DateFieldBackend backend;
+
+ QSignalSpy minimumDateChangedSpy(&backend, &Quick::DateFieldBackend::minimumDateChanged);
+ QSignalSpy maximumDateChangedSpy(&backend, &Quick::DateFieldBackend::maximumDateChanged);
+ QSignalSpy minimumDateMsecsChangedSpy(&backend, &Quick::DateFieldBackend::minimumDateMsecsChanged);
+ QSignalSpy maximumDateMsecsChangedSpy(&backend, &Quick::DateFieldBackend::maximumDateMsecsChanged);
+ QSignalSpy validDateChangedSpy(&backend, &Quick::DateFieldBackend::validDateChanged);
+
+ const auto minDate = QDate::currentDate().addDays(-5);
+ const auto maxDate = QDate::currentDate().addDays(5);
+ const auto minDateMs = minDate.startOfDay(Qt::UTC).toMSecsSinceEpoch();
+ const auto maxDateMs = maxDate.startOfDay(Qt::UTC).toMSecsSinceEpoch();
+ const auto invalidMinDate = minDate.addDays(-1);
+ const auto invalidMaxDate = maxDate.addDays(1);
+
+ // Set by QDate
+ backend.setMinimumDate(minDate);
+ backend.setMaximumDate(maxDate);
+
+ QCOMPARE(backend.minimumDate(), minDate);
+ QCOMPARE(backend.maximumDate(), maxDate);
+ QCOMPARE(backend.minimumDateMsecs(), minDateMs);
+ QCOMPARE(backend.maximumDateMsecs(), maxDateMs);
+
+ QCOMPARE(minimumDateChangedSpy.count(), 1);
+ QCOMPARE(maximumDateChangedSpy.count(), 1);
+ QCOMPARE(minimumDateMsecsChangedSpy.count(), 1);
+ QCOMPARE(maximumDateMsecsChangedSpy.count(), 1);
+ QCOMPARE(validDateChangedSpy.count(), 2); // Changes per each min/max date set
+
+ // Reset and try when setting by MSecs
+ backend.setMinimumDate({});
+ backend.setMaximumDate({});
+ backend.setMinimumDateMsecs(minDateMs);
+ backend.setMaximumDateMsecs(maxDateMs);
+
+ QCOMPARE(backend.minimumDate(), minDate);
+ QCOMPARE(backend.maximumDate(), maxDate);
+ QCOMPARE(backend.minimumDateMsecs(), minDateMs);
+ QCOMPARE(backend.maximumDateMsecs(), maxDateMs);
+
+ QCOMPARE(minimumDateChangedSpy.count(), 3);
+ QCOMPARE(maximumDateChangedSpy.count(), 3);
+ QCOMPARE(minimumDateMsecsChangedSpy.count(), 3);
+ QCOMPARE(maximumDateMsecsChangedSpy.count(), 3);
+ QCOMPARE(validDateChangedSpy.count(), 6);
+
+ // Since we default to the current date, the date should be valid
+ QVERIFY(backend.validDate());
+
+ // Now try with invalid dates
+ backend.setDate(invalidMinDate);
+ QVERIFY(!backend.validDate());
+ QCOMPARE(validDateChangedSpy.count(), 7);
+
+ backend.setDate(invalidMaxDate);
+ QVERIFY(!backend.validDate());
+ QCOMPARE(validDateChangedSpy.count(), 8);
+ }
+
+ void testDateSettingMethods()
+ {
+ Quick::DateFieldBackend backend;
+ backend._dateFormat = dateStringFormat;
+
+ QSignalSpy dateChangedSpy(&backend, &Quick::DateFieldBackend::dateChanged);
+ QSignalSpy dateMsecsChangedSpy(&backend, &Quick::DateFieldBackend::dateMsecsChanged);
+ QSignalSpy dateStringChangedSpy(&backend, &Quick::DateFieldBackend::dateStringChanged);
+
+ const auto testDate = QDate::currentDate().addDays(800);
+ const auto testDateMsecs = testDate.startOfDay(Qt::UTC).toMSecsSinceEpoch();
+ const auto testDateString = testDate.toString(dateStringFormat);
+
+ backend.setDate(testDate);
+ QCOMPARE(backend.date(), testDate);
+ QCOMPARE(dateChangedSpy.count(), 1);
+ QCOMPARE(dateMsecsChangedSpy.count(), 1);
+ QCOMPARE(dateStringChangedSpy.count(), 1);
+
+ backend.setDate({});
+ QVERIFY(backend.date() != testDate);
+ QCOMPARE(dateChangedSpy.count(), 2);
+ QCOMPARE(dateMsecsChangedSpy.count(), 2);
+ QCOMPARE(dateStringChangedSpy.count(), 2);
+
+ backend.setDateMsecs(testDateMsecs);
+ QCOMPARE(backend.date(), testDate);
+ QCOMPARE(dateChangedSpy.count(), 3);
+ QCOMPARE(dateMsecsChangedSpy.count(), 3);
+ QCOMPARE(dateStringChangedSpy.count(), 3);
+
+ backend.setDate({});
+ QVERIFY(backend.date() != testDate);
+ QCOMPARE(dateChangedSpy.count(), 4);
+ QCOMPARE(dateMsecsChangedSpy.count(), 4);
+ QCOMPARE(dateStringChangedSpy.count(), 4);
+
+ backend.setDateString(testDateString);
+ QCOMPARE(backend.date(), testDate);
+ QCOMPARE(dateChangedSpy.count(), 5);
+ QCOMPARE(dateMsecsChangedSpy.count(), 5);
+ QCOMPARE(dateStringChangedSpy.count(), 5);
+ }
+};
+
+QTEST_MAIN(TestDateFieldBackend)
+#include "testdatefieldbackend.moc"