Commit 19bfad50 authored by Volker Krause's avatar Volker Krause

Add unit test for weather forecast elements in the timeline

And following that, fix a number of previously unhandled cases.
parent 2bffe6fa
......@@ -20,6 +20,13 @@
#include <reservationmanager.h>
#include <timelinemodel.h>
#include <weatherforecast.h>
#include <weatherforecastmanager.h>
#include <KItinerary/Flight>
#include <KItinerary/Place>
#include <KItinerary/Reservation>
#include <QUrl>
#include <QtTest/qtest.h>
#include <QSignalSpy>
......@@ -185,6 +192,46 @@ private slots:
QCOMPARE(model.rowCount(), 1);
QCOMPARE(model.index(0, 0).data(TimelineModel::ElementTypeRole), TimelineModel::TodayMarker);
}
void testWeatherElements()
{
using namespace KItinerary;
ReservationManager resMgr;
clearReservations(&resMgr);
WeatherForecastManager weatherMgr;
weatherMgr.setTestModeEnabled(true);
TimelineModel model;
model.setReservationManager(&resMgr);
model.setWeatherForecastManager(&weatherMgr);
QCOMPARE(model.rowCount(), 1); // no weather data, as we don't know where we are
// Add an element that will result in a defined location
GeoCoordinates geo;
geo.setLatitude(52.0f);
geo.setLongitude(13.0f);
Airport a;
a.setGeo(geo);
Flight f;
f.setArrivalAirport(a);
f.setDepartureTime(QDateTime(QDate(2018, 1, 1), QTime(0, 0)));
FlightReservation res;
res.setReservationFor(f);
resMgr.addReservation(res);
QCOMPARE(model.rowCount(), 11); // 1x flight, 1x today, 9x weather
QCOMPARE(model.index(0, 0).data(TimelineModel::ElementTypeRole), TimelineModel::Flight);
QCOMPARE(model.index(1, 0).data(TimelineModel::ElementTypeRole), TimelineModel::TodayMarker);
QCOMPARE(model.index(2, 0).data(TimelineModel::ElementTypeRole), TimelineModel::WeatherForecast);
auto fc = model.index(2, 0).data(TimelineModel::WeatherForecastRole).value<WeatherForecast>();
QVERIFY(fc.isValid());
QCOMPARE(fc.dateTime().date(), QDate::currentDate());
QCOMPARE(model.index(10, 0).data(TimelineModel::ElementTypeRole), TimelineModel::WeatherForecast);
fc = model.index(10, 0).data(TimelineModel::WeatherForecastRole).value<WeatherForecast>();
QVERIFY(fc.isValid());
QCOMPARE(fc.dateTime().date(), QDate::currentDate().addDays(8));
}
};
QTEST_GUILESS_MAIN(TimelineModelTest)
......
......@@ -106,6 +106,36 @@ static QString destinationCountry(const QVariant &res)
return {};
}
static GeoCoordinates geoCoordinate(const QVariant &res)
{
if (JsonLd::isA<FlightReservation>(res)) {
return res.value<FlightReservation>().reservationFor().value<KItinerary::Flight>().arrivalAirport().geo();
}
if (JsonLd::isA<TrainReservation>(res)) {
return res.value<TrainReservation>().reservationFor().value<KItinerary::TrainTrip>().arrivalStation().geo();
}
if (JsonLd::isA<BusReservation>(res)) {
return res.value<BusReservation>().reservationFor().value<KItinerary::BusTrip>().arrivalStation().geo();
}
if (JsonLd::isA<LodgingReservation>(res)) {
return res.value<LodgingReservation>().reservationFor().value<LodgingBusiness>().geo();
}
if (JsonLd::isA<FoodEstablishmentReservation>(res)) {
return res.value<FoodEstablishmentReservation>().reservationFor().value<FoodEstablishment>().geo();
}
if (JsonLd::isA<TouristAttractionVisit>(res)) {
return res.value<TouristAttractionVisit>().touristAttraction().geo();
}
return {};
}
static bool isLocationChange(const QVariant &res)
{
return JsonLd::isA<FlightReservation>(res) || JsonLd::isA<TrainReservation>(res) || JsonLd::isA<BusReservation>(res);
}
TimelineModel::TimelineModel(QObject *parent)
: QAbstractListModel(parent)
{
......@@ -371,11 +401,33 @@ std::vector<TimelineModel::Element>::iterator TimelineModel::erasePreviousCounty
void TimelineModel::insertWeatherElements()
{
if (!m_weatherMgr) {
if (!m_weatherMgr || m_elements.empty()) {
return;
}
auto it = std::find_if(m_elements.begin(), m_elements.end(), [](const Element &e) { return e.elementType == TodayMarker; });
qDebug() << "recomputing weather elements";
GeoCoordinates geo;
// look through the past, clean up weather elements there and figure out where we are
auto it = m_elements.begin();
for (; it != m_elements.end() && (*it).dt < QDateTime::currentDateTimeUtc();) {
if ((*it).elementType == WeatherForecast) {
const auto row = std::distance(m_elements.begin(), it);
beginRemoveRows({}, row, row);
it = m_elements.erase(it);
endRemoveRows();
continue;
}
const auto res = m_resMgr->reservation((*it).id);
const auto newGeo = geoCoordinate(res);
if (isLocationChange(res) || newGeo.isValid()) {
geo = newGeo;
}
++it;
}
auto date = QDate::currentDate();
for (; it != m_elements.end() && date < QDate::currentDate().addDays(9);) {
if ((*it).dt.date() < date || (*it).elementType == TodayMarker) {
......@@ -388,7 +440,12 @@ void TimelineModel::insertWeatherElements()
continue;
}
const auto geo = geoCoordinate(it);
const auto res = m_resMgr->reservation((*it).id);
const auto newGeo = geoCoordinate(res);
if (isLocationChange(res) || newGeo.isValid()) {
geo = newGeo;
}
if (geo.isValid()) {
m_weatherMgr->monitorLocation(geo.latitude(), geo.longitude());
const auto fc = m_weatherMgr->forecast(geo.latitude(), geo.longitude(), QDateTime(date, QTime(0, 0)), QDateTime(date, QTime(23, 59)));
......@@ -397,13 +454,27 @@ void TimelineModel::insertWeatherElements()
beginInsertRows({}, row, row);
it = m_elements.insert(it, Element{{}, QVariant::fromValue(fc), QDateTime(date, QTime()), WeatherForecast, SelfContained});
endInsertRows();
date = date.addDays(1);
continue;
}
}
date = date.addDays(1);
++it;
}
// append weather elements beyond the end of the list if necessary
while (date < QDate::currentDate().addDays(9) && geo.isValid()) {
m_weatherMgr->monitorLocation(geo.latitude(), geo.longitude());
const auto fc = m_weatherMgr->forecast(geo.latitude(), geo.longitude(), QDateTime(date, QTime(0, 0)), QDateTime(date, QTime(23, 59)));
if (fc.isValid()) {
const auto row = std::distance(m_elements.begin(), it);
beginInsertRows({}, row, row);
it = m_elements.insert(it, Element{{}, QVariant::fromValue(fc), QDateTime(date, QTime()), WeatherForecast, SelfContained});
++it;
endInsertRows();
}
date = date.addDays(1);
}
qDebug() << "weather recomputation done";
}
void TimelineModel::updateWeatherElements()
......@@ -419,49 +490,3 @@ void TimelineModel::updateWeatherElements()
insertWeatherElements();
}
GeoCoordinates TimelineModel::geoCoordinate(std::vector<Element>::iterator it) const
{
if (it == m_elements.begin()) {
return {};
}
--it;
do {
if ((*it).id.isEmpty()) {
--it;
continue;
}
const auto res = m_resMgr->reservation((*it).id);
// things that change location
if (JsonLd::isA<FlightReservation>(res)) {
return res.value<FlightReservation>().reservationFor().value<KItinerary::Flight>().arrivalAirport().geo();
}
if (JsonLd::isA<TrainReservation>(res)) {
return res.value<TrainReservation>().reservationFor().value<KItinerary::TrainTrip>().arrivalStation().geo();
}
if (JsonLd::isA<BusReservation>(res)) {
return res.value<BusReservation>().reservationFor().value<KItinerary::BusTrip>().arrivalStation().geo();
}
// things that don't change location
GeoCoordinates geo;
if (JsonLd::isA<LodgingReservation>(res)) {
geo = res.value<LodgingReservation>().reservationFor().value<LodgingBusiness>().geo();
}
if (JsonLd::isA<FoodEstablishmentReservation>(res)) {
geo = res.value<FoodEstablishmentReservation>().reservationFor().value<FoodEstablishment>().geo();
}
if (JsonLd::isA<TouristAttractionVisit>(res)) {
geo = res.value<TouristAttractionVisit>().touristAttraction().geo();
}
if (geo.isValid()) {
return geo;
}
--it;
} while (it != m_elements.begin());
return {};
}
......@@ -104,8 +104,6 @@ private:
std::vector<Element>::iterator erasePreviousCountyInfo(std::vector<Element>::iterator it);
void insertWeatherElements();
void updateWeatherElements();
KItinerary::GeoCoordinates geoCoordinate(std::vector<Element>::iterator it) const;
ReservationManager *m_resMgr = nullptr;
WeatherForecastManager *m_weatherMgr = nullptr;
......
......@@ -85,6 +85,16 @@ WeatherForecast WeatherForecastManager::forecast(float latitude, float longitude
return {};
}
if (Q_UNLIKELY(m_testMode)) {
WeatherForecast fc;
fc.setDateTime(beginDt);
fc.setMinimumTemperature(23.0f);
fc.setMaximumTemperature(23.0f);
fc.setPrecipitation(0.0f);
fc.setSymbolType(WeatherForecast::LightClouds);
return fc;
}
WeatherTile tile{latitude, longitude};
if (!loadForecastData(tile)) {
return {};
......@@ -423,4 +433,9 @@ WeatherForecast WeatherForecastManager::parseForecastElement(QXmlStreamReader &r
return fc;
}
void WeatherForecastManager::setTestModeEnabled(bool testMode)
{
m_testMode = testMode;
}
#include "moc_weatherforecastmanager.cpp"
......@@ -51,6 +51,11 @@ public:
/** Get the forecast for the give time range and location. */
WeatherForecast forecast(float latitude, float longitude, const QDateTime &begin, const QDateTime &end) const;
/** Enable unit test mode.
* In this mode static forecast data is provided for all locations.
*/
void setTestModeEnabled(bool testMode);
signals:
/** Updated when new forecast data has been retrieved. */
void forecastUpdated();
......@@ -74,6 +79,7 @@ private:
QNetworkAccessManager *m_nam = nullptr;
QNetworkReply *m_pendingReply = nullptr;
bool m_allowNetwork = false;
bool m_testMode = false;
};
#endif // WEATHERFORECASTMANAGER_H
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment