blob: 0c707b65bf00d51d5f97811e0c240b3acaf46690 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QPLACEMANAGERENGINE_TEST_H
#define QPLACEMANAGERENGINE_TEST_H
#include <QtCore/QDateTime>
#include <QtCore/QFile>
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject>
#include <QtCore/QJsonArray>
#include <QtCore/QUuid>
#include <QtPositioning/QGeoCoordinate>
#include <QtPositioning/QGeoLocation>
#include <QtLocation/QPlaceContentReply>
#include <QtLocation/QPlaceManager>
#include <QtLocation/QPlaceManagerEngine>
#include <QtLocation/QPlaceReply>
#include <QtLocation/QPlaceDetailsReply>
#include <QtLocation/QPlaceEditorial>
#include <QtLocation/QPlaceIdReply>
#include <QtLocation/QPlaceImage>
#include <QtLocation/QPlaceSearchSuggestionReply>
#include <QtLocation/QPlaceSearchReply>
#include <QtLocation/QPlaceResult>
#include <QtLocation/QPlaceCategory>
#include <QtLocation/QPlace>
#include <QtLocation/QPlaceReview>
#include <QtLocation/private/qplace_p.h>
#include <QtTest/QTest>
QT_BEGIN_NAMESPACE
inline uint qHash(const QPlaceCategory &category)
{
return qHash(QUuid(category.categoryId().toLatin1()));
}
QT_END_NAMESPACE
QT_USE_NAMESPACE
class QPlacePrivateDefaultAlt : public QPlacePrivateDefault
{
public:
QPlacePrivateDefaultAlt() {}
QPlacePrivateDefaultAlt(const QPlacePrivateDefaultAlt &other)
: QPlacePrivateDefault(other)
{
}
~QPlacePrivateDefaultAlt() {}
QPlaceAttribute extendedAttribute(const QString &attributeType) const override
{
if (attributeType == QStringLiteral("x_provider")) {
QPlaceAttribute a;
a.setLabel(QStringLiteral("x_provider"));
a.setText(QStringLiteral("QPlacePrivateDefaultAlt"));
return a;
} else {
return QPlacePrivateDefault::extendedAttribute(attributeType);
}
}
};
class QPlaceAlt : public QPlace
{
public:
QPlaceAlt() : QPlace(QSharedDataPointer<QPlacePrivate>(new QPlacePrivateDefaultAlt()))
{
}
};
class PlaceReply : public QPlaceReply
{
Q_OBJECT
friend class QPlaceManagerEngineTest;
public:
PlaceReply(QObject *parent = 0)
: QPlaceReply(parent)
{ }
Q_INVOKABLE void emitFinished()
{
emit finished();
}
};
class ContentReply : public QPlaceContentReply
{
Q_OBJECT
friend class QPlaceManagerEngineTest;
public:
ContentReply(QObject *parent = 0)
: QPlaceContentReply(parent)
{}
Q_INVOKABLE void emitError()
{
emit error(error(), errorString());
}
Q_INVOKABLE void emitFinished()
{
emit finished();
}
};
class DetailsReply : public QPlaceDetailsReply
{
Q_OBJECT
friend class QPlaceManagerEngineTest;
public:
DetailsReply(QObject *parent = 0)
: QPlaceDetailsReply(parent)
{ }
Q_INVOKABLE void emitError()
{
emit error(error(), errorString());
}
Q_INVOKABLE void emitFinished()
{
emit finished();
}
};
class IdReply : public QPlaceIdReply
{
Q_OBJECT
friend class QPlaceManagerEngineTest;
public:
IdReply(QPlaceIdReply::OperationType type, QObject *parent = 0)
: QPlaceIdReply(type, parent)
{ }
Q_INVOKABLE void emitError()
{
emit error(error(), errorString());
}
Q_INVOKABLE void emitFinished()
{
emit finished();
}
};
class PlaceSearchReply : public QPlaceSearchReply
{
Q_OBJECT
public:
PlaceSearchReply(const QList<QPlaceSearchResult> &results, QObject *parent = 0)
: QPlaceSearchReply(parent)
{
setResults(results);
}
Q_INVOKABLE void emitError()
{
emit error(error(), errorString());
}
Q_INVOKABLE void emitFinished()
{
emit finished();
}
};
class SuggestionReply : public QPlaceSearchSuggestionReply
{
Q_OBJECT
public:
SuggestionReply(const QStringList &suggestions, QObject *parent = 0)
: QPlaceSearchSuggestionReply(parent)
{
setSuggestions(suggestions);
}
Q_INVOKABLE void emitError()
{
emit error(error(), errorString());
}
Q_INVOKABLE void emitFinished()
{
emit finished();
}
};
class QPlaceManagerEngineTest : public QPlaceManagerEngine
{
Q_OBJECT
public:
QPlaceManagerEngineTest(const QVariantMap &parameters)
: QPlaceManagerEngine(parameters)
{
m_locales << QLocale();
if (parameters.value(QStringLiteral("initializePlaceData"), false).toBool()) {
QFile placeData(QFINDTESTDATA("place_data.json"));
QVERIFY(placeData.exists());
if (placeData.open(QIODevice::ReadOnly)) {
QJsonDocument document = QJsonDocument::fromJson(placeData.readAll());
if (document.isObject()) {
QJsonObject o = document.object();
if (o.contains(QStringLiteral("categories"))) {
QJsonArray categories = o.value(QStringLiteral("categories")).toArray();
for (int i = 0; i < categories.count(); ++i) {
QJsonObject c = categories.at(i).toObject();
QPlaceCategory category;
category.setName(c.value(QStringLiteral("name")).toString());
category.setCategoryId(c.value(QStringLiteral("id")).toString());
m_categories.insert(category.categoryId(), category);
const QString parentId = c.value(QStringLiteral("parentId")).toString();
m_childCategories[parentId].append(category.categoryId());
}
}
if (o.contains(QStringLiteral("places"))) {
QJsonArray places = o.value(QStringLiteral("places")).toArray();
for (int i = 0; i < places.count(); ++i) {
QJsonObject p = places.at(i).toObject();
QPlace place;
if (p.value(QStringLiteral("alternateImplementation")).toBool(false)) {
place = QPlaceAlt();
QPlaceAttribute att;
att.setLabel(QStringLiteral("x_provider"));
att.setText(QStringLiteral("42")); // Doesn't matter, wont be used.
place.setExtendedAttribute(QStringLiteral("x_provider"), att);
}
place.setName(p.value(QStringLiteral("name")).toString());
place.setPlaceId(p.value(QStringLiteral("id")).toString());
QList<QPlaceCategory> categories;
QJsonArray ca = p.value(QStringLiteral("categories")).toArray();
for (int j = 0; j < ca.count(); ++j) {
QPlaceCategory c = m_categories.value(ca.at(j).toString());
if (!c.isEmpty())
categories.append(c);
}
place.setCategories(categories);
QGeoCoordinate coordinate;
QJsonObject lo = p.value(QStringLiteral("location")).toObject();
coordinate.setLatitude(lo.value(QStringLiteral("latitude")).toDouble());
coordinate.setLongitude(lo.value(QStringLiteral("longitude")).toDouble());
QGeoLocation location;
location.setCoordinate(coordinate);
place.setLocation(location);
m_places.insert(place.placeId(), place);
QStringList recommendations;
QJsonArray ra = p.value(QStringLiteral("recommendations")).toArray();
for (int j = 0; j < ra.count(); ++j)
recommendations.append(ra.at(j).toString());
m_placeRecommendations.insert(place.placeId(), recommendations);
QJsonArray revArray = p.value(QStringLiteral("reviews")).toArray();
QList<QPlaceReview> reviews;
for (int j = 0; j < revArray.count(); ++j) {
QJsonObject ro = revArray.at(j).toObject();
QPlaceReview review;
if (ro.contains(QStringLiteral("title")))
review.setTitle(ro.value(QStringLiteral("title")).toString());
if (ro.contains(QStringLiteral("text")))
review.setText(ro.value(QStringLiteral("text")).toString());
if (ro.contains(QStringLiteral("language")))
review.setLanguage(ro.value("language").toString());
if (ro.contains(QStringLiteral("rating")))
review.setRating(ro.value("rating").toDouble());
if (ro.contains(QStringLiteral("dateTime")))
review.setDateTime(QDateTime::fromString(
ro.value(QStringLiteral("dateTime")).toString(),
QStringLiteral("hh:mm dd-MM-yyyy")));
if (ro.contains(QStringLiteral("reviewId")))
review.setReviewId(ro.value("reviewId").toString());
reviews << review;
}
m_placeReviews.insert(place.placeId(), reviews);
QJsonArray imgArray = p.value(QStringLiteral("images")).toArray();
QList<QPlaceImage> images;
for (int j = 0; j < imgArray.count(); ++j) {
QJsonObject imgo = imgArray.at(j).toObject();
QPlaceImage image;
if (imgo.contains(QStringLiteral("url")))
image.setUrl(imgo.value(QStringLiteral("url")).toString());
if (imgo.contains("imageId"))
image.setImageId(imgo.value(QStringLiteral("imageId")).toString());
if (imgo.contains("mimeType"))
image.setMimeType(imgo.value(QStringLiteral("mimeType")).toString());
images << image;
}
m_placeImages.insert(place.placeId(), images);
QJsonArray edArray = p.value(QStringLiteral("editorials")).toArray();
QList<QPlaceEditorial> editorials;
for (int j = 0; j < edArray.count(); ++j) {
QJsonObject edo = edArray.at(j).toObject();
QPlaceEditorial editorial;
if (edo.contains(QStringLiteral("title")))
editorial.setTitle(edo.value(QStringLiteral("title")).toString());
if (edo.contains(QStringLiteral("text")))
editorial.setText(edo.value(QStringLiteral("text")).toString());
if (edo.contains(QStringLiteral("language")))
editorial.setLanguage(edo.value(QStringLiteral("language")).toString());
editorials << editorial;
}
m_placeEditorials.insert(place.placeId(), editorials);
}
}
}
}
}
}
QPlaceDetailsReply *getPlaceDetails(const QString &placeId) override
{
DetailsReply *reply = new DetailsReply(this);
if (placeId.isEmpty() || !m_places.contains(placeId)) {
reply->setError(QPlaceReply::PlaceDoesNotExistError, tr("Place does not exist"));
QMetaObject::invokeMethod(reply, "emitError", Qt::QueuedConnection);
} else {
reply->setPlace(m_places.value(placeId));
}
QMetaObject::invokeMethod(reply, "emitFinished", Qt::QueuedConnection);
return reply;
}
QPlaceContentReply *getPlaceContent(const QPlaceContentRequest &query) override
{
ContentReply *reply = new ContentReply(this);
if (query.placeId().isEmpty() || !m_places.contains(query.placeId())) {
reply->setError(QPlaceReply::PlaceDoesNotExistError, tr("Place does not exist"));
QMetaObject::invokeMethod(reply, "emitError", Qt::QueuedConnection);
} else {
QPlaceContent::Collection collection;
int totalCount = 0;
switch (query.contentType()) {
case QPlaceContent::ReviewType:
totalCount = m_placeReviews.value(query.placeId()).count();
break;
case QPlaceContent::ImageType:
totalCount = m_placeImages.value(query.placeId()).count();
break;
case QPlaceContent::EditorialType:
totalCount = m_placeEditorials.value(query.placeId()).count();
default:
//do nothing
break;
}
QVariantMap context = query.contentContext().toMap();
int offset = context.value(QStringLiteral("offset"), 0).toInt();
int max = (query.limit() == -1) ? totalCount
: qMin(offset + query.limit(), totalCount);
for (int i = offset; i < max; ++i) {
switch (query.contentType()) {
case QPlaceContent::ReviewType:
collection.insert(i, m_placeReviews.value(query.placeId()).at(i));
break;
case QPlaceContent::ImageType:
collection.insert(i, m_placeImages.value(query.placeId()).at(i));
break;
case QPlaceContent::EditorialType:
collection.insert(i, m_placeEditorials.value(query.placeId()).at(i));
default:
//do nothing
break;
}
}
reply->setContent(collection);
reply->setTotalCount(totalCount);
if (max != totalCount) {
context.clear();
context.insert(QStringLiteral("offset"), offset + query.limit());
QPlaceContentRequest request = query;
request.setContentContext(context);
reply->setNextPageRequest(request);
}
if (offset > 0) {
context.clear();
context.insert(QStringLiteral("offset"), qMin(0, offset - query.limit()));
QPlaceContentRequest request = query;
request.setContentContext(context);
reply->setPreviousPageRequest(request);
}
}
QMetaObject::invokeMethod(reply, "emitFinished", Qt::QueuedConnection);
return reply;
}
QPlaceSearchReply *search(const QPlaceSearchRequest &query) override
{
QList<QPlaceSearchResult> results;
if (!query.searchTerm().isEmpty()) {
foreach (const QPlace &place, m_places) {
if (!place.name().contains(query.searchTerm(), Qt::CaseInsensitive))
continue;
QPlaceResult r;
r.setPlace(place);
r.setTitle(place.name());
results.append(r);
}
} else if (!query.categories().isEmpty()) {
const auto &categoryList = query.categories();
const QSet<QPlaceCategory> categories(categoryList.cbegin(), categoryList.cend());
for (const QPlace &place : qAsConst(m_places)) {
const auto &placeCategoryList = place.categories();
const QSet<QPlaceCategory> placeCategories(placeCategoryList.cbegin(), placeCategoryList.cend());
if (!placeCategories.intersects(categories))
continue;
QPlaceResult r;
r.setPlace(place);
r.setTitle(place.name());
results.append(r);
}
} else if (!query.recommendationId().isEmpty()) {
QStringList recommendations = m_placeRecommendations.value(query.recommendationId());
foreach (const QString &id, recommendations) {
QPlaceResult r;
r.setPlace(m_places.value(id));
r.setTitle(r.place().name());
results.append(r);
}
}
PlaceSearchReply *reply = new PlaceSearchReply(results, this);
QMetaObject::invokeMethod(reply, "emitFinished", Qt::QueuedConnection);
return reply;
}
QPlaceSearchSuggestionReply *searchSuggestions(const QPlaceSearchRequest &query) override
{
QStringList suggestions;
if (query.searchTerm() == QLatin1String("test")) {
suggestions << QStringLiteral("test1");
suggestions << QStringLiteral("test2");
suggestions << QStringLiteral("test3");
}
SuggestionReply *reply = new SuggestionReply(suggestions, this);
QMetaObject::invokeMethod(reply, "emitFinished", Qt::QueuedConnection);
return reply;
}
QPlaceIdReply *savePlace(const QPlace &place) override
{
IdReply *reply = new IdReply(QPlaceIdReply::SavePlace, this);
if (!place.placeId().isEmpty() && !m_places.contains(place.placeId())) {
reply->setError(QPlaceReply::PlaceDoesNotExistError, tr("Place does not exist"));
QMetaObject::invokeMethod(reply, "emitError", Qt::QueuedConnection);
} else if (!place.placeId().isEmpty()) {
m_places.insert(place.placeId(), place);
reply->setId(place.placeId());
} else {
QPlace p = place;
p.setPlaceId(QUuid::createUuid().toString());
m_places.insert(p.placeId(), p);
reply->setId(p.placeId());
}
QMetaObject::invokeMethod(reply, "emitFinished", Qt::QueuedConnection);
return reply;
}
QPlaceIdReply *removePlace(const QString &placeId) override
{
IdReply *reply = new IdReply(QPlaceIdReply::RemovePlace, this);
reply->setId(placeId);
if (!m_places.contains(placeId)) {
reply->setError(QPlaceReply::PlaceDoesNotExistError, tr("Place does not exist"));
QMetaObject::invokeMethod(reply, "emitError", Qt::QueuedConnection);
} else {
m_places.remove(placeId);
}
QMetaObject::invokeMethod(reply, "emitFinished", Qt::QueuedConnection);
return reply;
}
QPlaceIdReply *saveCategory(const QPlaceCategory &category, const QString &parentId) override
{
IdReply *reply = new IdReply(QPlaceIdReply::SaveCategory, this);
if ((!category.categoryId().isEmpty() && !m_categories.contains(category.categoryId())) ||
(!parentId.isEmpty() && !m_categories.contains(parentId))) {
reply->setError(QPlaceReply::CategoryDoesNotExistError, tr("Category does not exist"));
QMetaObject::invokeMethod(reply, "emitError", Qt::QueuedConnection);
} else if (!category.categoryId().isEmpty()) {
m_categories.insert(category.categoryId(), category);
QStringList children = m_childCategories.value(parentId);
for (QStringList &c : m_childCategories)
c.removeAll(category.categoryId());
if (!children.contains(category.categoryId())) {
children.append(category.categoryId());
m_childCategories.insert(parentId, children);
}
reply->setId(category.categoryId());
} else {
QPlaceCategory c = category;
c.setCategoryId(QUuid::createUuid().toString());
m_categories.insert(c.categoryId(), c);
QStringList children = m_childCategories.value(parentId);
if (!children.contains(c.categoryId())) {
children.append(c.categoryId());
m_childCategories.insert(parentId, children);
}
reply->setId(c.categoryId());
}
QMetaObject::invokeMethod(reply, "emitFinished", Qt::QueuedConnection);
return reply;
}
QPlaceIdReply *removeCategory(const QString &categoryId) override
{
IdReply *reply = new IdReply(QPlaceIdReply::RemoveCategory, this);
reply->setId(categoryId);
if (!m_categories.contains(categoryId)) {
reply->setError(QPlaceReply::CategoryDoesNotExistError, tr("Category does not exist"));
QMetaObject::invokeMethod(reply, "emitError", Qt::QueuedConnection);
} else {
m_categories.remove(categoryId);
for (auto &c : m_childCategories)
c.removeAll(categoryId);
}
QMetaObject::invokeMethod(reply, "emitFinished", Qt::QueuedConnection);
return reply;
}
QPlaceReply *initializeCategories() override
{
QPlaceReply *reply = new PlaceReply(this);
QMetaObject::invokeMethod(reply, "emitFinished", Qt::QueuedConnection);
return reply;
}
QString parentCategoryId(const QString &categoryId) const override
{
for (auto i = m_childCategories.cbegin(), end = m_childCategories.cend(); i != end; ++i) {
if (i.value().contains(categoryId))
return i.key();
}
return QString();
}
virtual QStringList childCategoryIds(const QString &categoryId) const override
{
return m_childCategories.value(categoryId);
}
virtual QPlaceCategory category(const QString &categoryId) const override
{
return m_categories.value(categoryId);
}
QList<QPlaceCategory> childCategories(const QString &parentId) const override
{
QList<QPlaceCategory> categories;
foreach (const QString &id, m_childCategories.value(parentId))
categories.append(m_categories.value(id));
return categories;
}
QList<QLocale> locales() const override
{
return m_locales;
}
void setLocales(const QList<QLocale> &locales) override
{
m_locales = locales;
}
QUrl constructIconUrl(const QPlaceIcon &icon, const QSize &size) const override
{
QList<QPair<int, QUrl> > candidates;
QMap<QString, int> sizeDictionary;
sizeDictionary.insert(QStringLiteral("s"), 20);
sizeDictionary.insert(QStringLiteral("m"), 30);
sizeDictionary.insert(QStringLiteral("l"), 50);
QStringList sizeKeys;
sizeKeys << QStringLiteral("s") << QStringLiteral("m") << QStringLiteral("l");
foreach (const QString &sizeKey, sizeKeys)
{
if (icon.parameters().contains(sizeKey))
candidates.append(QPair<int, QUrl>(sizeDictionary.value(sizeKey),
icon.parameters().value(sizeKey).toUrl()));
}
if (candidates.isEmpty())
return QUrl();
else if (candidates.count() == 1) {
return candidates.first().second;
} else {
//we assume icons are squarish so we can use height to
//determine which particular icon to return
int requestedHeight = size.height();
for (int i = 0; i < candidates.count() - 1; ++i) {
int thresholdHeight = (candidates.at(i).first + candidates.at(i+1).first) / 2;
if (requestedHeight < thresholdHeight)
return candidates.at(i).second;
}
return candidates.last().second;
}
}
QPlace compatiblePlace(const QPlace &original) const override
{
QPlace place;
place.setName(original.name());
return place;
}
private:
QList<QLocale> m_locales;
QHash<QString, QPlace> m_places;
QHash<QString, QPlaceCategory> m_categories;
QHash<QString, QStringList> m_childCategories;
QHash<QString, QStringList> m_placeRecommendations;
QHash<QString, QList<QPlaceReview> > m_placeReviews;
QHash<QString, QList<QPlaceImage> > m_placeImages;
QHash<QString, QList<QPlaceEditorial> > m_placeEditorials;
};
#endif