diff --git a/tests/auto/corelib/platform/CMakeLists.txt b/tests/auto/corelib/platform/CMakeLists.txt index 3455736eabd..51bc13e56e9 100644 --- a/tests/auto/corelib/platform/CMakeLists.txt +++ b/tests/auto/corelib/platform/CMakeLists.txt @@ -4,6 +4,7 @@ if(ANDROID) add_subdirectory(android) add_subdirectory(android_appless) + add_subdirectory(androiditemmodel) endif() if(WIN32) add_subdirectory(windows) diff --git a/tests/auto/corelib/platform/androiditemmodel/CMakeLists.txt b/tests/auto/corelib/platform/androiditemmodel/CMakeLists.txt new file mode 100644 index 00000000000..e4abb3eabb5 --- /dev/null +++ b/tests/auto/corelib/platform/androiditemmodel/CMakeLists.txt @@ -0,0 +1,24 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_androiditemmodel Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_androiditemmodel LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_androiditemmodel + SOURCES + tst_androiditemmodel.cpp + LIBRARIES + Qt::Gui + Qt::CorePrivate +) + +set_property(TARGET tst_androiditemmodel APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR + ${CMAKE_CURRENT_SOURCE_DIR}/testdata +) diff --git a/tests/auto/corelib/platform/androiditemmodel/testdata/src/org/qtproject/qt/android/tests/TestModel.java b/tests/auto/corelib/platform/androiditemmodel/testdata/src/org/qtproject/qt/android/tests/TestModel.java new file mode 100644 index 00000000000..5af3390c244 --- /dev/null +++ b/tests/auto/corelib/platform/androiditemmodel/testdata/src/org/qtproject/qt/android/tests/TestModel.java @@ -0,0 +1,134 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +package org.qtproject.qt.android.tests; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import org.qtproject.qt.android.QtAbstractItemModel; +import org.qtproject.qt.android.QtModelIndex; + +public class TestModel extends QtAbstractItemModel +{ + int m_rows = 0; + int m_cols = 0; + + @Override + public int columnCount(QtModelIndex parent) + { + return parent.isValid() ? 0 : m_cols; + } + + @Override + public Object data(QtModelIndex index, int role) + { + int r = index.row(); + int c = index.column(); + if (r < 0 || c < 0 || c > m_cols || r > m_rows) + return null; + + switch (role) { + case 0: + return String.format("r%d/c%d", r, c); + case 1: + return new Boolean(((r + c) % 2) == 0); + case 2: + return new Integer((c << 8) + r); + case 3: + return new Double((r + 1.0) / (c + 1.0)); + case 4: + return new Long((c << 8) * (r << 8)); + default: + return null; + } + } + + @Override + public QtModelIndex index(int row, int column, QtModelIndex parent) + { + return hasIndex(row, column, parent) ? createIndex(row, column, 0) : new QtModelIndex(); + } + + @Override + public QtModelIndex parent(QtModelIndex qtModelIndex) + { + return new QtModelIndex(); + } + + @Override + public int rowCount(QtModelIndex parent) + { + return parent.isValid() ? 0 : m_rows; + } + + @Override + public HashMap roleNames() + { + final HashMap roles = new HashMap(); + roles.put(0, "stringRole"); + roles.put(1, "booleanRole"); + roles.put(2, "integerRole"); + roles.put(3, "doubleRole"); + roles.put(4, "longRole"); + return roles; + } + + @Override + public boolean canFetchMore(QtModelIndex parent) + { + return !parent.isValid() && (m_rows < 30); + } + + @Override + public void fetchMore(QtModelIndex parent) + { + if (!canFetchMore(parent)) + return; + int toAdd = Math.min(10, 30 - rowCount(parent)); + beginInsertRows(new QtModelIndex(), m_rows, m_rows + toAdd - 1); + m_rows += toAdd; + endInsertRows(); + } + + public void addRow() + { + beginInsertRows(new QtModelIndex(), m_rows, m_rows); + m_rows++; + endInsertRows(); + } + + public void removeRow() + { + if (m_rows == 0) + return; + beginRemoveRows(new QtModelIndex(), 0, 0); + m_rows--; + endRemoveRows(); + } + + public void addCol() + { + beginInsertColumns(new QtModelIndex(), m_cols, m_cols); + m_cols++; + endInsertColumns(); + } + + public void removeCol() + { + if (m_cols == 0) + return; + beginRemoveColumns(new QtModelIndex(), 0, 0); + m_cols--; + endRemoveColumns(); + } + + public void reset() + { + beginResetModel(); + m_rows = 0; + m_cols = 0; + endResetModel(); + } +} diff --git a/tests/auto/corelib/platform/androiditemmodel/tst_androiditemmodel.cpp b/tests/auto/corelib/platform/androiditemmodel/tst_androiditemmodel.cpp new file mode 100644 index 00000000000..507dcedb01b --- /dev/null +++ b/tests/auto/corelib/platform/androiditemmodel/tst_androiditemmodel.cpp @@ -0,0 +1,182 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +using namespace Qt::Literals; + +Q_DECLARE_JNI_CLASS(JTestModel, "org/qtproject/qt/android/tests/TestModel") + +class tst_AndroidItemModel : public QObject +{ + Q_OBJECT + JTestModel jModel; + QAbstractItemModel *qProxy; + void resetModel(); + +private slots: + void initTestCase(); + void cleanup(); + void addRow(); + void addColumn(); + void removeRow(); + void removeColumn(); + void roleNames(); + void fetchMore(); + void hasIndex(); + void data(); +}; + +void tst_AndroidItemModel::initTestCase() +{ + QVERIFY(jModel.isValid()); + qProxy = QAndroidItemModelProxy::createNativeProxy(jModel); + QVERIFY(qProxy); +} + +void tst_AndroidItemModel::cleanup() +{ + resetModel(); +} + +void tst_AndroidItemModel::addRow() +{ + const int rowsBefore = qProxy->rowCount(); + jModel.callMethod("addRow"); + QCOMPARE_EQ(qProxy->rowCount(), rowsBefore + 1); +} + +void tst_AndroidItemModel::addColumn() +{ + const int columnsBefore = qProxy->columnCount(); + jModel.callMethod("addCol"); + QCOMPARE_EQ(qProxy->columnCount(), columnsBefore + 1); +} + +void tst_AndroidItemModel::removeRow() +{ + jModel.callMethod("addRow"); + jModel.callMethod("addRow"); + QCOMPARE_EQ(qProxy->rowCount(), 2); + jModel.callMethod("removeRow"); + QCOMPARE_EQ(qProxy->rowCount(), 1); + jModel.callMethod("removeRow"); + QCOMPARE_EQ(qProxy->rowCount(), 0); +} + +void tst_AndroidItemModel::removeColumn() +{ + jModel.callMethod("addCol"); + jModel.callMethod("addCol"); + QCOMPARE_EQ(qProxy->columnCount(), 2); + jModel.callMethod("removeCol"); + QCOMPARE_EQ(qProxy->columnCount(), 1); + jModel.callMethod("removeCol"); + QCOMPARE_EQ(qProxy->columnCount(), 0); +} + +void tst_AndroidItemModel::roleNames() +{ + const static QHash expectedRoles = { { 0, "stringRole" }, + { 1, "booleanRole" }, + { 2, "integerRole" }, + { 3, "doubleRole" }, + { 4, "longRole" } }; + QCOMPARE(qProxy->roleNames(), expectedRoles); +} + +void tst_AndroidItemModel::fetchMore() +{ + // In the Java TestModel : + // canFetchMore() returns true when row count is less than 30 + // fetchMore() adds 10 rows at most, or the remaining until row count is 30 + QVERIFY(qProxy->canFetchMore(QModelIndex())); + qProxy->fetchMore(QModelIndex()); + QCOMPARE_EQ(qProxy->rowCount(), 10); + QVERIFY(qProxy->canFetchMore(QModelIndex())); + qProxy->fetchMore(QModelIndex()); + QCOMPARE_EQ(qProxy->rowCount(), 20); + jModel.callMethod("addRow"); + QVERIFY(qProxy->canFetchMore(QModelIndex())); + qProxy->fetchMore(QModelIndex()); + QCOMPARE_EQ(qProxy->rowCount(), 30); + QVERIFY(!qProxy->canFetchMore(QModelIndex())); +} + +void tst_AndroidItemModel::hasIndex() +{ + // fetchMore() adds 10 rows + qProxy->fetchMore(QModelIndex()); + jModel.callMethod("addCol"); + jModel.callMethod("addCol"); + + for (int r = 0; r < 10; ++r) { + for (int c = 0; c < 2; ++c) { + QVERIFY(qProxy->hasIndex(r, c)); + } + } +} + +void tst_AndroidItemModel::data() +{ + const static QHash roleToType = { { 0, QMetaType::QString }, + { 1, QMetaType::Bool }, + { 2, QMetaType::Int }, + { 3, QMetaType::Double }, + { 4, QMetaType::Long } }; + QVERIFY(qProxy->canFetchMore(QModelIndex())); + qProxy->fetchMore(QModelIndex()); + QCOMPARE_EQ(qProxy->rowCount(), 10); + jModel.callMethod("addCol"); + jModel.callMethod("addCol"); + jModel.callMethod("addCol"); + + for (int r = 0; r < 10; ++r) { + for (int c = 0; c < 3; ++c) { + QModelIndex index = qProxy->index(r, c); + for (int role : roleToType.keys()) { + const QVariant data = qProxy->data(index, role); + QCOMPARE_EQ(data.typeId(), roleToType[role]); + switch (role) { + case 0: + QCOMPARE(data.toString(), + "r%1/c%2"_L1.arg(QString::number(r), QString::number(c))); + break; + case 1: + QCOMPARE(data.toBool(), ((r + c) % 2) == 0); + break; + case 2: + QCOMPARE(data.toInt(), (c << 8) + r); + break; + case 3: + QVERIFY(qFuzzyCompare(data.toDouble(), (1.0 + r) / (1.0 + c))); + break; + case 4: + QCOMPARE(data.toULongLong(), ((c << 8) * (r << 8))); + break; + } + } + } + } +} + +void tst_AndroidItemModel::resetModel() +{ + jModel.callMethod("reset"); + QCOMPARE_EQ(qProxy->rowCount(), 0); + QCOMPARE_EQ(qProxy->columnCount(), 0); +} + +#include "tst_androiditemmodel.moc" + +QTEST_MAIN(tst_AndroidItemModel)