diff --git a/src/controller/DatasetDetailController.cpp b/src/controller/DatasetDetailController.cpp index 1def781..94355cb 100644 --- a/src/controller/DatasetDetailController.cpp +++ b/src/controller/DatasetDetailController.cpp @@ -6,6 +6,11 @@ namespace geopro::controller { DatasetDetailController::DatasetDetailController(data::IAsyncDatasetRepository& repo, QObject* parent) : QObject(parent), repo_(repo) {} +DatasetDetailController::~DatasetDetailController() { + if (chartLoad_) chartLoad_->abort(); // 退出契约:abort 在飞句柄,不依赖外部析构顺序兜底 + if (gridLoad_) gridLoad_->abort(); +} + void DatasetDetailController::openDataset(const QString& dsId, const QString& ddCode) { if (ddCode != QLatin1String("dd_inversion_data")) { // 首版仅支持 ERT 反演 emit loadFailed(dsId, QStringLiteral("暂不支持该数据类型的预览")); diff --git a/src/controller/DatasetDetailController.hpp b/src/controller/DatasetDetailController.hpp index e55a954..5bb39af 100644 --- a/src/controller/DatasetDetailController.hpp +++ b/src/controller/DatasetDetailController.hpp @@ -33,6 +33,7 @@ public: }; explicit DatasetDetailController(data::IAsyncDatasetRepository& repo, QObject* parent = nullptr); + ~DatasetDetailController() override; // 退出契约(spec §7):abort 在飞句柄,避免迟到信号打到已析构 this public slots: void openDataset(const QString& dsId, const QString& ddCode); void focusDataset(const QString& dsId); diff --git a/tests/controller/test_dataset_detail_controller.cpp b/tests/controller/test_dataset_detail_controller.cpp index 25d85ac..4e21148 100644 --- a/tests/controller/test_dataset_detail_controller.cpp +++ b/tests/controller/test_dataset_detail_controller.cpp @@ -17,6 +17,7 @@ struct StubGridLoad : data::GridLoad { bool aborted = false; void abort() override { aborted = true; } void fireDone() { emit done(data::GridParts{}); } + void fireFailed() { emit failed(QStringLiteral("x")); } }; struct StubAsyncRepo : data::IAsyncDatasetRepository { StubChartLoad* lastChart = nullptr; @@ -79,3 +80,36 @@ TEST(DatasetDetailController, DropsLateSignalFromAbortedLoad) { b->fireDone(); // 当前句柄 → 正常 EXPECT_EQ(spy.count(), 1); } + +TEST(DatasetDetailController, EmitsGridReadyOnDone) { + StubAsyncRepo repo; + controller::DatasetDetailController c(repo); + QSignalSpy spy(&c, &controller::DatasetDetailController::gridReady); + c.loadGridData("ds1", "dd_inversion_data"); + repo.lastGrid->fireDone(); + EXPECT_EQ(spy.count(), 1); +} + +TEST(DatasetDetailController, EmitsLoadFailedOnGridFailed) { + StubAsyncRepo repo; + controller::DatasetDetailController c(repo); + QSignalSpy spy(&c, &controller::DatasetDetailController::loadFailed); + c.loadGridData("ds1", "dd_inversion_data"); + repo.lastGrid->fireFailed(); + EXPECT_EQ(spy.count(), 1); +} + +TEST(DatasetDetailController, DropsLateGridSignalFromAbortedLoad) { + StubAsyncRepo repo; + controller::DatasetDetailController c(repo); + QSignalSpy spy(&c, &controller::DatasetDetailController::gridReady); + c.loadGridData("dsA", "dd_inversion_data"); + StubGridLoad* a = repo.lastGrid; + c.loadGridData("dsB", "dd_inversion_data"); // 替换 → 旧句柄被 abort + StubGridLoad* b = repo.lastGrid; + EXPECT_TRUE(a->aborted); + a->fireDone(); // 旧句柄迟到 → 身份比对丢弃 + EXPECT_EQ(spy.count(), 0); + b->fireDone(); // 当前句柄 → 正常 + EXPECT_EQ(spy.count(), 1); +}