diff --git a/discover/autotests/CMakeLists.txt b/discover/autotests/CMakeLists.txt index 8b7342ac..440eb957 100644 --- a/discover/autotests/CMakeLists.txt +++ b/discover/autotests/CMakeLists.txt @@ -1,22 +1,22 @@ set(plasma_discover_autotest_SRCS) ecm_qt_declare_logging_category(plasma_discover_autotest_SRCS HEADER discover_debug.h IDENTIFIER DISCOVER_LOG CATEGORY_NAME org.kde.plasma.discover) -ecm_add_test(PaginateModelTest.cpp ../PaginateModel.cpp ${plasma_discover_autotest_SRCS} ${CMAKE_SOURCE_DIR}/libdiscover/tests/modeltest.cpp TEST_NAME PaginateModelTest LINK_LIBRARIES Qt5::Test Qt5::Gui) +ecm_add_test(PaginateModelTest.cpp ../PaginateModel.cpp ${plasma_discover_autotest_SRCS} TEST_NAME PaginateModelTest LINK_LIBRARIES Qt5::Test Qt5::Gui) target_include_directories(PaginateModelTest PUBLIC ${CMAKE_SOURCE_DIR}/libdiscover/) if(BUILD_DummyBackend) add_test(NAME toplevels COMMAND Plasma::Discover --test "${CMAKE_CURRENT_SOURCE_DIR}/toplevels.qml") add_test(NAME install COMMAND Plasma::Discover --test "${CMAKE_CURRENT_SOURCE_DIR}/install.qml") add_test(NAME appstreamUrl COMMAND Plasma::Discover --test "${CMAKE_CURRENT_SOURCE_DIR}/appstreamUrl.qml" "dummy://techie1") add_test(NAME missingResource COMMAND Plasma::Discover --test "${CMAKE_CURRENT_SOURCE_DIR}/missingResource.qml" "dummy://caca") add_test(NAME apparg COMMAND Plasma::Discover --test "${CMAKE_CURRENT_SOURCE_DIR}/appstreamUrl.qml" --application "dummy://techie1") add_test(NAME categoryarg COMMAND Plasma::Discover --test "${CMAKE_CURRENT_SOURCE_DIR}/categoryArg.qml" --category "dummy 2.1") add_test(NAME wrongInput COMMAND Plasma::Discover --test "${CMAKE_CURRENT_SOURCE_DIR}/wrongInput.qml" "CMakeLists.txt") add_test(NAME packageArgument COMMAND Plasma::Discover --test "${CMAKE_CURRENT_SOURCE_DIR}/packageArgument.qml" --local-filename "${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt") add_test(NAME updateandinstall COMMAND Plasma::Discover --test "${CMAKE_CURRENT_SOURCE_DIR}/updateandinstall.qml") # Just make sure they exit successfully add_test(NAME listbackends COMMAND Plasma::Discover --listbackends) add_test(NAME listmodes COMMAND Plasma::Discover --listmodes) endif() diff --git a/discover/autotests/PaginateModelTest.cpp b/discover/autotests/PaginateModelTest.cpp index 60960051..88a3f08f 100644 --- a/discover/autotests/PaginateModelTest.cpp +++ b/discover/autotests/PaginateModelTest.cpp @@ -1,146 +1,146 @@ /* * Copyright (C) 2015 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library/Lesser General Public License * version 2, or (at your option) any later version, as published by the * Free Software Foundation * * 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 * * You should have received a copy of the GNU Library/Lesser General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include "../PaginateModel.h" -#include +#include #include class PaginateModelTest : public QObject { Q_OBJECT public: PaginateModelTest() : m_testModel(new QStandardItemModel) { for(int i=0; i<13; ++i) { m_testModel->appendRow(new QStandardItem(QStringLiteral("figui%1").arg(i))); } } private Q_SLOTS: void testPages() { PaginateModel pm; - new ModelTest(&pm, &pm); + new QAbstractItemModelTester(&pm, &pm); pm.setSourceModel(m_testModel); pm.setPageSize(5); QCOMPARE(pm.pageCount(), 3); QCOMPARE(pm.rowCount(), 5); QCOMPARE(pm.firstItem(), 0); QCOMPARE(pm.currentPage(), 0); pm.nextPage(); QCOMPARE(pm.rowCount(), 5); QCOMPARE(pm.currentPage(), 1); pm.nextPage(); QCOMPARE(pm.rowCount(), 3); QCOMPARE(pm.currentPage(), 2); pm.firstPage(); QCOMPARE(pm.firstItem(), 0); pm.setFirstItem(0); QCOMPARE(pm.firstItem(), 0); QCOMPARE(pm.currentPage(), 0); pm.lastPage(); QCOMPARE(pm.firstItem(), 10); QCOMPARE(pm.currentPage(), 2); } void testPageSize() { PaginateModel pm; - new ModelTest(&pm, &pm); + new QAbstractItemModelTester(&pm, &pm); pm.setSourceModel(m_testModel); pm.setPageSize(5); QCOMPARE(pm.pageCount(), 3); pm.setPageSize(10); QCOMPARE(pm.pageCount(), 2); pm.setPageSize(5); QCOMPARE(pm.pageCount(), 3); } void testItemAdded() { PaginateModel pm; - new ModelTest(&pm, &pm); + new QAbstractItemModelTester(&pm, &pm); pm.setSourceModel(m_testModel); pm.setPageSize(5); QCOMPARE(pm.pageCount(), 3); QSignalSpy spy(&pm, &QAbstractItemModel::rowsAboutToBeInserted); m_testModel->insertRow(3, new QStandardItem(QStringLiteral("mwahahaha"))); m_testModel->insertRow(3, new QStandardItem(QStringLiteral("mwahahaha"))); QCOMPARE(spy.count(), 0); m_testModel->appendRow(new QStandardItem(QStringLiteral("mwahahaha"))); pm.lastPage(); for (int i=0; i<7; ++i) m_testModel->appendRow(new QStandardItem(QStringLiteral("mwahahaha%1").arg(i))); QCOMPARE(spy.count(), 4); pm.firstPage(); for (int i=0; i<7; ++i) m_testModel->appendRow(new QStandardItem(QStringLiteral("faraway%1").arg(i))); QCOMPARE(spy.count(), 4); } void testItemAddBeginning() { QStandardItemModel smallerModel; PaginateModel pm; - new ModelTest(&pm, &pm); + new QAbstractItemModelTester(&pm, &pm); pm.setSourceModel(&smallerModel); pm.setPageSize(5); QCOMPARE(pm.pageCount(), 1); QCOMPARE(pm.rowCount(), 0); smallerModel.insertRow(0, new QStandardItem(QStringLiteral("just one"))); QCOMPARE(pm.pageCount(), 1); QCOMPARE(pm.rowCount(), 1); smallerModel.removeRow(0); QCOMPARE(pm.pageCount(), 1); QCOMPARE(pm.rowCount(), 0); } void testItemRemoved() { PaginateModel pm; - new ModelTest(&pm, &pm); + new QAbstractItemModelTester(&pm, &pm); pm.setSourceModel(m_testModel); pm.setPageSize(5); QCOMPARE(pm.pageCount(), 5); QSignalSpy spy(&pm, &QAbstractItemModel::rowsAboutToBeRemoved); m_testModel->removeRow(3); QCOMPARE(spy.count(), 0); spy.clear(); pm.lastPage(); m_testModel->removeRow(m_testModel->rowCount()-1); QCOMPARE(spy.count(), 1); } void testMove() { PaginateModel pm; - new ModelTest(&pm, &pm); + new QAbstractItemModelTester(&pm, &pm); pm.setSourceModel(m_testModel); pm.setPageSize(5); m_testModel->moveRow({}, 0, {}, 3); } private: QStandardItemModel* const m_testModel; }; QTEST_MAIN( PaginateModelTest ) #include "PaginateModelTest.moc" diff --git a/libdiscover/ApplicationAddonsModel.cpp b/libdiscover/ApplicationAddonsModel.cpp index 219b6ead..bcfe81f8 100644 --- a/libdiscover/ApplicationAddonsModel.cpp +++ b/libdiscover/ApplicationAddonsModel.cpp @@ -1,158 +1,158 @@ /*************************************************************************** * Copyright © 2012 Aleix Pol Gonzalez * * * * 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) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * 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. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "ApplicationAddonsModel.h" #include #include #include #include #include "libdiscover_debug.h" ApplicationAddonsModel::ApplicationAddonsModel(QObject* parent) : QAbstractListModel(parent) , m_app(nullptr) { -// new ModelTest(this, this); +// new QAbstractItemModelTester(this, this); connect(TransactionModel::global(), &TransactionModel::transactionRemoved, this, &ApplicationAddonsModel::transactionOver); } QHash< int, QByteArray > ApplicationAddonsModel::roleNames() const { QHash roles = QAbstractItemModel::roleNames(); roles.insert(Qt::CheckStateRole, "checked"); roles.insert(PackageNameRole, "packageName"); return roles; } void ApplicationAddonsModel::setApplication(AbstractResource* app) { if (app == m_app) return; if (m_app) disconnect(m_app, nullptr, this, nullptr); m_app = app; resetState(); if (m_app) { connect(m_app, &QObject::destroyed, this, [this](){ setApplication(nullptr); }); } emit applicationChanged(); } void ApplicationAddonsModel::resetState() { beginResetModel(); m_state.clear(); m_initial = m_app ? m_app->addonsInformation() : QList(); endResetModel(); emit stateChanged(); } AbstractResource* ApplicationAddonsModel::application() const { return m_app; } int ApplicationAddonsModel::rowCount(const QModelIndex& parent) const { return parent.isValid()? 0 : m_initial.size(); } QVariant ApplicationAddonsModel::data(const QModelIndex& index, int role) const { if(!index.isValid() || index.row()>=m_initial.size()) return QVariant(); switch(role) { case Qt::DisplayRole: return m_initial[index.row()].name(); case Qt::ToolTipRole: return m_initial[index.row()].description(); case PackageNameRole: return m_initial[index.row()].packageName(); case Qt::CheckStateRole: { const PackageState init = m_initial[index.row()]; const AddonList::State state = m_state.addonState(init.name()); if(state == AddonList::None) { return init.isInstalled() ? Qt::Checked : Qt::Unchecked; } else { return state == AddonList::ToInstall ? Qt::Checked : Qt::Unchecked; } } } return QVariant(); } void ApplicationAddonsModel::discardChanges() { //dataChanged should suffice, but it doesn't beginResetModel(); m_state.clear(); emit stateChanged(); endResetModel(); } void ApplicationAddonsModel::applyChanges() { ResourcesModel::global()->installApplication(m_app, m_state); } void ApplicationAddonsModel::changeState(const QString& packageName, bool installed) { auto it = m_initial.constBegin(); for(; it != m_initial.constEnd(); ++it) { if(it->packageName()==packageName) break; } Q_ASSERT(it != m_initial.constEnd()); const bool restored = it->isInstalled()==installed; if(restored) m_state.resetAddon(packageName); else m_state.addAddon(packageName, installed); emit stateChanged(); } bool ApplicationAddonsModel::hasChanges() const { return !m_state.isEmpty(); } bool ApplicationAddonsModel::isEmpty() const { return m_initial.isEmpty(); } void ApplicationAddonsModel::transactionOver(Transaction* t) { if (t->resource() != m_app) return; resetState(); } diff --git a/libdiscover/ScreenshotsModel.cpp b/libdiscover/ScreenshotsModel.cpp index e40f8561..3795e924 100644 --- a/libdiscover/ScreenshotsModel.cpp +++ b/libdiscover/ScreenshotsModel.cpp @@ -1,101 +1,101 @@ /*************************************************************************** * Copyright © 2012 Aleix Pol Gonzalez * * * * 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) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * 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. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "ScreenshotsModel.h" #include #include "libdiscover_debug.h" -// #include +// #include ScreenshotsModel::ScreenshotsModel(QObject* parent) : QAbstractListModel(parent) , m_resource(nullptr) {} QHash< int, QByteArray > ScreenshotsModel::roleNames() const { QHash roles = QAbstractItemModel::roleNames(); roles.insert(ThumbnailUrl, "small_image_url"); roles.insert(ScreenshotUrl, "large_image_url"); return roles; } void ScreenshotsModel::setResource(AbstractResource* res) { if(res == m_resource) return; if(m_resource) { disconnect(m_resource, &AbstractResource::screenshotsFetched, this, &ScreenshotsModel::screenshotsFetched); } m_resource = res; Q_EMIT resourceChanged(res); if(res) { connect(m_resource, &AbstractResource::screenshotsFetched, this, &ScreenshotsModel::screenshotsFetched); res->fetchScreenshots(); } else qCWarning(LIBDISCOVER_LOG) << "empty resource!"; } AbstractResource* ScreenshotsModel::resource() const { return m_resource; } void ScreenshotsModel::screenshotsFetched(const QList< QUrl >& thumbnails, const QList< QUrl >& screenshots) { Q_ASSERT(thumbnails.count()==screenshots.count()); if (thumbnails.isEmpty()) return; beginInsertRows(QModelIndex(), m_thumbnails.size(), m_thumbnails.size()+thumbnails.size()-1); m_thumbnails += thumbnails; m_screenshots += screenshots; endInsertRows(); emit countChanged(); } QVariant ScreenshotsModel::data(const QModelIndex& index, int role) const { if(!index.isValid() || index.parent().isValid()) return QVariant(); switch(role) { case ThumbnailUrl: return m_thumbnails[index.row()]; case ScreenshotUrl: return m_screenshots[index.row()]; } return QVariant(); } int ScreenshotsModel::rowCount(const QModelIndex& parent) const { return !parent.isValid() ? m_screenshots.count() : 0; } QUrl ScreenshotsModel::screenshotAt(int row) const { return m_screenshots[row]; } int ScreenshotsModel::count() const { return m_screenshots.count(); } diff --git a/libdiscover/backends/CMakeLists.txt b/libdiscover/backends/CMakeLists.txt index 75029fa3..99e6dd89 100644 --- a/libdiscover/backends/CMakeLists.txt +++ b/libdiscover/backends/CMakeLists.txt @@ -1,48 +1,48 @@ function(add_unit_test name) - add_executable(${name} ${CMAKE_SOURCE_DIR}/libdiscover/tests/modeltest.cpp ${ARGN}) + add_executable(${name} ${ARGN}) add_test(${name} ${name}) ecm_mark_as_test(${name}) target_link_libraries(${name} Discover::Common Qt5::Test Qt5::Core ${EXTRA_LIBS}) endfunction() if(KF5Attica_FOUND AND KF5NewStuff_FOUND) add_subdirectory(KNSBackend) endif() if(packagekitqt5_FOUND AND AppStreamQt_FOUND) add_subdirectory(PackageKitBackend) endif() option(BUILD_DummyBackend "Build the DummyBackend" "OFF") if(BUILD_DummyBackend) add_subdirectory(DummyBackend) endif() option(BUILD_FlatpakBackend "Build Flatpak support" "ON") if(Flatpak_FOUND AND AppStreamQt_FOUND AND BUILD_FlatpakBackend) add_subdirectory(FlatpakBackend) elseif(BUILD_FlatpakBackend) message(WARNING "BUILD_FlatpakBackend enabled but Flatpak=${Flatpak_FOUND} or AppStreamQt=${AppStreamQt_FOUND} not found") endif() find_package(Snapd) set_package_properties(Snapd PROPERTIES DESCRIPTION "Library that exposes Snapd" URL "https://www.snapcraft.io" PURPOSE "Required to build the Snap backend" TYPE OPTIONAL) option(BUILD_SnapBackend "Build Snap support." "ON") if(BUILD_SnapBackend AND Snapd_FOUND) add_subdirectory(SnapBackend) endif() option(BUILD_FwupdBackend "Build Fwupd support." "ON") if(BUILD_FwupdBackend AND TARGET PkgConfig::Fwupd) add_subdirectory(FwupdBackend) endif() diff --git a/libdiscover/backends/DummyBackend/tests/DummyTest.cpp b/libdiscover/backends/DummyBackend/tests/DummyTest.cpp index d068f870..8c0540db 100644 --- a/libdiscover/backends/DummyBackend/tests/DummyTest.cpp +++ b/libdiscover/backends/DummyBackend/tests/DummyTest.cpp @@ -1,298 +1,298 @@ /*************************************************************************** * Copyright © 2012 Aleix Pol Gonzalez * * * * 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) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * 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. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "DummyTest.h" #include "DiscoverBackendsFactory.h" #include #include -#include +#include #include #include #include #include #include #include #include #include #include #include #include QTEST_MAIN(DummyTest) AbstractResourcesBackend* backendByName(ResourcesModel* m, const QString& name) { QVector backends = m->backends(); foreach(AbstractResourcesBackend* backend, backends) { if(QString::fromLatin1(backend->metaObject()->className()) == name) { return backend; } } return nullptr; } DummyTest::DummyTest(QObject* parent): QObject(parent) { DiscoverBackendsFactory::setRequestedBackends({ QStringLiteral("dummy-backend") }); m_model = new ResourcesModel(QStringLiteral("dummy-backend"), this); m_appBackend = backendByName(m_model, QStringLiteral("DummyBackend")); CategoryModel::global()->populateCategories(); } void DummyTest::initTestCase() { QVERIFY(m_appBackend); while(m_appBackend->isFetching()) { QSignalSpy spy(m_appBackend, &AbstractResourcesBackend::fetchingChanged); QVERIFY(spy.wait()); } } QVector fetchResources(ResultsStream* stream) { QVector ret; QObject::connect(stream, &ResultsStream::resourcesFound, stream, [&ret](const QVector& res) { ret += res; }); QSignalSpy spy(stream, &ResultsStream::destroyed); Q_ASSERT(spy.wait()); return ret; } void DummyTest::testReadData() { const auto resources = fetchResources(m_appBackend->search({})); QCOMPARE(m_appBackend->property("startElements").toInt(), resources.size()); QBENCHMARK { for(AbstractResource* res: resources) { QVERIFY(!res->name().isEmpty()); } } } void DummyTest::testProxy() { ResourcesProxyModel pm; QSignalSpy spy(&pm, &ResourcesProxyModel::busyChanged); // QVERIFY(spy.wait()); QVERIFY(!pm.isBusy()); pm.setFiltersFromCategory(CategoryModel::global()->rootCategories().first()); pm.componentComplete(); QVERIFY(pm.isBusy()); QVERIFY(spy.wait()); QVERIFY(!pm.isBusy()); QCOMPARE(m_appBackend->property("startElements").toInt(), pm.rowCount()); pm.setSearch(QStringLiteral("techie")); QVERIFY(pm.isBusy()); QVERIFY(spy.wait()); QVERIFY(!pm.isBusy()); QCOMPARE(0, pm.rowCount()); QCOMPARE(pm.subcategories().count(), 7); pm.setSearch(QString()); QVERIFY(pm.isBusy()); QVERIFY(spy.wait()); QVERIFY(!pm.isBusy()); QCOMPARE(m_appBackend->property("startElements").toInt(), pm.rowCount()); } void DummyTest::testProxySorting() { ResourcesProxyModel pm; QSignalSpy spy(&pm, &ResourcesProxyModel::busyChanged); // QVERIFY(spy.wait()); QVERIFY(!pm.isBusy()); pm.setFiltersFromCategory(CategoryModel::global()->rootCategories().first()); pm.setSortOrder(Qt::DescendingOrder); pm.setSortRole(ResourcesProxyModel::RatingCountRole); pm.componentComplete(); QVERIFY(pm.isBusy()); QVERIFY(spy.wait()); QVERIFY(!pm.isBusy()); QCOMPARE(m_appBackend->property("startElements").toInt(), pm.rowCount()); QVariant lastRatingCount; for(int i=0, rc=pm.rowCount(); isearch({})); QCOMPARE(m_appBackend->property("startElements").toInt(), resources.count()); //fetches updates, adds new things m_appBackend->checkForUpdates(); QSignalSpy spy(m_model, SIGNAL(allInitialized())); QVERIFY(spy.wait(80000)); auto resources2 = fetchResources(m_appBackend->search({})); QCOMPARE(m_appBackend->property("startElements").toInt()*2, resources2.count()); } void DummyTest::testSort() { ResourcesProxyModel pm; QCollator c; QBENCHMARK_ONCE { pm.setSortRole(ResourcesProxyModel::NameRole); pm.sort(0); QCOMPARE(pm.sortOrder(), Qt::AscendingOrder); QString last; for(int i = 0, count = pm.rowCount(); isearch(filter)); QCOMPARE(resources.count(), 1); AbstractResource* res = resources.first(); QVERIFY(res); ApplicationAddonsModel m; - new ModelTest(&m, &m); + new QAbstractItemModelTester(&m, &m); m.setApplication(res); QCOMPARE(m.rowCount(), res->addonsInformation().count()); QCOMPARE(res->addonsInformation().at(0).isInstalled(), false); QString firstAddonName = m.data(m.index(0,0)).toString(); m.changeState(firstAddonName, true); QVERIFY(m.hasChanges()); m.applyChanges(); QSignalSpy sR(TransactionModel::global(), &TransactionModel::transactionRemoved); QVERIFY(sR.wait()); QVERIFY(!m.hasChanges()); QCOMPARE(m.data(m.index(0,0)).toString(), firstAddonName); QCOMPARE(res->addonsInformation().at(0).name(), firstAddonName); QCOMPARE(res->addonsInformation().at(0).isInstalled(), true); m.changeState(m.data(m.index(1,0)).toString(), true); QVERIFY(m.hasChanges()); for(int i=0, c=m.rowCount(); isearch(filter)); QCOMPARE(resources.count(), 1); AbstractResource* res = resources.first(); QVERIFY(res); ReviewsModel m; - new ModelTest(&m, &m); + new QAbstractItemModelTester(&m, &m); m.setResource(res); m.fetchMore(); QVERIFY(m.rowCount()>0); QCOMPARE(ReviewsModel::UserChoice(m.data(m.index(0,0), ReviewsModel::UsefulChoice).toInt()), ReviewsModel::None); m.markUseful(0, true); QCOMPARE(ReviewsModel::UserChoice(m.data(m.index(0,0), ReviewsModel::UsefulChoice).toInt()), ReviewsModel::Yes); m.markUseful(0, false); QCOMPARE(ReviewsModel::UserChoice(m.data(m.index(0,0), ReviewsModel::UsefulChoice).toInt()), ReviewsModel::No); const auto resources2 = fetchResources(m_appBackend->search(filter)); QCOMPARE(resources2.count(), 1); res = resources2.first(); m.setResource(res); m.fetchMore(); QSignalSpy spy(&m, &ReviewsModel::rowsChanged); QVERIFY(m.rowCount()>0); } void DummyTest::testUpdateModel() { const auto backend = m_model->backends().first(); ResourcesUpdatesModel ruModel; - new ModelTest(&ruModel, &ruModel); + new QAbstractItemModelTester(&ruModel, &ruModel); UpdateModel model; - new ModelTest(&model, &model); + new QAbstractItemModelTester(&model, &model); model.setBackend(&ruModel); QCOMPARE(model.rowCount(), 4*backend->property("startElements").toInt()/3); QCOMPARE(model.hasUpdates(), true); } void DummyTest::testScreenshotsModel() { AbstractResourcesBackend::Filters filter; filter.resourceUrl = QUrl(QStringLiteral("dummy://Dummy.1")); ScreenshotsModel m; - new ModelTest(&m, &m); + new QAbstractItemModelTester(&m, &m); const auto resources = fetchResources(m_appBackend->search(filter)); QCOMPARE(resources.count(), 1); AbstractResource* res = resources.first(); QVERIFY(res); m.setResource(res); QCOMPARE(res, m.resource()); int c=m.rowCount(); for(int i=0; i * * * * 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) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * 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. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "DummyTest.h" -#include +#include #include #include #include #include #include #include #include #include #include #include #include #include class UpdateDummyTest : public QObject { Q_OBJECT public: AbstractResourcesBackend* backendByName(ResourcesModel* m, const QString& name) { QVector backends = m->backends(); foreach(AbstractResourcesBackend* backend, backends) { if(QLatin1String(backend->metaObject()->className()) == name) { return backend; } } return nullptr; } UpdateDummyTest(QObject* parent = nullptr): QObject(parent) { m_model = new ResourcesModel(QStringLiteral("dummy-backend"), this); m_appBackend = backendByName(m_model, QStringLiteral("DummyBackend")); } private Q_SLOTS: void init() { QVERIFY(m_appBackend); while(m_appBackend->isFetching()) { QSignalSpy spy(m_appBackend, &AbstractResourcesBackend::fetchingChanged); QVERIFY(spy.wait()); } } void testInformation() { ResourcesUpdatesModel* rum = new ResourcesUpdatesModel(this); - new ModelTest(rum, rum); + new QAbstractItemModelTester(rum, rum); UpdateModel* m = new UpdateModel(this); - new ModelTest(m, m); + new QAbstractItemModelTester(m, m); m->setBackend(rum); rum->prepare(); QSignalSpy spySetup(m_appBackend->backendUpdater(), &AbstractBackendUpdater::progressingChanged); QVERIFY(!m_appBackend->backendUpdater()->isProgressing() || spySetup.wait()); QCOMPARE(m_appBackend->updatesCount(), m_appBackend->property("startElements").toInt()*2/3); QCOMPARE(m->hasUpdates(), true); QCOMPARE(m->index(0,0).data(UpdateModel::ChangelogRole).toString(), {}); QSignalSpy spy(m, &QAbstractItemModel::dataChanged); m->fetchUpdateDetails(0); QVERIFY(spy.count() || spy.wait()); QCOMPARE(spy.count(), 1); delete m; } void testUpdate() { ResourcesUpdatesModel* rum = new ResourcesUpdatesModel(this); - new ModelTest(rum, rum); + new QAbstractItemModelTester(rum, rum); UpdateModel* m = new UpdateModel(this); - new ModelTest(m, m); + new QAbstractItemModelTester(m, m); m->setBackend(rum); rum->prepare(); QSignalSpy spySetup(m_appBackend->backendUpdater(), &AbstractBackendUpdater::progressingChanged); QVERIFY(!m_appBackend->backendUpdater()->isProgressing() || spySetup.wait()); QCOMPARE(m_appBackend->updatesCount(), m_appBackend->property("startElements").toInt()*2/3); QCOMPARE(m->hasUpdates(), true); for(int i=0, c=m->rowCount(); iindex(i,0); QVERIFY(resourceIdx.isValid()); AbstractResource* res = qobject_cast(resourceIdx.data(UpdateModel::ResourceRole).value()); QVERIFY(res); QCOMPARE(Qt::CheckState(resourceIdx.data(Qt::CheckStateRole).toInt()), Qt::Checked); QVERIFY(m->setData(resourceIdx, int(Qt::Unchecked), Qt::CheckStateRole)); QCOMPARE(Qt::CheckState(resourceIdx.data(Qt::CheckStateRole).toInt()), Qt::Unchecked); QCOMPARE(resourceIdx.data(Qt::DisplayRole).toString(), res->name()); if (i!=0) { QVERIFY(m->setData(resourceIdx, int(Qt::Checked), Qt::CheckStateRole)); } } QSignalSpy spy(rum, &ResourcesUpdatesModel::progressingChanged); QVERIFY(!rum->isProgressing() || spy.wait()); QCOMPARE(rum->isProgressing(), false); QCOMPARE(m_appBackend->updatesCount(), m->rowCount()); QCOMPARE(m->hasUpdates(), true); rum->prepare(); spy.clear(); QCOMPARE(rum->isProgressing(), false); rum->updateAll(); QVERIFY(spy.count() || spy.wait()); QCOMPARE(rum->isProgressing(), true); QCOMPARE(TransactionModel::global()->rowCount(), 1); connect(TransactionModel::global(), &TransactionModel::progressChanged, this, []() { const int progress = TransactionModel::global()->progress(); static int lastProgress = -1; Q_ASSERT(progress >= lastProgress || (TransactionModel::global()->rowCount() == 0 && progress == 0)); lastProgress = progress; }); QTest::qWait(20); QScopedPointer rum2(new ResourcesUpdatesModel(this)); - new ModelTest(rum2.data(), rum2.data()); + new QAbstractItemModelTester(rum2.data(), rum2.data()); QScopedPointer m2(new UpdateModel(this)); - new ModelTest(m2.data(), m2.data()); + new QAbstractItemModelTester(m2.data(), m2.data()); m->setBackend(rum2.data()); QCOMPARE(rum->isProgressing(), true); QVERIFY(spy.wait()); QCOMPARE(rum->isProgressing(), false); QCOMPARE(m_appBackend->updatesCount(), 0); QCOMPARE(m->hasUpdates(), false); } private: ResourcesModel* m_model; AbstractResourcesBackend* m_appBackend; }; QTEST_MAIN(UpdateDummyTest) #include "UpdateDummyTest.moc" diff --git a/libdiscover/resources/ResourcesProxyModel.cpp b/libdiscover/resources/ResourcesProxyModel.cpp index e4cdcd3e..33cfa850 100644 --- a/libdiscover/resources/ResourcesProxyModel.cpp +++ b/libdiscover/resources/ResourcesProxyModel.cpp @@ -1,601 +1,601 @@ /*************************************************************************** * Copyright © 2010 Jonathan Thomas * * Copyright © 2012 Aleix Pol Gonzalez * * * * 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) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * 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. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "ResourcesProxyModel.h" #include "libdiscover_debug.h" #include #include #include "ResourcesModel.h" #include "AbstractResource.h" #include "AbstractResourcesBackend.h" #include #include #include ResourcesProxyModel::ResourcesProxyModel(QObject *parent) : QAbstractListModel(parent) , m_sortRole(NameRole) , m_sortOrder(Qt::AscendingOrder) , m_sortByRelevancy(false) , m_roles({ { NameRole, "name" }, { IconRole, "icon" }, { CommentRole, "comment" }, { StateRole, "state" }, { RatingRole, "rating" }, { RatingPointsRole, "ratingPoints" }, { RatingCountRole, "ratingCount" }, { SortableRatingRole, "sortableRating" }, { InstalledRole, "isInstalled" }, { ApplicationRole, "application" }, { OriginRole, "origin" }, { DisplayOriginRole, "displayOrigin" }, { CanUpgrade, "canUpgrade" }, { PackageNameRole, "packageName" }, { CategoryRole, "category" }, { CategoryDisplayRole, "categoryDisplay" }, { SectionRole, "section" }, { MimeTypes, "mimetypes" }, { LongDescriptionRole, "longDescription" }, { SourceIconRole, "sourceIcon" }, { SizeRole, "size" }, { ReleaseDateRole, "releaseDate" } }) , m_currentStream(nullptr) { -// new ModelTest(this, this); +// new QAbstractItemModelTester(this, this); connect(ResourcesModel::global(), &ResourcesModel::backendsChanged, this, &ResourcesProxyModel::invalidateFilter); connect(ResourcesModel::global(), &ResourcesModel::backendDataChanged, this, &ResourcesProxyModel::refreshBackend); connect(ResourcesModel::global(), &ResourcesModel::resourceDataChanged, this, &ResourcesProxyModel::refreshResource); connect(ResourcesModel::global(), &ResourcesModel::resourceRemoved, this, &ResourcesProxyModel::removeResource); connect(this, &QAbstractItemModel::modelReset, this, &ResourcesProxyModel::countChanged); connect(this, &QAbstractItemModel::rowsInserted, this, &ResourcesProxyModel::countChanged); connect(this, &QAbstractItemModel::rowsRemoved, this, &ResourcesProxyModel::countChanged); } void ResourcesProxyModel::componentComplete() { m_setup = true; invalidateFilter(); } QHash ResourcesProxyModel::roleNames() const { return m_roles; } void ResourcesProxyModel::setSortRole(Roles sortRole) { if (sortRole != m_sortRole) { Q_ASSERT(roleNames().contains(sortRole)); m_sortRole = sortRole; Q_EMIT sortRoleChanged(sortRole); invalidateSorting(); } } void ResourcesProxyModel::setSortOrder(Qt::SortOrder sortOrder) { if (sortOrder != m_sortOrder) { m_sortOrder = sortOrder; Q_EMIT sortRoleChanged(sortOrder); invalidateSorting(); } } void ResourcesProxyModel::setSearch(const QString &_searchText) { // 1-character searches are painfully slow. >= 2 chars are fine, though const QString searchText = _searchText.count() <= 1 ? QString() : _searchText; const bool diff = searchText != m_filters.search; if (diff) { m_filters.search = searchText; if (m_sortByRelevancy == searchText.isEmpty()) { m_sortByRelevancy = !searchText.isEmpty(); Q_EMIT sortByRelevancyChanged(m_sortByRelevancy); } invalidateFilter(); Q_EMIT searchChanged(m_filters.search); } } void ResourcesProxyModel::removeDuplicates(QVector& resources) { const auto cab = ResourcesModel::global()->currentApplicationBackend(); QHash::iterator> storedIds; for(auto it = m_displayedResources.begin(); it != m_displayedResources.end(); ++it) { const auto appstreamid = (*it)->appstreamId(); if (appstreamid.isEmpty()) { continue; } auto at = storedIds.find(appstreamid); if (at == storedIds.end()) { storedIds[appstreamid] = it; } else { qCWarning(LIBDISCOVER_LOG) << "We should have sanitized the displayed resources. There is a bug"; Q_UNREACHABLE(); } } QHash::iterator> ids; for(auto it = resources.begin(); it != resources.end(); ) { const auto appstreamid = (*it)->appstreamId(); if (appstreamid.isEmpty()) { ++it; continue; } auto at = storedIds.find(appstreamid); if (at == storedIds.end()) { auto at = ids.find(appstreamid); if (at == ids.end()) { ids[appstreamid] = it; ++it; } else { if ((*it)->backend() == cab) { qSwap(*it, **at); } it = resources.erase(it); } } else { if ((*it)->backend() == cab) { **at = *it; auto pos = index(*at - m_displayedResources.begin(), 0); Q_EMIT dataChanged(pos, pos); } it = resources.erase(it); } } } void ResourcesProxyModel::addResources(const QVector& _res) { auto res = _res; m_filters.filterJustInCase(res); if (res.isEmpty()) return; if (!m_sortByRelevancy) std::sort(res.begin(), res.end(), [this](AbstractResource* res, AbstractResource* res2){ return lessThan(res, res2); }); sortedInsertion(res); fetchSubcategories(); } void ResourcesProxyModel::invalidateSorting() { if (m_displayedResources.isEmpty()) return; if (!m_sortByRelevancy) { beginResetModel(); std::sort(m_displayedResources.begin(), m_displayedResources.end(), [this](AbstractResource* res, AbstractResource* res2){ return lessThan(res, res2); }); endResetModel(); } } QString ResourcesProxyModel::lastSearch() const { return m_filters.search; } void ResourcesProxyModel::setOriginFilter(const QString &origin) { if (origin == m_filters.origin) return; m_filters.origin = origin; invalidateFilter(); } QString ResourcesProxyModel::originFilter() const { return m_filters.origin; } void ResourcesProxyModel::setFiltersFromCategory(Category *category) { if(category==m_filters.category) return; m_filters.category = category; invalidateFilter(); emit categoryChanged(); } void ResourcesProxyModel::fetchSubcategories() { auto cats = kVectorToSet(m_filters.category ? m_filters.category->subCategories() : CategoryModel::global()->rootCategories()); const int count = rowCount(); QSet done; for (int i=0; icategoryObjects(kSetToVector(cats)); done.unite(found); cats.subtract(found); } const QVariantList ret = kTransform(done, [](Category* cat) { return QVariant::fromValue(cat); }); if (ret != m_subcategories) { m_subcategories = ret; Q_EMIT subcategoriesChanged(m_subcategories); } } QVariantList ResourcesProxyModel::subcategories() const { return m_subcategories; } void ResourcesProxyModel::invalidateFilter() { if (!m_setup || ResourcesModel::global()->backends().isEmpty()) { return; } if (m_currentStream) { qCWarning(LIBDISCOVER_LOG) << "last stream isn't over yet" << m_filters << this; delete m_currentStream; } m_currentStream = ResourcesModel::global()->search(m_filters); Q_EMIT busyChanged(true); if (!m_displayedResources.isEmpty()) { beginResetModel(); m_displayedResources.clear(); endResetModel(); } connect(m_currentStream, &AggregatedResultsStream::resourcesFound, this, &ResourcesProxyModel::addResources); connect(m_currentStream, &AggregatedResultsStream::finished, this, [this]() { m_currentStream = nullptr; Q_EMIT busyChanged(false); }); } int ResourcesProxyModel::rowCount(const QModelIndex& parent) const { return parent.isValid() ? 0 : m_displayedResources.count(); } bool ResourcesProxyModel::lessThan(AbstractResource* leftPackage, AbstractResource* rightPackage) const { auto role = m_sortRole; Qt::SortOrder order = m_sortOrder; QVariant leftValue; QVariant rightValue; //if we're comparing two equal values, we want the model sorted by application name if(role != NameRole) { leftValue = roleToValue(leftPackage, role); rightValue = roleToValue(rightPackage, role); if (leftValue == rightValue) { role = NameRole; order = Qt::AscendingOrder; } } bool ret; if(role == NameRole) { ret = leftPackage->nameSortKey().compare(rightPackage->nameSortKey()) < 0; } else if(role == CanUpgrade) { ret = leftValue.toBool(); } else { ret = leftValue < rightValue; } return ret != (order != Qt::AscendingOrder); } Category* ResourcesProxyModel::filteredCategory() const { return m_filters.category; } void ResourcesProxyModel::setStateFilter(AbstractResource::State s) { if (s != m_filters.state) { m_filters.state = s; invalidateFilter(); emit stateFilterChanged(); } } AbstractResource::State ResourcesProxyModel::stateFilter() const { return m_filters.state; } QString ResourcesProxyModel::mimeTypeFilter() const { return m_filters.mimetype; } void ResourcesProxyModel::setMimeTypeFilter(const QString& mime) { if (m_filters.mimetype != mime) { m_filters.mimetype = mime; invalidateFilter(); } } QString ResourcesProxyModel::extends() const { return m_filters.extends; } void ResourcesProxyModel::setExtends(const QString& extends) { if (m_filters.extends != extends) { m_filters.extends = extends; invalidateFilter(); } } void ResourcesProxyModel::setFilterMinimumState(bool filterMinimumState) { if (filterMinimumState != m_filters.filterMinimumState) { m_filters.filterMinimumState = filterMinimumState; invalidateFilter(); Q_EMIT filterMinimumStateChanged(m_filters.filterMinimumState); } } bool ResourcesProxyModel::filterMinimumState() const { return m_filters.filterMinimumState; } QUrl ResourcesProxyModel::resourcesUrl() const { return m_filters.resourceUrl; } void ResourcesProxyModel::setResourcesUrl(const QUrl& resourcesUrl) { if (m_filters.resourceUrl != resourcesUrl) { m_filters.resourceUrl = resourcesUrl; invalidateFilter(); } } bool ResourcesProxyModel::allBackends() const { return m_filters.allBackends; } void ResourcesProxyModel::setAllBackends(bool allBackends) { m_filters.allBackends = allBackends; } QVariant ResourcesProxyModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) { return QVariant(); } AbstractResource* const resource = m_displayedResources[index.row()]; return roleToValue(resource, role); } QVariant ResourcesProxyModel::roleToValue(AbstractResource* resource, int role) const { switch(role) { case ApplicationRole: return qVariantFromValue(resource); case RatingPointsRole: case RatingRole: case RatingCountRole: case SortableRatingRole: { Rating* const rating = resource->rating(); const int idx = Rating::staticMetaObject.indexOfProperty(roleNames().value(role).constData()); Q_ASSERT(idx >= 0); auto prop = Rating::staticMetaObject.property(idx); if (rating) return prop.read(rating); else { QVariant val(0); val.convert(prop.type()); return val; } } case Qt::DecorationRole: case Qt::DisplayRole: case Qt::StatusTipRole: case Qt::ToolTipRole: return QVariant(); default: { QByteArray roleText = roleNames().value(role); if(Q_UNLIKELY(roleText.isEmpty())) { qCDebug(LIBDISCOVER_LOG) << "unsupported role" << role; return {}; } static const QMetaObject* m = &AbstractResource::staticMetaObject; int propidx = roleText.isEmpty() ? -1 : m->indexOfProperty(roleText.constData()); if(Q_UNLIKELY(propidx < 0)) { qCWarning(LIBDISCOVER_LOG) << "unknown role:" << role << roleText; return QVariant(); } else return m->property(propidx).read(resource); } } } bool ResourcesProxyModel::isSorted(const QVector & resources) { auto last = resources.constFirst(); for(auto it = resources.constBegin()+1, itEnd = resources.constEnd(); it != itEnd; ++it) { if(!lessThan(last, *it)) { return false; } last = *it; } return true; } void ResourcesProxyModel::sortedInsertion(const QVector & _res) { auto resources = _res; Q_ASSERT(!resources.isEmpty()); if (!m_filters.allBackends) { removeDuplicates(resources); if (resources.isEmpty()) return; } if (m_sortByRelevancy || m_displayedResources.isEmpty()) { // Q_ASSERT(m_sortByRelevancy || isSorted(resources)); int rows = rowCount(); beginInsertRows({}, rows, rows+resources.count()-1); m_displayedResources += resources; endInsertRows(); return; } for(auto resource: qAsConst(resources)) { const auto finder = [this](AbstractResource* resource, AbstractResource* res){ return lessThan(resource, res); }; const auto it = std::upper_bound(m_displayedResources.constBegin(), m_displayedResources.constEnd(), resource, finder); const auto newIdx = it == m_displayedResources.constEnd() ? m_displayedResources.count() : (it - m_displayedResources.constBegin()); if ((it-1) != m_displayedResources.constEnd() && *(it-1) == resource) continue; beginInsertRows({}, newIdx, newIdx); m_displayedResources.insert(newIdx, resource); endInsertRows(); // Q_ASSERT(isSorted(resources)); } } void ResourcesProxyModel::refreshResource(AbstractResource* resource, const QVector& properties) { const auto residx = m_displayedResources.indexOf(resource); if (residx<0) { if (!m_sortByRelevancy && m_filters.shouldFilter(resource)) { sortedInsertion({resource}); } return; } if (!m_filters.shouldFilter(resource)) { beginRemoveRows({}, residx, residx); m_displayedResources.removeAt(residx); endRemoveRows(); return; } const QModelIndex idx = index(residx, 0); Q_ASSERT(idx.isValid()); const auto roles = propertiesToRoles(properties); if (!m_sortByRelevancy && roles.contains(m_sortRole)) { beginRemoveRows({}, residx, residx); m_displayedResources.removeAt(residx); endRemoveRows(); sortedInsertion({resource}); } else emit dataChanged(idx, idx, roles); } void ResourcesProxyModel::removeResource(AbstractResource* resource) { const auto residx = m_displayedResources.indexOf(resource); if (residx < 0) return; beginRemoveRows({}, residx, residx); m_displayedResources.removeAt(residx); endRemoveRows(); } void ResourcesProxyModel::refreshBackend(AbstractResourcesBackend* backend, const QVector& properties) { auto roles = propertiesToRoles(properties); const int count = m_displayedResources.count(); bool found = false; for(int i = 0; ibackend()) continue; int j = i+1; for(; jbackend(); ++j) {} Q_EMIT dataChanged(index(i, 0), index(j-1, 0), roles); i = j; found = true; } if (found && properties.contains(m_roles.value(m_sortRole))) { invalidateSorting(); } } QVector ResourcesProxyModel::propertiesToRoles(const QVector& properties) const { QVector roles = kTransform>(properties, [this](const QByteArray& arr) { return roleNames().key(arr, -1); }); roles.removeAll(-1); return roles; } int ResourcesProxyModel::indexOf(AbstractResource* res) { return m_displayedResources.indexOf(res); } AbstractResource * ResourcesProxyModel::resourceAt(int row) const { return m_displayedResources[row]; } bool ResourcesProxyModel::canFetchMore(const QModelIndex& parent) const { Q_ASSERT(!parent.isValid()); return m_currentStream; } void ResourcesProxyModel::fetchMore(const QModelIndex& parent) { Q_ASSERT(!parent.isValid()); if (!m_currentStream) return; Q_EMIT m_currentStream->fetchMore(); } bool ResourcesProxyModel::sortByRelevancy() const { return m_sortByRelevancy; } diff --git a/libdiscover/tests/modeltest.cpp b/libdiscover/tests/modeltest.cpp deleted file mode 100644 index 2509eeaf..00000000 --- a/libdiscover/tests/modeltest.cpp +++ /dev/null @@ -1,551 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). -** All rights reserved. -** Contact: Nokia Corporation (qt-info@nokia.com) -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** GNU Lesser General Public License Usage -** This file may be used under the terms of the GNU Lesser General Public -** License version 2.1 as published by the Free Software Foundation and -** appearing in the file LICENSE.LGPL included in the packaging of this -** file. Please review the following information to ensure the GNU Lesser -** General Public License version 2.1 requirements will be met: -** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Nokia gives you certain additional -** rights. These rights are described in the Nokia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU General -** Public License version 3.0 as published by the Free Software Foundation -** and appearing in the file LICENSE.GPL included in the packaging of this -** file. Please review the following information to ensure the GNU General -** Public License version 3.0 requirements will be met: -** http://www.gnu.org/copyleft/gpl.html. -** -** Other Usage -** Alternatively, this file may be used in accordance with the terms and -** conditions contained in a signed written agreement between you and Nokia. -** -** -** -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - - -#include - -#include "modeltest.h" - -#include - -Q_DECLARE_METATYPE ( QModelIndex ) - -/*! - Connect to all of the models signals. Whenever anything happens recheck everything. -*/ -ModelTest::ModelTest ( QAbstractItemModel *_model, QObject *parent ) : QObject ( parent ), model ( _model ), fetchingMore ( false ) -{ - Q_ASSERT ( model ); - - connect(model, &QAbstractItemModel::columnsAboutToBeInserted, this, &ModelTest::runAllTests); - connect(model, &QAbstractItemModel::columnsAboutToBeRemoved, this, &ModelTest::runAllTests); - connect(model, &QAbstractItemModel::columnsInserted, this, &ModelTest::runAllTests); - connect(model, &QAbstractItemModel::columnsRemoved, this, &ModelTest::runAllTests); - connect(model, &QAbstractItemModel::dataChanged, this, &ModelTest::runAllTests); - connect(model, &QAbstractItemModel::headerDataChanged, this, &ModelTest::runAllTests); - connect(model, &QAbstractItemModel::layoutAboutToBeChanged, this, &ModelTest::runAllTests); - connect(model, &QAbstractItemModel::layoutChanged, this, &ModelTest::runAllTests); - connect(model, &QAbstractItemModel::modelReset, this, &ModelTest::runAllTests); - connect(model, &QAbstractItemModel::rowsAboutToBeInserted, this, &ModelTest::runAllTests); - connect(model, &QAbstractItemModel::rowsAboutToBeRemoved, this, &ModelTest::runAllTests); - connect(model, &QAbstractItemModel::rowsInserted, this, &ModelTest::runAllTests); - connect(model, &QAbstractItemModel::rowsRemoved, this, &ModelTest::runAllTests); - - // Special checks for inserting/removing - connect(model, &QAbstractItemModel::layoutAboutToBeChanged, this, &ModelTest::layoutAboutToBeChanged); - connect(model, &QAbstractItemModel::layoutChanged, this, &ModelTest::layoutChanged); - - connect(model, &QAbstractItemModel::rowsAboutToBeInserted, this, &ModelTest::rowsAboutToBeInserted); - connect(model, &QAbstractItemModel::rowsAboutToBeRemoved, this, &ModelTest::rowsAboutToBeRemoved); - connect(model, &QAbstractItemModel::rowsInserted, this, &ModelTest::rowsInserted); - connect(model, &QAbstractItemModel::rowsRemoved, this, &ModelTest::rowsRemoved); - - runAllTests(); -} - -void ModelTest::runAllTests() -{ - if ( fetchingMore ) - return; - nonDestructiveBasicTest(); - rowCount(); - columnCount(); - hasIndex(); - index(); - parent(); - data(); -} - -/*! - nonDestructiveBasicTest tries to call a number of the basic functions (not all) - to make sure the model doesn't outright segfault, testing the functions that makes sense. -*/ -void ModelTest::nonDestructiveBasicTest() -{ - Q_ASSERT ( model->buddy ( QModelIndex() ) == QModelIndex() ); - model->canFetchMore ( QModelIndex() ); - Q_ASSERT ( model->columnCount ( QModelIndex() ) >= 0 ); - Q_ASSERT ( model->data ( QModelIndex() ) == QVariant() ); - fetchingMore = true; - model->fetchMore ( QModelIndex() ); - fetchingMore = false; - Qt::ItemFlags flags = model->flags ( QModelIndex() ); - Q_ASSERT ( flags == Qt::ItemIsDropEnabled || flags == 0 ); - model->hasChildren ( QModelIndex() ); - model->hasIndex ( 0, 0 ); - model->headerData ( 0, Qt::Horizontal ); - model->index ( 0, 0 ); - model->itemData ( QModelIndex() ); - QVariant cache; - model->match ( QModelIndex(), -1, cache ); - model->mimeTypes(); - Q_ASSERT ( model->parent ( QModelIndex() ) == QModelIndex() ); - Q_ASSERT ( model->rowCount() >= 0 ); - QVariant variant; - model->setData ( QModelIndex(), variant, -1 ); - model->setHeaderData ( -1, Qt::Horizontal, QVariant() ); - model->setHeaderData ( 999999, Qt::Horizontal, QVariant() ); - QMap roles; - model->sibling ( 0, 0, QModelIndex() ); - model->span ( QModelIndex() ); - model->supportedDropActions(); -} - -/*! - Tests model's implementation of QAbstractItemModel::rowCount() and hasChildren() - - Models that are dynamically populated are not as fully tested here. - */ -void ModelTest::rowCount() -{ -// qDebug() << "rc"; - // check top row - QModelIndex topIndex = model->index ( 0, 0, QModelIndex() ); - int rows = model->rowCount ( topIndex ); - Q_ASSERT ( rows >= 0 ); - if ( rows > 0 ) - Q_ASSERT ( model->hasChildren ( topIndex ) == true ); - - QModelIndex secondLevelIndex = model->index ( 0, 0, topIndex ); - if ( secondLevelIndex.isValid() ) { // not the top level - // check a row count where parent is valid - rows = model->rowCount ( secondLevelIndex ); - Q_ASSERT ( rows >= 0 ); - if ( rows > 0 ) - Q_ASSERT ( model->hasChildren ( secondLevelIndex ) == true ); - } - - // The models rowCount() is tested more extensively in checkChildren(), - // but this catches the big mistakes -} - -/*! - Tests model's implementation of QAbstractItemModel::columnCount() and hasChildren() - */ -void ModelTest::columnCount() -{ - // check top row - QModelIndex topIndex = model->index ( 0, 0, QModelIndex() ); - Q_ASSERT ( model->columnCount ( topIndex ) >= 0 ); - - // check a column count where parent is valid - QModelIndex childIndex = model->index ( 0, 0, topIndex ); - if ( childIndex.isValid() ) - Q_ASSERT ( model->columnCount ( childIndex ) >= 0 ); - - // columnCount() is tested more extensively in checkChildren(), - // but this catches the big mistakes -} - -/*! - Tests model's implementation of QAbstractItemModel::hasIndex() - */ -void ModelTest::hasIndex() -{ -// qDebug() << "hi"; - // Make sure that invalid values returns an invalid index - Q_ASSERT ( model->hasIndex ( -2, -2 ) == false ); - Q_ASSERT ( model->hasIndex ( -2, 0 ) == false ); - Q_ASSERT ( model->hasIndex ( 0, -2 ) == false ); - - int rows = model->rowCount(); - int columns = model->columnCount(); - - // check out of bounds - Q_ASSERT ( model->hasIndex ( rows, columns ) == false ); - Q_ASSERT ( model->hasIndex ( rows + 1, columns + 1 ) == false ); - - if ( rows > 0 ) - Q_ASSERT ( model->hasIndex ( 0, 0 ) == true ); - - // hasIndex() is tested more extensively in checkChildren(), - // but this catches the big mistakes -} - -/*! - Tests model's implementation of QAbstractItemModel::index() - */ -void ModelTest::index() -{ -// qDebug() << "i"; - // Make sure that invalid values returns an invalid index - Q_ASSERT ( model->index ( -2, -2 ) == QModelIndex() ); - Q_ASSERT ( model->index ( -2, 0 ) == QModelIndex() ); - Q_ASSERT ( model->index ( 0, -2 ) == QModelIndex() ); - - int rows = model->rowCount(); - int columns = model->columnCount(); - - if ( rows == 0 ) - return; - - // Catch off by one errors - Q_ASSERT ( model->index ( rows, columns ) == QModelIndex() ); - Q_ASSERT ( model->index ( 0, 0 ).isValid() == true ); - - // Make sure that the same index is *always* returned - QModelIndex a = model->index ( 0, 0 ); - QModelIndex b = model->index ( 0, 0 ); - Q_ASSERT ( a == b ); - - // index() is tested more extensively in checkChildren(), - // but this catches the big mistakes -} - -/*! - Tests model's implementation of QAbstractItemModel::parent() - */ -void ModelTest::parent() -{ -// qDebug() << "p"; - // Make sure the model wont crash and will return an invalid QModelIndex - // when asked for the parent of an invalid index. - Q_ASSERT ( model->parent ( QModelIndex() ) == QModelIndex() ); - - if ( model->rowCount() == 0 ) - return; - - // Column 0 | Column 1 | - // QModelIndex() | | - // \- topIndex | topIndex1 | - // \- childIndex | childIndex1 | - - // Common error test #1, make sure that a top level index has a parent - // that is a invalid QModelIndex. - QModelIndex topIndex = model->index ( 0, 0, QModelIndex() ); - Q_ASSERT ( model->parent ( topIndex ) == QModelIndex() ); - - // Common error test #2, make sure that a second level index has a parent - // that is the first level index. - if ( model->rowCount ( topIndex ) > 0 ) { - QModelIndex childIndex = model->index ( 0, 0, topIndex ); - Q_ASSERT ( model->parent ( childIndex ) == topIndex ); - } - - // Common error test #3, the second column should NOT have the same children - // as the first column in a row. - // Usually the second column shouldn't have children. - QModelIndex topIndex1 = model->index ( 0, 1, QModelIndex() ); - if ( model->rowCount ( topIndex1 ) > 0 ) { - QModelIndex childIndex = model->index ( 0, 0, topIndex ); - QModelIndex childIndex1 = model->index ( 0, 0, topIndex1 ); - Q_ASSERT ( childIndex != childIndex1 ); - } - - // Full test, walk n levels deep through the model making sure that all - // parent's children correctly specify their parent. - checkChildren ( QModelIndex() ); -} - -/*! - Called from the parent() test. - - A model that returns an index of parent X should also return X when asking - for the parent of the index. - - This recursive function does pretty extensive testing on the whole model in an - effort to catch edge cases. - - This function assumes that rowCount(), columnCount() and index() already work. - If they have a bug it will point it out, but the above tests should have already - found the basic bugs because it is easier to figure out the problem in - those tests then this one. - */ -void ModelTest::checkChildren ( const QModelIndex &parent, int currentDepth ) -{ - // First just try walking back up the tree. - QModelIndex p = parent; - while ( p.isValid() ) - p = p.parent(); - - // For models that are dynamically populated - if ( model->canFetchMore ( parent ) ) { - fetchingMore = true; - model->fetchMore ( parent ); - fetchingMore = false; - } - - int rows = model->rowCount ( parent ); - int columns = model->columnCount ( parent ); - - if ( rows > 0 ) - Q_ASSERT ( model->hasChildren ( parent ) ); - - // Some further testing against rows(), columns(), and hasChildren() - Q_ASSERT ( rows >= 0 ); - Q_ASSERT ( columns >= 0 ); - if ( rows > 0 ) - Q_ASSERT ( model->hasChildren ( parent ) == true ); - - //qDebug() << "parent:" << model->data(parent).toString() << "rows:" << rows - // << "columns:" << columns << "parent column:" << parent.column(); - - Q_ASSERT ( model->hasIndex ( rows + 1, 0, parent ) == false ); - for ( int r = 0; r < rows; ++r ) { - if ( model->canFetchMore ( parent ) ) { - fetchingMore = true; - model->fetchMore ( parent ); - fetchingMore = false; - } - Q_ASSERT ( model->hasIndex ( r, columns + 1, parent ) == false ); - for ( int c = 0; c < columns; ++c ) { - Q_ASSERT ( model->hasIndex ( r, c, parent ) == true ); - QModelIndex index = model->index ( r, c, parent ); - // rowCount() and columnCount() said that it existed... - Q_ASSERT ( index.isValid() == true ); - - // index() should always return the same index when called twice in a row - QModelIndex modifiedIndex = model->index ( r, c, parent ); - Q_ASSERT ( index == modifiedIndex ); - - // Make sure we get the same index if we request it twice in a row - QModelIndex a = model->index ( r, c, parent ); - QModelIndex b = model->index ( r, c, parent ); - Q_ASSERT ( a == b ); - - // Some basic checking on the index that is returned - Q_ASSERT ( index.model() == model ); - Q_ASSERT ( index.row() == r ); - Q_ASSERT ( index.column() == c ); - // While you can technically return a QVariant usually this is a sign - // of an bug in data() Disable if this really is ok in your model. -// Q_ASSERT ( model->data ( index, Qt::DisplayRole ).isValid() == true ); - - // If the next test fails here is some somewhat useful debug you play with. - - if (model->parent(index) != parent) { - qDebug() << r << c << currentDepth << model->data(index).toString() - << model->data(parent).toString(); - qDebug() << index << parent << model->parent(index); -// And a view that you can even use to show the model. -// QTreeView view; -// view.setModel(model); -// view.show(); - } - - // Check that we can get back our real parent. -// qDebug() << model->parent ( index ) << parent ; - Q_ASSERT ( model->parent ( index ) == parent ); - - // recursively go down the children - if ( model->hasChildren ( index ) && currentDepth < 10 ) { - //qDebug() << r << c << "has children" << model->rowCount(index); - checkChildren ( index, ++currentDepth ); - }/* else { if (currentDepth >= 10) qDebug() << "checked 10 deep"; };*/ - - // make sure that after testing the children that the index doesn't change. - QModelIndex newerIndex = model->index ( r, c, parent ); - Q_ASSERT ( index == newerIndex ); - } - } -} - -/*! - Tests model's implementation of QAbstractItemModel::data() - */ -void ModelTest::data() -{ - // Invalid index should return an invalid qvariant - Q_ASSERT ( !model->data ( QModelIndex() ).isValid() ); - - if ( model->rowCount() == 0 ) - return; - - // A valid index should have a valid QVariant data - Q_ASSERT ( model->index ( 0, 0 ).isValid() ); - - // shouldn't be able to set data on an invalid index - Q_ASSERT ( model->setData ( QModelIndex(), QLatin1String ( "foo" ), Qt::DisplayRole ) == false ); - - // General Purpose roles that should return a QString - QVariant variant = model->data ( model->index ( 0, 0 ), Qt::ToolTipRole ); - if ( variant.isValid() ) { - Q_ASSERT ( variant.canConvert() ); - } - variant = model->data ( model->index ( 0, 0 ), Qt::StatusTipRole ); - if ( variant.isValid() ) { - Q_ASSERT ( variant.canConvert() ); - } - variant = model->data ( model->index ( 0, 0 ), Qt::WhatsThisRole ); - if ( variant.isValid() ) { - Q_ASSERT ( variant.canConvert() ); - } - - // General Purpose roles that should return a QSize - variant = model->data ( model->index ( 0, 0 ), Qt::SizeHintRole ); - if ( variant.isValid() ) { - Q_ASSERT ( variant.canConvert() ); - } - - // General Purpose roles that should return a QFont - QVariant fontVariant = model->data ( model->index ( 0, 0 ), Qt::FontRole ); - if ( fontVariant.isValid() ) { - Q_ASSERT ( fontVariant.canConvert() ); - } - - // Check that the alignment is one we know about - QVariant textAlignmentVariant = model->data ( model->index ( 0, 0 ), Qt::TextAlignmentRole ); - if ( textAlignmentVariant.isValid() ) { - int alignment = textAlignmentVariant.toInt(); - Q_ASSERT ( alignment == ( alignment & ( Qt::AlignHorizontal_Mask | Qt::AlignVertical_Mask ) ) ); - } - - // General Purpose roles that should return a QColor - QVariant colorVariant = model->data ( model->index ( 0, 0 ), Qt::BackgroundRole ); - if ( colorVariant.isValid() ) { - Q_ASSERT ( colorVariant.canConvert() ); - } - - colorVariant = model->data ( model->index ( 0, 0 ), Qt::ForegroundRole ); - if ( colorVariant.isValid() ) { - Q_ASSERT ( colorVariant.canConvert() ); - } - - // Check that the "check state" is one we know about. - QVariant checkStateVariant = model->data ( model->index ( 0, 0 ), Qt::CheckStateRole ); - if ( checkStateVariant.isValid() ) { - int state = checkStateVariant.toInt(); - Q_ASSERT ( state == Qt::Unchecked || - state == Qt::PartiallyChecked || - state == Qt::Checked ); - } -} - -/*! - Store what is about to be inserted to make sure it actually happens - - \sa rowsInserted() - */ -void ModelTest::rowsAboutToBeInserted ( const QModelIndex &parent, int start, int end ) -{ - Q_ASSERT(start <= end); -// Q_UNUSED(end); -// qDebug() << "rowsAboutToBeInserted" << "start=" << start << "end=" << end << "parent=" << model->data ( parent ).toString() -// << "current count of parent=" << model->rowCount ( parent ); // << "display of last=" << model->data( model->index(start-1, 0, parent) ); -// qDebug() << model->index(start-1, 0, parent) << model->data( model->index(start-1, 0, parent) ); - Changing c; - c.parent = parent; - c.oldSize = model->rowCount ( parent ); - c.last = model->data ( model->index ( start - 1, 0, parent ) ); - c.next = model->data ( model->index ( start, 0, parent ) ); - insert.push ( c ); -} - -/*! - Confirm that what was said was going to happen actually did - - \sa rowsAboutToBeInserted() - */ -void ModelTest::rowsInserted ( const QModelIndex & parent, int start, int end ) -{ - Changing c = insert.pop(); - Q_ASSERT ( c.parent == parent ); -// qDebug() << "rowsInserted" << "start=" << start << "end=" << end << "oldsize=" << c.oldSize -// << "parent=" << model->data ( parent ).toString() << "current rowcount of parent=" << model->rowCount ( parent ); - - if (c.oldSize + ( end - start + 1 ) != model->rowCount ( parent )) { - for (int ii=start; ii <= end; ii++) - { - qDebug() << "itemWasInserted:" << ii << model->data ( model->index ( ii, 0, parent )); - } - qDebug(); - } - - Q_ASSERT ( c.oldSize + ( end - start + 1 ) == model->rowCount ( parent ) ); - Q_ASSERT ( c.last == model->data ( model->index ( start - 1, 0, c.parent ) ) ); - - if (c.next != model->data(model->index(end + 1, 0, c.parent))) { - qDebug() << start << end; - for (int i=0; i < model->rowCount(); ++i) - qDebug() << model->index(i, 0).data().toString(); - qDebug() << c.next << model->data(model->index(end + 1, 0, c.parent)); - } - - Q_ASSERT ( c.next == model->data ( model->index ( end + 1, 0, c.parent ) ) ); -} - -void ModelTest::layoutAboutToBeChanged() -{ - for ( int i = 0; i < qBound ( 0, model->rowCount(), 100 ); ++i ) - changing.append ( QPersistentModelIndex ( model->index ( i, 0 ) ) ); -} - -void ModelTest::layoutChanged() -{ - for ( int i = 0; i < changing.count(); ++i ) { - QPersistentModelIndex p = changing[i]; - Q_ASSERT ( p == model->index ( p.row(), p.column(), p.parent() ) ); - } - changing.clear(); -} - -/*! - Store what is about to be inserted to make sure it actually happens - - \sa rowsRemoved() - */ -void ModelTest::rowsAboutToBeRemoved ( const QModelIndex &parent, int start, int end ) -{ -qDebug() << "ratbr" << parent << start << end; - Changing c; - c.parent = parent; - c.oldSize = model->rowCount ( parent ); - c.last = model->data ( model->index ( start - 1, 0, parent ) ); - c.next = model->data ( model->index ( end + 1, 0, parent ) ); - remove.push ( c ); -} - -/*! - Confirm that what was said was going to happen actually did - - \sa rowsAboutToBeRemoved() - */ -void ModelTest::rowsRemoved ( const QModelIndex & parent, int start, int end ) -{ - qDebug() << "rr" << parent << start << end; - Changing c = remove.pop(); - Q_ASSERT ( c.parent == parent ); - Q_ASSERT ( c.oldSize - ( end - start + 1 ) == model->rowCount ( parent ) ); - Q_ASSERT ( c.last == model->data ( model->index ( start - 1, 0, c.parent ) ) ); - Q_ASSERT ( c.next == model->data ( model->index ( start, 0, c.parent ) ) ); -} - - diff --git a/libdiscover/tests/modeltest.h b/libdiscover/tests/modeltest.h deleted file mode 100644 index f74efef9..00000000 --- a/libdiscover/tests/modeltest.h +++ /dev/null @@ -1,94 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). -** All rights reserved. -** Contact: Nokia Corporation (qt-info@nokia.com) -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** GNU Lesser General Public License Usage -** This file may be used under the terms of the GNU Lesser General Public -** License version 2.1 as published by the Free Software Foundation and -** appearing in the file LICENSE.LGPL included in the packaging of this -** file. Please review the following information to ensure the GNU Lesser -** General Public License version 2.1 requirements will be met: -** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Nokia gives you certain additional -** rights. These rights are described in the Nokia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU General -** Public License version 3.0 as published by the Free Software Foundation -** and appearing in the file LICENSE.GPL included in the packaging of this -** file. Please review the following information to ensure the GNU General -** Public License version 3.0 requirements will be met: -** http://www.gnu.org/copyleft/gpl.html. -** -** Other Usage -** Alternatively, this file may be used in accordance with the terms and -** conditions contained in a signed written agreement between you and Nokia. -** -** -** -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - - -#ifndef MODELTEST_H -#define MODELTEST_H - -#include -#include -#include - -class ModelTest : public QObject -{ - Q_OBJECT - -public: - ModelTest( QAbstractItemModel *model, QObject *parent = nullptr ); - -private Q_SLOTS: - void nonDestructiveBasicTest(); - void rowCount(); - void columnCount(); - void hasIndex(); - void index(); - void parent(); - void data(); - -protected Q_SLOTS: - void runAllTests(); - void layoutAboutToBeChanged(); - void layoutChanged(); - void rowsAboutToBeInserted( const QModelIndex &parent, int start, int end ); - void rowsInserted( const QModelIndex & parent, int start, int end ); - void rowsAboutToBeRemoved( const QModelIndex &parent, int start, int end ); - void rowsRemoved( const QModelIndex & parent, int start, int end ); - -private: - void checkChildren( const QModelIndex &parent, int currentDepth = 0 ); - - QAbstractItemModel *model; - - struct Changing { - QModelIndex parent; - int oldSize; - QVariant last; - QVariant next; - }; - QStack insert; - QStack remove; - - bool fetchingMore; - - QList changing; -}; - -#endif