gui: finish Add URL -> File Info -> Progress dialog flow
Wires up the three dialogs from build order step 5 (docs/03-gui-spec.md §§2-3) and the MainWindow slots that were declared but never implemented: - AddUrlDialog: clipboard prefill is the explicit path docs/06 R2 calls for (no passive monitoring, not advertised). - FileInfoDialog: async download.probe never blocks the UI; ends by calling download.add itself (Now / Later / Add to Queue). buildSpec() is a pure static so the optional-field-omission logic is unit-testable without touching a widget. - ProgressDialog: non-modal, WA_DeleteOnClose, driven by the taskProgress/ taskStateChanged signals RpcClient already re-broadcasts; download.get seeds state once for a dialog opened mid-transfer. Hosts SegmentBarsWidget and SpeedGraphWidget. MainWindow: openAddUrlDialog/openPropertiesForSelection/showTableContextMenu now have bodies; category.list/queue.list responses are cached so File Info can populate its category combo and queue menu without a second round trip. The row context menu covers what already exists (Resume/Pause/Stop/Delete/ Properties) and deliberately leaves out Open/Open With/Move-Rename/ Redownload/Add to Queue — those need dialogs later build-order steps haven't reached yet. util/Format.hpp: pulled the bytes/rate/eta formatting out of MainWindow and DownloadTableModel once the dialogs wanted the same strings a third time. Verified end-to-end against a running mockd (category.list/queue.list, download.probe, download.add, download.get, and live event.task.progress/ event.task.state) under ASan+UBSan: all three dialogs render correctly against real fixture data and the flow runs clean with no leaks or sanitizer reports. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01NSCdCWFXBTSBBK3MzWtJiC
This commit is contained in:
@@ -70,6 +70,18 @@ set_tests_properties(gui_speedgraphwidget PROPERTIES
|
||||
LABELS "gui"
|
||||
ENVIRONMENT "QT_QPA_PLATFORM=offscreen")
|
||||
|
||||
# tst_fileinfodialog — FileInfoDialog::buildSpec, the pure DownloadSpec builder.
|
||||
# Red when: an empty optional field starts getting sent instead of omitted, or the
|
||||
# queueId stops being dropped for a startMode other than "queue".
|
||||
add_executable(tst_fileinfodialog tst_fileinfodialog.cpp)
|
||||
target_compile_features(tst_fileinfodialog PRIVATE cxx_std_23)
|
||||
target_compile_options(tst_fileinfodialog PRIVATE -Wall -Wextra -Wpedantic -Werror)
|
||||
target_link_libraries(tst_fileinfodialog PRIVATE velox-gui-lib Qt6::Widgets Qt6::Test)
|
||||
add_test(NAME gui_fileinfodialog COMMAND tst_fileinfodialog)
|
||||
set_tests_properties(gui_fileinfodialog PROPERTIES
|
||||
LABELS "gui"
|
||||
ENVIRONMENT "QT_QPA_PLATFORM=offscreen")
|
||||
|
||||
# gui_no_download_logic — CLAUDE.md §3 as an executable check, not a hope.
|
||||
# Red when: a download-logic token (curl, raw pwrite, sqlite, QSqlDatabase) appears
|
||||
# under gui/src. grep exits 0 only when it finds a match, so a hit fails the test.
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
// FileInfoDialog::buildSpec unit tests. Lane GUI.
|
||||
//
|
||||
// Pure function, no widgets involved — see the comment on the declaration. Red when an
|
||||
// optional field starts being sent empty instead of omitted, or queueId leaks into a spec
|
||||
// whose startMode isn't "queue".
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QtTest>
|
||||
|
||||
#include "dialogs/FileInfoDialog.hpp"
|
||||
|
||||
using velox::gui::FileInfoDialog;
|
||||
|
||||
class TstFileInfoDialog : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void requiredFieldsAlwaysPresent();
|
||||
void emptyOptionalFieldsAreOmitted();
|
||||
void fullSpecIncludesEveryField();
|
||||
void queueIdOnlySentForQueueStartMode();
|
||||
};
|
||||
|
||||
void TstFileInfoDialog::requiredFieldsAlwaysPresent() {
|
||||
const QJsonObject spec =
|
||||
FileInfoDialog::buildSpec("https://example.test/file.bin", "", "", "", "", 0, 0, "now", "");
|
||||
QCOMPARE(spec.value("url").toString(), QStringLiteral("https://example.test/file.bin"));
|
||||
QCOMPARE(spec.value("startMode").toString(), QStringLiteral("now"));
|
||||
}
|
||||
|
||||
void TstFileInfoDialog::emptyOptionalFieldsAreOmitted() {
|
||||
const QJsonObject spec =
|
||||
FileInfoDialog::buildSpec("https://example.test/file.bin", "", "", "", "", 0, 0, "now", "");
|
||||
QVERIFY(!spec.contains("filename"));
|
||||
QVERIFY(!spec.contains("saveDir"));
|
||||
QVERIFY(!spec.contains("categoryId"));
|
||||
QVERIFY(!spec.contains("description"));
|
||||
QVERIFY(!spec.contains("segments"));
|
||||
QVERIFY(!spec.contains("bufferBytes"));
|
||||
QVERIFY(!spec.contains("queueId"));
|
||||
}
|
||||
|
||||
void TstFileInfoDialog::fullSpecIncludesEveryField() {
|
||||
const QJsonObject spec =
|
||||
FileInfoDialog::buildSpec("https://example.test/file.bin", "file.bin", "/home/x/Downloads",
|
||||
"cat1", "a note", 8, 4 * 1024 * 1024, "later", "");
|
||||
QCOMPARE(spec.value("filename").toString(), QStringLiteral("file.bin"));
|
||||
QCOMPARE(spec.value("saveDir").toString(), QStringLiteral("/home/x/Downloads"));
|
||||
QCOMPARE(spec.value("categoryId").toString(), QStringLiteral("cat1"));
|
||||
QCOMPARE(spec.value("description").toString(), QStringLiteral("a note"));
|
||||
QCOMPARE(spec.value("segments").toInt(), 8);
|
||||
QCOMPARE(spec.value("bufferBytes").toDouble(), static_cast<double>(4 * 1024 * 1024));
|
||||
}
|
||||
|
||||
void TstFileInfoDialog::queueIdOnlySentForQueueStartMode() {
|
||||
const QJsonObject queued = FileInfoDialog::buildSpec("https://example.test/file.bin", "", "",
|
||||
"", "", 0, 0, "queue", "q1");
|
||||
QCOMPARE(queued.value("queueId").toString(), QStringLiteral("q1"));
|
||||
|
||||
const QJsonObject notQueued = FileInfoDialog::buildSpec("https://example.test/file.bin", "", "",
|
||||
"", "", 0, 0, "now", "q1");
|
||||
QVERIFY(!notQueued.contains("queueId"));
|
||||
}
|
||||
|
||||
QTEST_MAIN(TstFileInfoDialog)
|
||||
#include "tst_fileinfodialog.moc"
|
||||
Reference in New Issue
Block a user