QtQuick: Add delegateModelAccess test for QQuickTreeView

QQuickTreeView can't handle array or object models and therefore
we don't need to take special measures to propagate changes to arrays
back out of the view.

Change-Id: I0d0bc2ea04b5862c1a2eac71cb9f5195c5b07af6
Reviewed-by: Sami Shalayel <sami.shalayel@qt.io>
This commit is contained in:
Ulf Hermann 2025-09-12 14:49:39 +02:00
parent 6bc664ac8c
commit de368f8f4d
2 changed files with 223 additions and 0 deletions

View File

@ -0,0 +1,92 @@
import QtQuick
import Test
Item {
width: 100
height: 100
property alias treeView: root
TreeView {
id: root
width: 100
height: 100
property Component typedDelegate: Item {
implicitWidth: 10
implicitHeight: 10
required property QtObject model
required property real a
property real immediateX: a
property real modelX: model.a
function writeImmediate() {
a = 1;
}
function writeThroughModel() {
model.a = 3;
}
}
property Component untypedDelegate: Item {
implicitWidth: 10
implicitHeight: 10
property real immediateX: a
property real modelX: model.a
function writeImmediate() {
a = 1;
}
function writeThroughModel() {
model.a = 3;
}
}
property ListModel singularModel: ListModel {
ListElement {
a: 11
}
}
property ListModel listModel: ListModel {
ListElement {
a: 11
y: 12
}
}
function aAt0() : real {
switch (modelIndex) {
case Model.Singular:
case Model.List:
return model.get(0).a
}
return -1;
}
property int modelIndex: Model.None
property int delegateIndex: Delegate.None
model: {
switch (modelIndex) {
case Model.Singular: return singularModel
case Model.List: return listModel
}
return undefined;
}
delegate: {
switch (delegateIndex) {
case Delegate.Untyped: return untypedDelegate
case Delegate.Typed: return typedDelegate
}
return null
}
}
}

View File

@ -98,6 +98,8 @@ private slots:
void editUsingEditTriggers();
void editOnNonEditableCell_data();
void editOnNonEditableCell();
void delegateModelAccess_data();
void delegateModelAccess();
};
tst_qquicktreeview::tst_qquicktreeview()
@ -1643,6 +1645,135 @@ void tst_qquicktreeview::editOnNonEditableCell()
}
}
namespace Model {
Q_NAMESPACE
QML_ELEMENT
enum Kind : qint8
{
None = -1,
Singular,
List,
};
Q_ENUM_NS(Kind)
}
namespace Delegate {
Q_NAMESPACE
QML_ELEMENT
enum Kind : qint8
{
None = -1,
Untyped,
Typed
};
Q_ENUM_NS(Kind)
}
template<typename Enum>
const char *enumKey(Enum value) {
const QMetaObject *mo = qt_getEnumMetaObject(value);
const QMetaEnum metaEnum = mo->enumerator(mo->indexOfEnumerator(qt_getEnumName(value)));
return metaEnum.valueToKey(value);
}
void tst_qquicktreeview::delegateModelAccess_data()
{
QTest::addColumn<QQmlDelegateModel::DelegateModelAccess>("access");
QTest::addColumn<Model::Kind>("modelKind");
QTest::addColumn<Delegate::Kind>("delegateKind");
using Access = QQmlDelegateModel::DelegateModelAccess;
for (auto access : { Access::Qt5ReadWrite, Access::ReadOnly, Access::ReadWrite }) {
for (auto model : { Model::Singular, Model::List }) {
for (auto delegate : { Delegate::Untyped, Delegate::Typed }) {
QTest::addRow("%s-%s-%s", enumKey(access), enumKey(model), enumKey(delegate))
<< access << model << delegate;
}
}
}
}
void tst_qquicktreeview::delegateModelAccess()
{
static const bool initialized = []() {
qmlRegisterNamespaceAndRevisions(&Model::staticMetaObject, "Test", 1);
qmlRegisterNamespaceAndRevisions(&Delegate::staticMetaObject, "Test", 1);
return true;
}();
QVERIFY(initialized);
QFETCH(QQmlDelegateModel::DelegateModelAccess, access);
QFETCH(Model::Kind, modelKind);
QFETCH(Delegate::Kind, delegateKind);
const QUrl url = testFileUrl("delegateModelAccess.qml");
LOAD_TREEVIEW("delegateModelAccess.qml");
QSignalSpy modelChangedSpy(treeView, &QQuickTreeView::modelChanged);
if (access == QQmlDelegateModel::ReadOnly) {
const QRegularExpression message(
url.toString() + ":[0-9]+: TypeError: Cannot assign to read-only property \"a\"");
QTest::ignoreMessage(QtWarningMsg, message);
if (delegateKind == Delegate::Untyped)
QTest::ignoreMessage(QtWarningMsg, message);
}
treeView->setProperty("delegateModelAccess", access);
treeView->setProperty("modelIndex", modelKind);
treeView->setProperty("delegateIndex", delegateKind);
WAIT_UNTIL_POLISHED;
QCOMPARE(QQuickTreeViewPrivate::get(treeView)->loadedItems.size(), 1);
QObject *delegate = QQuickTreeViewPrivate::get(treeView)->loadedItems.begin().value()->item;
QVERIFY(delegate);
const bool modelWritable = access != QQmlDelegateModel::ReadOnly;
const bool immediateWritable = (delegateKind == Delegate::Untyped)
? access != QQmlDelegateModel::ReadOnly
: access == QQmlDelegateModel::ReadWrite;
double expected = 11;
// Initial setting of the model, signals one update. Beyond that, no updates are signaled
// because we only accept QAIM and the model object doesn't change.
QCOMPARE(modelChangedSpy.count(), 1);
QCOMPARE(delegate->property("immediateX").toDouble(), expected);
QCOMPARE(delegate->property("modelX").toDouble(), expected);
if (modelWritable)
expected = 3;
QMetaObject::invokeMethod(delegate, "writeThroughModel");
QCOMPARE(delegate->property("immediateX").toDouble(), expected);
QCOMPARE(delegate->property("modelX").toDouble(), expected);
QCOMPARE(modelChangedSpy.count(), 1);
double aAt0 = -1;
QMetaObject::invokeMethod(treeView, "aAt0", Q_RETURN_ARG(double, aAt0));
QCOMPARE(aAt0, expected);
if (immediateWritable)
expected = 1;
QMetaObject::invokeMethod(delegate, "writeImmediate");
// Writes to required properties always succeed, but might not be propagated to the model
QCOMPARE(delegate->property("immediateX").toDouble(),
delegateKind == Delegate::Untyped ? expected : 1);
QCOMPARE(delegate->property("modelX").toDouble(), expected);
QCOMPARE(modelChangedSpy.count(), 1);
aAt0 = -1;
QMetaObject::invokeMethod(treeView, "aAt0", Q_RETURN_ARG(double, aAt0));
QCOMPARE(aAt0, expected);
}
QTEST_MAIN(tst_qquicktreeview)
#include "tst_qquicktreeview.moc"