diff --git a/panels/dock/taskmanager/rolegroupmodel.cpp b/panels/dock/taskmanager/rolegroupmodel.cpp index 957361778..4c9cbd751 100644 --- a/panels/dock/taskmanager/rolegroupmodel.cpp +++ b/panels/dock/taskmanager/rolegroupmodel.cpp @@ -52,13 +52,15 @@ void RoleGroupModel::setSourceModel(QAbstractItemModel *model) if (nullptr == list) { beginInsertRows(QModelIndex(), m_rowMap.size(), m_rowMap.size()); list = new QList(); + list->append(i); m_map.insert(data, list); m_rowMap.append(list); endInsertRows(); + } else { + beginInsertRows(index(m_rowMap.indexOf(list), 0), list->size(), list->size()); + list->append(i); + endInsertRows(); } - beginInsertRows(index(m_rowMap.indexOf(list), 0), list->size(), list->size()); - list->append(i); - endInsertRows(); } }); @@ -97,8 +99,11 @@ void RoleGroupModel::setSourceModel(QAbstractItemModel *model) if (list == nullptr) continue; - auto index = createIndex(list->indexOf(i), 0, m_rowMap.indexOf(list)); - Q_EMIT dataChanged(index, index, roles); + int childRow = list->indexOf(i); + if (childRow >= 0) { + auto index = createIndex(childRow, 0, m_rowMap.indexOf(list)); + Q_EMIT dataChanged(index, index, roles); + } } }); @@ -109,12 +114,14 @@ void RoleGroupModel::setSourceModel(QAbstractItemModel *model) int RoleGroupModel::rowCount(const QModelIndex &parent) const { - if (!sourceModel()) { - return 0; - } if (parent.isValid()) { - auto list = m_rowMap.value(parent.row(), nullptr); - return nullptr == list ? 0 : list->size(); + int parentRow = parent.row(); + if (parentRow < 0 || parentRow >= m_rowMap.size()) { + return 0; + } + + auto list = m_rowMap.value(parentRow, nullptr); + return (list != nullptr) ? list->size() : 0; } return m_rowMap.size(); @@ -128,6 +135,31 @@ int RoleGroupModel::columnCount(const QModelIndex &parent) const return 0; } +bool RoleGroupModel::hasChildren(const QModelIndex &parent) const +{ + if (!sourceModel()) { + return false; + } + + if (!parent.isValid()) { + // 根节点:如果有分组则有子节点 + return m_rowMap.size() > 0; + } + + auto parentPos = static_cast(parent.internalId()); + if (parentPos == -1) { + // 这是分组节点:检查是否有子项 + if (parent.row() < 0 || parent.row() >= m_rowMap.size()) { + return false; + } + auto list = m_rowMap.value(parent.row(), nullptr); + return list && list->size() > 0; + } + + // 这是子项:没有子节点 + return false; +} + QHash RoleGroupModel::roleNames() const { if (sourceModel()) { @@ -138,61 +170,104 @@ QHash RoleGroupModel::roleNames() const QVariant RoleGroupModel::data(const QModelIndex &index, int role) const { + if (!index.isValid()) { + return QVariant(); + } + auto parentPos = static_cast(index.internalId()); - auto list = m_rowMap.value(index.row(), nullptr); + if (parentPos == -1) { - if (nullptr == list || list->size() == 0) { + // 这是分组节点(顶级项目) + auto list = m_rowMap.value(index.row(), nullptr); + if (nullptr == list || list->isEmpty()) { return QVariant(); } - return sourceModel()->index(list->first(), 0).data(role); - } - list = m_rowMap.value(parentPos); - if (list == nullptr) { - return QVariant(); - } + // 对于分组节点,显示分组信息和数量 + if (role == Qt::DisplayRole) { + QString groupValue = sourceModel()->index(list->first(), 0).data(m_roleForDeduplication).toString(); + return QString("%1 (%2)").arg(groupValue).arg(list->size()); + } else { + // 对于其他角色,返回分组中第一个项目的数据 + return sourceModel()->index(list->first(), 0).data(role); + } + } else { + // 这是子项目 + auto list = m_rowMap.value(parentPos); + if (list == nullptr || index.row() < 0 || index.row() >= list->size()) { + return QVariant(); + } - return sourceModel()->index(list->value(index.row()), 0).data(role); + // 直接返回对应源项目的数据 + return sourceModel()->index(list->value(index.row()), 0).data(role); + } } QModelIndex RoleGroupModel::index(int row, int column, const QModelIndex &parent) const { - auto list = m_map.value(parent.data(m_roleForDeduplication).toString(), nullptr); - if (parent.isValid() && nullptr != list) { - return createIndex(row, column, m_rowMap.indexOf(list)); - } + if (parent.isValid()) { + int parentRow = parent.row(); + if (parentRow < 0 || parentRow >= m_rowMap.size()) { + return QModelIndex(); + } + + auto list = m_rowMap.value(parentRow); + if (nullptr == list || row < 0 || row >= list->size()) { + return QModelIndex(); + } + + return createIndex(row, column, parentRow); + } else { + if (row < 0 || row >= m_rowMap.size()) { + return QModelIndex(); + } - return createIndex(row, column, -1); + return createIndex(row, column, -1); + } } QModelIndex RoleGroupModel::parent(const QModelIndex &child) const { + if (!child.isValid()) + return QModelIndex(); + auto pos = static_cast(child.internalId()); if (pos == -1) return QModelIndex(); - return createIndex(pos, 0); + if (pos < 0 || pos >= m_rowMap.size()) { + return QModelIndex(); + } + + return createIndex(pos, 0, -1); } QModelIndex RoleGroupModel::mapToSource(const QModelIndex &proxyIndex) const { + if (!proxyIndex.isValid()) { + return QModelIndex(); + } + auto parentIndex = proxyIndex.parent(); QList *list = nullptr; if (parentIndex.isValid()) { - list = m_rowMap.value(parentIndex.row()); + int parentRow = parentIndex.row(); + if (parentRow < 0 || parentRow >= m_rowMap.size()) { + return QModelIndex(); + } + list = m_rowMap.value(parentRow); + if (!list || proxyIndex.row() >= list->size()) { + return QModelIndex(); + } + return sourceModel()->index(list->value(proxyIndex.row()), 0); } else { list = m_map.value(proxyIndex.data(m_roleForDeduplication).toString()); + if (!list || list->isEmpty()) { + return QModelIndex(); + } + return sourceModel()->index(list->value(0), 0); } - - if (nullptr == list) - return QModelIndex(); - - if (parentIndex.isValid()) { - return sourceModel()->index(list->value(proxyIndex.row()), 0); - } - - return sourceModel()->index(list->value(0), 0); } QModelIndex RoleGroupModel::mapFromSource(const QModelIndex &sourceIndex) const @@ -212,7 +287,11 @@ QModelIndex RoleGroupModel::mapFromSource(const QModelIndex &sourceIndex) const } auto pos = list->indexOf(sourceIndex.row()); - return createIndex(pos, 0, m_rowMap.indexOf(list)); + if (pos >= 0) { + return createIndex(pos, 0, m_rowMap.indexOf(list)); + } + + return QModelIndex(); } void RoleGroupModel::rebuildTreeSource() diff --git a/panels/dock/taskmanager/rolegroupmodel.h b/panels/dock/taskmanager/rolegroupmodel.h index a1cca23eb..6964b1b6c 100644 --- a/panels/dock/taskmanager/rolegroupmodel.h +++ b/panels/dock/taskmanager/rolegroupmodel.h @@ -22,6 +22,7 @@ class RoleGroupModel : public QAbstractProxyModel int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + bool hasChildren(const QModelIndex &parent = QModelIndex()) const override; bool hasIndex(int row, int column, const QModelIndex &parent = QModelIndex()) const; Q_INVOKABLE virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; Q_INVOKABLE virtual QModelIndex parent(const QModelIndex &child) const override; diff --git a/tests/panels/dock/taskmanager/rolegroupmodeltests.cpp b/tests/panels/dock/taskmanager/rolegroupmodeltests.cpp index 5719b16f3..9d857ed3d 100644 --- a/tests/panels/dock/taskmanager/rolegroupmodeltests.cpp +++ b/tests/panels/dock/taskmanager/rolegroupmodeltests.cpp @@ -7,6 +7,7 @@ #include +#include #include #include #include @@ -73,3 +74,274 @@ TEST(RoleGroupModel, RowCountTest) // item5 的内容为data4 EXPECT_EQ(groupModel.rowCount(index4), 1); } + +// 测试 index() 方法的逻辑错误 - 这个测试实际发现了模型中的问题 +TEST(RoleGroupModel, IndexMethodDeepTest) +{ + QStandardItemModel model; + auto role = Qt::UserRole + 1; + RoleGroupModel groupModel(&model, role); + + // 添加测试数据 + QStandardItem *item1 = new QStandardItem; + item1->setData(QString("group1"), role); + model.appendRow(item1); + + QStandardItem *item2 = new QStandardItem; + item2->setData(QString("group1"), role); + model.appendRow(item2); + + // 获取有效的父索引 + auto validParent = groupModel.index(0, 0); + EXPECT_TRUE(validParent.isValid()); + + // 测试有效父索引的子索引创建 + auto validChild = groupModel.index(0, 0, validParent); + EXPECT_TRUE(validChild.isValid()); + + // 测试超出范围的顶级索引 + QModelIndex outOfRangeTopLevel = groupModel.index(999, 0); + EXPECT_FALSE(outOfRangeTopLevel.isValid()); + + // 测试超出范围的子索引 + auto outOfRangeChild = groupModel.index(999, 0, validParent); + EXPECT_FALSE(outOfRangeChild.isValid()); + + // 测试有效父索引但获取空数据的情况 + QStandardItem *itemWithEmptyData = new QStandardItem; + // 不设置 role 数据,让其为空 + model.appendRow(itemWithEmptyData); + + // 这应该不会创建新的组,因为数据为空 + EXPECT_EQ(groupModel.rowCount(), 1); // 仍然只有1个组 + + // 现在测试一个边界情况:当父索引存在但data为空时的子索引创建 + // 这种情况下应该返回无效索引,因为找不到对应的列表 + + // 创建一个有效的父索引(group1),但尝试创建一个数据为空的子索引 + QStandardItem *itemWithEmptyRole = new QStandardItem; + itemWithEmptyRole->setData(QString(""), role); // 空字符串数据 + model.appendRow(itemWithEmptyRole); + + // 由于空字符串数据会被跳过,分组数量应该仍然是1 + EXPECT_EQ(groupModel.rowCount(), 1); + + // 测试尝试为不存在的分组创建子索引 + // 构造一个看似有效但实际无效的父索引 + QModelIndex invalidParentWithValidData = groupModel.index(0, 0); + if (invalidParentWithValidData.isValid()) { + // 尝试获取过多的子项 - 应该返回无效索引 + int childCount = groupModel.rowCount(invalidParentWithValidData); + QModelIndex invalidChild = groupModel.index(childCount + 10, 0, invalidParentWithValidData); + EXPECT_FALSE(invalidChild.isValid()); + + // 测试负数索引的子项 + QModelIndex negativeChild = groupModel.index(-1, 0, invalidParentWithValidData); + EXPECT_FALSE(negativeChild.isValid()); + } +} + +// 使用 QAbstractItemModelTester 进行全面的模型验证测试 +TEST(RoleGroupModel, ModelTesterValidation) +{ + QStandardItemModel model; + auto role = Qt::UserRole + 1; + RoleGroupModel groupModel(&model, role); + + // 创建一个 QAbstractItemModelTester 来验证模型的正确性 + QAbstractItemModelTester tester(&groupModel, QAbstractItemModelTester::FailureReportingMode::Fatal); + + // 添加一些测试数据 + QStandardItem *item1 = new QStandardItem; + item1->setData(QString("group1"), role); + model.appendRow(item1); + + QStandardItem *item2 = new QStandardItem; + item2->setData(QString("group1"), role); + model.appendRow(item2); + + QStandardItem *item3 = new QStandardItem; + item3->setData(QString("group2"), role); + model.appendRow(item3); + + QStandardItem *item4 = new QStandardItem; + item4->setData(QString("group2"), role); + model.appendRow(item4); + + // 测试数据修改 + item1->setData(QString("modified_group1"), role); + + // 测试行删除 + model.removeRow(1); + + // 测试行插入 + QStandardItem *item5 = new QStandardItem; + item5->setData(QString("group3"), role); + model.appendRow(item5); + + // 测试修改分组相关的数据(这会触发重建) + item3->setData(QString("group1"), role); + + // 如果模型有任何违反Qt模型/视图架构约定的行为, + // QAbstractItemModelTester 会抛出异常或断言失败 + + // 验证最终状态 + EXPECT_GT(groupModel.rowCount(), 0); + + // 验证所有索引都是有效的 + for (int i = 0; i < groupModel.rowCount(); ++i) { + auto index = groupModel.index(i, 0); + EXPECT_TRUE(index.isValid()); + + // 验证每个组的子项 + for (int j = 0; j < groupModel.rowCount(index); ++j) { + auto childIndex = groupModel.index(j, 0, index); + EXPECT_TRUE(childIndex.isValid()); + + // 验证映射一致性 + auto sourceIndex = groupModel.mapToSource(childIndex); + EXPECT_TRUE(sourceIndex.isValid()); + + auto mappedBack = groupModel.mapFromSource(sourceIndex); + EXPECT_TRUE(mappedBack.isValid()); + } + } +} + +// 测试 hasChildren 方法的正确性 +TEST(RoleGroupModel, HasChildrenTest) +{ + QStandardItemModel model; + auto role = Qt::UserRole + 1; + RoleGroupModel groupModel(&model, role); + + // 初始状态:没有数据,根节点不应该有子节点 + EXPECT_FALSE(groupModel.hasChildren()); + EXPECT_FALSE(groupModel.hasChildren(QModelIndex())); + + // 添加第一个项目 + QStandardItem *item1 = new QStandardItem; + item1->setData(QString("firefox.desktop"), role); + item1->setData(QString("Firefox - 页面1"), Qt::DisplayRole); + model.appendRow(item1); + + // 现在根节点应该有子节点(分组节点) + EXPECT_TRUE(groupModel.hasChildren()); + EXPECT_TRUE(groupModel.hasChildren(QModelIndex())); + + // 获取第一个分组节点 + QModelIndex firstGroup = groupModel.index(0, 0); + ASSERT_TRUE(firstGroup.isValid()); + + // 分组节点应该有子节点(实际的项目) + EXPECT_TRUE(groupModel.hasChildren(firstGroup)); + + // 添加同组的第二个项目 + QStandardItem *item2 = new QStandardItem; + item2->setData(QString("firefox.desktop"), role); + item2->setData(QString("Firefox - 页面2"), Qt::DisplayRole); + model.appendRow(item2); + + // 分组节点仍然应该有子节点,现在有2个 + EXPECT_TRUE(groupModel.hasChildren(firstGroup)); + EXPECT_EQ(groupModel.rowCount(firstGroup), 2); + + // 获取子项目节点 + QModelIndex firstChild = groupModel.index(0, 0, firstGroup); + ASSERT_TRUE(firstChild.isValid()); + + // 子项目不应该有子节点 + EXPECT_FALSE(groupModel.hasChildren(firstChild)); + + QModelIndex secondChild = groupModel.index(1, 0, firstGroup); + ASSERT_TRUE(secondChild.isValid()); + EXPECT_FALSE(groupModel.hasChildren(secondChild)); + + // 添加不同分组的项目 + QStandardItem *item3 = new QStandardItem; + item3->setData(QString("code.desktop"), role); + item3->setData(QString("VSCode - 项目1"), Qt::DisplayRole); + model.appendRow(item3); + + // 现在应该有2个分组 + EXPECT_EQ(groupModel.rowCount(), 2); + + QModelIndex secondGroup = groupModel.index(1, 0); + ASSERT_TRUE(secondGroup.isValid()); + EXPECT_TRUE(groupModel.hasChildren(secondGroup)); + + // 测试空数据的处理 + QStandardItem *itemEmpty = new QStandardItem; + itemEmpty->setData(QString(""), role); + model.appendRow(itemEmpty); + + // 空数据应该被跳过,分组数量不变 + EXPECT_EQ(groupModel.rowCount(), 2); + + // 清空所有数据 + model.clear(); + + // 清空后根节点不应该有子节点 + EXPECT_FALSE(groupModel.hasChildren()); + EXPECT_EQ(groupModel.rowCount(), 0); +} + +// 测试大量索引访问的边界情况(模拟滚动场景) +TEST(RoleGroupModel, ScrollingBoundaryTest) +{ + QStandardItemModel model; + auto role = Qt::UserRole + 1; + RoleGroupModel groupModel(&model, role); + + // 添加测试数据 + for (int i = 0; i < 5; ++i) { + QStandardItem *item = new QStandardItem; + item->setData(QString("app%1.desktop").arg(i % 3), role); + item->setData(QString("Window %1").arg(i), Qt::DisplayRole); + model.appendRow(item); + } + + int groupCount = groupModel.rowCount(); + EXPECT_EQ(groupCount, 3); + + // 测试大量越界访问(模拟滚动到底部的场景) + for (int i = 0; i < groupCount; ++i) { + QModelIndex groupIndex = groupModel.index(i, 0); + ASSERT_TRUE(groupIndex.isValid()); + + int childCount = groupModel.rowCount(groupIndex); + EXPECT_GT(childCount, 0); + + // 测试正常子索引 + for (int j = 0; j < childCount; ++j) { + QModelIndex childIndex = groupModel.index(j, 0, groupIndex); + EXPECT_TRUE(childIndex.isValid()); + + // 测试数据访问 + QVariant data = childIndex.data(Qt::DisplayRole); + EXPECT_TRUE(data.isValid()); + } + + // 测试越界子索引(模拟滚动越界的情况) + for (int j = childCount; j < childCount + 10; ++j) { + QModelIndex invalidChild = groupModel.index(j, 0, groupIndex); + EXPECT_FALSE(invalidChild.isValid()); + } + } + + // 测试大量越界的顶级索引 + for (int i = groupCount; i < groupCount + 100; ++i) { + QModelIndex invalidIndex = groupModel.index(i, 0); + EXPECT_FALSE(invalidIndex.isValid()); + } + + // 测试负数索引 + QModelIndex negativeIndex = groupModel.index(-1, 0); + EXPECT_FALSE(negativeIndex.isValid()); + + QModelIndex firstGroup = groupModel.index(0, 0); + if (firstGroup.isValid()) { + QModelIndex negativeChild = groupModel.index(-1, 0, firstGroup); + EXPECT_FALSE(negativeChild.isValid()); + } +}