Commit bfaf2b24 authored by Andreas Cord-Landwehr's avatar Andreas Cord-Landwehr
Browse files

Revisit SkeletonModel and add unit test

- factor EditableCourseStub out of existing unit tests
- implement a unit test for the model
- cleanup model API
parent 4eb49dea
......@@ -41,7 +41,7 @@ public:
{
auto course = std::make_shared<CourseStub>(language, units);
course->setSelf(course);
return course;
return std::static_pointer_cast<ICourse>(course);
}
void setSelf(std::shared_ptr<ICourse> self) override
......
/*
* Copyright 2019 Andreas Cord-Landwehr <cordlandwehr@kde.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "editablecoursestub.h"
// define one virtual method out of line to pin EditableCourseStub to this translation unit
EditableCourseStub::~EditableCourseStub() = default;
/*
* Copyright 2019 Andreas Cord-Landwehr <cordlandwehr@kde.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef EDITABLECOURSESTUB_H
#define EDITABLECOURSESTUB_H
#include "src/core/ieditablecourse.h"
#include "src/core/ilanguage.h"
#include "src/core/unit.h"
#include <QObject>
class EditableCourseStub : public IEditableCourse
{
public:
EditableCourseStub(std::shared_ptr<ILanguage> language, QVector<std::shared_ptr<Unit>> units)
: IEditableCourse()
, m_language(language)
, m_units(units)
{
for (auto unit : m_units) {
unit->setCourse(this);
}
}
~EditableCourseStub() override;
static std::shared_ptr<IEditableCourse> create(std::shared_ptr<ILanguage> language, QVector<std::shared_ptr<Unit>> units)
{
auto course = std::make_shared<EditableCourseStub>(language, units);
course->setSelf(course);
return std::static_pointer_cast<IEditableCourse>(course);
}
void setSelf(std::shared_ptr<ICourse> self) override
{
m_self = self;
}
std::shared_ptr<IEditableCourse> self() const override
{
return std::static_pointer_cast<IEditableCourse>(m_self.lock());
}
QString id() const override
{
return m_id;
}
void setId(QString id) override
{
m_id = id;
emit idChanged();
}
QString foreignId() const override
{
return m_foreignId;
}
void setForeignId(QString id) override
{
m_foreignId = id;
}
QString title() const override
{
return m_title;
}
void setTitle(QString title) override
{
m_title = title;
emit titleChanged();
}
QString i18nTitle() const override
{
return m_i18nTitle;
}
void setI18nTitle(QString title) override
{
m_i18nTitle = title;
}
QString description() const override
{
return m_description;
}
void setDescription(QString description) override
{
m_description = description;
emit descriptionChanged();
}
std::shared_ptr<ILanguage> language() const override
{
return m_language;
}
void setLanguage(std::shared_ptr<ILanguage> language) override
{
m_language = language;
emit languageChanged();
}
QVector<std::shared_ptr<Unit>> units() override
{
return m_units;
}
std::shared_ptr<Unit> addUnit(std::unique_ptr<Unit> unit) override
{
m_units.append(std::move(unit));
auto unitPtr = m_units.last();
unitPtr->setCourse(this);
return unitPtr;
}
QUrl file() const override
{
return QUrl();
}
bool sync() override
{
return false;
}
void updateFrom(std::shared_ptr<ICourse>) override
{
// not implemented
}
bool isModified() const override
{
return false;
}
bool exportToFile(const QUrl &) const override
{
// do nothing
return false;
}
private:
std::weak_ptr<ICourse> m_self;
QString m_id{ "courseid" };
QString m_foreignId{ "foreigncourseid" };
QString m_title{ "title" };
QString m_i18nTitle{ "i18n title" };
QString m_description{ "description of the course" };
std::shared_ptr<ILanguage> m_language;
QVector<std::shared_ptr<Unit>> m_units;
};
#endif
......@@ -91,6 +91,40 @@ public:
{
return m_languages;
}
void appendCourse(std::shared_ptr<IEditableCourse> course)
{
emit courseAboutToBeAdded(course, m_courses.count());
m_courses.append(course);
emit courseAdded();
}
void removeCourse(std::shared_ptr<IEditableCourse> course)
{
auto index = m_courses.indexOf(course);
Q_ASSERT(index >= 0);
if (index >= 0) {
emit courseAboutToBeRemoved(index);
m_courses.remove(index);
emit courseRemoved();
}
}
void appendSkeleton(std::shared_ptr<IEditableCourse> skeleton)
{
emit skeletonAboutToBeAdded(skeleton, m_skeletons.count());
m_skeletons.append(skeleton);
emit skeletonAdded();
}
void removeSkeleton(std::shared_ptr<IEditableCourse> skeleton)
{
auto index = m_skeletons.indexOf(skeleton);
Q_ASSERT(index >= 0);
if (index >= 0) {
emit skeletonAboutToBeRemoved(index);
m_skeletons.remove(index);
emit skeletonRemoved();
}
}
void updateCourseFromSkeleton(std::shared_ptr<IEditableCourse> course) override
{
Q_UNUSED(course);
......
......@@ -68,6 +68,7 @@ ecm_mark_as_test(test_trainingsession)
set(TestEditorSession_SRCS
editorsession/test_editorsession.cpp
../mocks/editablerepositorystub.cpp
../mocks/editablecoursestub.cpp
../mocks/languagestub.cpp
)
add_executable(test_editorsession ${TestEditorSession_SRCS})
......@@ -138,10 +139,28 @@ set(TestCourseModel_SRCS
)
qt5_add_resources(TestCourseModel_SRCS ../../data/languages.qrc)
qt5_add_resources(TestCourseModel_SRCS ../testdata/testdata.qrc)
add_executable(test_coursemodel ${TestCourseModel_SRCS} )
add_executable(test_coursemodel ${TestCourseModel_SRCS})
target_link_libraries(test_coursemodel
artikulatecore
Qt5::Test
)
add_test(NAME test_coursemodel COMMAND test_coursemodel)
ecm_mark_as_test(test_coursemodel)
# test skeleton model class
set(TestSkeletonModel_SRCS
skeletonmodel/test_skeletonmodel.cpp
../mocks/editablerepositorystub.cpp
../mocks/editablecoursestub.cpp
../mocks/languagestub.cpp
)
qt5_add_resources(TestSkeletonModel_SRCS ../../data/languages.qrc)
qt5_add_resources(TestSkeletonModel_SRCS ../testdata/testdata.qrc)
add_executable(test_skeletonmodel ${TestSkeletonModel_SRCS})
target_link_libraries(test_skeletonmodel
artikulatecore
Qt5::Test
)
add_test(NAME test_skeletonmodel COMMAND test_skeletonmodel)
ecm_mark_as_test(test_skeletonmodel)
......@@ -27,140 +27,19 @@
#include "src/core/language.h"
#include "src/core/resources/skeletonresource.h"
#include "src/core/unit.h"
#include "../mocks/editablecoursestub.h"
#include "../mocks/languagestub.h"
#include <QTest>
#include <QSignalSpy>
class EditableCourseStub : public IEditableCourse
{
public:
EditableCourseStub(std::shared_ptr<ILanguage> language, QVector<std::shared_ptr<Unit>> units)
: IEditableCourse()
, m_language(language)
, m_units(units)
{
for (auto unit : m_units) {
unit->setCourse(this);
}
}
~EditableCourseStub() override;
void setSelf(std::shared_ptr<ICourse> self) override
{
m_self = self;
}
std::shared_ptr<IEditableCourse> self() const override
{
return std::static_pointer_cast<IEditableCourse>(m_self.lock());
}
QString id() const override
{
return m_id;
}
void setId(QString id) override
{
m_id = id;
emit idChanged();
}
QString foreignId() const override
{
return m_foreignId;
}
void setForeignId(QString id) override
{
m_foreignId = id;
}
QString title() const override
{
return m_title;
}
void setTitle(QString title) override
{
m_title = title;
emit titleChanged();
}
QString i18nTitle() const override
{
return m_i18nTitle;
}
void setI18nTitle(QString title) override
{
m_i18nTitle = title;
}
QString description() const override
{
return m_description;
}
void setDescription(QString description) override
{
m_description = description;
emit descriptionChanged();
}
std::shared_ptr<ILanguage> language() const override
{
return m_language;
}
void setLanguage(std::shared_ptr<ILanguage> language) override
{
m_language = language;
emit languageChanged();
}
QVector<std::shared_ptr<Unit>> units() override
{
return m_units;
}
std::shared_ptr<Unit> addUnit(std::unique_ptr<Unit> unit) override
{
m_units.append(std::move(unit));
auto unitPtr = m_units.last();
unitPtr->setCourse(this);
return unitPtr;
}
QUrl file() const override
{
return QUrl();
}
bool sync() override
{
return false;
}
void updateFrom(std::shared_ptr<ICourse>) override
{
// not implemented
}
bool isModified() const override
{
return false;
}
bool exportToFile(const QUrl &) const override
{
// do nothing
return false;
}
private:
std::weak_ptr<ICourse> m_self;
QString m_id{ "courseid" };
QString m_foreignId{ "foreigncourseid" };
QString m_title{ "title" };
QString m_i18nTitle{ "i18n title" };
QString m_description{ "description of the course" };
std::shared_ptr<ILanguage> m_language;
QVector<std::shared_ptr<Unit>> m_units;
};
// define one virtual method out of line to pin CourseStub to this translation unit
EditableCourseStub::~EditableCourseStub() = default;
void TestEditorSession::init()
{
// TODO initialization of test case
// no initialization of test case
}
void TestEditorSession::cleanup()
{
// TODO cleanup after test run
// no cleanup after test run
}
void TestEditorSession::createEditorSession()
......
/*
* Copyright 2019 Andreas Cord-Landwehr <cordlandwehr@kde.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "test_skeletonmodel.h"
#include "src/core/icourse.h"
#include "src/core/language.h"
#include "src/models/skeletonmodel.h"
#include "../mocks/editablerepositorystub.h"
#include "../mocks/editablecoursestub.h"
#include "../mocks/languagestub.h"
#include <QTest>
#include <QSignalSpy>
void TestSkeletonModel::init()
{
// no initialization of test case
}
void TestSkeletonModel::cleanup()
{
// no cleanup after test run
}
void TestSkeletonModel::testInit()
{
// boilerplate
std::shared_ptr<ILanguage> language(new LanguageStub("de"));
std::vector<std::shared_ptr<ILanguage>> languages;
languages.push_back(language);
auto skeleton = EditableCourseStub::create(language, QVector<std::shared_ptr<Unit>>({}));
EditableRepositoryStub repository(languages, {skeleton}, {});
// test initialization
SkeletonModel model(&repository);
QVERIFY(model.resourceRepository() == &repository);
QCOMPARE(model.rowCount(), 1);
QCOMPARE(model.skeleton(0).value<QObject*>(), skeleton.get());
}
void TestSkeletonModel::testAddRemoveOperations()
{
// boilerplate
std::shared_ptr<ILanguage> language(new LanguageStub("de"));
std::vector<std::shared_ptr<ILanguage>> languages;
languages.push_back(language);
EditableRepositoryStub repository(languages, {}, {});
// test initialization
SkeletonModel model(&repository);
QVERIFY(model.resourceRepository() == &repository);
QCOMPARE(model.rowCount(), 0);
auto skeleton = EditableCourseStub::create(language, QVector<std::shared_ptr<Unit>>({}));
{ // add skeleton
QSignalSpy spyAboutToBeAdded(&repository, SIGNAL(skeletonAboutToBeAdded(std::shared_ptr<IEditableCourse>,int)));
QSignalSpy spyAdded(&repository, SIGNAL(skeletonAdded()));
QCOMPARE(spyAboutToBeAdded.count(), 0);
QCOMPARE(spyAdded.count(), 0);
repository.appendSkeleton(skeleton);
QCOMPARE(model.rowCount(), 1);
QCOMPARE(model.skeleton(0).value<QObject*>(), skeleton.get());
QCOMPARE(spyAboutToBeAdded.count(), 1);
QCOMPARE(spyAdded.count(), 1);
}
{ // remove skeleton
QSignalSpy spyAboutToBeRemoved(&repository, SIGNAL(skeletonAboutToBeRemoved(int)));
QSignalSpy spyRemoved(&repository, SIGNAL(skeletonRemoved()));
QCOMPARE(spyAboutToBeRemoved.count(), 0);
QCOMPARE(spyRemoved.count(), 0);
repository.removeSkeleton(skeleton);
QCOMPARE(model.rowCount(), 0);
QCOMPARE(spyAboutToBeRemoved.count(), 1);
QCOMPARE(spyRemoved.count(), 1);
}
}
void TestSkeletonModel::testDataChangedSignals()
{
// boilerplate
std::shared_ptr<ILanguage> language(new LanguageStub("de"));
std::vector<std::shared_ptr<ILanguage>> languages;
languages.push_back(language);
auto skeleton = EditableCourseStub::create(language, QVector<std::shared_ptr<Unit>>({}));
EditableRepositoryStub repository(languages, {skeleton}, {});
// test initialization
SkeletonModel model(&repository);
QVERIFY(model.resourceRepository() == &repository);
QCOMPARE(model.rowCount(), 1);
QCOMPARE(model.skeleton(0).value<QObject*>(), skeleton.get());
{ // test adding of connections
QSignalSpy spyUpdate(&model, SIGNAL(dataChanged(const QModelIndex &,const QModelIndex &, const QVector<int>)));
QCOMPARE(spyUpdate.count(), 0);
std::static_pointer_cast<EditableCourseStub>(skeleton)->setTitle("TitleSwitched");
QCOMPARE(spyUpdate.count(), 1);
}
{ // test removal of connections
QSignalSpy spyUpdate(&model, SIGNAL(dataChanged(const QModelIndex &,const QModelIndex &, const QVector<int>)));
QCOMPARE(spyUpdate.count(), 0);
repository.removeSkeleton(skeleton);
std::static_pointer_cast<EditableCourseStub>(skeleton)->setTitle("TitleSwitchedAgain");
QCOMPARE(spyUpdate.count(), 0);
}
}
QTEST_GUILESS_MAIN(TestSkeletonModel)
/*
* Copyright 2019 Andreas Cord-Landwehr <cordlandwehr@kde.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef TEST_SKELETONMODEL_H
#define TEST_SKELETONMODEL_H
#include <QObject>
class TestSkeletonModel : public QObject
{
Q_OBJECT
public:
TestSkeletonModel() = default;
private Q_SLOTS:
/**
* Called before every test case.
*/
void init();
/**
* Called after every test case.
*/
void cleanup();
/**
* @brief Test course model initialization from a resource repository stub
*/
void testInit();
/**
* @brief Test add/remove signal propagation as emitted from resource repository stub
*/
void testAddRemoveOperations();
/**
* @brief Test data changed signal emit
*/