feat(StatusQ): General purpose js function-based aggregator
Closes: #14617
This commit is contained in:
parent
ae636ef5a7
commit
587594f3b9
|
@ -0,0 +1,155 @@
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
|
import StatusQ 0.1
|
||||||
|
import StatusQ.Core.Utils 0.1
|
||||||
|
|
||||||
|
import Storybook 1.0
|
||||||
|
|
||||||
|
|
||||||
|
Control {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
font.pixelSize: 16
|
||||||
|
padding: 10
|
||||||
|
|
||||||
|
ListModel {
|
||||||
|
id: sourceModel
|
||||||
|
|
||||||
|
ListElement {
|
||||||
|
symbol: "SNT"
|
||||||
|
balance: "4"
|
||||||
|
}
|
||||||
|
ListElement {
|
||||||
|
symbol: "ETH"
|
||||||
|
balance: "14"
|
||||||
|
}
|
||||||
|
ListElement {
|
||||||
|
symbol: "ZRX"
|
||||||
|
balance: "24"
|
||||||
|
}
|
||||||
|
ListElement {
|
||||||
|
symbol: "DAI"
|
||||||
|
balance: "43"
|
||||||
|
}
|
||||||
|
ListElement {
|
||||||
|
symbol: "UNI"
|
||||||
|
balance: "2"
|
||||||
|
}
|
||||||
|
ListElement {
|
||||||
|
symbol: "PEPE"
|
||||||
|
balance: "1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FunctionAggregator {
|
||||||
|
id: totalBalanceAggregator
|
||||||
|
|
||||||
|
model: sourceModel
|
||||||
|
initialValue: "0"
|
||||||
|
roleName: "balance"
|
||||||
|
|
||||||
|
aggregateFunction: (aggr, value) => AmountsArithmetic.sum(
|
||||||
|
AmountsArithmetic.fromString(aggr),
|
||||||
|
AmountsArithmetic.fromString(value)).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
FunctionAggregator {
|
||||||
|
id: maxBalanceAggregator
|
||||||
|
|
||||||
|
model: sourceModel
|
||||||
|
initialValue: "0"
|
||||||
|
roleName: "balance"
|
||||||
|
|
||||||
|
aggregateFunction: (aggr, value) => AmountsArithmetic.cmp(
|
||||||
|
AmountsArithmetic.fromString(aggr),
|
||||||
|
AmountsArithmetic.fromString(value)) > 0
|
||||||
|
? aggr : value
|
||||||
|
}
|
||||||
|
|
||||||
|
FunctionAggregator {
|
||||||
|
id: tokensListAggregator
|
||||||
|
|
||||||
|
model: sourceModel
|
||||||
|
initialValue: []
|
||||||
|
roleName: "symbol"
|
||||||
|
|
||||||
|
aggregateFunction: (aggr, value) => [...aggr, value]
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
Label {
|
||||||
|
text: "SUMMARY"
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: "Total balance: " + totalBalanceAggregator.value
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: "Max balance: " + maxBalanceAggregator.value
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: "Tokens list: " + tokensListAggregator.value
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.preferredHeight: 20
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: "MODEL (click rows to change)"
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
|
||||||
|
GenericListView {
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
model: sourceModel
|
||||||
|
|
||||||
|
onRowClicked: {
|
||||||
|
if (role === "balance") {
|
||||||
|
const balance = sourceModel.get(index).balance
|
||||||
|
|
||||||
|
sourceModel.setProperty(index, "balance",
|
||||||
|
(Number(balance) + 1).toString())
|
||||||
|
} else {
|
||||||
|
const symbol = sourceModel.get(index).symbol
|
||||||
|
|
||||||
|
sourceModel.setProperty(index, "symbol", symbol + "_")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
insetComponent: Button {
|
||||||
|
height: 20
|
||||||
|
font.pixelSize: 11
|
||||||
|
text: "remove"
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
sourceModel.remove(model.index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: "Add token"
|
||||||
|
|
||||||
|
property int counter: 1
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
sourceModel.append({
|
||||||
|
symbol: "NEW_" + counter,
|
||||||
|
balance: "" + counter * 2
|
||||||
|
})
|
||||||
|
counter++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// category: Models
|
|
@ -37,6 +37,7 @@ ListView {
|
||||||
bottomMargin: margin
|
bottomMargin: margin
|
||||||
|
|
||||||
signal moveRequested(int from, int to)
|
signal moveRequested(int from, int to)
|
||||||
|
signal rowClicked(int index, string role)
|
||||||
|
|
||||||
ListModel {
|
ListModel {
|
||||||
id: rowModel
|
id: rowModel
|
||||||
|
@ -154,6 +155,14 @@ ListView {
|
||||||
readonly property string separator: last ? "" : ","
|
readonly property string separator: last ? "" : ","
|
||||||
|
|
||||||
text: `${roleName}: ${valueSanitized}${separator}`
|
text: `${roleName}: ${valueSanitized}${separator}`
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
onClicked: root.rowClicked(
|
||||||
|
delegateRoot.topModel.index,
|
||||||
|
roleName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,6 +99,7 @@ add_library(StatusQ SHARED
|
||||||
include/StatusQ/fastexpressionrole.h
|
include/StatusQ/fastexpressionrole.h
|
||||||
include/StatusQ/fastexpressionsorter.h
|
include/StatusQ/fastexpressionsorter.h
|
||||||
include/StatusQ/formatteddoubleproperty.h
|
include/StatusQ/formatteddoubleproperty.h
|
||||||
|
include/StatusQ/functionaggregator.h
|
||||||
include/StatusQ/leftjoinmodel.h
|
include/StatusQ/leftjoinmodel.h
|
||||||
include/StatusQ/modelutilsinternal.h
|
include/StatusQ/modelutilsinternal.h
|
||||||
include/StatusQ/movablemodel.h
|
include/StatusQ/movablemodel.h
|
||||||
|
@ -120,6 +121,7 @@ add_library(StatusQ SHARED
|
||||||
src/fastexpressionrole.cpp
|
src/fastexpressionrole.cpp
|
||||||
src/fastexpressionsorter.cpp
|
src/fastexpressionsorter.cpp
|
||||||
src/formatteddoubleproperty.cpp
|
src/formatteddoubleproperty.cpp
|
||||||
|
src/functionaggregator.cpp
|
||||||
src/leftjoinmodel.cpp
|
src/leftjoinmodel.cpp
|
||||||
src/modelutilsinternal.cpp
|
src/modelutilsinternal.cpp
|
||||||
src/movablemodel.cpp
|
src/movablemodel.cpp
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QJSValue>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QVariant>
|
||||||
|
|
||||||
|
#include "StatusQ/singleroleaggregator.h"
|
||||||
|
|
||||||
|
class FunctionAggregator : public SingleRoleAggregator
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
Q_PROPERTY(QVariant initialValue READ initialValue WRITE setInitialValue
|
||||||
|
NOTIFY initialValueChanged)
|
||||||
|
|
||||||
|
Q_PROPERTY(QJSValue aggregateFunction READ aggregateFunction
|
||||||
|
WRITE setAggregateFunction NOTIFY aggregateFunctionChanged)
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit FunctionAggregator(QObject* parent = nullptr);
|
||||||
|
|
||||||
|
const QVariant& initialValue() const;
|
||||||
|
void setInitialValue(const QVariant& initialValue);
|
||||||
|
|
||||||
|
const QJSValue& aggregateFunction() const;
|
||||||
|
void setAggregateFunction(const QJSValue& aggregateFunction);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void initialValueChanged();
|
||||||
|
void aggregateFunctionChanged();
|
||||||
|
|
||||||
|
protected slots:
|
||||||
|
QVariant calculateAggregation() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QVariant m_initialValue;
|
||||||
|
QJSValue m_aggregateFunction;
|
||||||
|
};
|
|
@ -0,0 +1,98 @@
|
||||||
|
#include "StatusQ/functionaggregator.h"
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QJSEngine>
|
||||||
|
|
||||||
|
FunctionAggregator::FunctionAggregator(QObject* parent)
|
||||||
|
: SingleRoleAggregator(parent)
|
||||||
|
{
|
||||||
|
recalculate();
|
||||||
|
}
|
||||||
|
|
||||||
|
const QVariant& FunctionAggregator::initialValue() const
|
||||||
|
{
|
||||||
|
return m_initialValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FunctionAggregator::setInitialValue(const QVariant& initialValue)
|
||||||
|
{
|
||||||
|
if (m_initialValue == initialValue)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_initialValue = initialValue;
|
||||||
|
|
||||||
|
emit initialValueChanged();
|
||||||
|
recalculate();
|
||||||
|
}
|
||||||
|
|
||||||
|
const QJSValue& FunctionAggregator::aggregateFunction() const
|
||||||
|
{
|
||||||
|
return m_aggregateFunction;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FunctionAggregator::setAggregateFunction(const QJSValue& aggregateFunction)
|
||||||
|
{
|
||||||
|
if (m_aggregateFunction.strictlyEquals(aggregateFunction))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!aggregateFunction.isCallable() && !aggregateFunction.isUndefined()) {
|
||||||
|
qWarning() << "FunctionAggregator::aggregateFunction must be a "
|
||||||
|
"callable object.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_aggregateFunction = aggregateFunction;
|
||||||
|
|
||||||
|
emit aggregateFunctionChanged();
|
||||||
|
recalculate();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant FunctionAggregator::calculateAggregation()
|
||||||
|
{
|
||||||
|
// Check if m_model exists and role name is initialized
|
||||||
|
if (!model() || roleName().isEmpty())
|
||||||
|
return m_initialValue;
|
||||||
|
|
||||||
|
// Check if m_roleName is part of the roles of the model
|
||||||
|
QHash<int, QByteArray> roles = model()->roleNames();
|
||||||
|
if (!roleExists() && model()->rowCount()) {
|
||||||
|
qWarning() << "Provided role name does not exist in the current model.";
|
||||||
|
return m_initialValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_initialValue.isValid())
|
||||||
|
return m_initialValue;
|
||||||
|
|
||||||
|
if (m_aggregateFunction.isUndefined())
|
||||||
|
return m_initialValue;
|
||||||
|
|
||||||
|
QJSEngine* engine = qjsEngine(this);
|
||||||
|
|
||||||
|
if (engine == nullptr) {
|
||||||
|
qWarning() << "FunctionAggregator is intended to be used in JS "
|
||||||
|
"environment. QJSEngine must be available.";
|
||||||
|
return m_initialValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJSValue aggregation = engine->toScriptValue(m_initialValue);
|
||||||
|
|
||||||
|
auto rows = model()->rowCount();
|
||||||
|
auto role = roles.key(roleName());
|
||||||
|
|
||||||
|
for (int i = 0; i < rows; ++i) {
|
||||||
|
QModelIndex index = model()->index(i, 0);
|
||||||
|
QVariant value = model()->data(index, role);
|
||||||
|
|
||||||
|
QJSValue valueJs = engine->toScriptValue(value);
|
||||||
|
|
||||||
|
aggregation = m_aggregateFunction.call({aggregation, valueJs});
|
||||||
|
|
||||||
|
if (aggregation.isError()) {
|
||||||
|
qWarning() << "Aggregation calculation failed. Error type:"
|
||||||
|
<< aggregation.errorType();
|
||||||
|
return m_initialValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return aggregation.toVariant();
|
||||||
|
}
|
|
@ -9,6 +9,7 @@
|
||||||
#include "StatusQ/fastexpressionrole.h"
|
#include "StatusQ/fastexpressionrole.h"
|
||||||
#include "StatusQ/fastexpressionsorter.h"
|
#include "StatusQ/fastexpressionsorter.h"
|
||||||
#include "StatusQ/formatteddoubleproperty.h"
|
#include "StatusQ/formatteddoubleproperty.h"
|
||||||
|
#include "StatusQ/functionaggregator.h"
|
||||||
#include "StatusQ/leftjoinmodel.h"
|
#include "StatusQ/leftjoinmodel.h"
|
||||||
#include "StatusQ/modelutilsinternal.h"
|
#include "StatusQ/modelutilsinternal.h"
|
||||||
#include "StatusQ/movablemodel.h"
|
#include "StatusQ/movablemodel.h"
|
||||||
|
@ -56,6 +57,7 @@ public:
|
||||||
qmlRegisterType<RoleRename>("StatusQ", 0, 1, "RoleRename");
|
qmlRegisterType<RoleRename>("StatusQ", 0, 1, "RoleRename");
|
||||||
qmlRegisterType<RolesRenamingModel>("StatusQ", 0, 1, "RolesRenamingModel");
|
qmlRegisterType<RolesRenamingModel>("StatusQ", 0, 1, "RolesRenamingModel");
|
||||||
qmlRegisterType<SumAggregator>("StatusQ", 0, 1, "SumAggregator");
|
qmlRegisterType<SumAggregator>("StatusQ", 0, 1, "SumAggregator");
|
||||||
|
qmlRegisterType<FunctionAggregator>("StatusQ", 0, 1, "FunctionAggregator");
|
||||||
qmlRegisterType<WritableProxyModel>("StatusQ", 0, 1, "WritableProxyModel");
|
qmlRegisterType<WritableProxyModel>("StatusQ", 0, 1, "WritableProxyModel");
|
||||||
qmlRegisterType<FormattedDoubleProperty>("StatusQ", 0, 1, "FormattedDoubleProperty");
|
qmlRegisterType<FormattedDoubleProperty>("StatusQ", 0, 1, "FormattedDoubleProperty");
|
||||||
|
|
||||||
|
|
|
@ -82,6 +82,10 @@ add_executable(SumAggregatorTest tst_SumAggregator.cpp)
|
||||||
target_link_libraries(SumAggregatorTest PRIVATE StatusQ StatusQTestLib)
|
target_link_libraries(SumAggregatorTest PRIVATE StatusQ StatusQTestLib)
|
||||||
add_test(NAME SumAggregatorTest COMMAND SumAggregatorTest)
|
add_test(NAME SumAggregatorTest COMMAND SumAggregatorTest)
|
||||||
|
|
||||||
|
add_executable(FunctionAggregatorTest tst_FunctionAggregator.cpp)
|
||||||
|
target_link_libraries(FunctionAggregatorTest PRIVATE StatusQ StatusQTestLib)
|
||||||
|
add_test(NAME FunctionAggregatorTest COMMAND FunctionAggregatorTest)
|
||||||
|
|
||||||
add_executable(ConcatModelTest tst_ConcatModel.cpp)
|
add_executable(ConcatModelTest tst_ConcatModel.cpp)
|
||||||
target_link_libraries(ConcatModelTest PRIVATE StatusQ StatusQTestLib SortFilterProxyModel)
|
target_link_libraries(ConcatModelTest PRIVATE StatusQ StatusQTestLib SortFilterProxyModel)
|
||||||
add_test(NAME ConcatModelTest COMMAND ConcatModelTest)
|
add_test(NAME ConcatModelTest COMMAND ConcatModelTest)
|
||||||
|
|
|
@ -0,0 +1,147 @@
|
||||||
|
#include <QtTest>
|
||||||
|
|
||||||
|
#include <QQmlEngine>
|
||||||
|
|
||||||
|
#include <StatusQ/functionaggregator.h>
|
||||||
|
#include <TestHelpers/testmodel.h>
|
||||||
|
|
||||||
|
class TestFunctionAggregator : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private:
|
||||||
|
void makeQmlEngineAvailable(QQmlEngine& engine, QObject& obj)
|
||||||
|
{
|
||||||
|
auto jsObj = engine.newQObject(&obj);
|
||||||
|
engine.setObjectOwnership(&obj, QQmlEngine::CppOwnership);
|
||||||
|
Q_UNUSED(jsObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void basicTest() {
|
||||||
|
QQmlEngine engine;
|
||||||
|
FunctionAggregator aggregator;
|
||||||
|
makeQmlEngineAvailable(engine, aggregator);
|
||||||
|
|
||||||
|
auto jsLambda = engine.evaluate("(aggr, val) => [...aggr, val]");
|
||||||
|
QCOMPARE(jsLambda.isError(), false);
|
||||||
|
QCOMPARE(jsLambda.isCallable(), true);
|
||||||
|
|
||||||
|
TestModel sourceModel({
|
||||||
|
{ "chainId", { "12", "13", "1", "321" }},
|
||||||
|
{ "balance", { "4", "3", "5", "5" }}
|
||||||
|
});
|
||||||
|
|
||||||
|
aggregator.setModel(&sourceModel);
|
||||||
|
aggregator.setRoleName("balance");
|
||||||
|
aggregator.setInitialValue(QVariantList());
|
||||||
|
aggregator.setAggregateFunction(jsLambda);
|
||||||
|
|
||||||
|
QVariantList expected{"4", "3", "5", "5"};
|
||||||
|
QCOMPARE(aggregator.value(), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
void typeMismatchTest() {
|
||||||
|
QQmlEngine engine;
|
||||||
|
FunctionAggregator aggregator;
|
||||||
|
makeQmlEngineAvailable(engine, aggregator);
|
||||||
|
|
||||||
|
auto jsLambda = engine.evaluate("(aggr, val) => [...aggr, val]");
|
||||||
|
QCOMPARE(jsLambda.isError(), false);
|
||||||
|
QCOMPARE(jsLambda.isCallable(), true);
|
||||||
|
|
||||||
|
TestModel sourceModel({{ "balance", { "4", "3", "5", "5" }}});
|
||||||
|
|
||||||
|
aggregator.setModel(&sourceModel);
|
||||||
|
aggregator.setRoleName("balance");
|
||||||
|
aggregator.setInitialValue(0);
|
||||||
|
|
||||||
|
QTest::ignoreMessage(QtWarningMsg,
|
||||||
|
"Aggregation calculation failed. Error type: 6");
|
||||||
|
|
||||||
|
aggregator.setAggregateFunction(jsLambda);
|
||||||
|
|
||||||
|
QCOMPARE(aggregator.value(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void roleNameNotFoundTest() {
|
||||||
|
QQmlEngine engine;
|
||||||
|
FunctionAggregator aggregator;
|
||||||
|
makeQmlEngineAvailable(engine, aggregator);
|
||||||
|
|
||||||
|
TestModel sourceModel({{ "balance", { "4", "3", "5", "5" }}});
|
||||||
|
aggregator.setModel(&sourceModel);
|
||||||
|
aggregator.setInitialValue(0);
|
||||||
|
|
||||||
|
QTest::ignoreMessage(QtWarningMsg,
|
||||||
|
"Provided role name does not exist in the current model.");
|
||||||
|
|
||||||
|
aggregator.setRoleName("notExisiting");
|
||||||
|
QCOMPARE(aggregator.value(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void invalidFunctionTest() {
|
||||||
|
FunctionAggregator aggregator;
|
||||||
|
|
||||||
|
QTest::ignoreMessage(QtWarningMsg,
|
||||||
|
"FunctionAggregator::aggregateFunction must be a "
|
||||||
|
"callable object.");
|
||||||
|
aggregator.setAggregateFunction(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
void noJsEngineTest() {
|
||||||
|
QQmlEngine engine;
|
||||||
|
FunctionAggregator aggregator;
|
||||||
|
|
||||||
|
auto jsLambda = engine.evaluate("(aggr) => aggr");
|
||||||
|
QCOMPARE(jsLambda.isError(), false);
|
||||||
|
QCOMPARE(jsLambda.isCallable(), true);
|
||||||
|
|
||||||
|
TestModel sourceModel({
|
||||||
|
{ "balance", { "4", "3", "5", "5" }}
|
||||||
|
});
|
||||||
|
|
||||||
|
aggregator.setModel(&sourceModel);
|
||||||
|
aggregator.setRoleName("balance");
|
||||||
|
aggregator.setInitialValue(0);
|
||||||
|
|
||||||
|
QTest::ignoreMessage(QtWarningMsg,
|
||||||
|
"FunctionAggregator is intended to be used in JS "
|
||||||
|
"environment. QJSEngine must be available.");
|
||||||
|
|
||||||
|
aggregator.setAggregateFunction(jsLambda);
|
||||||
|
QCOMPARE(aggregator.value(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void providingInitialValueIfNotReadyTest() {
|
||||||
|
QQmlEngine engine;
|
||||||
|
FunctionAggregator aggregator;
|
||||||
|
makeQmlEngineAvailable(engine, aggregator);
|
||||||
|
|
||||||
|
auto jsLambda = engine.evaluate("(aggr, val) => aggr + val");
|
||||||
|
QCOMPARE(jsLambda.isError(), false);
|
||||||
|
QCOMPARE(jsLambda.isCallable(), true);
|
||||||
|
|
||||||
|
TestModel sourceModel({
|
||||||
|
{ "chainId", { "12", "13", "1", "321" }},
|
||||||
|
{ "balance", { "4", "3", "5", "5" }}
|
||||||
|
});
|
||||||
|
|
||||||
|
QCOMPARE(aggregator.value(), {});
|
||||||
|
|
||||||
|
aggregator.setInitialValue("-");
|
||||||
|
QCOMPARE(aggregator.value(), "-");
|
||||||
|
|
||||||
|
aggregator.setModel(&sourceModel);
|
||||||
|
QCOMPARE(aggregator.value(), "-");
|
||||||
|
|
||||||
|
aggregator.setRoleName("balance");
|
||||||
|
QCOMPARE(aggregator.value(), "-");
|
||||||
|
|
||||||
|
aggregator.setAggregateFunction(jsLambda);
|
||||||
|
QCOMPARE(aggregator.value(), "-4355");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
QTEST_MAIN(TestFunctionAggregator)
|
||||||
|
#include "tst_FunctionAggregator.moc"
|
Loading…
Reference in New Issue