// DownloadTableModel unit tests. Lane GUI. #include #include #include #include #include "models/DownloadTableModel.hpp" using velox::gui::DownloadTableModel; namespace { QJsonObject summary(const QString &id, const QString &state, qint64 size, qint64 done) { return QJsonObject{ {"taskId", id}, {"filename", id + ".iso"}, {"url", "https://example.test/" + id}, {"state", state}, {"sizeBytes", double(size)}, {"downloadedBytes", double(done)}, {"speedBps", 0.0}, {"segments", 1.0}, {"resumable", true}, {"createdAt", "2026-09-10T00:00:00Z"}, }; } } // namespace class TstDownloadTableModel : public QObject { Q_OBJECT private slots: void resetPopulatesRows(); void progressPatchIsNarrowAndDoesNotReset(); void progressForUnknownTaskIsIgnored(); void addThenRemove(); }; void TstDownloadTableModel::resetPopulatesRows() { DownloadTableModel model; model.resetFromJson( QJsonArray{summary("a", "downloading", 1000, 500), summary("b", "paused", 2000, 0)}); QCOMPARE(model.rowCount(), 2); QCOMPARE(model.index(0, DownloadTableModel::ColName).data().toString(), QStringLiteral("a.iso")); QCOMPARE(model.index(0, DownloadTableModel::ColStatus).data().toString(), QStringLiteral("50.0 %")); QCOMPARE(model.index(0, 0).data(DownloadTableModel::ProgressRole).toDouble(), 0.5); } void TstDownloadTableModel::progressPatchIsNarrowAndDoesNotReset() { DownloadTableModel model; model.resetFromJson(QJsonArray{summary("a", "downloading", 1000, 100)}); QSignalSpy dataChangedSpy(&model, &QAbstractItemModel::dataChanged); QSignalSpy resetSpy(&model, &QAbstractItemModel::modelReset); QSignalSpy rowsInsertedSpy(&model, &QAbstractItemModel::rowsInserted); model.applyProgress(QJsonArray{QJsonObject{ {"taskId", "a"}, {"downloadedBytes", 600.0}, {"speedBps", 50.0}, {"etaSeconds", 8.0}}}); QCOMPARE(resetSpy.count(), 0); QCOMPARE(rowsInsertedSpy.count(), 0); QCOMPARE(dataChangedSpy.count(), 1); const auto args = dataChangedSpy.takeFirst(); const auto topLeft = args.at(0).toModelIndex(); const auto bottomRight = args.at(1).toModelIndex(); QCOMPARE(topLeft.row(), 0); QCOMPARE(bottomRight.row(), 0); QCOMPARE(topLeft.column(), int(DownloadTableModel::ColSize)); QCOMPARE(bottomRight.column(), int(DownloadTableModel::ColSpeed)); // The whole row must NOT be in the patch — Name/Queue stay untouched on a tick. QVERIFY(topLeft.column() > int(DownloadTableModel::ColName)); QCOMPARE(model.index(0, DownloadTableModel::ColStatus).data().toString(), QStringLiteral("60.0 %")); } void TstDownloadTableModel::progressForUnknownTaskIsIgnored() { DownloadTableModel model; model.resetFromJson(QJsonArray{summary("a", "downloading", 1000, 100)}); QSignalSpy dataChangedSpy(&model, &QAbstractItemModel::dataChanged); model.applyProgress(QJsonArray{ QJsonObject{{"taskId", "ghost"}, {"downloadedBytes", 999.0}, {"speedBps", 1.0}}}); QCOMPARE(dataChangedSpy.count(), 0); QCOMPARE(model.rowCount(), 1); } void TstDownloadTableModel::addThenRemove() { DownloadTableModel model; model.resetFromJson(QJsonArray{summary("a", "downloading", 1000, 100)}); QSignalSpy insertedSpy(&model, &QAbstractItemModel::rowsInserted); model.applyTaskAdded(summary("c", "connecting", 4000, 0)); QCOMPARE(insertedSpy.count(), 1); QCOMPARE(model.rowCount(), 2); QSignalSpy removedSpy(&model, &QAbstractItemModel::rowsRemoved); model.applyTaskRemoved(QStringLiteral("a")); QCOMPARE(removedSpy.count(), 1); QCOMPARE(model.rowCount(), 1); QCOMPARE(model.index(0, 0).data(DownloadTableModel::TaskIdRole).toString(), QStringLiteral("c")); } QTEST_GUILESS_MAIN(TstDownloadTableModel) #include "tst_downloadtablemodel.moc"