summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorJedidiah Barber <contact@jedbarber.id.au>2021-07-14 11:49:10 +1200
committerJedidiah Barber <contact@jedbarber.id.au>2021-07-14 11:49:10 +1200
commitd24f813f3f2a05c112e803e4256b53535895fc98 (patch)
tree601e6ae9a1cd44bcfdcf91739a5ca36aedd827c9 /tests
Initial mirror commitHEADmaster
Diffstat (limited to 'tests')
-rw-r--r--tests/common/RecordsParam.cpp51
-rw-r--r--tests/common/RecordsParam.h58
-rw-r--r--tests/common/RecordsParam_create.cpp62
-rw-r--r--tests/common/printQtTypes.cpp31
-rw-r--r--tests/common/printQtTypes.h23
-rw-r--r--tests/fute/charts/charts.pro35
-rw-r--r--tests/fute/charts/charts_test.cpp52
-rw-r--r--tests/fute/charts/charts_test.h31
-rw-r--r--tests/fute/charts/main.cpp12
-rw-r--r--tests/fute/pieCharts/main.cpp12
-rw-r--r--tests/fute/pieCharts/pieCharts.pro29
-rw-r--r--tests/fute/pieCharts/pieCharts_test.cpp52
-rw-r--r--tests/fute/pieCharts/pieCharts_test.h34
-rw-r--r--tests/fute/timeCharts/main.cpp12
-rw-r--r--tests/fute/timeCharts/timeCharts.pro35
-rw-r--r--tests/fute/timeCharts/timeCharts_test.cpp58
-rw-r--r--tests/fute/timeCharts/timeCharts_test.h30
-rw-r--r--tests/mocks/CardPack_mock.cpp31
-rw-r--r--tests/mocks/CardPack_mock.h23
-rw-r--r--tests/mocks/Dictionary_mock.cpp6
-rw-r--r--tests/mocks/Dictionary_mock.h32
-rw-r--r--tests/mocks/RandomGenerator_mock.h33
-rw-r--r--tests/mocks/TimeProvider_mock.cpp9
-rw-r--r--tests/unit/Card/Card_GenerateAnswers_test.cpp66
-rw-r--r--tests/unit/Card/Card_GenerateAnswers_test.h34
-rw-r--r--tests/unit/Card/Card_test.cpp36
-rw-r--r--tests/unit/Card/Card_test.h18
-rw-r--r--tests/unit/Card/Card_test_QuestionAnswer.cpp30
-rw-r--r--tests/unit/Card/card.pri10
-rw-r--r--tests/unit/CardPack/CardPack_GenerateCards_test.cpp43
-rw-r--r--tests/unit/CardPack/CardPack_GenerateCards_test.h26
-rw-r--r--tests/unit/CardPack/CardPack_test.cpp80
-rw-r--r--tests/unit/CardPack/CardPack_test.h31
-rw-r--r--tests/unit/CardPack/cPack.pri7
-rw-r--r--tests/unit/CardSideView/CardSideView_test.cpp49
-rw-r--r--tests/unit/CardSideView/CardSideView_test.h21
-rw-r--r--tests/unit/CardSideView/csView.pri8
-rw-r--r--tests/unit/RandomGenerator/RandomGenerator_test.cpp79
-rw-r--r--tests/unit/RandomGenerator/rndGen.pri6
-rw-r--r--tests/unit/Settings/FieldStyleFactory_test.cpp88
-rw-r--r--tests/unit/Settings/FieldStyleFactory_test.h29
-rw-r--r--tests/unit/Settings/StudySettings_test.cpp42
-rw-r--r--tests/unit/Settings/StudySettings_test.h22
-rw-r--r--tests/unit/Settings/TestSettings.cpp16
-rw-r--r--tests/unit/Settings/TestSettings.h11
-rw-r--r--tests/unit/Settings/set.pri10
-rw-r--r--tests/unit/SpacedRepetitionModel/SRModel_pickCard_test.cpp420
-rw-r--r--tests/unit/SpacedRepetitionModel/SRModel_pickCard_test.h27
-rw-r--r--tests/unit/SpacedRepetitionModel/SRModel_schedule_test.cpp304
-rw-r--r--tests/unit/SpacedRepetitionModel/SRModel_schedule_test.h24
-rw-r--r--tests/unit/SpacedRepetitionModel/SRModel_showGrades_test.cpp36
-rw-r--r--tests/unit/SpacedRepetitionModel/SRModel_showGrades_test.h14
-rw-r--r--tests/unit/SpacedRepetitionModel/SRModel_test.cpp67
-rw-r--r--tests/unit/SpacedRepetitionModel/SRModel_test.h46
-rw-r--r--tests/unit/SpacedRepetitionModel/srModel.pri16
-rw-r--r--tests/unit/cards.pri24
-rw-r--r--tests/unit/common.pri3
-rw-r--r--tests/unit/main.cpp9
-rw-r--r--tests/unit/random.pri1
-rw-r--r--tests/unit/studySets.pri9
-rw-r--r--tests/unit/unit_tests.pro29
61 files changed, 2542 insertions, 0 deletions
diff --git a/tests/common/RecordsParam.cpp b/tests/common/RecordsParam.cpp
new file mode 100644
index 0000000..74ad802
--- /dev/null
+++ b/tests/common/RecordsParam.cpp
@@ -0,0 +1,51 @@
+#include "RecordsParam.h"
+
+#include "../../src/dictionary/Field.h"
+#include "../../src/dictionary/DicRecord.h"
+
+const vector<string> RecordsParam::fieldNames {"English", "Russian", "Finnish"};
+
+RecordsParam::RecordsParam(
+ vector<int> packFields,
+ vector<vector<string> > records,
+ vector<string> questions,
+ vector<vector<string> > answers):
+ packFields(packFields),
+ questions(questions),
+ answers(answers)
+{
+ for(vector<string> fieldValues: records)
+ {
+ DicRecord* record = new DicRecord;
+ for(unsigned i = 0; i < fieldValues.size(); i++)
+ record->setField(fieldNames[i].c_str(), fieldValues[i].c_str());
+ this->records << record;
+ }
+}
+
+vector<string> RecordsParam::hashToStrVector(const QHash<QString, QString>& hash,
+ const vector<string>& keys)
+{
+ vector<string> res;
+ for(unsigned i = 0; i < keys.size(); i++)
+ res.push_back( hash[keys[i].c_str()].toStdString() );
+ return res;
+}
+
+vector<string> RecordsParam::recordsToStr() const
+{
+ vector<string> res;
+ for(DicRecord* record: records)
+ {
+ vector<string> fieldValues = hashToStrVector(record->getFields(), fieldNames);
+ res.push_back(string("(") + vectorToStr<string>(fieldValues) + ")");
+ }
+ return res;
+}
+
+ostream& operator<<(ostream& os, const RecordsParam& param)
+{
+ os << "Fields(" << param.vectorToStr<int>(param.packFields) << ") ";
+ os << "{" << param.vectorToStr<string>( param.recordsToStr() ) << "}";
+ return os;
+}
diff --git a/tests/common/RecordsParam.h b/tests/common/RecordsParam.h
new file mode 100644
index 0000000..19d4532
--- /dev/null
+++ b/tests/common/RecordsParam.h
@@ -0,0 +1,58 @@
+#ifndef RECORDS_PARAM_H
+#define RECORDS_PARAM_H
+
+#include <iostream>
+#include <vector>
+#include <string>
+#include <sstream>
+#include <QtCore>
+
+class DicRecord;
+
+using std::vector;
+using std::string;
+using std::ostream;
+
+struct RecordsParam
+{
+public:
+ static vector<RecordsParam> createParams();
+
+ template <typename T>
+ static string vectorToStr(const vector<T>& v);
+
+ static vector<string> hashToStrVector(const QHash<QString, QString>& hash,
+ const vector<string>& keys);
+
+public:
+ RecordsParam(vector<int> packFields, vector<vector<string> > records,
+ vector<string> questions, vector<vector<string> > answers);
+
+ vector<string> recordsToStr() const;
+
+public:
+ static const vector<string> fieldNames;
+
+public:
+ QList<DicRecord*> records;
+ vector<int> packFields;
+ vector<string> questions;
+ vector<vector<string> > answers;
+};
+
+ostream& operator<<(ostream& os, const RecordsParam& param);
+
+template <typename T>
+string RecordsParam::vectorToStr(const vector<T>& v)
+{
+ std::stringstream ss;
+ for(unsigned i = 0; i < v.size(); i++)
+ {
+ if(i != 0)
+ ss << ", ";
+ ss << v[i];
+ }
+ return ss.str();
+}
+
+#endif
diff --git a/tests/common/RecordsParam_create.cpp b/tests/common/RecordsParam_create.cpp
new file mode 100644
index 0000000..b247f35
--- /dev/null
+++ b/tests/common/RecordsParam_create.cpp
@@ -0,0 +1,62 @@
+#include "RecordsParam.h"
+
+typedef RecordsParam RP;
+
+vector<RecordsParam> RecordsParam::createParams()
+{
+return
+ {
+ RP({0, 1},
+ {{"table", "стол"}, {"window", "окно"}},
+ {"table", "window"},
+ {{"table", "стол"}, {"window", "окно"}}),
+ RP({0, 1},
+ {{"table", "стол"}, {"window", "окно"}, {"world", "мир"}},
+ {"table", "window", "world"},
+ {{"table", "стол"}, {"window", "окно"}, {"world", "мир"}}),
+ RP({1, 0},
+ {{"table", "стол"}, {"window", "окно"}},
+ {"стол", "окно"},
+ {{"стол", "table"}, {"окно", "window"}}),
+ RP({0, 1},
+ {{"table", "стол"}, {"table", "стол"}},
+ {"table"},
+ {{"table", "стол"}}),
+ RP({0, 1},
+ {{"table", "стол"}, {"table", "таблица"}},
+ {"table"},
+ {{"table", "стол; таблица"}}),
+ RP({1, 0},
+ {{"world", "мир"}, {"peace", "мир"}},
+ {"мир"},
+ {{"мир", "world; peace"}}),
+ RP({1, 0},
+ {{"man", "человек; мужчина"}, {"man", "мужик"}},
+ {"человек", "мужчина", "мужик"},
+ {{"человек", "man"}, {"мужчина", "man"}, {"мужик", "man"}}),
+ RP({0, 1},
+ {{"better; best", "лучший"}},
+ {"better", "best"},
+ {{"better", "лучший"}, {"best", "лучший"}}),
+ RP({0, 1},
+ {{"table", ""},},
+ {},
+ {}),
+ RP({0, 1},
+ {{"", "стол"},},
+ {},
+ {}),
+ RP({0, 1, 2},
+ {{"table", "стол", "pöytä"}, {"window", "окно", "ikkuna"}},
+ {"table", "window"},
+ {{"table", "стол", "pöytä"}, {"window", "окно", "ikkuna"}}),
+ RP({0, 2, 1},
+ {{"table", "стол", "pöytä"}, {"window", "окно", "ikkuna"}},
+ {"table", "window"},
+ {{"table", "pöytä", "стол"}, {"window", "ikkuna", "окно"}}),
+ RP({0, 1, 2},
+ {{"table", "", "pöytä"}, {"window", "окно", ""}},
+ {"table", "window"},
+ {{"table", "", "pöytä"}, {"window", "окно", ""}}),
+ };
+}
diff --git a/tests/common/printQtTypes.cpp b/tests/common/printQtTypes.cpp
new file mode 100644
index 0000000..72975b7
--- /dev/null
+++ b/tests/common/printQtTypes.cpp
@@ -0,0 +1,31 @@
+#include "printQtTypes.h"
+
+void PrintTo(const QString& str, ::std::ostream* os)
+{
+ *os << "\"" << str.toStdString() << "\"";
+}
+
+void PrintTo(const QStringList& list, ::std::ostream* os)
+{
+ *os << "(" << list.join(", ").toStdString() << ")";
+}
+
+void PrintTo(const QDateTime& time, ::std::ostream* os)
+{
+ *os << time.toString("yyyy-MM-dd HH:mm:ss").toStdString();
+}
+
+void PrintTo(const QByteArray& array, ::std::ostream* os)
+{
+ *os << "\"";
+ const char* hex = array.toHex().constData();
+ for(int i = 0; i < array.size(); i++)
+ {
+ unsigned char ch = array.constData()[i];
+ if(ch >= 32 && ch <= 126)
+ *os << ch;
+ else
+ *os << "\\x" << hex[i * 2] << hex[i * 2 + 1] << " ";
+ }
+ *os << "\"";
+}
diff --git a/tests/common/printQtTypes.h b/tests/common/printQtTypes.h
new file mode 100644
index 0000000..20c23d6
--- /dev/null
+++ b/tests/common/printQtTypes.h
@@ -0,0 +1,23 @@
+#ifndef PRINT_QT_TYPES_H
+#define PRINT_QT_TYPES_H
+
+#include <iostream>
+#include <QtCore>
+#include <vector>
+
+using std::ostream;
+using std::vector;
+
+void PrintTo(const QString& str, ::std::ostream* os);
+void PrintTo(const QStringList& list, ::std::ostream* os);
+
+#define ASSERT_EQ_QSTR(x, y) ASSERT_EQ(x, y) << "\"" << x.toStdString() << "\"" << \
+ " != " << "\"" << y.toStdString() << "\"";
+
+#define ASSERT_EQ_QSTRLIST(x, y) ASSERT_EQ(x, y) << "(" << x.join(", ").toStdString() << ")" << \
+ " != " << "(" << y.join(", ").toStdString() << ")";
+
+void PrintTo(const QDateTime& time, ::std::ostream* os);
+void PrintTo(const QByteArray& array, ::std::ostream* os);
+
+#endif
diff --git a/tests/fute/charts/charts.pro b/tests/fute/charts/charts.pro
new file mode 100644
index 0000000..613fee8
--- /dev/null
+++ b/tests/fute/charts/charts.pro
@@ -0,0 +1,35 @@
+TEMPLATE = app
+QT += widgets
+TARGET = charts_test
+DEPENDPATH += .
+INCLUDEPATH += .
+QMAKE_CXXFLAGS += -std=gnu++11
+DESTDIR = ./
+
+win32: {
+ CONFIG += console
+ }
+
+HEADERS = \
+ charts_test.h \
+ ../../../src/charts/Chart.h \
+ ../../../src/charts/TimeChart.h \
+ ../../../src/charts/DataPoint.h \
+ ../../../src/charts/ChartScene.h \
+ ../../../src/charts/ChartView.h \
+ ../../../src/charts/ChartAxes.h \
+ ../../../src/charts/ChartDataLine.h \
+ ../../../src/charts/ChartMarker.h \
+ ../../../src/charts/ChartToolTip.h
+
+SOURCES = \
+ main.cpp \
+ charts_test.cpp \
+ ../../../src/charts/Chart.cpp \
+ ../../../src/charts/TimeChart.cpp \
+ ../../../src/charts/ChartScene.cpp \
+ ../../../src/charts/ChartView.cpp \
+ ../../../src/charts/ChartAxes.cpp \
+ ../../../src/charts/ChartDataLine.cpp \
+ ../../../src/charts/ChartMarker.cpp \
+ ../../../src/charts/ChartToolTip.cpp
diff --git a/tests/fute/charts/charts_test.cpp b/tests/fute/charts/charts_test.cpp
new file mode 100644
index 0000000..0870027
--- /dev/null
+++ b/tests/fute/charts/charts_test.cpp
@@ -0,0 +1,52 @@
+#include "charts_test.h"
+#include "../../../src/charts/Chart.h"
+
+#include <cstdlib>
+#include <time.h>
+
+ChartsTest::ChartsTest()
+{
+ srand(time(NULL));
+ createUi();
+ changeDataSet();
+}
+
+void ChartsTest::changeDataSet()
+{
+ const int daysNum = 7;
+ yValuesStr = "Values: ";
+ dataSet.clear();
+ for (int i = 0; i < daysNum; i++)
+ addDataPoint(i);
+ chart->setDataSet(dataSet);
+ valuesLabel->setText(yValuesStr);
+}
+
+void ChartsTest::addDataPoint(int index)
+{
+ const int firstDay = 15;
+ QString xLabel = QString::number(firstDay + index) + ".11";
+ int yValue = rand() % 70;
+ dataSet << DataPoint(xLabel, yValue, xLabel);
+ yValuesStr += QString::number(yValue) + ", ";
+}
+
+void ChartsTest::createUi()
+{
+ QPushButton* newBtn = new QPushButton(tr("New chart"));
+ connect(newBtn, SIGNAL(clicked()), SLOT(changeDataSet()));
+
+ valuesLabel = new QLabel;
+ chart = new Chart;
+ chart->setLabels("Date", "Value");
+
+ QHBoxLayout* controlLt = new QHBoxLayout;
+ controlLt->addWidget(valuesLabel);
+ controlLt->addWidget(newBtn);
+
+ QVBoxLayout* mainLt = new QVBoxLayout;
+ mainLt->addLayout(controlLt);
+ mainLt->addWidget(chart);
+ setLayout(mainLt);
+ resize(800, 500);
+}
diff --git a/tests/fute/charts/charts_test.h b/tests/fute/charts/charts_test.h
new file mode 100644
index 0000000..3975444
--- /dev/null
+++ b/tests/fute/charts/charts_test.h
@@ -0,0 +1,31 @@
+#ifndef CHARTS_TEST_H
+#define CHARTS_TEST_H
+
+#include <QtCore>
+#include <QtWidgets>
+
+#include "../../../src/charts/DataPoint.h"
+
+class Chart;
+
+class ChartsTest: public QWidget
+{
+ Q_OBJECT
+public:
+ ChartsTest();
+
+private:
+ void createUi();
+ void addDataPoint(int index);
+
+private slots:
+ void changeDataSet();
+
+private:
+ QString yValuesStr;
+ Chart* chart;
+ QList<DataPoint> dataSet;
+ QLabel* valuesLabel;
+};
+
+#endif
diff --git a/tests/fute/charts/main.cpp b/tests/fute/charts/main.cpp
new file mode 100644
index 0000000..346133d
--- /dev/null
+++ b/tests/fute/charts/main.cpp
@@ -0,0 +1,12 @@
+#include <QtWidgets>
+
+#include "charts_test.h"
+
+int main(int argc, char *argv[])
+{
+ QApplication app(argc, argv);
+ ChartsTest mainWin;
+ mainWin.show();
+ return app.exec();
+}
+
diff --git a/tests/fute/pieCharts/main.cpp b/tests/fute/pieCharts/main.cpp
new file mode 100644
index 0000000..ed7ce6e
--- /dev/null
+++ b/tests/fute/pieCharts/main.cpp
@@ -0,0 +1,12 @@
+#include <QtWidgets>
+
+#include "pieCharts_test.h"
+
+int main(int argc, char *argv[])
+{
+ QApplication app(argc, argv);
+ PieChartsTest mainWin;
+ mainWin.show();
+ return app.exec();
+}
+
diff --git a/tests/fute/pieCharts/pieCharts.pro b/tests/fute/pieCharts/pieCharts.pro
new file mode 100644
index 0000000..960dd59
--- /dev/null
+++ b/tests/fute/pieCharts/pieCharts.pro
@@ -0,0 +1,29 @@
+TEMPLATE = app
+QT += widgets
+TARGET = piecharts_test
+DEPENDPATH += .
+INCLUDEPATH += .
+QMAKE_CXXFLAGS += -std=gnu++11
+DESTDIR = ./
+
+win32: {
+ CONFIG += console
+ }
+
+HEADERS = \
+ pieCharts_test.h \
+ ../../../src/charts/PieChart.h \
+ ../../../src/charts/DataPoint.h \
+ ../../../src/charts/PieChartScene.h \
+ ../../../src/charts/ChartView.h \
+ ../../../src/charts/PieRound.h \
+ ../../../src/charts/PieLegend.h
+
+SOURCES = \
+ main.cpp \
+ pieCharts_test.cpp \
+ ../../../src/charts/PieChart.cpp \
+ ../../../src/charts/PieChartScene.cpp \
+ ../../../src/charts/ChartView.cpp \
+ ../../../src/charts/PieRound.cpp \
+ ../../../src/charts/PieLegend.cpp
diff --git a/tests/fute/pieCharts/pieCharts_test.cpp b/tests/fute/pieCharts/pieCharts_test.cpp
new file mode 100644
index 0000000..d88981a
--- /dev/null
+++ b/tests/fute/pieCharts/pieCharts_test.cpp
@@ -0,0 +1,52 @@
+#include "pieCharts_test.h"
+#include "../../../src/charts/PieChart.h"
+
+#include <cstdlib>
+#include <time.h>
+
+const QStringList PieChartsTest::Labels =
+ {"Studied", "Scheduled for today", "New"};
+
+PieChartsTest::PieChartsTest()
+{
+ srand(time(NULL));
+ createUi();
+ changeDataSet();
+}
+
+void PieChartsTest::changeDataSet()
+{
+ yValuesStr = "Values: ";
+ dataSet.clear();
+ for (int i = 0; i < 3; i++)
+ addDataPoint(i);
+ chart->setDataSet(dataSet);
+ valuesLabel->setText(yValuesStr);
+}
+
+void PieChartsTest::addDataPoint(int index)
+{
+ int yValue = rand() % 200;
+ dataSet << DataPoint(Labels[index], yValue, "");
+ yValuesStr += QString::number(yValue) + ", ";
+}
+
+void PieChartsTest::createUi()
+{
+ QPushButton* newBtn = new QPushButton(tr("New chart"));
+ connect(newBtn, SIGNAL(clicked()), SLOT(changeDataSet()));
+
+ valuesLabel = new QLabel;
+ chart = new PieChart;
+ chart->setColors({"#39c900", "#ece900", "#ff0000"});
+
+ QHBoxLayout* controlLt = new QHBoxLayout;
+ controlLt->addWidget(valuesLabel);
+ controlLt->addWidget(newBtn);
+
+ QVBoxLayout* mainLt = new QVBoxLayout;
+ mainLt->addLayout(controlLt);
+ mainLt->addWidget(chart);
+ setLayout(mainLt);
+ resize(800, 500);
+}
diff --git a/tests/fute/pieCharts/pieCharts_test.h b/tests/fute/pieCharts/pieCharts_test.h
new file mode 100644
index 0000000..835ff72
--- /dev/null
+++ b/tests/fute/pieCharts/pieCharts_test.h
@@ -0,0 +1,34 @@
+#ifndef PIE_CHARTS_TEST_H
+#define PIE_CHARTS_TEST_H
+
+#include <QtCore>
+#include <QtWidgets>
+
+#include "../../../src/charts/DataPoint.h"
+
+class PieChart;
+
+class PieChartsTest: public QWidget
+{
+ Q_OBJECT
+public:
+ static const QStringList Labels;
+
+public:
+ PieChartsTest();
+
+private:
+ void createUi();
+ void addDataPoint(int index);
+
+private slots:
+ void changeDataSet();
+
+private:
+ QString yValuesStr;
+ PieChart* chart;
+ QList<DataPoint> dataSet;
+ QLabel* valuesLabel;
+};
+
+#endif
diff --git a/tests/fute/timeCharts/main.cpp b/tests/fute/timeCharts/main.cpp
new file mode 100644
index 0000000..3abb753
--- /dev/null
+++ b/tests/fute/timeCharts/main.cpp
@@ -0,0 +1,12 @@
+#include <QtWidgets>
+
+#include "timeCharts_test.h"
+
+int main(int argc, char *argv[])
+{
+ QApplication app(argc, argv);
+ TimeChartsTest mainWin;
+ mainWin.show();
+ return app.exec();
+}
+
diff --git a/tests/fute/timeCharts/timeCharts.pro b/tests/fute/timeCharts/timeCharts.pro
new file mode 100644
index 0000000..a570829
--- /dev/null
+++ b/tests/fute/timeCharts/timeCharts.pro
@@ -0,0 +1,35 @@
+TEMPLATE = app
+QT += widgets
+TARGET = timeCharts_test
+DEPENDPATH += .
+INCLUDEPATH += .
+QMAKE_CXXFLAGS += -std=gnu++11
+DESTDIR = ./
+
+win32: {
+ CONFIG += console
+ }
+
+HEADERS = \
+ timeCharts_test.h \
+ ../../../src/charts/Chart.h \
+ ../../../src/charts/TimeChart.h \
+ ../../../src/charts/DataPoint.h \
+ ../../../src/charts/ChartScene.h \
+ ../../../src/charts/ChartView.h \
+ ../../../src/charts/ChartAxes.h \
+ ../../../src/charts/ChartDataLine.h \
+ ../../../src/charts/ChartMarker.h \
+ ../../../src/charts/ChartToolTip.h
+
+SOURCES = \
+ main.cpp \
+ timeCharts_test.cpp \
+ ../../../src/charts/Chart.cpp \
+ ../../../src/charts/TimeChart.cpp \
+ ../../../src/charts/ChartScene.cpp \
+ ../../../src/charts/ChartView.cpp \
+ ../../../src/charts/ChartAxes.cpp \
+ ../../../src/charts/ChartDataLine.cpp \
+ ../../../src/charts/ChartMarker.cpp \
+ ../../../src/charts/ChartToolTip.cpp
diff --git a/tests/fute/timeCharts/timeCharts_test.cpp b/tests/fute/timeCharts/timeCharts_test.cpp
new file mode 100644
index 0000000..53f0921
--- /dev/null
+++ b/tests/fute/timeCharts/timeCharts_test.cpp
@@ -0,0 +1,58 @@
+#include "../../../src/charts/TimeChart.h"
+
+#include <cstdlib>
+#include <time.h>
+#include "timeCharts_test.h"
+
+TimeChartsTest::TimeChartsTest()
+{
+ srand(time(NULL));
+ createUi();
+ changeDataSet();
+}
+
+void TimeChartsTest::changeDataSet()
+{
+ int daysNum = periodBox->value();
+ yValuesStr = "Values: ";
+ dates.clear();
+ for (int i = 0; i < daysNum; i++)
+ addDataPoint(i);
+ chart->setDates(dates, daysNum, 1);
+ valuesLabel->setText(yValuesStr);
+}
+
+void TimeChartsTest::addDataPoint(int index)
+{
+ QDateTime date = QDateTime::currentDateTime().addDays(index);
+ int dayCardsNum = rand() % 50;
+ for(int i = 0; i < dayCardsNum; i++)
+ dates << date;
+ yValuesStr += QString::number(dayCardsNum) + ", ";
+}
+
+void TimeChartsTest::createUi()
+{
+ QPushButton* newBtn = new QPushButton(tr("New chart"));
+ connect(newBtn, SIGNAL(clicked()), SLOT(changeDataSet()));
+
+ valuesLabel = new QLabel;
+ valuesLabel->setMaximumWidth(750);
+
+ periodBox = new QSpinBox;
+ periodBox->setRange(7, 5000);
+
+ chart = new TimeChart;
+ chart->setLabels(tr("Date"), tr("Cards"));
+
+ QHBoxLayout* controlLt = new QHBoxLayout;
+ controlLt->addWidget(periodBox);
+ controlLt->addWidget(newBtn);
+
+ QVBoxLayout* mainLt = new QVBoxLayout;
+ mainLt->addLayout(controlLt);
+ mainLt->addWidget(valuesLabel);
+ mainLt->addWidget(chart);
+ setLayout(mainLt);
+ resize(800, 500);
+}
diff --git a/tests/fute/timeCharts/timeCharts_test.h b/tests/fute/timeCharts/timeCharts_test.h
new file mode 100644
index 0000000..49d6346
--- /dev/null
+++ b/tests/fute/timeCharts/timeCharts_test.h
@@ -0,0 +1,30 @@
+#ifndef CHARTS_TEST_H
+#define CHARTS_TEST_H
+
+#include <QtCore>
+#include <QtWidgets>
+
+class TimeChart;
+
+class TimeChartsTest: public QWidget
+{
+ Q_OBJECT
+public:
+ TimeChartsTest();
+
+private:
+ void createUi();
+ void addDataPoint(int index);
+
+private slots:
+ void changeDataSet();
+
+private:
+ QString yValuesStr;
+ TimeChart* chart;
+ QList<QDateTime> dates;
+ QLabel* valuesLabel;
+ QSpinBox* periodBox;
+};
+
+#endif
diff --git a/tests/mocks/CardPack_mock.cpp b/tests/mocks/CardPack_mock.cpp
new file mode 100644
index 0000000..159e0de
--- /dev/null
+++ b/tests/mocks/CardPack_mock.cpp
@@ -0,0 +1,31 @@
+#include "CardPack_mock.h"
+
+void CardPackMock::addStudyRecord(const QString cardId, const StudyRecord& studyRecord)
+{
+ studyRecords.insert(cardId, studyRecord);
+}
+
+QList<StudyRecord> CardPackMock::getStudyRecords(QString cardId) const
+{
+ return studyRecords.values(cardId);
+}
+
+StudyRecord CardPackMock::getStudyRecord(QString cardId) const
+{
+ return studyRecords.values(cardId).first();
+}
+
+QList<DicRecord*> CardPackMock::getRecords() const
+{
+ return QList<DicRecord*>();
+}
+
+const Field* CardPackMock::getQuestionField() const
+{
+ return NULL;
+}
+
+QList<const Field*> CardPackMock::getAnswerFields() const
+{
+ return QList<const Field*>();
+}
diff --git a/tests/mocks/CardPack_mock.h b/tests/mocks/CardPack_mock.h
new file mode 100644
index 0000000..50e6c13
--- /dev/null
+++ b/tests/mocks/CardPack_mock.h
@@ -0,0 +1,23 @@
+#ifndef CARDPACK_MOCK_H
+#define CARDPACK_MOCK_H
+
+#include <QtCore>
+
+#include "../../src/dictionary/ICardPack.h"
+
+class CardPackMock: public ICardPack
+{
+public:
+ void addStudyRecord(const QString cardId, const StudyRecord& studyRecord);
+ QList<StudyRecord> getStudyRecords(QString cardId) const;
+ StudyRecord getStudyRecord(QString cardId) const;
+
+ QList<DicRecord*> getRecords() const;
+ const Field* getQuestionField() const;
+ QList<const Field*> getAnswerFields() const;
+
+private:
+ QMultiHash< QString, StudyRecord > studyRecords;
+};
+
+#endif
diff --git a/tests/mocks/Dictionary_mock.cpp b/tests/mocks/Dictionary_mock.cpp
new file mode 100644
index 0000000..905a2f2
--- /dev/null
+++ b/tests/mocks/Dictionary_mock.cpp
@@ -0,0 +1,6 @@
+#include "Dictionary_mock.h"
+
+void MockDictionary::addCardPack(CardPack* aCardPack)
+{
+ m_cardPacks << aCardPack;
+}
diff --git a/tests/mocks/Dictionary_mock.h b/tests/mocks/Dictionary_mock.h
new file mode 100644
index 0000000..0b29fbb
--- /dev/null
+++ b/tests/mocks/Dictionary_mock.h
@@ -0,0 +1,32 @@
+#ifndef DICTIONARY_MOCK_H
+#define DICTIONARY_MOCK_H
+
+#include <QtCore>
+
+#include "../../src/dictionary/IDictionary.h"
+#include "../../src/dictionary/TreeItem.h"
+
+class MockDictionary: public TreeItem, public IDictionary
+{
+Q_OBJECT
+public:
+ const TreeItem* parent() const { return NULL; }
+ const TreeItem* child(int) const { return NULL; }
+ int childCount() const { return 0; }
+ int columnCount() const { return 0; }
+ QVariant data(int) const { return QVariant(); }
+ int row() const { return 0; }
+ int topParentRow() const { return 0; }
+
+ const Field* field(int) const { return NULL; }
+ const Field* field(const QString) const { return NULL; }
+ int indexOfCardPack(CardPack*) const { return 0; }
+
+ void addCardPack(CardPack* aCardPack);
+
+signals:
+ void entryChanged( int aEntryIx, int aFieldIx );
+ void entriesRemoved( int aIndex, int aNum );
+};
+
+#endif
diff --git a/tests/mocks/RandomGenerator_mock.h b/tests/mocks/RandomGenerator_mock.h
new file mode 100644
index 0000000..0d3ef97
--- /dev/null
+++ b/tests/mocks/RandomGenerator_mock.h
@@ -0,0 +1,33 @@
+#ifndef RANDOM_GENERATOR_MOCK_H
+#define RANDOM_GENERATOR_MOCK_H
+
+#include "../../src/utils/IRandomGenerator.h"
+
+class MockRandomGenerator: public IRandomGenerator
+{
+public:
+ MockRandomGenerator():
+ dRandom(0), rand(0) {}
+ double getInRange_11() const { return dRandom; }
+ double getInRange_01() const { return dRandom; }
+ int getRand() const { return rand; }
+ int getRand(int maxNum) const
+ {
+ int r = rand;
+ if(r >= maxNum)
+ r = maxNum -1;
+ return r;
+ }
+ QByteArray getArray() const { return array; }
+
+ void setDouble(double dRandom) { this->dRandom = dRandom; }
+ void setRand(int rand) { this->rand = rand; }
+ void setArray(const QByteArray& array) { this->array = array; }
+
+private:
+ double dRandom;
+ int rand;
+ QByteArray array;
+};
+
+#endif
diff --git a/tests/mocks/TimeProvider_mock.cpp b/tests/mocks/TimeProvider_mock.cpp
new file mode 100644
index 0000000..22cfb43
--- /dev/null
+++ b/tests/mocks/TimeProvider_mock.cpp
@@ -0,0 +1,9 @@
+#include "../../src/utils/TimeProvider.h"
+
+// TODO: Make interface with virtual function. Use Time provider as singleton.
+
+QDateTime TimeProvider::get()
+{
+ static QDateTime time = QDateTime::currentDateTime();
+ return time;
+}
diff --git a/tests/unit/Card/Card_GenerateAnswers_test.cpp b/tests/unit/Card/Card_GenerateAnswers_test.cpp
new file mode 100644
index 0000000..f381daf
--- /dev/null
+++ b/tests/unit/Card/Card_GenerateAnswers_test.cpp
@@ -0,0 +1,66 @@
+#include "Card_GenerateAnswers_test.h"
+
+#include <initializer_list>
+
+#include "../../common/printQtTypes.h"
+#include "../../../src/dictionary/IDictionary.h"
+#include "../../../src/dictionary/Field.h"
+#include "../../../src/dictionary/DicRecord.h"
+#include "../../../src/dictionary/Card.h"
+
+INSTANTIATE_TEST_CASE_P(, GenerateAnswersTest,
+ testing::ValuesIn(RecordsParam::createParams()) );
+
+void GenerateAnswersTest::TearDown()
+{
+ for(Field* field: fields)
+ delete field;
+}
+
+TEST_P(GenerateAnswersTest, generateAnswers)
+ {
+ auto param = GetParam();
+ dict.addRecords(param.records);
+ for(int i: param.packFields)
+ addFieldToPack(i);
+
+ unsigned i = 0;
+ for(vector<string> expectedCardFields: param.answers)
+ {
+ SCOPED_TRACE(i);
+ Card card(&pack, expectedCardFields[0].c_str());
+ for(unsigned j = 1; j < expectedCardFields.size(); j++)
+ {
+ SCOPED_TRACE(j);
+ ASSERT_EQ_QSTR(QString(expectedCardFields[j].c_str()),
+ card.getAnswers()[j - 1]);
+ }
+ i++;
+ }
+}
+
+TEST_F(GenerateAnswersTest, dropAnswers)
+ {
+ const vector<string> fieldValues {"table", "стол"};
+
+ DicRecord* record = new DicRecord;
+ dict.addRecord(record);
+ for(unsigned i = 0; i < fieldValues.size(); i++)
+ {
+ addFieldToPack(i);
+ record->setField(RecordsParam::fieldNames[i].c_str(), fieldValues[i].c_str());
+ }
+ Card card(&pack, fieldValues[0].c_str());
+
+ ASSERT_EQ(QString(fieldValues[1].c_str()), card.getAnswers().first());
+
+ record->setField("Russian", "кровать");
+ ASSERT_EQ(QString("кровать"), card.getAnswers().first());
+}
+
+void GenerateAnswersTest::addFieldToPack(unsigned fieldId)
+{
+ Field* field = new Field(RecordsParam::fieldNames[fieldId].c_str(), "Normal");
+ fields.push_back(field);
+ pack.addField(field);
+}
diff --git a/tests/unit/Card/Card_GenerateAnswers_test.h b/tests/unit/Card/Card_GenerateAnswers_test.h
new file mode 100644
index 0000000..1b69cba
--- /dev/null
+++ b/tests/unit/Card/Card_GenerateAnswers_test.h
@@ -0,0 +1,34 @@
+#include <vector>
+#include <string>
+#include <gtest/gtest.h>
+
+#include "../../../src/dictionary/CardPack.h"
+#include "../../mocks/Dictionary_mock.h"
+#include "../../common/RecordsParam.h"
+
+class Field;
+
+using std::vector;
+using std::string;
+
+class GenerateAnswersTest: public testing::TestWithParam<RecordsParam>
+{
+public:
+ GenerateAnswersTest():
+ pack(&dict) {}
+
+protected:
+ void TearDown();
+ void addFieldToPack(unsigned fieldId);
+
+public:
+ static const vector<string> fieldNames;
+
+protected:
+ vector<Field*> fields;
+
+protected:
+ MockDictionary dict;
+ CardPack pack;
+};
+
diff --git a/tests/unit/Card/Card_test.cpp b/tests/unit/Card/Card_test.cpp
new file mode 100644
index 0000000..bfae243
--- /dev/null
+++ b/tests/unit/Card/Card_test.cpp
@@ -0,0 +1,36 @@
+#include <gtest/gtest.h>
+#include <QtCore>
+
+#include "Card_test.h"
+#include "../../common/printQtTypes.h"
+#include "../../../src/dictionary/Card.h"
+#include "../../../src/dictionary/ICardPack.h"
+#include "../../mocks/CardPack_mock.h"
+
+void CardTest::SetUp()
+ {
+ defaultPack = new CardPackMock;
+ }
+
+void CardTest::TearDown()
+ {
+ delete defaultPack;
+ }
+
+TEST_F(CardTest, Create)
+ {
+ Card card(defaultPack);
+
+ ASSERT_EQ(defaultPack, card.getCardPack());
+ ASSERT_TRUE(card.getQuestion().isEmpty());
+ ASSERT_TRUE(card.getAnswers().isEmpty());
+ }
+
+TEST_F(CardTest, Create_NullPack)
+ {
+ Card card(NULL);
+
+ ASSERT_EQ(NULL, card.getCardPack());
+ ASSERT_TRUE(card.getQuestion().isEmpty());
+ ASSERT_TRUE(card.getAnswers().isEmpty());
+ }
diff --git a/tests/unit/Card/Card_test.h b/tests/unit/Card/Card_test.h
new file mode 100644
index 0000000..e9a0891
--- /dev/null
+++ b/tests/unit/Card/Card_test.h
@@ -0,0 +1,18 @@
+#ifndef CARD_TEST_H
+#define CARD_TEST_H
+
+#include <gtest/gtest.h>
+
+class ICardPack;
+
+class CardTest: public testing::Test
+{
+public:
+ void SetUp();
+ void TearDown();
+
+protected:
+ ICardPack* defaultPack;
+};
+
+#endif
diff --git a/tests/unit/Card/Card_test_QuestionAnswer.cpp b/tests/unit/Card/Card_test_QuestionAnswer.cpp
new file mode 100644
index 0000000..6100f75
--- /dev/null
+++ b/tests/unit/Card/Card_test_QuestionAnswer.cpp
@@ -0,0 +1,30 @@
+#include <gtest/gtest.h>
+
+#include "Card_test.h"
+#include "../../../src/dictionary/Card.h"
+#include "../../mocks/CardPack_mock.h"
+
+using testing::Combine;
+using testing::Values;
+using testing::WithParamInterface;
+
+class QuestionCardTest: public CardTest,
+ public WithParamInterface<const char*>
+{};
+
+INSTANTIATE_TEST_CASE_P(, QuestionCardTest,
+ Values("", "Question"));
+
+TEST_P(QuestionCardTest, getName)
+ {
+ QString question = GetParam();
+ Card card(defaultPack, question);
+ ASSERT_EQ(question, card.getName());
+ }
+
+TEST_P(QuestionCardTest, getQuestion)
+ {
+ QString question = GetParam();
+ Card card(defaultPack, question);
+ ASSERT_EQ(question, card.getQuestion());
+ }
diff --git a/tests/unit/Card/card.pri b/tests/unit/Card/card.pri
new file mode 100644
index 0000000..2eae07d
--- /dev/null
+++ b/tests/unit/Card/card.pri
@@ -0,0 +1,10 @@
+HEADERS += \
+ $$PWD/Card_test.h \
+ $$PWD/Card_GenerateAnswers_test.h \
+ $$TESTS/mocks/CardPack_mock.h
+
+SOURCES += \
+ $$PWD/Card_test.cpp \
+ $$PWD/Card_test_QuestionAnswer.cpp \
+ $$PWD/Card_GenerateAnswers_test.cpp \
+ $$TESTS/mocks/CardPack_mock.cpp
diff --git a/tests/unit/CardPack/CardPack_GenerateCards_test.cpp b/tests/unit/CardPack/CardPack_GenerateCards_test.cpp
new file mode 100644
index 0000000..c7e7fcb
--- /dev/null
+++ b/tests/unit/CardPack/CardPack_GenerateCards_test.cpp
@@ -0,0 +1,43 @@
+#include "CardPack_GenerateCards_test.h"
+
+#include <initializer_list>
+
+#include "../../common/printQtTypes.h"
+#include "../../../src/dictionary/IDictionary.h"
+#include "../../../src/dictionary/Field.h"
+#include "../../../src/dictionary/DicRecord.h"
+
+vector<Field*> GenerateCardsTest::fields;
+
+INSTANTIATE_TEST_CASE_P(, GenerateCardsTest,
+ testing::ValuesIn(RecordsParam::createParams()) );
+
+vector<Field*> GenerateCardsTest::getFields()
+{
+ static vector<string> names {"English", "Russian"};
+ if(fields.empty())
+ for(string name: names)
+ fields.push_back(new Field(name.c_str(), "Normal"));
+ return fields;
+}
+
+void GenerateCardsTest::TearDownTestCase()
+{
+ for(Field* field: fields)
+ delete field;
+}
+
+TEST_P(GenerateCardsTest, generateQuestions)
+ {
+ auto param = GetParam();
+ dict.addRecords(param.records);
+ for(int fieldId: param.packFields)
+ pack.addField(getFields()[ fieldId ]);
+ pack.generateQuestions();
+
+ QStringList questions;
+ for(string question: param.questions)
+ questions << question.c_str();
+
+ ASSERT_EQ(questions, pack.getCardQuestions());
+}
diff --git a/tests/unit/CardPack/CardPack_GenerateCards_test.h b/tests/unit/CardPack/CardPack_GenerateCards_test.h
new file mode 100644
index 0000000..299ee66
--- /dev/null
+++ b/tests/unit/CardPack/CardPack_GenerateCards_test.h
@@ -0,0 +1,26 @@
+#include <iostream>
+#include <vector>
+#include <string>
+#include <gtest/gtest.h>
+#include <QtCore>
+
+#include "CardPack_test.h"
+#include "../../common/RecordsParam.h"
+
+class DicRecord;
+
+using std::vector;
+using std::string;
+using std::ostream;
+
+class GenerateCardsTest: public CardPackTest,
+ public testing::WithParamInterface<RecordsParam>
+{
+public:
+ static void TearDownTestCase();
+ static vector<Field*> getFields();
+
+private:
+ static vector<Field*> fields;
+
+};
diff --git a/tests/unit/CardPack/CardPack_test.cpp b/tests/unit/CardPack/CardPack_test.cpp
new file mode 100644
index 0000000..47b2387
--- /dev/null
+++ b/tests/unit/CardPack/CardPack_test.cpp
@@ -0,0 +1,80 @@
+#include "CardPack_test.h"
+#include "../../common/printQtTypes.h"
+#include "../../../src/dictionary/IDictionary.h"
+#include "../../../src/dictionary/Field.h"
+#include "../../../src/dictionary/DicRecord.h"
+
+ostream& operator<<(ostream& os, const Field* field)
+{
+ return os << field->name().toStdString();
+}
+
+CardPackTest::CardPackTest():
+ pack(&dict),
+ qstField("Question", "Normal"),
+ ansField1("Answer1", "Normal"),
+ ansField2("Answer2", "Normal")
+{}
+
+TEST_F(CardPackTest, empty_QuestionAnswers)
+ {
+ ASSERT_EQ(NULL, pack.getQuestionField());
+ ASSERT_EQ(QList<const Field*>(), pack.getAnswerFields());
+ }
+
+TEST_F(CardPackTest, empty_Id)
+ {
+ ASSERT_EQ("(empty pack)", pack.id());
+ }
+
+TEST_F(CardPackTest, empty_cardQuestions)
+ {
+ ASSERT_EQ(QStringList(), pack.getCardQuestions());
+ }
+
+TEST_F(CardPackTest, setQstField)
+ {
+ pack.setQstField(&qstField);
+ ASSERT_EQ(&qstField, pack.getQuestionField());
+ }
+
+TEST_F(CardPackTest, setAnsField_1)
+ {
+ ansFields << &ansField1;
+
+ pack.setAnsFields(ansFields);
+ ASSERT_EQ(ansFields, pack.getAnswerFields());
+ }
+
+TEST_F(CardPackTest, setAnsFields_2)
+ {
+ ansFields << &ansField1 << &ansField2;
+
+ pack.setAnsFields(ansFields);
+ ASSERT_EQ(ansFields, pack.getAnswerFields());
+ }
+
+TEST_F(CardPackTest, addFields)
+ {
+ QList<const Field*> allFields;
+ allFields << &qstField << &ansField1 << &ansField2;
+ ansFields << &ansField1 << &ansField2;
+
+ pack.addField(&qstField);
+ pack.addField(&ansField1);
+ pack.addField(&ansField2);
+
+ ASSERT_EQ(allFields, pack.getFields());
+ ASSERT_EQ(&qstField, pack.getQuestionField());
+ ASSERT_EQ(ansFields, pack.getAnswerFields());
+ }
+
+TEST_F(CardPackTest, id)
+ {
+ ansFields << &ansField1 << &ansField2;
+
+ pack.setQstField(&qstField);
+ pack.setAnsFields(ansFields);
+
+ ASSERT_EQ("Question - Answer1, Answer2", pack.id());
+ }
diff --git a/tests/unit/CardPack/CardPack_test.h b/tests/unit/CardPack/CardPack_test.h
new file mode 100644
index 0000000..e25c75d
--- /dev/null
+++ b/tests/unit/CardPack/CardPack_test.h
@@ -0,0 +1,31 @@
+#ifndef CARDPACK_TEST_H
+#define CARDPACK_TEST_H
+
+#include <gtest/gtest.h>
+#include <iostream>
+#include <QtCore>
+
+#include "../../../src/dictionary/CardPack.h"
+#include "../../mocks/Dictionary_mock.h"
+
+using std::ostream;
+
+class Field;
+
+class CardPackTest: public testing::Test
+{
+public:
+ CardPackTest();
+
+protected:
+ MockDictionary dict;
+ CardPack pack;
+ Field qstField;
+ Field ansField1;
+ Field ansField2;
+ QList<const Field*> ansFields;
+};
+
+ostream& operator<<(ostream& os, const Field* field);
+
+#endif
diff --git a/tests/unit/CardPack/cPack.pri b/tests/unit/CardPack/cPack.pri
new file mode 100644
index 0000000..e3befe3
--- /dev/null
+++ b/tests/unit/CardPack/cPack.pri
@@ -0,0 +1,7 @@
+HEADERS += \
+ $$PWD/CardPack_test.h \
+ $$PWD/CardPack_GenerateCards_test.h
+
+SOURCES += \
+ $$PWD/CardPack_test.cpp \
+ $$PWD/CardPack_GenerateCards_test.cpp
diff --git a/tests/unit/CardSideView/CardSideView_test.cpp b/tests/unit/CardSideView/CardSideView_test.cpp
new file mode 100644
index 0000000..9b08dc0
--- /dev/null
+++ b/tests/unit/CardSideView/CardSideView_test.cpp
@@ -0,0 +1,49 @@
+#include <gtest/gtest.h>
+#include <QtCore>
+#include <QtWidgets>
+
+#include "CardSideView_test.h"
+#include "../../common/printQtTypes.h"
+#include "../../../src/dictionary/Card.h"
+#include "../../../src/study/CardSideView.h"
+
+void CardSideViewTest::SetUp()
+ {
+ cardPack.addField(new Field("English", "Normal"));
+ cardPack.addField(new Field("Example", "Example"));
+ cardPack.addField(new Field("Russian", "Normal"));
+ }
+
+TEST_F(CardSideViewTest, getFormattedQuestion)
+ {
+ CardSideView view;
+ view.setPack(&cardPack);
+ view.setQstAnsr("First", QStringList());
+ ASSERT_EQ("<span style=\"font-family:'Times New Roman'; "\
+ "font-size:18pt; font-weight:bold\">First</span>",
+ view.getFormattedText());
+ }
+
+TEST_F(CardSideViewTest, getFormattedAnswer)
+ {
+ CardSideView view(CardSideView::AnsMode);
+ view.setPack(&cardPack);
+ QStringList answers = QStringList() << "First example" << "Pervyj";
+ view.setQstAnsr("First", answers);
+ ASSERT_EQ("<span style=\"font-family:'Times New Roman'; "\
+ "font-size:14pt\"><span style=\"; color:#0000ff\">First</span> example</span><br/><br/>"\
+ "<span style=\"font-family:'Times New Roman'; font-size:18pt; "\
+ "font-weight:bold\">Pervyj</span>",
+ view.getFormattedText());
+ }
+
+TEST_F(CardSideViewTest, getFormattedAnswer_1missing)
+ {
+ CardSideView view(CardSideView::AnsMode);
+ view.setPack(&cardPack);
+ QStringList answers = QStringList() << "" << "Pervyj";
+ view.setQstAnsr("First", answers);
+ ASSERT_EQ("<br/><br/><span style=\"font-family:'Times New Roman'; font-size:18pt; "\
+ "font-weight:bold\">Pervyj</span>",
+ view.getFormattedText());
+ }
diff --git a/tests/unit/CardSideView/CardSideView_test.h b/tests/unit/CardSideView/CardSideView_test.h
new file mode 100644
index 0000000..491ebe4
--- /dev/null
+++ b/tests/unit/CardSideView/CardSideView_test.h
@@ -0,0 +1,21 @@
+#ifndef CARDSIDEVIEW_TEST_H
+#define CARDSIDEVIEW_TEST_H
+
+#include <gtest/gtest.h>
+
+#include "../../mocks/Dictionary_mock.h"
+#include "../../../src/dictionary/CardPack.h"
+
+class CardSideViewTest: public testing::Test
+{
+public:
+ CardSideViewTest():
+ cardPack(&dict) {}
+ void SetUp();
+
+protected:
+ MockDictionary dict;
+ CardPack cardPack;
+};
+
+#endif
diff --git a/tests/unit/CardSideView/csView.pri b/tests/unit/CardSideView/csView.pri
new file mode 100644
index 0000000..6eeae5f
--- /dev/null
+++ b/tests/unit/CardSideView/csView.pri
@@ -0,0 +1,8 @@
+HEADERS += \
+ $$PWD/CardSideView_test.h \
+ $$SRC/study/CardSideView.h
+
+SOURCES += \
+ $$PWD/CardSideView_test.cpp \
+ $$SRC/study/CardSideView.cpp
+ \ No newline at end of file
diff --git a/tests/unit/RandomGenerator/RandomGenerator_test.cpp b/tests/unit/RandomGenerator/RandomGenerator_test.cpp
new file mode 100644
index 0000000..d334c9b
--- /dev/null
+++ b/tests/unit/RandomGenerator/RandomGenerator_test.cpp
@@ -0,0 +1,79 @@
+#include <gtest/gtest.h>
+#include <cmath>
+#include "../../../src/utils/RandomGenerator.h"
+
+class RandomGeneratorTest: public testing::Test
+{
+protected:
+ void generateRandoms();
+ void checkStats();
+
+private:
+ void checkMinMeanMax(double min, double mean, double max);
+ void minMeanMax(double& min, double& mean, double& max);
+ double stddev(double mean);
+
+protected:
+ static const int Num = 10000;
+
+protected:
+ RandomGenerator random;
+ double x[Num];
+};
+
+TEST_F(RandomGeneratorTest, Stats)
+{
+ generateRandoms();
+ checkStats();
+}
+
+TEST_F(RandomGeneratorTest, getArray)
+{
+ QByteArray ba = random.getArray();
+}
+
+void RandomGeneratorTest::generateRandoms()
+{
+ for(int i = 0; i < Num; i++)
+ x[i] = random.getInRange_11();
+}
+
+void RandomGeneratorTest::checkStats()
+{
+ double min;
+ double mean;
+ double max;
+ minMeanMax(min, mean, max);
+
+ checkMinMeanMax(min, mean, max);
+ ASSERT_GT(stddev(mean), 0.55);
+}
+
+void RandomGeneratorTest::checkMinMeanMax(double min, double mean, double max)
+{
+ ASSERT_NEAR(-1, min, 0.01);
+ ASSERT_NEAR(0, mean, 0.2);
+ ASSERT_NEAR(1, max, 0.01);
+}
+
+void RandomGeneratorTest::minMeanMax(double& min, double& mean, double& max)
+{
+ double sum = 0;
+ min = 1000;
+ max = -1000;
+ for(int i = 0; i < Num; i++)
+ {
+ min = fmin(x[i], min);
+ max = fmax(x[i], max);
+ sum += x[i];
+ }
+ mean = sum / Num;
+}
+
+double RandomGeneratorTest::stddev(double mean)
+{
+ double varSum = 0;
+ for(int i = 0; i < Num; i++)
+ varSum += pow((mean - x[i]), 2);
+ return sqrt(varSum / Num);
+}
diff --git a/tests/unit/RandomGenerator/rndGen.pri b/tests/unit/RandomGenerator/rndGen.pri
new file mode 100644
index 0000000..dbc9300
--- /dev/null
+++ b/tests/unit/RandomGenerator/rndGen.pri
@@ -0,0 +1,6 @@
+HEADERS += \
+ $$SRC/utils/RandomGenerator.h
+
+SOURCES += \
+ $$PWD/RandomGenerator_test.cpp \
+ $$SRC/utils/RandomGenerator.cpp
diff --git a/tests/unit/Settings/FieldStyleFactory_test.cpp b/tests/unit/Settings/FieldStyleFactory_test.cpp
new file mode 100644
index 0000000..be53e7c
--- /dev/null
+++ b/tests/unit/Settings/FieldStyleFactory_test.cpp
@@ -0,0 +1,88 @@
+#include "FieldStyleFactory_test.h"
+#include "../../common/printQtTypes.h"
+#include "TestSettings.h"
+
+void FieldStyleFactoryTest::SetUp()
+{
+ TestSettings::init();
+}
+
+FieldStyleFactory FieldStyleFactoryTest::getDefaults()
+{
+ FieldStyleFactory factory;
+ factory.cardBgColor.setNamedColor("#ffffff");
+ factory.setStyle(FieldStyleFactory::DefaultStyle,
+ {"Times New Roman", 18, true, false, "black" , "", ""});
+ factory.setStyle("Example",
+ {"Times New Roman", 14, false, false, "black", "", "", true, "blue"});
+ factory.setStyle("Transcription",
+ {"Arial", 18, false, false, "black", "/", "/"});
+ factory.setStyle("Big", {"Arial", 26, true, false, "black"});
+ factory.setStyle("Color1", {"Times New Roman", 18, true, false, "red"});
+ factory.setStyle("Color2", {"Times New Roman", 18, true, false, "blue"});
+ return factory;
+}
+
+FieldStyleFactory FieldStyleFactoryTest::getUserSettings()
+{
+ FieldStyleFactory factory;
+ factory.cardBgColor.setNamedColor("#ffff00");
+ factory.setStyle(FieldStyleFactory::DefaultStyle,
+ {"Georgia", 19, false, true, "#550000", "", ""});
+ factory.setStyle("Example",
+ {"Times New Roman", 15, true, false, "black", "", ""});
+ factory.setStyle("Transcription",
+ {"Verdana", 18, true, false, "black", "[", "]"});
+ factory.setStyle("Big",
+ {"Arial", 27, false, false, "black", "", "", true, "blue"});
+ factory.setStyle("Color1", {"Times New Roman", 18, true, false, "green"});
+ factory.setStyle("Color2", {"Times New Roman", 18, true, false, "#00FFFF"});
+ return factory;
+}
+
+TEST_F(FieldStyleFactoryTest, DefaultValues)
+{
+ TestFieldStyleFactory factory;
+ SCOPED_TRACE("Default values");
+ factory.check(getDefaults());
+}
+
+TEST_F(FieldStyleFactoryTest, UserValues)
+{
+ FieldStyleFactory::inst()->load();
+ SCOPED_TRACE("User values");
+ static_cast<TestFieldStyleFactory*>(FieldStyleFactory::inst())->
+ check(getUserSettings());
+}
+
+void TestFieldStyleFactory::check(const FieldStyleFactory& expFactory)
+{
+ ASSERT_EQ(expFactory.cardBgColor.name(), cardBgColor.name());
+ checkStyles(expFactory);
+}
+
+void TestFieldStyleFactory::checkStyles(const FieldStyleFactory& expFactory)
+{
+ ASSERT_EQ(expFactory.getStyleNames(), getStyleNames());
+ foreach(QString styleName, getStyleNames())
+ {
+ SCOPED_TRACE(styleName.toStdString());
+ checkStyle(expFactory.getStyle(styleName), styleName);
+ }
+}
+
+void TestFieldStyleFactory::checkStyle(const FieldStyle& expStyle,
+ const QString& actualStyleName)
+{
+ FieldStyle actualStyle = getStyle(actualStyleName);
+ ASSERT_EQ(expStyle.font.family(), actualStyle.font.family());
+ ASSERT_EQ(expStyle.font.pointSize(), actualStyle.font.pointSize());
+ ASSERT_EQ(expStyle.font.bold(), actualStyle.font.bold());
+ ASSERT_EQ(expStyle.font.italic(), actualStyle.font.italic());
+ ASSERT_EQ(expStyle.color.name(), actualStyle.color.name());
+ ASSERT_EQ(expStyle.prefix, actualStyle.prefix);
+ ASSERT_EQ(expStyle.suffix, actualStyle.suffix);
+ ASSERT_EQ(expStyle.hasKeyword, actualStyle.hasKeyword);
+ if(expStyle.hasKeyword)
+ ASSERT_EQ(expStyle.keywordColor.name(), actualStyle.keywordColor.name());
+}
diff --git a/tests/unit/Settings/FieldStyleFactory_test.h b/tests/unit/Settings/FieldStyleFactory_test.h
new file mode 100644
index 0000000..9b7d410
--- /dev/null
+++ b/tests/unit/Settings/FieldStyleFactory_test.h
@@ -0,0 +1,29 @@
+#ifndef FIELD_STYLE_FACTORY_TEST_H
+#define FIELD_STYLE_FACTORY_TEST_H
+
+#include <gtest/gtest.h>
+#include <QtCore>
+
+#include "../../../src/field-styles/FieldStyleFactory.h"
+
+class TestFieldStyleFactory: public FieldStyleFactory
+{
+public:
+ void check(const FieldStyleFactory& expFactory);
+
+private:
+ void checkStyles(const FieldStyleFactory& expFactory);
+ void checkStyle(const FieldStyle& expStyle, const QString& actualStyleName);
+};
+
+class FieldStyleFactoryTest: public testing::Test
+{
+public:
+ static FieldStyleFactory getDefaults();
+ static FieldStyleFactory getUserSettings();
+
+public:
+ void SetUp();
+};
+
+#endif
diff --git a/tests/unit/Settings/StudySettings_test.cpp b/tests/unit/Settings/StudySettings_test.cpp
new file mode 100644
index 0000000..d66b8a1
--- /dev/null
+++ b/tests/unit/Settings/StudySettings_test.cpp
@@ -0,0 +1,42 @@
+#include "StudySettings_test.h"
+#include "../../common/printQtTypes.h"
+#include "TestSettings.h"
+
+void StudySettingsTest::SetUp()
+{
+ TestSettings::init();
+}
+
+StudySettings StudySettingsTest::getDefaults()
+{
+ StudySettings defaults;
+ defaults.dayShift = 3;
+ return defaults;
+}
+
+StudySettings StudySettingsTest::getUserSettings()
+{
+
+ StudySettings user;
+ user.dayShift = 4;
+ return user;
+}
+
+TEST_F(StudySettingsTest, DefaultValues)
+{
+ StudySettings settings;
+ SCOPED_TRACE("Default values");
+ check(getDefaults(), settings);
+}
+
+TEST_F(StudySettingsTest, UserValues)
+{
+ StudySettings::inst()->load();
+ SCOPED_TRACE("User values");
+ check(getUserSettings(), *StudySettings::inst());
+}
+
+void StudySettingsTest::check(const StudySettings& expected, const StudySettings& actual)
+{
+ ASSERT_EQ(expected.dayShift, actual.dayShift);
+}
diff --git a/tests/unit/Settings/StudySettings_test.h b/tests/unit/Settings/StudySettings_test.h
new file mode 100644
index 0000000..aa45ead
--- /dev/null
+++ b/tests/unit/Settings/StudySettings_test.h
@@ -0,0 +1,22 @@
+#ifndef STUDY_SETTINGS_TEST_H
+#define STUDY_SETTINGS_TEST_H
+
+#include <gtest/gtest.h>
+#include <QtCore>
+
+#include "../../../src/study/StudySettings.h"
+
+class StudySettingsTest: public testing::Test
+{
+public:
+ static StudySettings getDefaults();
+ static StudySettings getUserSettings();
+
+protected:
+ void check(const StudySettings& expected, const StudySettings& actual);
+
+public:
+ void SetUp();
+};
+
+#endif
diff --git a/tests/unit/Settings/TestSettings.cpp b/tests/unit/Settings/TestSettings.cpp
new file mode 100644
index 0000000..1f3fbb4
--- /dev/null
+++ b/tests/unit/Settings/TestSettings.cpp
@@ -0,0 +1,16 @@
+#include <QtCore>
+#include <gtest/gtest.h>
+
+#include "TestSettings.h"
+
+void TestSettings::init()
+{
+ QSettings::setDefaultFormat(QSettings::IniFormat);
+ QCoreApplication::setOrganizationName("freshmemory");
+ QCoreApplication::setApplicationName("freshmemory");
+ QString appDir = qApp->applicationDirPath();
+ QString userSettingsDir = appDir + "/../common";
+ QString systemSettingsDir = appDir + "/../../config";
+ QSettings::setPath(QSettings::IniFormat, QSettings::UserScope, userSettingsDir);
+ QSettings::setPath(QSettings::IniFormat, QSettings::SystemScope, systemSettingsDir);
+}
diff --git a/tests/unit/Settings/TestSettings.h b/tests/unit/Settings/TestSettings.h
new file mode 100644
index 0000000..60b8be8
--- /dev/null
+++ b/tests/unit/Settings/TestSettings.h
@@ -0,0 +1,11 @@
+#ifndef TEST_SETTINGS_H
+#define TEST_SETTINGS_H
+
+class TestSettings
+{
+public:
+ static void init();
+
+};
+
+#endif
diff --git a/tests/unit/Settings/set.pri b/tests/unit/Settings/set.pri
new file mode 100644
index 0000000..df1871f
--- /dev/null
+++ b/tests/unit/Settings/set.pri
@@ -0,0 +1,10 @@
+HEADERS += \
+ $$PWD/TestSettings.h \
+ $$PWD/StudySettings_test.h \
+ $$PWD/FieldStyleFactory_test.h
+
+SOURCES += \
+ $$PWD/TestSettings.cpp \
+ $$PWD/StudySettings_test.cpp \
+ $$PWD/FieldStyleFactory_test.cpp
+ \ No newline at end of file
diff --git a/tests/unit/SpacedRepetitionModel/SRModel_pickCard_test.cpp b/tests/unit/SpacedRepetitionModel/SRModel_pickCard_test.cpp
new file mode 100644
index 0000000..73ab023
--- /dev/null
+++ b/tests/unit/SpacedRepetitionModel/SRModel_pickCard_test.cpp
@@ -0,0 +1,420 @@
+#include "SRModel_pickCard_test.h"
+#include "../../common/printQtTypes.h"
+#include "../../../src/dictionary/Card.h"
+#include "../../mocks/RandomGenerator_mock.h"
+#include "../../../src/utils/TimeProvider.h"
+
+static const StudySettings* ss = StudySettings::inst();
+
+const double SRModel_pickCard_Test::HourAgo = -1. / 24;
+
+void SRModel_pickCard_Test::SetUp()
+{
+ randomGenerator->setRand(1);
+}
+
+// Time interval
+
+TEST_F(SRModel_pickCard_Test, pick_active1)
+{
+ addStudied(-1, 1);
+ testPicked({0});
+}
+
+TEST_F(SRModel_pickCard_Test, pick_active2)
+{
+ addStudied(-8.5, 8.1);
+ testPicked({0});
+}
+
+TEST_F(SRModel_pickCard_Test, pick_active_and_future)
+{
+ addStudied(-1, 1);
+ addStudied(-1, 2);
+ testPicked({0});
+}
+
+TEST_F(SRModel_pickCard_Test, pick_2active1)
+{
+ addStudied(-1, 1);
+ addStudied(-1.1, 1);
+
+ randomGenerator->setRand(0);
+ testPicked({0});
+}
+
+TEST_F(SRModel_pickCard_Test, pick_2active2)
+{
+ addStudied(-1, 1);
+ addStudied(-1.1, 1);
+ testPicked({1, 0});
+}
+
+TEST_F(SRModel_pickCard_Test, pick_3active)
+{
+ addStudied(-1, 1);
+ addStudied(-1.1, 1);
+ addStudied(-1.2, 1);
+ testPicked({1, 2, 0});
+}
+
+TEST_F(SRModel_pickCard_Test, pick_future1)
+{
+ addStudied(-1, 2);
+ testPicked({NoCard});
+}
+
+TEST_F(SRModel_pickCard_Test, pick_future2)
+{
+ addStudied(-1, 2);
+ addStudied(-1.1, 2);
+ testPicked({NoCard});
+}
+
+
+// Priority cards with small intervals
+// Always pick first cards with small interval
+
+TEST_F(SRModel_pickCard_Test, pick_zeroInterval) // Unreal
+{
+ addStudied(HourAgo, 0);
+ testPicked({0});
+}
+
+TEST_F(SRModel_pickCard_Test, pick_unknown)
+{
+ addUnknown(HourAgo);
+ testPicked({0});
+}
+
+TEST_F(SRModel_pickCard_Test, pick_unknown_and_active)
+{
+ addUnknown(HourAgo);
+ addStudied(-1, 1);
+ testPicked({0, 1});
+}
+
+TEST_F(SRModel_pickCard_Test, pick_learning3min_and_active)
+{
+ addLearning(-3./(24*60)); // 3 min back
+ addStudied(-1, 1);
+ testPicked({1, 0});
+}
+
+TEST_F(SRModel_pickCard_Test, pick_learning10min_and_active)
+{
+ addLearning(-10./(24*60));
+ addStudied(-1, 1);
+ testPicked({0, 1});
+}
+
+TEST_F(SRModel_pickCard_Test, pick_learningPrevDay_and_active)
+{
+ addLearning(-1);
+ addStudied(-1, 1);
+ testPicked({0, 1});
+}
+
+TEST_F(SRModel_pickCard_Test, pick_unknown_and_2actives)
+{
+ addUnknown(HourAgo);
+ addStudied(-1, 1);
+ addStudied(-1.1, 1);
+ testPicked({0, 2, 1});
+}
+
+TEST_F(SRModel_pickCard_Test, pick_remembered_and_2actives)
+{
+ addStudied(-1, ss->nextDayInterval);
+ addStudied(-1, 1);
+ addStudied(-1.1, 1);
+ testPicked({0, 2, 1});
+}
+
+TEST_F(SRModel_pickCard_Test, pick_competing_unknown_and_new)
+{
+ addUnknown(HourAgo);
+ addNew();
+ addStudied(-1, 1);
+ testPicked({0, 1, 2});
+}
+
+TEST_F(SRModel_pickCard_Test, pick_competing_remembered_and_new)
+{
+ addStudied(-1, ss->nextDayInterval);
+ addNew();
+ addStudied(-1, 1);
+ testPicked({0, 1, 2});
+}
+
+TEST_F(SRModel_pickCard_Test, pick_2unknowns_and_2actives)
+{
+ addUnknown(HourAgo);
+ addUnknown(2 * HourAgo);
+ addStudied(-1, 1);
+ addStudied(-1.1, 1);
+ testPicked({1, 0, 3, 2});
+}
+
+TEST_F(SRModel_pickCard_Test, pick_unknown_and_3actives)
+{
+ addUnknown(HourAgo);
+ addStudied(-1, 1);
+ addStudied(-1.1, 1);
+ addStudied(-1.2, 1);
+ testPicked({0, 2, 3, 1});
+}
+
+TEST_F(SRModel_pickCard_Test, pick_unknown_and_remembered)
+{
+ addUnknown(HourAgo);
+ addStudied(-1, ss->nextDayInterval);
+ addStudied(-1, 1);
+ testPicked({0, 1, 2});
+}
+
+TEST_F(SRModel_pickCard_Test, pick_incorrect_and_remembered)
+{
+ addIncorrect(HourAgo);
+ addStudied(-1, ss->nextDayInterval);
+ addStudied(-1, 1);
+ testPicked({0, 1, 2});
+}
+
+TEST_F(SRModel_pickCard_Test, pick_unknown_and_incorrect)
+{
+ addUnknown(HourAgo);
+ addIncorrect(HourAgo);
+ addStudied(-1, 1);
+ testPicked({0, 1, 2});
+}
+
+// New cards
+
+TEST_F(SRModel_pickCard_Test, pick_new)
+{
+ addNew();
+ testPicked({0});
+}
+
+TEST_F(SRModel_pickCard_Test, pick_new_and_studied1)
+{
+ addNew();
+ addStudied(-1.1, 1);
+
+ randomGenerator->setDouble(0.3); // Pick studied first
+ testPicked({1, 0});
+}
+
+TEST_F(SRModel_pickCard_Test, pick_new_and_studied2)
+{
+ addNew();
+ addStudied(-1.1, 1);
+
+ // Pick new first
+ testPicked({0, 1});
+}
+
+TEST_F(SRModel_pickCard_Test, pick_2new)
+{
+ addNew();
+ addNew();
+ testPicked({1, 0});
+}
+
+TEST_F(SRModel_pickCard_Test, pick_3new)
+{
+ for(int i = 0; i < 3; i++)
+ addNew();
+ testPicked({1, 2, 0});
+}
+
+TEST_F(SRModel_pickCard_Test, pick_2new_and_2studied1)
+{
+ addNew();
+ addNew();
+ addStudied(-1, 1);
+ addStudied(-1.1, 1);
+
+ randomGenerator->setDouble(0.3); // Pick studied first
+ testPicked({3, 2, 1, 0});
+}
+
+TEST_F(SRModel_pickCard_Test, pick_2new_and_2studied2)
+{
+ addNew();
+ addNew();
+ addStudied(-1, 1);
+ addStudied(-1.1, 1);
+
+ // Pick new first, double random = 0
+ testPicked({1, 0, 3, 2});
+}
+
+TEST_F(SRModel_pickCard_Test, pick_2new_and_2studied3)
+{
+ addNew();
+ addNew();
+ addStudied(-1, 1);
+ addStudied(-1.1, 1);
+
+ randomGenerator->setDouble(0.3); // Pick studied first
+ model.testPickCard();
+ assertCurCard(3);
+ randomGenerator->setDouble(0.1); // Pick new first
+ model.scheduleCard(4);
+ assertCurCard(1);
+ randomGenerator->setDouble(0.3);
+ model.scheduleCard(4);
+ assertCurCard(2);
+ randomGenerator->setDouble(0.1);
+ model.scheduleCard(4);
+ assertCurCard(0);
+}
+
+TEST_F(SRModel_pickCard_Test, pick_incorrect_new_and_studied)
+{
+ addIncorrect(HourAgo);
+ addNew();
+ addStudied(-1, 1);
+
+ randomGenerator->setDouble(0.3); // Pick studied first
+ testPicked({0, 2, 1});
+}
+
+TEST_F(SRModel_pickCard_Test, pick_newAndActive_newLimitReached)
+{
+ for(int i = 0; i < ss->newCardsDayLimit; i++)
+ addStudied(HourAgo, ss->nextDayInterval);
+
+ addNew();
+ addStudied(-1, 1);
+
+ int studiedCard = ss->newCardsDayLimit + 1;
+ testPicked({studiedCard, NoCard});
+}
+
+TEST_F(SRModel_pickCard_Test, pick_2new_newLimitReached)
+{
+ for(int i = 0; i < ss->newCardsDayLimit; i++)
+ addStudied(HourAgo, ss->nextDayInterval);
+
+ addNew();
+ addNew();
+
+ testPicked({NoCard});
+}
+
+// Remaining incorrect cards
+// Must be taken in the end (both active and new cards are finished)
+
+TEST_F(SRModel_pickCard_Test, pick_remaining_unknown)
+{
+ addUnknown(Now);
+ addStudied(-1, 1);
+ testPicked({1, 0});
+}
+
+TEST_F(SRModel_pickCard_Test, pick_remaining_incorrect)
+{
+ addIncorrect(Now);
+ addStudied(-1, 1);
+ testPicked({1, 0});
+}
+
+TEST_F(SRModel_pickCard_Test, pick_remaining_remembered)
+{
+ addStudied(Now, ss->nextDayInterval);
+ addStudied(-1, 1);
+ testPicked({1, NoCard});
+}
+
+TEST_F(SRModel_pickCard_Test, pick_remaining_unknownAndIncorrect)
+{
+ addIncorrect(HourAgo);
+ addUnknown(Now);
+ addStudied(-1, 1);
+ testPicked({0, 2, 1});
+}
+
+TEST_F(SRModel_pickCard_Test, pick_remaining_after_new)
+{
+ addNew();
+ testPicked({0, 0, 0, NoCard});
+}
+
+TEST_F(SRModel_pickCard_Test, pick_remaining_after_2new)
+{
+ addNew();
+ addNew();
+ testPicked({1, 0, 1, 0, 1, 0, NoCard});
+}
+
+
+
+QDateTime SRModel_pickCard_Test::timeFromDelta(double daysDelta)
+{
+ return TimeProvider::get().addSecs((int)(daysDelta * 24 * 60 * 60));
+}
+
+void SRModel_pickCard_Test::addStudied(double daysDelta, double interval,
+ int grade, int level)
+{
+ QString name = addRecord();
+ pack.generateQuestions();
+ StudyRecord study(level, grade, 2.5, interval);
+ study.date = timeFromDelta(daysDelta);
+ pack.addStudyRecord(name, study);
+}
+
+void SRModel_pickCard_Test::addUnknown(double daysDelta)
+{
+ addStudied(daysDelta, ss->unknownInterval, StudyRecord::Unknown,
+ StudyRecord::ShortLearning);
+}
+
+void SRModel_pickCard_Test::addIncorrect(double daysDelta)
+{
+ addStudied(daysDelta, ss->incorrectInterval, StudyRecord::Incorrect,
+ StudyRecord::ShortLearning);
+}
+
+void SRModel_pickCard_Test::addLearning(double daysDelta)
+{
+ addStudied(daysDelta, ss->learningInterval, StudyRecord::Good,
+ StudyRecord::LongLearning);
+}
+
+void SRModel_pickCard_Test::addNew()
+{
+ addRecord();
+ pack.generateQuestions();
+}
+
+void SRModel_pickCard_Test::assertCurCard(int expCard)
+{
+ if(expCard < 0)
+ {
+ ASSERT_FALSE(model.getCurCard());
+ return;
+ }
+ ASSERT_TRUE(model.getCurCard());
+ ASSERT_EQ(expCard, model.getCurCard()->getQuestion().toInt());
+}
+
+void SRModel_pickCard_Test::testPicked(const vector<int>& expCards)
+{
+ vector<int> pickedCards;
+ for(unsigned i = 0; i < expCards.size(); i++)
+ {
+ if(i == 0)
+ model.testPickCard();
+ else
+ model.scheduleCard(StudyRecord::Good);
+ if(model.getCurCard())
+ pickedCards.push_back(model.getCurCard()->getQuestion().toInt());
+ else
+ pickedCards.push_back(-1);
+ }
+ ASSERT_EQ(expCards, pickedCards);
+}
diff --git a/tests/unit/SpacedRepetitionModel/SRModel_pickCard_test.h b/tests/unit/SpacedRepetitionModel/SRModel_pickCard_test.h
new file mode 100644
index 0000000..aacb59a
--- /dev/null
+++ b/tests/unit/SpacedRepetitionModel/SRModel_pickCard_test.h
@@ -0,0 +1,27 @@
+#include "SRModel_test.h"
+
+#include <vector>
+
+using namespace std;
+
+class SRModel_pickCard_Test: public SRModelTest
+{
+public:
+ static const int NoCard = -1;
+ static const int Now = 0;
+ static const double HourAgo;
+
+protected:
+ static QDateTime timeFromDelta(double daysDelta);
+
+protected:
+ void SetUp();
+ void addStudied(double daysDelta, double interval, int grade = 4,
+ int level = StudyRecord::Repeating);
+ void addUnknown(double daysDelta);
+ void addIncorrect(double daysDelta);
+ void addLearning(double daysDelta);
+ void addNew();
+ void assertCurCard(int expCard);
+ void testPicked(const vector<int>& expCards);
+};
diff --git a/tests/unit/SpacedRepetitionModel/SRModel_schedule_test.cpp b/tests/unit/SpacedRepetitionModel/SRModel_schedule_test.cpp
new file mode 100644
index 0000000..cd65154
--- /dev/null
+++ b/tests/unit/SpacedRepetitionModel/SRModel_schedule_test.cpp
@@ -0,0 +1,304 @@
+#include "SRModel_schedule_test.h"
+#include "../../../src/utils/TimeProvider.h"
+
+const double SRModel_schedule_Test::startE = StudySettings().initEasiness;
+
+void SRModel_schedule_Test::SetUp()
+{
+ StudySettings::inst()->schedRandomness = StudySettings().schedRandomness;
+ randomGenerator->setDouble(0.3);
+ cardName = createCard();
+}
+
+static const StudySettings* ss = StudySettings::inst();
+
+
+// New
+// Grades 1, 2, 3 not used
+
+TEST_F(SRModel_schedule_Test, new_good) // Levels: 1 -> 2
+ {
+ setNewCard();
+ schedule(4);
+ checkStudy(StudyRecord::ShortLearning, startE, ss->unknownInterval);
+ }
+
+TEST_F(SRModel_schedule_Test, new_easy) // Levels: 1 -> 3
+ {
+ setNewCard();
+ schedule(5);
+ checkStudy(StudyRecord::LongLearning, startE, ss->learningInterval);
+ }
+
+
+// Short Learning
+
+TEST_F(SRModel_schedule_Test, short_unknown) // same level
+ {
+ setStudy({StudyRecord::ShortLearning, 4, startE, ss->unknownInterval});
+ schedule(1);
+ checkStudy(StudyRecord::ShortLearning, startE, ss->unknownInterval);
+ }
+
+TEST_F(SRModel_schedule_Test, short_incor) // same level with longer interval
+ {
+ setStudy({StudyRecord::ShortLearning, 4, startE, ss->unknownInterval});
+ schedule(2);
+ checkStudy(StudyRecord::ShortLearning, startE, ss->incorrectInterval);
+ }
+
+ // grade 3 not used
+
+TEST_F(SRModel_schedule_Test, short_good) // Levels: 2 -> 3
+ {
+ setStudy({StudyRecord::ShortLearning, 4, startE, ss->unknownInterval});
+ schedule(4);
+ checkStudy(StudyRecord::LongLearning, startE, ss->learningInterval);
+ }
+
+TEST_F(SRModel_schedule_Test, short_easy) // Levels: 2 -> repeat next day
+ {
+ setStudy({StudyRecord::ShortLearning, 4, startE, ss->unknownInterval});
+ schedule(5);
+ checkStudy(StudyRecord::Repeating, startE, ss->nextDayInterval);
+ }
+
+
+// Short learning from previous day:
+// Is promoted directly to next day repeating
+
+TEST_F(SRModel_schedule_Test, short_prevDay_difficult)
+ {
+ setStudy({StudyRecord::ShortLearning, 4, 2.5, ss->unknownInterval}, -1);
+ schedule(3);
+ checkStudy(StudyRecord::Repeating, 2.36, 2.18772);
+ }
+
+TEST_F(SRModel_schedule_Test, short_prevDay_good)
+ {
+ setStudy({StudyRecord::ShortLearning, 4, startE, ss->unknownInterval}, -1);
+ schedule(4);
+ checkStudy(StudyRecord::Repeating, startE, 2.3175);
+ }
+
+TEST_F(SRModel_schedule_Test, short_prevDay_easy)
+ {
+ setStudy({StudyRecord::ShortLearning, 4, 2.5, ss->unknownInterval}, -1);
+ schedule(5);
+ checkStudy(StudyRecord::Repeating, 2.6, 2.4102);
+ }
+
+
+// Long Learning
+
+TEST_F(SRModel_schedule_Test, long_unknown) // Levels: 3 -> 2
+ {
+ setStudy({StudyRecord::LongLearning, 4, startE, ss->learningInterval});
+ schedule(1);
+ checkStudy(StudyRecord::ShortLearning, startE, ss->unknownInterval);
+ }
+
+TEST_F(SRModel_schedule_Test, long_incor) // Levels: 3 -> 2
+ {
+ setStudy({StudyRecord::LongLearning, 4, startE, ss->learningInterval});
+ schedule(2);
+ checkStudy(StudyRecord::ShortLearning, startE, ss->incorrectInterval);
+ }
+
+ // grade 3 not used
+
+TEST_F(SRModel_schedule_Test, long_good) // Levels: 3 -> repeat next day
+ {
+ setStudy({StudyRecord::LongLearning, 4, startE, ss->learningInterval});
+ schedule(4);
+ checkStudy(StudyRecord::Repeating, startE, ss->nextDayInterval);
+ }
+
+TEST_F(SRModel_schedule_Test, long_easy) // Levels: 3 -> repeat in 2 days
+ {
+ setStudy({StudyRecord::LongLearning, 4, startE, ss->learningInterval});
+ schedule(5);
+ checkStudy(StudyRecord::Repeating, startE, ss->twoDaysInterval);
+ }
+
+
+// Long learning from previous day:
+// Is promoted directly to next day repeating
+
+TEST_F(SRModel_schedule_Test, long_prevDay_difficult)
+ {
+ setStudy({StudyRecord::LongLearning, 4, 2.5, ss->learningInterval}, -1);
+ schedule(3);
+ checkStudy(StudyRecord::Repeating, 2.36, 2.18772);
+ }
+
+TEST_F(SRModel_schedule_Test, long_prevDay_good)
+ {
+ setStudy({StudyRecord::LongLearning, 4, startE, ss->learningInterval}, -1);
+ schedule(4);
+ checkStudy(StudyRecord::Repeating, startE, 2.3175);
+ }
+
+TEST_F(SRModel_schedule_Test, long_prevDay_easy)
+ {
+ setStudy({StudyRecord::LongLearning, 4, 2.5, ss->learningInterval}, -1);
+ schedule(5);
+ checkStudy(StudyRecord::Repeating, 2.6, 2.4102);
+ }
+
+
+// First repeating day
+
+TEST_F(SRModel_schedule_Test, firstRep_unknown) // -> 2
+ {
+ setStudy({StudyRecord::Repeating, 4, startE, ss->nextDayInterval});
+ schedule(1);
+ checkStudy(StudyRecord::ShortLearning, startE, ss->unknownInterval);
+ }
+
+TEST_F(SRModel_schedule_Test, firstRep_incor) // -> 2
+ {
+ setStudy({StudyRecord::Repeating, 4, startE, ss->nextDayInterval});
+ schedule(2);
+ checkStudy(StudyRecord::ShortLearning, startE, ss->incorrectInterval);
+ }
+
+TEST_F(SRModel_schedule_Test, firstRep_difficult)
+ {
+ setStudy({StudyRecord::Repeating, 4, startE, ss->nextDayInterval});
+ schedule(3);
+ checkStudy(StudyRecord::Repeating, 2.36, 2.18772);
+ }
+
+TEST_F(SRModel_schedule_Test, firstRep_good)
+ {
+ setStudy({StudyRecord::Repeating, 4, startE, ss->nextDayInterval});
+ schedule(4);
+ checkStudy(StudyRecord::Repeating, startE, 2.3175);
+ }
+
+TEST_F(SRModel_schedule_Test, firstRep_easy)
+ {
+ setStudy({StudyRecord::Repeating, 4, startE, ss->nextDayInterval});
+ schedule(5);
+ checkStudy(StudyRecord::Repeating, 2.6, 2.4102);
+ }
+
+
+// Normal repeating
+
+TEST_F(SRModel_schedule_Test, repeat_unknown)
+ {
+ setStudy({StudyRecord::Repeating, 4, startE, 5});
+ schedule(1);
+ checkStudy(StudyRecord::ShortLearning, startE, ss->unknownInterval);
+ }
+
+TEST_F(SRModel_schedule_Test, repeat_incor)
+ {
+ setStudy({StudyRecord::Repeating, 4, startE, 5});
+ schedule(2);
+ checkStudy(StudyRecord::ShortLearning, startE, ss->incorrectInterval);
+ }
+
+TEST_F(SRModel_schedule_Test, repeat_difficult)
+ {
+ setStudy({StudyRecord::Repeating, 4, 2.5, 5});
+ schedule(3);
+ checkStudy(StudyRecord::Repeating, 2.36, 12.1540);
+ }
+
+TEST_F(SRModel_schedule_Test, repeat_good)
+ {
+ setStudy({StudyRecord::Repeating, 4, startE, 5});
+ schedule(4);
+ checkStudy(StudyRecord::Repeating, startE, 12.875);
+ }
+
+TEST_F(SRModel_schedule_Test, repeat_easy)
+ {
+ setStudy({StudyRecord::Repeating, 4, 2.5, 5});
+ schedule(5);
+ checkStudy(StudyRecord::Repeating, 2.6, 13.39);
+ }
+
+
+// Mature repeating
+
+TEST_F(SRModel_schedule_Test, mature_difficult)
+ {
+ setStudy({StudyRecord::Repeating, 4, 3.1, 23});
+ schedule(3);
+ checkStudy(StudyRecord::Repeating, 2.96, 70.1224);
+ }
+
+TEST_F(SRModel_schedule_Test, mature_good)
+ {
+ setStudy({StudyRecord::Repeating, 4, 3.1, 23});
+ schedule(4);
+ checkStudy(StudyRecord::Repeating, 3.1, 73.439);
+ }
+
+
+// Re-learning unknown and incorrect cards
+
+TEST_F(SRModel_schedule_Test, unknown_incor)
+ {
+ setStudy({StudyRecord::ShortLearning, 1, startE, ss->unknownInterval});
+ schedule(2);
+ checkStudy(StudyRecord::ShortLearning, startE, ss->incorrectInterval);
+ }
+
+TEST_F(SRModel_schedule_Test, incor_unknown)
+ {
+ setStudy({StudyRecord::ShortLearning, 2, startE, ss->incorrectInterval});
+ schedule(1);
+ checkStudy(StudyRecord::ShortLearning, startE, ss->unknownInterval);
+ }
+
+TEST_F(SRModel_schedule_Test, unknown_good)
+ {
+ setStudy({StudyRecord::ShortLearning, 1, startE, ss->unknownInterval});
+ schedule(4);
+ checkStudy(StudyRecord::LongLearning, startE, ss->learningInterval);
+ }
+
+TEST_F(SRModel_schedule_Test, incor_good)
+ {
+ setStudy({StudyRecord::ShortLearning, 2, startE, ss->incorrectInterval});
+ schedule(4);
+ checkStudy(StudyRecord::LongLearning, startE, ss->learningInterval);
+ }
+
+
+void SRModel_schedule_Test::setNewCard()
+{
+ pack.addStudyRecord(cardName, StudyRecord());
+}
+
+void SRModel_schedule_Test::setStudy(StudyRecord study, double daysDelta)
+{
+ study.date = timeFromDelta(daysDelta);
+ pack.addStudyRecord(cardName, study);
+}
+
+QDateTime SRModel_schedule_Test::timeFromDelta(double daysDelta)
+{
+ return TimeProvider::get().addSecs((int)(daysDelta * 24 * 60 * 60));
+}
+
+void SRModel_schedule_Test::schedule(int grade)
+{
+ this->grade = grade;
+ model.scheduleCard(grade);
+}
+
+void SRModel_schedule_Test::checkStudy(int expLevel, double expEasiness,
+ double expInterval)
+{
+ StudyRecord newStudy = pack.getStudyRecord(cardName);
+ ASSERT_EQ(grade, newStudy.grade);
+ ASSERT_EQ(expLevel, newStudy.level);
+ ASSERT_DOUBLE_EQ(expEasiness, newStudy.easiness);
+ ASSERT_NEAR(expInterval, newStudy.interval, 0.00000001);
+}
diff --git a/tests/unit/SpacedRepetitionModel/SRModel_schedule_test.h b/tests/unit/SpacedRepetitionModel/SRModel_schedule_test.h
new file mode 100644
index 0000000..6da4365
--- /dev/null
+++ b/tests/unit/SpacedRepetitionModel/SRModel_schedule_test.h
@@ -0,0 +1,24 @@
+#include "SRModel_test.h"
+#include "../../../src/dictionary/Card.h"
+#include "../../../src/study/StudySettings.h"
+#include "../../mocks/RandomGenerator_mock.h"
+
+class SRModel_schedule_Test: public SRModelTest
+{
+protected:
+ static const double startE;
+
+protected:
+ void SetUp();
+ void setNewCard();
+ void setStudy(StudyRecord study, double daysDelta = 0);
+ void schedule(int grade);
+ void checkStudy(int expLevel, double expEasiness, double expInterval);
+
+private:
+ static QDateTime timeFromDelta(double daysDelta);
+
+protected:
+ QString cardName;
+ int grade;
+};
diff --git a/tests/unit/SpacedRepetitionModel/SRModel_showGrades_test.cpp b/tests/unit/SpacedRepetitionModel/SRModel_showGrades_test.cpp
new file mode 100644
index 0000000..2fd0675
--- /dev/null
+++ b/tests/unit/SpacedRepetitionModel/SRModel_showGrades_test.cpp
@@ -0,0 +1,36 @@
+#include "SRModel_showGrades_test.h"
+
+static const StudySettings* ss = StudySettings::inst();
+
+void SRModel_showGrades_Test::SetUp()
+{
+ StudySettings::inst()->schedRandomness = StudySettings().schedRandomness;
+ randomGenerator->setDouble(0.3);
+ cardName = createCard();
+}
+
+TEST_F(SRModel_showGrades_Test, new)
+ {
+ setStudy({StudyRecord::New, 4, ss->initEasiness, 0});
+ QList<int> expGrades = {4, 5};
+ ASSERT_EQ(expGrades, model.getAvailableGrades());
+ }
+
+TEST_F(SRModel_showGrades_Test, learning)
+ {
+ setStudy({StudyRecord::ShortLearning, 4, ss->initEasiness, ss->unknownInterval});
+ QList<int> expGrades = {1, 2, 4, 5};
+ ASSERT_EQ(expGrades, model.getAvailableGrades());
+ }
+
+TEST_F(SRModel_showGrades_Test, repeating)
+ {
+ setStudy({StudyRecord::Repeating, 4, ss->initEasiness, 2});
+ QList<int> expGrades = {1, 2, 3, 4, 5};
+ ASSERT_EQ(expGrades, model.getAvailableGrades());
+ }
+
+void SRModel_showGrades_Test::setStudy(const StudyRecord& study)
+{
+ pack.addStudyRecord(cardName, study);
+}
diff --git a/tests/unit/SpacedRepetitionModel/SRModel_showGrades_test.h b/tests/unit/SpacedRepetitionModel/SRModel_showGrades_test.h
new file mode 100644
index 0000000..c45a760
--- /dev/null
+++ b/tests/unit/SpacedRepetitionModel/SRModel_showGrades_test.h
@@ -0,0 +1,14 @@
+#include "SRModel_test.h"
+#include "../../../src/dictionary/Card.h"
+#include "../../../src/study/StudySettings.h"
+#include "../../mocks/RandomGenerator_mock.h"
+
+class SRModel_showGrades_Test: public SRModelTest
+{
+protected:
+ void SetUp();
+ void setStudy(const StudyRecord& study);
+
+protected:
+ QString cardName;
+};
diff --git a/tests/unit/SpacedRepetitionModel/SRModel_test.cpp b/tests/unit/SpacedRepetitionModel/SRModel_test.cpp
new file mode 100644
index 0000000..420dfa7
--- /dev/null
+++ b/tests/unit/SpacedRepetitionModel/SRModel_test.cpp
@@ -0,0 +1,67 @@
+#include "SRModel_test.h"
+#include "../../common/printQtTypes.h"
+#include "../../../src/dictionary/DicRecord.h"
+#include "../../../src/dictionary/Card.h"
+#include "../../../src/study/StudySettings.h"
+#include "../../mocks/RandomGenerator_mock.h"
+
+SRModelTest::SRModelTest():
+ recordId(0),
+ pack(&dict),
+ randomGenerator(new MockRandomGenerator),
+ model(&pack, randomGenerator)
+{
+ field1 = new Field("English", "Normal");
+ field2 = new Field("Russian", "Normal");
+ pack.addField(field1);
+ pack.addField(field2);
+ dict.addCardPack(&pack);
+}
+
+void SRModelTest::SetUp()
+{
+ QCoreApplication::setOrganizationName("freshmemory");
+ QCoreApplication::setApplicationName("freshmemory");
+ QSettings::setPath(QSettings::IniFormat, QSettings::UserScope, "");
+ StudySettings::inst()->load();
+}
+
+void SRModelTest::TearDown()
+{
+ delete field1;
+ delete field2;
+}
+
+TEST_F(SRModelTest, EmptyStats)
+ {
+ ASSERT_EQ(0, model.estimatedNewReviewedCardsToday());
+ ASSERT_EQ(0, model.countTodayRemainingCards());
+ }
+
+TEST_F(SRModelTest, FirstStudy)
+ {
+ QString cardName = createCard();
+ StudyRecord newStudy = pack.getStudyRecord(cardName);
+
+ ASSERT_EQ(StudyRecord::Unknown, newStudy.grade);
+ ASSERT_DOUBLE_EQ(StudySettings::inst()->initEasiness, newStudy.easiness);
+ ASSERT_DOUBLE_EQ(0, newStudy.interval);
+ }
+
+QString SRModelTest::createCard()
+{
+ addRecord();
+ pack.generateQuestions();
+ return model.getCurCard()->getQuestion();
+}
+
+QString SRModelTest::addRecord()
+{
+ DicRecord* record = new DicRecord;
+ QString recordName = QString::number(recordId);
+ record->setField("English", recordName);
+ record->setField("Russian", "Odin");
+ recordId++;
+ dict.addRecord(record);
+ return recordName;
+}
diff --git a/tests/unit/SpacedRepetitionModel/SRModel_test.h b/tests/unit/SpacedRepetitionModel/SRModel_test.h
new file mode 100644
index 0000000..9e5d669
--- /dev/null
+++ b/tests/unit/SpacedRepetitionModel/SRModel_test.h
@@ -0,0 +1,46 @@
+#ifndef SPACED_REPETITION_MODEL_TEST_H
+#define SPACED_REPETITION_MODEL_TEST_H
+
+#include <gtest/gtest.h>
+#include <iostream>
+#include <QtCore>
+
+#include "../../../src/study/SpacedRepetitionModel.h"
+#include "../../../src/dictionary/CardPack.h"
+#include "../../mocks/Dictionary_mock.h"
+
+using std::ostream;
+
+class MockRandomGenerator;
+class Field;
+
+class TestSpacedRepetitionModel: public SpacedRepetitionModel
+{
+public:
+ TestSpacedRepetitionModel(CardPack* pack, IRandomGenerator* random):
+ SpacedRepetitionModel(pack, random) {}
+ void testPickCard() { pickNextCardAndNotify(); }
+};
+
+class SRModelTest: public testing::Test
+{
+public:
+ SRModelTest();
+
+protected:
+ void SetUp();
+ void TearDown();
+ QString createCard();
+ QString addRecord();
+
+protected:
+ MockDictionary dict;
+ int recordId;
+ Field* field1;
+ Field* field2;
+ CardPack pack;
+ MockRandomGenerator* randomGenerator;
+ TestSpacedRepetitionModel model;
+};
+
+#endif
diff --git a/tests/unit/SpacedRepetitionModel/srModel.pri b/tests/unit/SpacedRepetitionModel/srModel.pri
new file mode 100644
index 0000000..19daca1
--- /dev/null
+++ b/tests/unit/SpacedRepetitionModel/srModel.pri
@@ -0,0 +1,16 @@
+HEADERS += \
+ $$PWD/SRModel_test.h \
+ $$PWD/SRModel_schedule_test.h \
+ $$PWD/SRModel_pickCard_test.h \
+ $$PWD/SRModel_showGrades_test.h \
+ $$SRC/study/SpacedRepetitionModel.h \
+ $$SRC/study/IStudyModel.h \
+ $$TESTS/mocks/RandomGenerator_mock.h
+
+SOURCES += \
+ $$PWD/SRModel_test.cpp \
+ $$PWD/SRModel_schedule_test.cpp \
+ $$PWD/SRModel_pickCard_test.cpp \
+ $$PWD/SRModel_showGrades_test.cpp \
+ $$SRC/study/SpacedRepetitionModel.cpp \
+ $$SRC/study/IStudyModel.cpp
diff --git a/tests/unit/cards.pri b/tests/unit/cards.pri
new file mode 100644
index 0000000..e3c563e
--- /dev/null
+++ b/tests/unit/cards.pri
@@ -0,0 +1,24 @@
+HEADERS += \
+ $$SRC/dictionary/Card.h \
+ $$SRC/dictionary/ICardPack.h \
+ $$SRC/dictionary/CardPack.h \
+ $$SRC/dictionary/IDictionary.h \
+ $$SRC/dictionary/Field.h \
+ $$SRC/dictionary/DicRecord.h \
+ $$SRC/study/StudyRecord.h \
+ $$SRC/utils/TimeProvider.h \
+ $$TESTS/mocks/Dictionary_mock.h \
+ $$TESTS/common/RecordsParam.h
+
+SOURCES += \
+ $$SRC/dictionary/Card.cpp \
+ $$SRC/dictionary/ICardPack.cpp \
+ $$SRC/dictionary/CardPack.cpp \
+ $$SRC/dictionary/IDictionary.cpp \
+ $$SRC/dictionary/Field.cpp \
+ $$SRC/dictionary/DicRecord.cpp \
+ $$SRC/study/StudyRecord.cpp \
+ $$TESTS/mocks/Dictionary_mock.cpp \
+ $$TESTS/mocks/TimeProvider_mock.cpp \
+ $$TESTS/common/RecordsParam.cpp \
+ $$TESTS/common/RecordsParam_create.cpp
diff --git a/tests/unit/common.pri b/tests/unit/common.pri
new file mode 100644
index 0000000..482dab7
--- /dev/null
+++ b/tests/unit/common.pri
@@ -0,0 +1,3 @@
+SOURCES += \
+ main.cpp \
+ $$TESTS/common/printQtTypes.cpp
diff --git a/tests/unit/main.cpp b/tests/unit/main.cpp
new file mode 100644
index 0000000..eb8f2b9
--- /dev/null
+++ b/tests/unit/main.cpp
@@ -0,0 +1,9 @@
+#include <gtest/gtest.h>
+#include <QtWidgets>
+
+int main(int argc, char** argv)
+{
+ QApplication app(argc, argv);
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/tests/unit/random.pri b/tests/unit/random.pri
new file mode 100644
index 0000000..003c663
--- /dev/null
+++ b/tests/unit/random.pri
@@ -0,0 +1 @@
+HEADERS += $$SRC/utils/IRandomGenerator.h
diff --git a/tests/unit/studySets.pri b/tests/unit/studySets.pri
new file mode 100644
index 0000000..50c6bd1
--- /dev/null
+++ b/tests/unit/studySets.pri
@@ -0,0 +1,9 @@
+HEADERS += \
+ $$SRC/field-styles/FieldStyleFactory.h \
+ $$SRC/field-styles/FieldStyle.h \
+ $$SRC/study/StudySettings.h
+
+SOURCES += \
+ $$SRC/field-styles/FieldStyleFactory.cpp \
+ $$SRC/field-styles/FieldStyle.cpp \
+ $$SRC/study/StudySettings.cpp \ No newline at end of file
diff --git a/tests/unit/unit_tests.pro b/tests/unit/unit_tests.pro
new file mode 100644
index 0000000..49f74d0
--- /dev/null
+++ b/tests/unit/unit_tests.pro
@@ -0,0 +1,29 @@
+TEMPLATE = app
+TARGET = unittest
+QT += widgets
+LIBS += -lgtest -lgtest_main
+QMAKE_CXXFLAGS += -std=gnu++11
+DESTDIR = ./
+
+win32: {
+ QMAKE_LIBS_QT_ENTRY =
+ CONFIG += console
+ }
+
+MOC_DIR = moc
+OBJECTS_DIR = obj
+
+SRC = $$PWD/../../src
+TESTS = $$PWD/..
+
+include(common.pri)
+include(cards.pri)
+include(studySets.pri)
+include(random.pri)
+
+include(Card/card.pri)
+include(CardPack/cPack.pri)
+include(CardSideView/csView.pri)
+include(RandomGenerator/rndGen.pri)
+include(Settings/set.pri)
+include(SpacedRepetitionModel/srModel.pri)