File indexing completed on 2024-05-12 05:13:47
0001 /* 0002 SPDX-FileCopyrightText: 2018 Volker Krause <vkrause@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "modelverificationpoint.h" 0008 #include "testhelper.h" 0009 0010 #include "applicationcontroller.h" 0011 #include "favoritelocationmodel.h" 0012 #include "livedatamanager.h" 0013 #include "locationinformation.h" 0014 #include "pkpassmanager.h" 0015 #include "reservationmanager.h" 0016 #include "timelinemodel.h" 0017 #include "tripgroupmanager.h" 0018 #include "transfermanager.h" 0019 #include "weatherinformation.h" 0020 0021 #include "weatherforecast.h" 0022 #include "weatherforecastmanager.h" 0023 0024 #include <KItinerary/Flight> 0025 #include <KItinerary/Place> 0026 #include <KItinerary/Reservation> 0027 0028 #include <QAbstractItemModelTester> 0029 #include <QDirIterator> 0030 #include <QUrl> 0031 #include <QtTest/qtest.h> 0032 #include <QSignalSpy> 0033 #include <QStandardPaths> 0034 0035 void initLocale() 0036 { 0037 qputenv("LC_ALL", "en_US.utf-8"); 0038 qputenv("LANG", "en_US"); 0039 qputenv("TZ", "UTC"); 0040 } 0041 0042 Q_CONSTRUCTOR_FUNCTION(initLocale) 0043 0044 class TimelineModelTest : public QObject 0045 { 0046 Q_OBJECT 0047 private Q_SLOTS: 0048 void initTestCase() 0049 { 0050 QStandardPaths::setTestModeEnabled(true); 0051 } 0052 0053 void init() 0054 { 0055 TripGroupManager::clear(); 0056 } 0057 0058 void testElementCompare_data() 0059 { 0060 QTest::addColumn<TimelineElement>("lhs"); 0061 QTest::addColumn<TimelineElement>("rhs"); 0062 0063 const auto dt = QDateTime::currentDateTimeUtc(); 0064 0065 TimelineElement group(nullptr, TimelineElement::TripGroup, dt); 0066 group.rangeType = TimelineElement::RangeBegin; 0067 QTest::newRow("transfer-group-begin") << group << TimelineElement(nullptr, TimelineElement::Transfer, dt); 0068 group.rangeType = TimelineElement::RangeEnd; 0069 QTest::newRow("transfer-group-end") << TimelineElement(nullptr, TimelineElement::Transfer, dt) << group; 0070 0071 TimelineElement locInfo(nullptr, TimelineElement::LocationInfo, dt); 0072 TimelineElement hotel(nullptr, TimelineElement::Hotel, dt); 0073 hotel.rangeType = TimelineElement::RangeBegin; 0074 QTest::newRow("locinfo-before-hotel") << locInfo << hotel; 0075 TimelineElement flight(nullptr, TimelineElement::Flight, dt); 0076 QTest::newRow("locinfo-before-flight") << locInfo << flight; 0077 0078 QTest::newRow("location-info-before-transfer") << TimelineElement(nullptr, TimelineElement::LocationInfo, dt) << TimelineElement(nullptr, TimelineElement::Transfer, dt); 0079 } 0080 0081 void testElementCompare() 0082 { 0083 QFETCH(TimelineElement, lhs); 0084 QFETCH(TimelineElement, rhs); 0085 QCOMPARE(lhs < rhs, true); 0086 QCOMPARE(rhs < lhs, false); 0087 } 0088 0089 void testModel() 0090 { 0091 PkPassManager mgr; 0092 Test::clearAll(&mgr); 0093 ReservationManager resMgr; 0094 Test::clearAll(&resMgr); 0095 auto ctrl = Test::makeAppController(); 0096 ctrl->setPkPassManager(&mgr); 0097 ctrl->setReservationManager(&resMgr); 0098 0099 TimelineModel model; 0100 QAbstractItemModelTester tester(&model); 0101 model.setReservationManager(&resMgr); 0102 0103 QSignalSpy insertSpy(&model, &TimelineModel::rowsInserted); 0104 QVERIFY(insertSpy.isValid()); 0105 QSignalSpy updateSpy(&model, &TimelineModel::dataChanged); 0106 QVERIFY(updateSpy.isValid()); 0107 QSignalSpy rmSpy(&model, &TimelineModel::rowsRemoved); 0108 QVERIFY(rmSpy.isValid()); 0109 0110 QCOMPARE(model.rowCount(), 1); 0111 QCOMPARE(model.index(0, 0).data(TimelineModel::ElementTypeRole), TimelineElement::TodayMarker); 0112 ctrl->importFromUrl(QUrl::fromLocalFile(QLatin1StringView(SOURCE_DIR "/data/boardingpass-v1.pkpass"))); 0113 QCOMPARE(insertSpy.size(), 1); 0114 QCOMPARE(insertSpy.at(0).at(1).toInt(), 0); 0115 QCOMPARE(insertSpy.at(0).at(2).toInt(), 0); 0116 QVERIFY(updateSpy.isEmpty()); 0117 QCOMPARE(model.rowCount(), 2); 0118 QCOMPARE(model.index(0, 0).data(TimelineModel::ElementTypeRole), TimelineElement::Flight); 0119 0120 ctrl->importFromUrl(QUrl::fromLocalFile(QLatin1StringView(SOURCE_DIR "/data/boardingpass-v2.pkpass"))); 0121 QCOMPARE(insertSpy.size(), 1); 0122 QCOMPARE(updateSpy.size(), 1); 0123 QCOMPARE(updateSpy.at(0).at(0).toModelIndex().row(), 0); 0124 QCOMPARE(model.rowCount(), 2); 0125 0126 Test::clearAll(&resMgr); 0127 QCOMPARE(insertSpy.size(), 1); 0128 QCOMPARE(updateSpy.size(), 1); 0129 QCOMPARE(rmSpy.size(), 1); 0130 QCOMPARE(model.rowCount(), 1); 0131 } 0132 0133 void testNestedElements() 0134 { 0135 ReservationManager resMgr; 0136 Test::clearAll(&resMgr); 0137 auto ctrl = Test::makeAppController(); 0138 ctrl->setReservationManager(&resMgr); 0139 0140 TimelineModel model; 0141 QAbstractItemModelTester tester(&model); 0142 model.setHomeCountryIsoCode(QStringLiteral("DE")); 0143 model.setReservationManager(&resMgr); 0144 0145 QSignalSpy insertSpy(&model, &TimelineModel::rowsInserted); 0146 QVERIFY(insertSpy.isValid()); 0147 QSignalSpy updateSpy(&model, &TimelineModel::dataChanged); 0148 QVERIFY(updateSpy.isValid()); 0149 QSignalSpy rmSpy(&model, &TimelineModel::rowsRemoved); 0150 QVERIFY(rmSpy.isValid()); 0151 0152 QCOMPARE(model.rowCount(), 1); 0153 QCOMPARE(model.index(0, 0).data(TimelineModel::ElementTypeRole), TimelineElement::TodayMarker); 0154 ctrl->importFromUrl(QUrl::fromLocalFile(QLatin1StringView(SOURCE_DIR "/data/haus-randa-v1.json"))); 0155 QCOMPARE(insertSpy.size(), 3); 0156 QCOMPARE(insertSpy.at(0).at(1).toInt(), 0); 0157 QCOMPARE(insertSpy.at(0).at(2).toInt(), 0); 0158 QCOMPARE(insertSpy.at(1).at(1).toInt(), 1); 0159 QCOMPARE(insertSpy.at(1).at(2).toInt(), 1); 0160 QVERIFY(updateSpy.isEmpty()); 0161 QCOMPARE(model.rowCount(), 4); 0162 QCOMPARE(model.index(0, 0).data(TimelineModel::ElementTypeRole), TimelineElement::LocationInfo); 0163 QCOMPARE(model.index(1, 0).data(TimelineModel::ElementTypeRole), TimelineElement::Hotel); 0164 QCOMPARE(model.index(1, 0).data(TimelineModel::ElementRangeRole), TimelineElement::RangeBegin); 0165 QCOMPARE(model.index(2, 0).data(TimelineModel::ElementTypeRole), TimelineElement::Hotel); 0166 QCOMPARE(model.index(2, 0).data(TimelineModel::ElementRangeRole), TimelineElement::RangeEnd); 0167 0168 // move end date of a hotel booking: dataChanged on RangeBegin, move (or del/ins) on RangeEnd 0169 ctrl->importFromUrl(QUrl::fromLocalFile(QLatin1StringView(SOURCE_DIR "/data/haus-randa-v2.json"))); 0170 QCOMPARE(insertSpy.size(), 5); 0171 QCOMPARE(updateSpy.size(), 1); 0172 QCOMPARE(rmSpy.size(), 2); 0173 QCOMPARE(updateSpy.at(0).at(0).toModelIndex().row(), 1); 0174 QCOMPARE(insertSpy.at(2).at(1).toInt(), 0); 0175 QCOMPARE(insertSpy.at(2).at(2).toInt(), 0); 0176 QCOMPARE(rmSpy.at(0).at(1), 2); 0177 QCOMPARE(model.rowCount(), 4); 0178 0179 // delete a split element 0180 const auto resId = model.data(model.index(1, 0), TimelineModel::BatchIdRole).toString(); 0181 QVERIFY(!resId.isEmpty()); 0182 resMgr.removeReservation(resId); 0183 QCOMPARE(rmSpy.size(), 5); 0184 QCOMPARE(model.rowCount(), 1); 0185 QCOMPARE(model.index(0, 0).data(TimelineModel::ElementTypeRole), TimelineElement::TodayMarker); 0186 } 0187 0188 void testCountryInfos() 0189 { 0190 ReservationManager resMgr; 0191 Test::clearAll(&resMgr); 0192 auto ctrl = Test::makeAppController(); 0193 ctrl->setReservationManager(&resMgr); 0194 0195 TimelineModel model; 0196 QAbstractItemModelTester tester(&model); 0197 model.setHomeCountryIsoCode(QStringLiteral("DE")); 0198 model.setReservationManager(&resMgr); 0199 0200 QCOMPARE(model.rowCount(), 1); 0201 QCOMPARE(model.index(0, 0).data(TimelineModel::ElementTypeRole), TimelineElement::TodayMarker); 0202 0203 ctrl->importFromUrl(QUrl::fromLocalFile(QLatin1StringView(SOURCE_DIR "/data/flight-txl-lhr-sfo.json"))); 0204 QCOMPARE(model.rowCount(), 5); // 2x country info, 2x flights, today marker 0205 0206 QCOMPARE(model.index(0, 0).data(TimelineModel::ElementTypeRole), TimelineElement::Flight); 0207 QCOMPARE(model.index(1, 0).data(TimelineModel::ElementTypeRole), TimelineElement::LocationInfo); 0208 auto countryInfo = model.index(1, 0).data(TimelineModel::LocationInformationRole).value<LocationInformation>(); 0209 QCOMPARE(countryInfo.drivingSide(), KItinerary::KnowledgeDb::DrivingSide::Left); 0210 QCOMPARE(countryInfo.drivingSideDiffers(), true); 0211 QCOMPARE(countryInfo.powerPlugCompatibility(), LocationInformation::Incompatible); 0212 0213 QCOMPARE(model.index(2, 0).data(TimelineModel::ElementTypeRole), TimelineElement::Flight); 0214 QCOMPARE(model.index(3, 0).data(TimelineModel::ElementTypeRole), TimelineElement::LocationInfo); 0215 countryInfo = model.index(3, 0).data(TimelineModel::LocationInformationRole).value<LocationInformation>(); 0216 QCOMPARE(countryInfo.drivingSide(), KItinerary::KnowledgeDb::DrivingSide::Right); 0217 QCOMPARE(countryInfo.drivingSideDiffers(), false); 0218 QCOMPARE(countryInfo.powerPlugCompatibility(), LocationInformation::Incompatible); 0219 QCOMPARE(model.index(4, 0).data(TimelineModel::ElementTypeRole), TimelineElement::TodayMarker); 0220 0221 // remove the GB flight should also remove the GB country info 0222 auto resId = model.index(0, 0).data(TimelineModel::BatchIdRole).toString(); 0223 resMgr.removeReservation(resId); 0224 QCOMPARE(model.rowCount(), 3); 0225 QCOMPARE(model.index(0, 0).data(TimelineModel::ElementTypeRole), TimelineElement::Flight); 0226 QCOMPARE(model.index(1, 0).data(TimelineModel::ElementTypeRole), TimelineElement::LocationInfo); 0227 QCOMPARE(model.index(2, 0).data(TimelineModel::ElementTypeRole), TimelineElement::TodayMarker); 0228 0229 // remove the US flight should also remove the US country info 0230 resId = model.index(0, 0).data(TimelineModel::BatchIdRole).toString(); 0231 resMgr.removeReservation(resId); 0232 QCOMPARE(model.rowCount(), 1); 0233 QCOMPARE(model.index(0, 0).data(TimelineModel::ElementTypeRole), TimelineElement::TodayMarker); 0234 } 0235 0236 void testWeatherElements() 0237 { 0238 using namespace KItinerary; 0239 0240 ReservationManager resMgr; 0241 Test::clearAll(&resMgr); 0242 WeatherForecastManager weatherMgr; 0243 weatherMgr.setTestModeEnabled(true); 0244 0245 TimelineModel model; 0246 QAbstractItemModelTester tester(&model); 0247 model.setReservationManager(&resMgr); 0248 model.setWeatherForecastManager(&weatherMgr); 0249 QCOMPARE(model.rowCount(), 1); // no weather data, as we don't know where we are 0250 0251 // Add an element that will result in a defined location 0252 GeoCoordinates geo; 0253 geo.setLatitude(52.0f); 0254 geo.setLongitude(13.0f); 0255 Airport a; 0256 a.setGeo(geo); 0257 Flight f; 0258 f.setArrivalAirport(a); 0259 f.setDepartureTime(QDateTime(QDate(2018, 1, 1), QTime(0, 0))); 0260 FlightReservation res; 0261 res.setReservationFor(f); 0262 resMgr.addReservation(res); 0263 0264 // if there's only <1h left in a day, we wont get a weather element 0265 auto currentDate = QDate::currentDate(); 0266 if (QDateTime::currentDateTimeUtc().time().hour() >= 23) { 0267 currentDate = currentDate.addDays(1); 0268 } 0269 0270 QCOMPARE(model.rowCount(), 11); // 1x flight, 1x today, 9x weather 0271 QCOMPARE(model.index(0, 0).data(TimelineModel::ElementTypeRole), TimelineElement::Flight); 0272 QCOMPARE(model.index(1, 0).data(TimelineModel::ElementTypeRole), TimelineElement::TodayMarker); 0273 QCOMPARE(model.index(2, 0).data(TimelineModel::ElementTypeRole), TimelineElement::WeatherForecast); 0274 auto fc = model.index(2, 0).data(TimelineModel::WeatherForecastRole).value<WeatherInformation>().forecast; 0275 QVERIFY(fc.isValid()); 0276 QCOMPARE(fc.dateTime().date(), currentDate); 0277 QCOMPARE(fc.minimumTemperature(), 13.0f); 0278 QCOMPARE(fc.maximumTemperature(), 52.0f); 0279 QCOMPARE(model.index(10, 0).data(TimelineModel::ElementTypeRole), TimelineElement::WeatherForecast); 0280 fc = model.index(10, 0).data(TimelineModel::WeatherForecastRole).value<WeatherInformation>().forecast; 0281 QVERIFY(fc.isValid()); 0282 QCOMPARE(fc.dateTime().date(), currentDate.addDays(8)); 0283 0284 // Add a flight one day from now changing location mid-day 0285 geo.setLatitude(46.0f); 0286 geo.setLongitude(8.0f); 0287 a.setGeo(geo); 0288 f.setArrivalAirport(a); 0289 f.setDepartureTime(QDateTime(currentDate.addDays(1), QTime(12, 0))); 0290 f.setArrivalTime(QDateTime(currentDate.addDays(1), QTime(14, 0))); 0291 res.setReservationFor(f); 0292 resMgr.addReservation(res); 0293 0294 QCOMPARE(model.rowCount(), 13); // 2x flight, 1x today, 10x weather 0295 QCOMPARE(model.index(0, 0).data(TimelineModel::ElementTypeRole), TimelineElement::Flight); 0296 QCOMPARE(model.index(1, 0).data(TimelineModel::ElementTypeRole), TimelineElement::TodayMarker); 0297 QCOMPARE(model.index(2, 0).data(TimelineModel::ElementTypeRole), TimelineElement::WeatherForecast); 0298 fc = model.index(2, 0).data(TimelineModel::WeatherForecastRole).value<WeatherInformation>().forecast; 0299 QVERIFY(fc.isValid()); 0300 QCOMPARE(fc.dateTime().date(), currentDate); 0301 QCOMPARE(model.index(3, 0).data(TimelineModel::ElementTypeRole), TimelineElement::WeatherForecast); 0302 fc = model.index(3, 0).data(TimelineModel::WeatherForecastRole).value<WeatherInformation>().forecast; 0303 QVERIFY(fc.isValid()); 0304 QCOMPARE(fc.minimumTemperature(), 13.0f); 0305 QCOMPARE(fc.maximumTemperature(), 52.0f); 0306 QCOMPARE(fc.dateTime(), QDateTime(currentDate.addDays(1), QTime(0, 0))); 0307 QCOMPARE(model.index(4, 0).data(TimelineModel::ElementTypeRole), TimelineElement::Flight); 0308 QCOMPARE(model.index(5, 0).data(TimelineModel::ElementTypeRole), TimelineElement::WeatherForecast); 0309 fc = model.index(5, 0).data(TimelineModel::WeatherForecastRole).value<WeatherInformation>().forecast; 0310 QVERIFY(fc.isValid()); 0311 QCOMPARE(fc.minimumTemperature(), 8.0f); 0312 QCOMPARE(fc.maximumTemperature(), 46.0f); 0313 QCOMPARE(fc.dateTime(), QDateTime(currentDate.addDays(1), QTime(14, 0))); 0314 QCOMPARE(model.index(6, 0).data(TimelineModel::ElementTypeRole), TimelineElement::WeatherForecast); 0315 fc = model.index(6, 0).data(TimelineModel::WeatherForecastRole).value<WeatherInformation>().forecast; 0316 QCOMPARE(fc.minimumTemperature(), 8.0f); 0317 QCOMPARE(fc.maximumTemperature(), 46.0f); 0318 QVERIFY(fc.isValid()); 0319 QCOMPARE(fc.dateTime(), QDateTime(currentDate.addDays(2), QTime(0, 0))); 0320 0321 // check we get update signals for all weather elements 0322 QSignalSpy spy(&model, &TimelineModel::dataChanged); 0323 QVERIFY(spy.isValid()); 0324 Q_EMIT weatherMgr.forecastUpdated(); 0325 QCOMPARE(spy.size(), 10); 0326 0327 fc = model.index(3, 0).data(TimelineModel::WeatherForecastRole).value<WeatherInformation>().forecast; 0328 QVERIFY(fc.isValid()); 0329 QCOMPARE(fc.minimumTemperature(), 13.0f); 0330 QCOMPARE(fc.maximumTemperature(), 52.0f); 0331 QCOMPARE(fc.dateTime(), QDateTime(currentDate.addDays(1), QTime(0, 0))); 0332 fc = model.index(9, 0).data(TimelineModel::WeatherForecastRole).value<WeatherInformation>().forecast; 0333 QVERIFY(fc.isValid()); 0334 QCOMPARE(fc.minimumTemperature(), 8.0f); 0335 QCOMPARE(fc.maximumTemperature(), 46.0f); 0336 QCOMPARE(fc.dateTime(), QDateTime(currentDate.addDays(5), QTime(0, 0))); 0337 0338 // add a location change far in the future, this must not change anything 0339 geo.setLatitude(60.0f); 0340 geo.setLongitude(11.0f); 0341 a.setGeo(geo); 0342 f.setArrivalAirport(a); 0343 f.setDepartureTime(QDateTime(currentDate.addYears(1), QTime(6, 0))); 0344 res.setReservationFor(f); 0345 resMgr.addReservation(res); 0346 QCOMPARE(model.rowCount(), 14); 0347 0348 fc = model.index(3, 0).data(TimelineModel::WeatherForecastRole).value<WeatherInformation>().forecast; 0349 QVERIFY(fc.isValid()); 0350 QCOMPARE(fc.minimumTemperature(), 13.0f); 0351 QCOMPARE(fc.maximumTemperature(), 52.0f); 0352 QCOMPARE(fc.dateTime(), QDateTime(currentDate.addDays(1), QTime(0, 0))); 0353 fc = model.index(9, 0).data(TimelineModel::WeatherForecastRole).value<WeatherInformation>().forecast; 0354 QVERIFY(fc.isValid()); 0355 QCOMPARE(fc.minimumTemperature(), 8.0f); 0356 QCOMPARE(fc.maximumTemperature(), 46.0f); 0357 QCOMPARE(fc.dateTime(), QDateTime(currentDate.addDays(5), QTime(0, 0))); 0358 0359 // result is the same when data hasn't been added incrementally 0360 model.setReservationManager(nullptr); 0361 model.setReservationManager(&resMgr); 0362 QCOMPARE(model.rowCount(), 14); 0363 0364 fc = model.index(3, 0).data(TimelineModel::WeatherForecastRole).value<WeatherInformation>().forecast; 0365 QVERIFY(fc.isValid()); 0366 QCOMPARE(fc.minimumTemperature(), 13.0f); 0367 QCOMPARE(fc.maximumTemperature(), 52.0f); 0368 QCOMPARE(fc.dateTime(), QDateTime(currentDate.addDays(1), QTime(0, 0))); 0369 fc = model.index(9, 0).data(TimelineModel::WeatherForecastRole).value<WeatherInformation>().forecast; 0370 QVERIFY(fc.isValid()); 0371 QCOMPARE(fc.minimumTemperature(), 8.0f); 0372 QCOMPARE(fc.maximumTemperature(), 46.0f); 0373 QCOMPARE(fc.dateTime(), QDateTime(currentDate.addDays(5), QTime(0, 0))); 0374 0375 // clean up 0376 auto resId = model.index(13, 0).data(TimelineModel::BatchIdRole).toString(); 0377 resMgr.removeReservation(resId); 0378 resId = model.index(4, 0).data(TimelineModel::BatchIdRole).toString(); 0379 resMgr.removeReservation(resId); 0380 QCOMPARE(model.rowCount(), 11); 0381 0382 // test case: two consecutive location changes, the first one to an unknown location 0383 // result: the weather element before the first location change ends with the start of that 0384 // result 2: we get a second weather element the same day after the second location change 0385 // TODO 0386 } 0387 0388 // multi-day event at different location from last known location, but no explicit transfers/location changes 0389 // should produce weather elements on event site 0390 void testWeatherNoLocationChange() 0391 { 0392 ReservationManager resMgr; 0393 Test::clearAll(&resMgr); 0394 auto ctrl = Test::makeAppController(); 0395 ctrl->setReservationManager(&resMgr); 0396 WeatherForecastManager weatherMgr; 0397 weatherMgr.setTestModeEnabled(true); 0398 0399 TimelineModel model; 0400 QAbstractItemModelTester tester(&model); 0401 model.setHomeCountryIsoCode(QStringLiteral("DE")); 0402 model.setCurrentDateTime(QDateTime({2023, 3, 16}, {12, 34})); 0403 model.setReservationManager(&resMgr); 0404 model.setWeatherForecastManager(&weatherMgr); 0405 0406 ctrl->importFromUrl(QUrl::fromLocalFile(QLatin1StringView(SOURCE_DIR "/data/weather-no-location-change.json"))); 0407 ModelVerificationPoint vp0(QLatin1StringView(SOURCE_DIR "/data/weather-no-location-change.model")); 0408 vp0.setRoleFilter({TimelineModel::BatchIdRole}); 0409 QVERIFY(vp0.verify(&model)); 0410 } 0411 0412 void testMultiTraveller() 0413 { 0414 using namespace KItinerary; 0415 0416 ReservationManager resMgr; 0417 Test::clearAll(&resMgr); 0418 auto ctrl = Test::makeAppController(); 0419 ctrl->setReservationManager(&resMgr); 0420 TimelineModel model; 0421 QAbstractItemModelTester tester(&model); 0422 model.setReservationManager(&resMgr); 0423 QCOMPARE(model.rowCount(), 1); // 1x TodayMarker 0424 0425 QSignalSpy insertSpy(&model, &TimelineModel::rowsInserted); 0426 QVERIFY(insertSpy.isValid()); 0427 QSignalSpy updateSpy(&model, &TimelineModel::dataChanged); 0428 QVERIFY(updateSpy.isValid()); 0429 QSignalSpy rmSpy(&model, &TimelineModel::rowsRemoved); 0430 QVERIFY(rmSpy.isValid()); 0431 0432 // full import at runtime 0433 ctrl->importFromUrl(QUrl::fromLocalFile(QLatin1StringView(SOURCE_DIR "/data/google-multi-passenger-flight.json"))); 0434 QCOMPARE(model.rowCount(), 3); // 2x Flight, 1x TodayMarger 0435 QCOMPARE(insertSpy.count(), 2); 0436 QCOMPARE(updateSpy.count(), 2); 0437 QCOMPARE(model.index(0, 0).data(TimelineModel::ElementTypeRole), TimelineElement::Flight); 0438 QCOMPARE(model.index(1, 0).data(TimelineModel::ElementTypeRole), TimelineElement::Flight); 0439 QCOMPARE(model.index(2, 0).data(TimelineModel::ElementTypeRole), TimelineElement::TodayMarker); 0440 QCOMPARE(resMgr.reservationsForBatch(model.index(0, 0).data(TimelineModel::BatchIdRole).toString()).size(), 2); 0441 QCOMPARE(resMgr.reservationsForBatch(model.index(1, 0).data(TimelineModel::BatchIdRole).toString()).size(), 2); 0442 0443 // already existing data 0444 model.setReservationManager(nullptr); 0445 model.setReservationManager(&resMgr); 0446 QCOMPARE(model.rowCount(), 3); // 2x Flight, 1x TodayMarger 0447 QCOMPARE(model.index(0, 0).data(TimelineModel::ElementTypeRole), TimelineElement::Flight); 0448 QCOMPARE(model.index(1, 0).data(TimelineModel::ElementTypeRole), TimelineElement::Flight); 0449 QCOMPARE(model.index(2, 0).data(TimelineModel::ElementTypeRole), TimelineElement::TodayMarker); 0450 QCOMPARE(resMgr.reservationsForBatch(model.index(0, 0).data(TimelineModel::BatchIdRole).toString()).size(), 2); 0451 QCOMPARE(resMgr.reservationsForBatch(model.index(1, 0).data(TimelineModel::BatchIdRole).toString()).size(), 2); 0452 0453 // update splits element 0454 updateSpy.clear(); 0455 insertSpy.clear(); 0456 auto resId = model.index(1, 0).data(TimelineModel::BatchIdRole).toString(); 0457 QVERIFY(!resId.isEmpty()); 0458 auto res = resMgr.reservation(resId).value<FlightReservation>(); 0459 auto flight = res.reservationFor().value<Flight>(); 0460 flight.setDepartureTime(flight.departureTime().addDays(1)); 0461 res.setReservationFor(flight); 0462 resMgr.updateReservation(resId, res); 0463 QCOMPARE(model.rowCount(), 4); 0464 QCOMPARE(updateSpy.count(), 1); 0465 QCOMPARE(insertSpy.count(), 1); 0466 QCOMPARE(rmSpy.count(), 0); 0467 0468 // update merges two elements 0469 updateSpy.clear(); 0470 insertSpy.clear(); 0471 rmSpy.clear(); 0472 flight.setDepartureTime(flight.departureTime().addDays(-1)); 0473 res.setReservationFor(flight); 0474 resMgr.updateReservation(resId, res); 0475 QCOMPARE(model.rowCount(), 3); 0476 QCOMPARE(updateSpy.count(), 1); 0477 QCOMPARE(rmSpy.count(), 1); 0478 QCOMPARE(insertSpy.count(), 0); 0479 0480 // removal of merged items 0481 updateSpy.clear(); 0482 rmSpy.clear(); 0483 Test::clearAll(&resMgr); 0484 QCOMPARE(model.rowCount(), 1); 0485 QCOMPARE(rmSpy.count(), 2); 0486 QCOMPARE(updateSpy.count(), 2); 0487 } 0488 0489 void testDayChange() 0490 { 0491 ReservationManager resMgr; 0492 Test::clearAll(&resMgr); 0493 auto ctrl = Test::makeAppController(); 0494 ctrl->setReservationManager(&resMgr); 0495 WeatherForecastManager weatherMgr; 0496 weatherMgr.setTestModeEnabled(true); 0497 0498 TimelineModel model; 0499 QAbstractItemModelTester tester(&model); 0500 model.setHomeCountryIsoCode(QStringLiteral("DE")); 0501 model.setCurrentDateTime(QDateTime({2196, 10, 14}, {12, 34})); 0502 model.setReservationManager(&resMgr); 0503 model.setWeatherForecastManager(&weatherMgr); 0504 0505 ModelVerificationPoint vp0(QLatin1StringView(SOURCE_DIR "/data/timeline/daychange-r0.model")); 0506 vp0.setRoleFilter({TimelineModel::BatchIdRole}); 0507 QVERIFY(vp0.verify(&model)); 0508 0509 // changing the day should move the today marker 0510 model.setCurrentDateTime(QDateTime({2196, 10, 15}, {0, 15})); 0511 ModelVerificationPoint vp1(QLatin1StringView(SOURCE_DIR "/data/timeline/daychange-r1.model")); 0512 vp1.setRoleFilter({TimelineModel::BatchIdRole}); 0513 QVERIFY(vp1.verify(&model)); 0514 0515 // load something to define the current location, so we get weather 0516 ctrl->importFromUrl(QUrl::fromLocalFile(QLatin1StringView(SOURCE_DIR "/data/flight-txl-lhr-sfo.json"))); 0517 ModelVerificationPoint vp2(QLatin1StringView(SOURCE_DIR "/data/timeline/daychange-r2.model")); 0518 vp2.setRoleFilter({TimelineModel::BatchIdRole}); 0519 QVERIFY(vp2.verify(&model)); 0520 0521 // changing the day should move the today marker and weather one day forward 0522 model.setCurrentDateTime(QDateTime({2196, 10, 16}, {19, 30})); 0523 ModelVerificationPoint vp3(QLatin1StringView(SOURCE_DIR "/data/timeline/daychange-r3.model")); 0524 vp3.setRoleFilter({TimelineModel::BatchIdRole}); 0525 QVERIFY(vp3.verify(&model)); 0526 } 0527 0528 void testContent_data() 0529 { 0530 QTest::addColumn<QString>("baseName"); 0531 0532 QDirIterator it(QLatin1StringView(SOURCE_DIR "/data/timeline/"), {QLatin1StringView("*.json")}); 0533 while (it.hasNext()) { 0534 it.next(); 0535 const auto baseName = it.fileInfo().baseName(); 0536 QTest::newRow(baseName.toUtf8().constData()) << baseName; 0537 } 0538 } 0539 0540 void testContent() 0541 { 0542 QFETCH(QString, baseName); 0543 ReservationManager resMgr; 0544 Test::clearAll(&resMgr); 0545 auto ctrl = Test::makeAppController(); 0546 ctrl->setReservationManager(&resMgr); 0547 ctrl->importFromUrl(QUrl::fromLocalFile(QLatin1StringView(SOURCE_DIR "/data/timeline/") + baseName + QLatin1StringView(".json"))); 0548 TripGroupManager groupMgr; 0549 groupMgr.setReservationManager(&resMgr); 0550 WeatherForecastManager weatherMgr; 0551 weatherMgr.setTestModeEnabled(true); 0552 0553 FavoriteLocationModel favLocModel; 0554 while (favLocModel.rowCount()) { 0555 favLocModel.removeLocation(0); 0556 } 0557 FavoriteLocation favLoc; 0558 favLoc.setLatitude(52.51860f); 0559 favLoc.setLongitude(13.37630f); 0560 favLoc.setName(QStringLiteral("Home")); 0561 favLocModel.setFavoriteLocations({favLoc}); 0562 QCOMPARE(favLocModel.rowCount(), 1); 0563 0564 LiveDataManager liveMgr; 0565 0566 TransferManager::clear(); 0567 TransferManager transferMgr; 0568 transferMgr.overrideCurrentDateTime(QDateTime({1996, 10, 14}, {12, 34})); 0569 transferMgr.setReservationManager(&resMgr); 0570 transferMgr.setTripGroupManager(&groupMgr); 0571 transferMgr.setFavoriteLocationModel(&favLocModel); 0572 transferMgr.setLiveDataManager(&liveMgr); 0573 0574 TimelineModel model; 0575 QAbstractItemModelTester tester(&model); 0576 model.setHomeCountryIsoCode(QStringLiteral("DE")); 0577 model.setCurrentDateTime(QDateTime({1996, 10, 14}, {12, 34})); 0578 model.setReservationManager(&resMgr); 0579 model.setTripGroupManager(&groupMgr); 0580 model.setWeatherForecastManager(&weatherMgr); 0581 model.setTransferManager(&transferMgr); 0582 0583 // check state is correct for data imported at the start 0584 ModelVerificationPoint vp(QLatin1StringView(SOURCE_DIR "/data/timeline/") + baseName + QLatin1StringView(".model")); 0585 vp.setRoleFilter({TimelineModel::BatchIdRole, TimelineModel::TripGroupIdRole}); 0586 vp.setJsonPropertyFilter({QStringLiteral("reservationId")}); 0587 QVERIFY(vp.verify(&model)); 0588 0589 // retry with loading during runtime 0590 Test::clearAll(&resMgr); 0591 ctrl->importFromUrl(QUrl::fromLocalFile(QLatin1StringView(SOURCE_DIR "/data/timeline/") + baseName + QLatin1StringView(".json"))); 0592 QVERIFY(vp.verify(&model)); 0593 } 0594 0595 void testCurrentReservation() 0596 { 0597 ReservationManager resMgr; 0598 Test::clearAll(&resMgr); 0599 auto ctrl = Test::makeAppController(); 0600 ctrl->setReservationManager(&resMgr); 0601 0602 TimelineModel model; 0603 model.setCurrentDateTime(QDateTime({2017, 8, 1}, {23, 0}, QTimeZone("Europe/Zurich"))); 0604 QAbstractItemModelTester tester(&model); 0605 model.setReservationManager(&resMgr); 0606 0607 QSignalSpy currentResChangedSpy(&model, &TimelineModel::currentBatchChanged); 0608 0609 ctrl->importFromUrl(QUrl::fromLocalFile(QLatin1StringView(SOURCE_DIR "/../tests/randa2017.json"))); 0610 QCOMPARE(model.rowCount(), 13); 0611 QVERIFY(!currentResChangedSpy.empty()); 0612 0613 model.setCurrentDateTime(QDateTime({2017, 8, 1}, {23, 0}, QTimeZone("Europe/Zurich"))); 0614 QVERIFY(model.currentBatchId().isEmpty()); 0615 0616 model.setCurrentDateTime(QDateTime({2017, 9, 9}, {23, 0}, QTimeZone("Europe/Zurich"))); 0617 QVERIFY(!model.currentBatchId().isEmpty()); 0618 QCOMPARE(model.currentBatchId(), model.index(1, 0).data(TimelineModel::BatchIdRole).toString()); 0619 0620 model.setCurrentDateTime(QDateTime({2017, 9, 10}, {14, 0}, QTimeZone("Europe/Zurich"))); 0621 QCOMPARE(model.index(2, 0).data(TimelineModel::ElementTypeRole), TimelineElement::TrainTrip); 0622 QVERIFY(!model.currentBatchId().isEmpty()); 0623 QCOMPARE(model.currentBatchId(), model.index(2, 0).data(TimelineModel::BatchIdRole).toString()); 0624 0625 model.setCurrentDateTime(QDateTime({2017, 9, 10}, {14, 5}, QTimeZone("Europe/Zurich"))); 0626 QVERIFY(!model.currentBatchId().isEmpty()); 0627 QCOMPARE(model.currentBatchId(), model.index(3, 0).data(TimelineModel::BatchIdRole).toString()); 0628 0629 model.setCurrentDateTime(QDateTime({2017, 9, 10}, {20, 0}, QTimeZone("Europe/Zurich"))); 0630 QVERIFY(model.currentBatchId().isEmpty()); 0631 0632 model.setCurrentDateTime(QDateTime({2019, 1, 1}, {0, 0}, QTimeZone("Europe/Zurich"))); 0633 QVERIFY(model.currentBatchId().isEmpty()); 0634 } 0635 }; 0636 0637 QTEST_GUILESS_MAIN(TimelineModelTest) 0638 0639 #include "timelinemodeltest.moc"