feat(SQ/Aggregator): Created utility `Aggregator`

- Abstract class created: `Aggregator`
- Derived abstract class created: `SingleRoleAggregator`
- Derived class created:  `SumAggregator`
- `Storybook` page created.
- Added unit tests.

Closes #12685
This commit is contained in:
Noelia 2023-11-30 13:50:04 +01:00 committed by Noelia
parent e7c4cd7c6c
commit c2bfc6b8f4
13 changed files with 930 additions and 0 deletions

View File

@ -0,0 +1,174 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import StatusQ 0.1
Item {
id: root
QtObject {
id: d
readonly property string balanceRoleName: "balance"
property string roleName: balanceRoleName
}
ListModel {
id: srcModel
ListElement {
key: "ETH"
balances: [
ListElement { chainId: "1"; balance: 3 },
ListElement { chainId: "2"; balance: 4 },
ListElement { chainId: "31"; balance: 2 }
]
}
ListElement {
key: "SNT"
balances: [
ListElement { chainId: "2"; balance: 42 }
]
}
ListElement {
key: "DAI"
balances: [
ListElement { chainId: "1"; balance: 4 },
ListElement { chainId: "3"; balance: 9 }
]
}
}
ColumnLayout {
anchors.fill: parent
anchors.margins: 40
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
border.color: "gray"
ListView {
anchors.fill: parent
anchors.margins: 10
model: srcModel
spacing: 10
header: Label {
height: implicitHeight * 2
text: `Source model (${srcModel.count})`
font.bold: true
color: "blue"
verticalAlignment: Text.AlignVCenter
}
ScrollBar.vertical: ScrollBar {}
delegate: ColumnLayout {
height: implicitHeight
spacing: 0
Label {
Layout.fillWidth: true
Layout.bottomMargin: 4
text: `KEY: ${model.key}`
font.bold: true
}
Label {
Layout.fillWidth: true
Layout.bottomMargin: 4
text: `Total balances: ${aggregator.value}`
font.bold: true
color: "red"
}
ListView {
Layout.fillWidth: true
Layout.preferredHeight: childrenRect.height
model: balances
delegate: Label {
height: implicitHeight
width: ListView.view.width
text: `chainID: ${model.chainId}, balance: ${model.balance}`
}
}
SumAggregator {
id: aggregator
model: balances
roleName: d.roleName
}
}
}
}
RowLayout {
Button {
id: addRows
text: "Add rows"
onClicked: {
srcModel.get(0).balances.append( {"chainId": "1", "balance": Math.random()} )
srcModel.get(1).balances.append( {"chainId": "22", "balance": Math.random()} )
srcModel.get(2).balances.append( {"chainId": "34", "balance": Math.random()} )
}
}
Button {
id: removeRows
text: "Remove rows"
onClicked: {
if(srcModel.get(0).balances.count > 1)
srcModel.get(0).balances.remove(0)
if(srcModel.get(1).balances.count > 1)
srcModel.get(1).balances.remove(0)
if(srcModel.get(2).balances.count > 1)
srcModel.get(2).balances.remove(0)
}
}
Button {
id: resetModel
text: "Reset model"
onClicked: {
srcModel.get(0).balances.clear()
srcModel.get(1).balances.clear()
srcModel.get(2).balances.clear()
}
}
Button {
id: changeData
text: "Change data"
onClicked: {
srcModel.get(0).balances.get(0).balance = Math.random()
srcModel.get(1).balances.get(0).balance = Math.random()
srcModel.get(2).balances.get(0).balance = Math.random()
}
}
Button {
id: changeRoleName
text: "Change role name"
onClicked: {
if(d.roleName === d.balanceRoleName)
d.roleName = "chainId"
else
d.roleName = d.balanceRoleName
}
}
}
}
}
// category: Models

View File

@ -98,6 +98,9 @@ add_library(StatusQ SHARED
include/StatusQ/statuswindow.h
include/StatusQ/stringutilsinternal.h
include/StatusQ/submodelproxymodel.h
include/StatusQ/aggregator.h
include/StatusQ/singleroleaggregator.h
include/StatusQ/sumaggregator.h
src/QClipboardProxy.cpp
src/leftjoinmodel.cpp
src/modelutilsinternal.cpp
@ -109,6 +112,9 @@ add_library(StatusQ SHARED
src/statuswindow.cpp
src/stringutilsinternal.cpp
src/submodelproxymodel.cpp
src/aggregator.cpp
src/singleroleaggregator.cpp
src/sumaggregator.cpp
# wallet
src/wallet/managetokenscontroller.cpp

View File

@ -0,0 +1,39 @@
#pragma once
#include <QAbstractListModel>
#include <QObject>
#include <QVariant>
class QAbstractItemModel;
class Aggregator : public QObject {
Q_OBJECT
Q_PROPERTY(QAbstractItemModel* model READ model WRITE setModel NOTIFY modelChanged)
Q_PROPERTY(QVariant value READ value NOTIFY valueChanged)
public:
explicit Aggregator(QObject *parent = nullptr);
QAbstractItemModel* model() const;
void setModel(QAbstractItemModel* model);
QVariant value() const;
signals:
void modelChanged();
void valueChanged();
protected slots:
virtual QVariant calculateAggregation() = 0;
protected:
void recalculate();
virtual bool acceptRoles(const QVector<int>& roles) { return true; };
private:
QAbstractItemModel* m_model = nullptr;
QVariant m_value;
void connectToModel();
};

View File

@ -0,0 +1,27 @@
#pragma once
#include <QObject>
#include "StatusQ/aggregator.h"
class SingleRoleAggregator : public Aggregator {
Q_OBJECT
Q_PROPERTY(QByteArray roleName READ roleName WRITE setRoleName NOTIFY roleNameChanged)
public:
explicit SingleRoleAggregator(QObject *parent = nullptr);
const QByteArray& roleName() const;
void setRoleName(const QByteArray &roleName);
signals:
void roleNameChanged();
protected:
bool acceptRoles(const QVector<int>& roles) override;
bool roleExists() const;
private:
QByteArray m_roleName;
};

View File

@ -0,0 +1,15 @@
#pragma once
#include <QObject>
#include <QVariant>
#include "StatusQ/singleroleaggregator.h"
class SumAggregator : public SingleRoleAggregator {
public:
explicit SumAggregator(QObject *parent = nullptr);
protected slots:
QVariant calculateAggregation() override;
};

View File

@ -0,0 +1,51 @@
#include "StatusQ/aggregator.h"
#include <QAbstractItemModel>
Aggregator::Aggregator(QObject *parent)
: QObject(parent){
connect(this, &Aggregator::modelChanged, this, &Aggregator::recalculate);
}
QAbstractItemModel* Aggregator::model() const {
return m_model;
}
void Aggregator::setModel(QAbstractItemModel* model) {
if(m_model == model)
return;
if(m_model)
disconnect(m_model, nullptr, this, nullptr);
m_model = model;
connectToModel();
emit modelChanged();
}
QVariant Aggregator::value() const {
return m_value;
}
void Aggregator::recalculate()
{
auto newValue = calculateAggregation();
if (m_value == newValue)
return;
m_value = newValue;
emit valueChanged();
}
void Aggregator:: connectToModel() {
if(m_model) {
connect(m_model, &QAbstractItemModel::rowsInserted, this, &Aggregator::recalculate);
connect(m_model, &QAbstractItemModel::rowsRemoved, this, &Aggregator::recalculate);
connect(m_model, &QAbstractItemModel::modelReset, this, &Aggregator::recalculate);
connect(m_model, &QAbstractItemModel::dataChanged, this,
[this](auto&, auto&, const QVector<int>& roles) {
if (this->acceptRoles(roles))
this->recalculate();
});
}
}

View File

@ -13,6 +13,7 @@
#include "StatusQ/statuswindow.h"
#include "StatusQ/stringutilsinternal.h"
#include "StatusQ/submodelproxymodel.h"
#include "StatusQ/sumaggregator.h"
#include "wallet/managetokenscontroller.h"
@ -38,6 +39,7 @@ public:
qmlRegisterType<SubmodelProxyModel>("StatusQ", 0, 1, "SubmodelProxyModel");
qmlRegisterType<RoleRename>("StatusQ", 0, 1, "RoleRename");
qmlRegisterType<RolesRenamingModel>("StatusQ", 0, 1, "RolesRenamingModel");
qmlRegisterType<SumAggregator>("StatusQ", 0, 1, "SumAggregator");
qmlRegisterSingletonType<QClipboardProxy>("StatusQ", 0, 1, "QClipboardProxy", &QClipboardProxy::qmlInstance);

View File

@ -0,0 +1,38 @@
#include "StatusQ/singleroleaggregator.h"
SingleRoleAggregator::SingleRoleAggregator(QObject *parent)
: Aggregator(parent) {}
const QByteArray& SingleRoleAggregator::roleName() const {
return m_roleName;
}
void SingleRoleAggregator::setRoleName(const QByteArray &roleName) {
if (m_roleName == roleName)
return;
m_roleName = roleName;
emit roleNameChanged();
recalculate();
}
bool SingleRoleAggregator::acceptRoles(const QVector<int>& roles) {
QHash<int, QByteArray> roleNames = model()->roleNames();
for (const int role : roles) {
if (roleNames.contains(role)) {
QString roleName = QString::fromUtf8(roleNames[role]);
// Check if the role name is equal to the expected one:
if (roleName == m_roleName) {
return true;
}
}
}
// If we reach this point, none of the roles match m_roleName
return false;
}
bool SingleRoleAggregator::roleExists() const {
return !model()->roleNames().keys(m_roleName).empty();
}

View File

@ -0,0 +1,39 @@
#include "StatusQ/sumaggregator.h"
#include <QDebug>
SumAggregator::SumAggregator(QObject *parent)
: SingleRoleAggregator(parent) {
recalculate();
}
QVariant SumAggregator::calculateAggregation() {
// Check if m_model exists and role name is initialized
if (!model() || roleName().isEmpty())
return 0.0;
// Check if m_roleName is part of the roles of the model
QHash<int, QByteArray> roles = model()->roleNames();
if (!roleExists()) {
qWarning() << "Provided role name does not exist in the current model";
return 0.0;
}
// Do the aggregation
double total = 0.0;
int rows = model()->rowCount();
int role = roles.key(roleName());
for (int row = 0; row < rows; ++row) {
QModelIndex index = model()->index(row, 0);
QVariant value = model()->data(index, role);
bool ok;
total += value.toDouble(&ok);
if (!ok)
qWarning() << "Unsupported type for given role (not convertible to double)";
}
return total;
}

View File

@ -52,3 +52,15 @@ add_test(NAME LeftJoinModelTest COMMAND LeftJoinModelTest)
add_executable(SubmodelProxyModelTest tst_SubmodelProxyModel.cpp)
target_link_libraries(SubmodelProxyModelTest PRIVATE Qt5::Qml Qt5::Test StatusQ)
add_test(NAME SubmodelProxyModelTest COMMAND SubmodelProxyModelTest)
add_executable(AggregatorTest tst_Aggregator.cpp)
target_link_libraries(AggregatorTest PRIVATE Qt5::Test StatusQ)
add_test(NAME AggregatorTest COMMAND AggregatorTest)
add_executable(SingleRoleAggregatorTest tst_SingleRoleAggregator.cpp)
target_link_libraries(SingleRoleAggregatorTest PRIVATE Qt5::Test StatusQ)
add_test(NAME SingleRoleAggregatorTest COMMAND SingleRoleAggregatorTest)
add_executable(SumAggregatorTest tst_SumAggregator.cpp)
target_link_libraries(SumAggregatorTest PRIVATE Qt5::Test StatusQ)
add_test(NAME SumAggregatorTest COMMAND SumAggregatorTest)

View File

@ -0,0 +1,148 @@
#include <QtTest>
#include <QAbstractListModel>
#include <QCoreApplication>
#include "StatusQ/aggregator.h"
namespace {
// TODO: To be removed once issue #12843 is resolved and we have a testing utils
class TestSourceModel : public QAbstractListModel {
public:
explicit TestSourceModel(QList<QPair<QString, QVariantList>> data)
: m_data(std::move(data)) {
m_roles.reserve(m_data.size());
for (auto i = 0; i < m_data.size(); i++)
m_roles.insert(i, m_data.at(i).first.toUtf8());
}
int rowCount(const QModelIndex& parent) const override {
Q_ASSERT(m_data.size());
return m_data.first().second.size();
}
QVariant data(const QModelIndex& index, int role) const override {
if (!index.isValid() || role < 0 || role >= m_data.size())
return {};
const auto row = index.row();
if (role >= m_data.length() || row >= m_data.at(0).second.length())
return {};
return m_data.at(role).second.at(row);
}
bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override {
beginInsertRows(parent, row, row + count - 1);
m_data.insert(row, QPair<QString, QVariantList>());
endInsertRows();
return true;
}
void update(int index, int role, QVariant value) {
Q_ASSERT(role < m_data.size() && index < m_data[role].second.size());
m_data[role].second[index].setValue(std::move(value));
emit dataChanged(this->index(index, 0), this->index(index, 0), { role });
}
void remove(int index) {
beginRemoveRows(QModelIndex{}, index, index);
for (int i = 0; i < m_data.size(); i++) {
auto& roleVariantList = m_data[i].second;
Q_ASSERT(index < roleVariantList.size());
roleVariantList.removeAt(index);
}
endRemoveRows();
}
QHash<int, QByteArray> roleNames() const override {
return m_roles;
}
private:
QList<QPair<QString, QVariantList>> m_data;
QHash<int, QByteArray> m_roles;
};
class ChildAggregator : public Aggregator {
Q_OBJECT
public:
explicit ChildAggregator(QObject *parent = nullptr) {}
protected slots:
QVariant calculateAggregation() override {
return {counter++};
}
private:
int counter = 0;
};
} // anonymous namespace
class TestAggregator : public QObject
{
Q_OBJECT
private:
QString m_roleNameWarningText = "Provided role name does not exist in the current model";
QString m_unsuportedTypeWarningText = "Unsupported type for given role (not convertible to double)";
private slots:
void testModel() {
ChildAggregator aggregator;
TestSourceModel sourceModel({
{ "chainId", { "12", "13", "1", "321" }},
{ "balance", { "0.123", "0.0000015", "1.45", "25.45221001" }}
});
QSignalSpy modelChangedSpy(&aggregator, &Aggregator::modelChanged);
QSignalSpy valueChangedSpy(&aggregator, &Aggregator::valueChanged);
// Test 1: Real model
aggregator.setModel(&sourceModel);
QCOMPARE(aggregator.model(), &sourceModel);
QCOMPARE(modelChangedSpy.count(), 1);
QCOMPARE(valueChangedSpy.count(), 1);
// Test 2: Non existing model
aggregator.setModel(nullptr);
QCOMPARE(aggregator.model(), nullptr);
QCOMPARE(modelChangedSpy.count(), 2);
QCOMPARE(valueChangedSpy.count(), 2);
}
void testCalculateAggregationTrigger() {
ChildAggregator aggregator;
TestSourceModel sourceModel({
{ "chainId", { "12", "13", "1", "321" }},
{ "balance", { 0.123, 1.0, 1.45, 25.45 }}
});
QSignalSpy valueChangedSpy(&aggregator, &Aggregator::valueChanged);
int valueChangedSpyCount = 0;
// Test 1 - Initial:
aggregator.setModel(&sourceModel);
valueChangedSpyCount++;
QCOMPARE(valueChangedSpy.count(), valueChangedSpyCount);
// Test 2 - Delete row:
sourceModel.remove(0);
valueChangedSpyCount++;
QCOMPARE(valueChangedSpy.count(), valueChangedSpyCount);
// Test 3 - Update value row:
sourceModel.update(2, 1, 26.45);
valueChangedSpyCount++;
QCOMPARE(valueChangedSpy.count(), valueChangedSpyCount);
}
};
QTEST_MAIN(TestAggregator)
#include "tst_Aggregator.moc"

View File

@ -0,0 +1,121 @@
#include <QtTest>
#include <QAbstractListModel>
#include <QCoreApplication>
#include "StatusQ/singleroleaggregator.h"
namespace {
// TODO: To be removed once issue #12843 is resolved and we have a testing utils
class TestSourceModel : public QAbstractListModel {
public:
explicit TestSourceModel(QList<QPair<QString, QVariantList>> data)
: m_data(std::move(data)) {
m_roles.reserve(m_data.size());
for (auto i = 0; i < m_data.size(); i++)
m_roles.insert(i, m_data.at(i).first.toUtf8());
}
int rowCount(const QModelIndex& parent) const override {
Q_ASSERT(m_data.size());
return m_data.first().second.size();
}
QVariant data(const QModelIndex& index, int role) const override {
if (!index.isValid() || role < 0 || role >= m_data.size())
return {};
const auto row = index.row();
if (role >= m_data.length() || row >= m_data.at(0).second.length())
return {};
return m_data.at(role).second.at(row);
}
bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override {
beginInsertRows(parent, row, row + count - 1);
m_data.insert(row, QPair<QString, QVariantList>());
endInsertRows();
return true;
}
void update(int index, int role, QVariant value) {
Q_ASSERT(role < m_data.size() && index < m_data[role].second.size());
m_data[role].second[index].setValue(std::move(value));
emit dataChanged(this->index(index, 0), this->index(index, 0), { role });
}
void remove(int index) {
beginRemoveRows(QModelIndex{}, index, index);
for (int i = 0; i < m_data.size(); i++) {
auto& roleVariantList = m_data[i].second;
Q_ASSERT(index < roleVariantList.size());
roleVariantList.removeAt(index);
}
endRemoveRows();
}
QHash<int, QByteArray> roleNames() const override {
return m_roles;
}
private:
QList<QPair<QString, QVariantList>> m_data;
QHash<int, QByteArray> m_roles;
};
class ChildSingleRoleAggregator : public SingleRoleAggregator {
Q_OBJECT
public:
explicit ChildSingleRoleAggregator(QObject *parent = nullptr) {}
protected slots:
QVariant calculateAggregation() override { return {}; }
};
} // anonymous namespace
class TestSingleRoleAggregator : public QObject
{
Q_OBJECT
private:
QString m_roleNameWarningText = "Provided role name does not exist in the current model";
private slots:
void testRoleName() {
ChildSingleRoleAggregator aggregator;
TestSourceModel sourceModel({
{ "chainId", { "12", "13", "1", "321" }},
{ "balance", { "0.123", "0.0000015", "1.45", "25.45221001" }}
});
QSignalSpy roleNameSpy(&aggregator, &SingleRoleAggregator::roleNameChanged);
// Test 1 - Assign role name but model is nullptr
aggregator.setRoleName("TestRole");
QCOMPARE(aggregator.roleName(), QString("TestRole"));
QCOMPARE(roleNameSpy.count(), 1);
// Test 2 - New role but doesn't exist in the current model
aggregator.setModel(&sourceModel);
aggregator.setRoleName("TestRole2");
QCOMPARE(aggregator.roleName(), QString("TestRole2"));
QCOMPARE(roleNameSpy.count(), 2);
// Test 3 - New role existing in the current model
aggregator.setRoleName("balance");
QCOMPARE(aggregator.roleName(), QString("balance"));
QCOMPARE(roleNameSpy.count(), 3);
}
};
QTEST_MAIN(TestSingleRoleAggregator)
#include "tst_SingleRoleAggregator.moc"

View File

@ -0,0 +1,258 @@
#include <QtTest>
#include <QAbstractListModel>
#include <QCoreApplication>
#include "StatusQ/sumaggregator.h"
namespace {
// TODO: To be removed once issue #12843 is resolved and we have a testing utils
class TestSourceModel : public QAbstractListModel {
public:
explicit TestSourceModel(QList<QPair<QString, QVariantList>> data)
: m_data(std::move(data)) {
m_roles.reserve(m_data.size());
for (auto i = 0; i < m_data.size(); i++)
m_roles.insert(i, m_data.at(i).first.toUtf8());
}
int rowCount(const QModelIndex& parent) const override {
Q_ASSERT(m_data.size());
return m_data.first().second.size();
}
QVariant data(const QModelIndex& index, int role) const override {
if (!index.isValid() || role < 0 || role >= m_data.size())
return {};
const auto row = index.row();
if (role >= m_data.length() || row >= m_data.at(0).second.length())
return {};
return m_data.at(role).second.at(row);
}
bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override {
beginInsertRows(parent, row, row + count - 1);
m_data.insert(row, QPair<QString, QVariantList>());
endInsertRows();
return true;
}
void update(int index, int role, QVariant value) {
Q_ASSERT(role < m_data.size() && index < m_data[role].second.size());
m_data[role].second[index].setValue(std::move(value));
emit dataChanged(this->index(index, 0), this->index(index, 0), { role });
}
void remove(int index) {
beginRemoveRows(QModelIndex{}, index, index);
for (int i = 0; i < m_data.size(); i++) {
auto& roleVariantList = m_data[i].second;
Q_ASSERT(index < roleVariantList.size());
roleVariantList.removeAt(index);
}
endRemoveRows();
}
QHash<int, QByteArray> roleNames() const override {
return m_roles;
}
private:
QList<QPair<QString, QVariantList>> m_data;
QHash<int, QByteArray> m_roles;
};
} // anonymous namespace
class TestSumAggregator : public QObject
{
Q_OBJECT
private:
QString m_roleNameWarningText = "Provided role name does not exist in the current model";
QString m_unsuportedTypeWarningText = "Unsupported type for given role (not convertible to double)";
private slots:
void testEmpty() {
SumAggregator aggregator;
QCOMPARE(aggregator.value(), 0.0);
}
void testModel() {
SumAggregator aggregator;
TestSourceModel sourceModel({
{ "chainId", { "12", "13", "1", "321" }},
{ "balance", { "0.123", "0.0000015", "1.45", "25.45221001" }}
});
QSignalSpy modelChangedSpy(&aggregator, &Aggregator::modelChanged);
QSignalSpy valueChangedSpy(&aggregator, &Aggregator::valueChanged);
// Test 1: Real model
aggregator.setModel(&sourceModel);
QCOMPARE(aggregator.model(), &sourceModel);
QCOMPARE(modelChangedSpy.count(), 1);
QCOMPARE(valueChangedSpy.count(), 0);
// Test 2: Non existing model
aggregator.setModel(nullptr);
QCOMPARE(aggregator.model(), nullptr);
QCOMPARE(modelChangedSpy.count(), 2);
QCOMPARE(valueChangedSpy.count(), 0);
}
void testRoleName() {
SumAggregator aggregator;
TestSourceModel sourceModel({
{ "chainId", { "12", "13", "1", "321" }},
{ "balance", { "0.123", "0.0000015", "1.45", "25.45221001" }}
});
QSignalSpy roleNameSpy(&aggregator, &SingleRoleAggregator::roleNameChanged);
QSignalSpy valueChangedSpy(&aggregator, &Aggregator::valueChanged);
// Test 1 - Assign role name but model is nullptr
aggregator.setRoleName("TestRole");
QTest::ignoreMessage(QtWarningMsg,
m_roleNameWarningText.toUtf8());
QCOMPARE(aggregator.roleName(), QString("TestRole"));
QCOMPARE(roleNameSpy.count(), 1);
QCOMPARE(valueChangedSpy.count(), 0);
// Test 2 - New role but doesn't exist in the current model
aggregator.setModel(&sourceModel);
QTest::ignoreMessage(QtWarningMsg,
m_roleNameWarningText.toUtf8());
aggregator.setRoleName("TestRole2");
QCOMPARE(aggregator.roleName(), QString("TestRole2"));
QCOMPARE(roleNameSpy.count(), 2);
QCOMPARE(valueChangedSpy.count(), 0);
// Test 3 - New role existing in the current model
aggregator.setRoleName("balance");
QCOMPARE(aggregator.roleName(), QString("balance"));
QCOMPARE(roleNameSpy.count(), 3);
QCOMPARE(valueChangedSpy.count(), 1);
}
void testStringTypeValue() {
SumAggregator aggregator;
TestSourceModel sourceModel({
{ "chainId", { "12", "13", "1", "321" }},
{ "balance", { "0.123", "1", "1.45", "25.45" }}
});
QSignalSpy valueChangedSpy(&aggregator, &Aggregator::valueChanged);
int valueChangedSpyCount = 0;
// Test 1 - Initial:
aggregator.setModel(&sourceModel);
QCOMPARE(valueChangedSpy.count(), valueChangedSpyCount);
aggregator.setRoleName("balance");
valueChangedSpyCount++;
QCOMPARE(valueChangedSpy.count(), valueChangedSpyCount);
QCOMPARE(aggregator.value().toDouble(), 28.023);
// Test 2 - Delete row:
sourceModel.remove(0);
valueChangedSpyCount++;
QCOMPARE(valueChangedSpy.count(), valueChangedSpyCount);
QCOMPARE(aggregator.value().toDouble(), 27.9);
// Test 3 - Update value row:
sourceModel.update(2, 1, "26.45");
valueChangedSpyCount++;
QCOMPARE(valueChangedSpy.count(), valueChangedSpyCount);
QCOMPARE(aggregator.value().toDouble(), 28.9);
// Test 4 - Update value row but other role, not `balance`, so it will not trigger a value change:
sourceModel.update(2, 0, "52");
QCOMPARE(valueChangedSpy.count(), valueChangedSpyCount);
QCOMPARE(aggregator.value().toDouble(), 28.9);
}
void testFloatTypeValue() {
SumAggregator aggregator;
TestSourceModel sourceModel({
{ "chainId", { "12", "13", "1", "321" }},
{ "balance", { 0.123, 1.0, 1.45, 25.45 }}
});
QSignalSpy valueChangedSpy(&aggregator, &Aggregator::valueChanged);
int valueChangedSpyCount = 0;
// Test 1 - Initial:
aggregator.setModel(&sourceModel);
QCOMPARE(valueChangedSpy.count(), valueChangedSpyCount);
aggregator.setRoleName("balance");
valueChangedSpyCount++;
QCOMPARE(valueChangedSpy.count(), valueChangedSpyCount);
QCOMPARE(aggregator.value().toDouble(), 28.023);
// Test 2 - Delete row:
sourceModel.remove(0);
valueChangedSpyCount++;
QCOMPARE(valueChangedSpy.count(), valueChangedSpyCount);
QCOMPARE(aggregator.value().toDouble(), 27.9);
// Test 3 - Update value row:
sourceModel.update(2, 1, 26.45);
valueChangedSpyCount++;
QCOMPARE(valueChangedSpy.count(), valueChangedSpyCount);
QCOMPARE(aggregator.value().toDouble(), 28.9);
// Test 4 - Update value row but other role, not `balance`, so it will not trigger a value change:
sourceModel.update(2, 0, "52");
QCOMPARE(valueChangedSpy.count(), valueChangedSpyCount);
QCOMPARE(aggregator.value().toDouble(), 28.9);
}
void testStringUnsupportedTypeValue() {
SumAggregator aggregator;
TestSourceModel sourceModel({
{ "chainId", { "12", "13" }},
{ "balance", { "aa", "bb" }}
});
QSignalSpy valueChangedSpy(&aggregator, &Aggregator::valueChanged);
aggregator.setModel(&sourceModel);
QCOMPARE(valueChangedSpy.count(), 0);
QTest::ignoreMessage(QtWarningMsg,
m_unsuportedTypeWarningText.toUtf8());
QTest::ignoreMessage(QtWarningMsg,
m_unsuportedTypeWarningText.toUtf8());
aggregator.setRoleName("balance");
// Value didn't change, it was an unsuported type!
QCOMPARE(valueChangedSpy.count(), 0);
QCOMPARE(aggregator.value().toDouble(), 0);
}
void testUnsupportedTypeValue() {
SumAggregator aggregator;
TestSourceModel sourceModel({
{ "chainId", { "12", "13" }},
{ "balance", { QByteArray(), QByteArray() }}
});
QSignalSpy valueChangedSpy(&aggregator, &Aggregator::valueChanged);
aggregator.setModel(&sourceModel);
QCOMPARE(valueChangedSpy.count(), 0);
QTest::ignoreMessage(QtWarningMsg,
m_unsuportedTypeWarningText.toUtf8());
QTest::ignoreMessage(QtWarningMsg,
m_unsuportedTypeWarningText.toUtf8());
aggregator.setRoleName("balance");
// Value didn't change, it was an unsuported type!
QCOMPARE(valueChangedSpy.count(), 0);
QCOMPARE(aggregator.value().toDouble(), 0);
}
};
QTEST_MAIN(TestSumAggregator)
#include "tst_SumAggregator.moc"