#include #include #include #include "DatasetDetailController.hpp" #include "DatasetDetailTab.hpp" #include "IDatasetChartStrategy.hpp" #include "repo/IAsyncDatasetRepository.hpp" #include "api/DatasetLoadHandles.hpp" using namespace geopro; namespace { // 反演策略桩:散点(非 lazy) + 网格(lazy) 两页签。 struct InversionStrategy : controller::IDatasetChartStrategy { std::string ddCode() const override { return "dd_inversion_data"; } std::vector tabs() const override { return { {QStringLiteral("原数据"), controller::ViewKind::Scatter, QStringLiteral("inversion.scatter"), /*lazy*/ false, /*paginated*/ false}, {QStringLiteral("网格数据"), controller::ViewKind::FilledContour, QStringLiteral("inversion.grid"), /*lazy*/ true, /*paginated*/ false}, }; } }; // 单页签策略桩(仅一个非 lazy 页签)。 struct SingleTabStrategy : controller::IDatasetChartStrategy { std::string ddCode() const override { return "dd_scatter_only"; } std::vector tabs() const override { return {{QStringLiteral("散点"), controller::ViewKind::Scatter, QStringLiteral("scatter.only"), /*lazy*/ false, /*paginated*/ false}}; } }; // 注册了反演策略的注册表(多数用例复用)。 controller::ChartStrategyRegistry makeInversionRegistry() { controller::ChartStrategyRegistry reg; reg.add(std::make_unique()); return reg; } // 桩详情句柄:不声明 Q_OBJECT —— 发射继承自 data::DetailLoad 的信号、override abort。 struct StubDetailLoad : data::DetailLoad { bool aborted = false; void abort() override { aborted = true; } void fireDone() { emit done(QVariant{}); } void fireFailed() { emit failed(QStringLiteral("x")); } }; // 桩仓储:每个 loaderKey 都造一个新句柄,记录最近一个用于 fire。 struct StubAsyncRepo : data::IAsyncDatasetRepository { StubDetailLoad* last = nullptr; int lastPageNo = 0, lastPageSize = 0; // 记录最近一次分页参数(验证 loadTabPaged 透传) data::DetailLoad* loadAsync(const std::string&, const std::string&, int pageNo, int pageSize) override { lastPageNo = pageNo; lastPageSize = pageSize; last = new StubDetailLoad; return last; } }; } // namespace TEST(DatasetDetailController, OpenEmitsDatasetOpenedWithTabsAndDdCode) { StubAsyncRepo repo; auto reg = makeInversionRegistry(); controller::DatasetDetailController c(repo, reg); QSignalSpy spy(&c, &controller::DatasetDetailController::datasetOpened); c.openDataset("ds1", "dd_inversion_data", "名称"); ASSERT_EQ(spy.count(), 1); const auto args = spy.takeFirst(); EXPECT_EQ(args.at(0).toString(), QStringLiteral("ds1")); EXPECT_EQ(args.at(1).toString(), QStringLiteral("dd_inversion_data")); // ddCode 透传 EXPECT_EQ(args.at(2).toString(), QStringLiteral("名称")); } TEST(DatasetDetailController, OpenLoadsNonLazyTabsOnly) { StubAsyncRepo repo; auto reg = makeInversionRegistry(); controller::DatasetDetailController c(repo, reg); QSignalSpy started(&c, &controller::DatasetDetailController::tabLoadStarted); c.openDataset("ds1", "dd_inversion_data"); // 反演:tab0 非 lazy 自动加载,tab1 lazy 不加载。 ASSERT_EQ(started.count(), 1); EXPECT_EQ(started.takeFirst().at(1).toInt(), 0); } TEST(DatasetDetailController, EmitsTabReadyOnDone) { StubAsyncRepo repo; auto reg = makeInversionRegistry(); controller::DatasetDetailController c(repo, reg); QSignalSpy spy(&c, &controller::DatasetDetailController::tabReady); c.openDataset("ds1", "dd_inversion_data"); repo.last->fireDone(); ASSERT_EQ(spy.count(), 1); EXPECT_EQ(spy.takeFirst().at(1).toInt(), 0); // tabIndex 0 } TEST(DatasetDetailController, EmitsLoadFailedOnFailed) { StubAsyncRepo repo; auto reg = makeInversionRegistry(); controller::DatasetDetailController c(repo, reg); QSignalSpy spy(&c, &controller::DatasetDetailController::loadFailed); c.openDataset("ds1", "dd_inversion_data"); repo.last->fireFailed(); EXPECT_EQ(spy.count(), 1); } TEST(DatasetDetailController, UnsupportedTypeFailsImmediately) { StubAsyncRepo repo; auto reg = makeInversionRegistry(); // 注册了反演,但未注册 dd_other controller::DatasetDetailController c(repo, reg); QSignalSpy spy(&c, &controller::DatasetDetailController::loadFailed); c.openDataset("ds1", "dd_other"); EXPECT_EQ(spy.count(), 1); EXPECT_EQ(repo.last, nullptr); // 未发起加载 } // 空注册表 → 任意 ddCode 都不支持 → loadFailed,不发起加载。 TEST(DatasetDetailController, EmptyRegistryFailsAnyType) { StubAsyncRepo repo; controller::ChartStrategyRegistry reg; // 空 controller::DatasetDetailController c(repo, reg); QSignalSpy spy(&c, &controller::DatasetDetailController::loadFailed); c.openDataset("ds1", "dd_inversion_data"); EXPECT_EQ(spy.count(), 1); EXPECT_EQ(repo.last, nullptr); } // 越界 tabIndex → loadTab 静默不加载。 TEST(DatasetDetailController, LoadTabOutOfRangeDoesNothing) { StubAsyncRepo repo; auto reg = makeInversionRegistry(); controller::DatasetDetailController c(repo, reg); c.loadTab("ds1", "dd_inversion_data", 99); EXPECT_EQ(repo.last, nullptr); } // lazy 页签经 loadTab 触发后加载。 TEST(DatasetDetailController, LoadTabLazyTabStartsLoad) { StubAsyncRepo repo; auto reg = makeInversionRegistry(); controller::DatasetDetailController c(repo, reg); QSignalSpy ready(&c, &controller::DatasetDetailController::tabReady); c.loadTab("ds1", "dd_inversion_data", 1); // lazy 网格页 ASSERT_NE(repo.last, nullptr); repo.last->fireDone(); ASSERT_EQ(ready.count(), 1); EXPECT_EQ(ready.takeFirst().at(1).toInt(), 1); } // loadTab(非分页)用默认页参(pageNo=1/pageSize=0);loadTabPaged 透传分页参数到仓储。 TEST(DatasetDetailController, LoadTabUsesDefaultPageParams) { StubAsyncRepo repo; auto reg = makeInversionRegistry(); controller::DatasetDetailController c(repo, reg); c.loadTab("ds1", "dd_inversion_data", 0); EXPECT_EQ(repo.lastPageNo, 1); EXPECT_EQ(repo.lastPageSize, 0); } TEST(DatasetDetailController, LoadTabPagedThreadsPageParams) { StubAsyncRepo repo; auto reg = makeInversionRegistry(); controller::DatasetDetailController c(repo, reg); c.loadTabPaged("ds1", "dd_inversion_data", 0, 3, 100); ASSERT_NE(repo.last, nullptr); EXPECT_EQ(repo.lastPageNo, 3); EXPECT_EQ(repo.lastPageSize, 100); } TEST(DatasetDetailController, AbortsPreviousOnSameSlotReload) { StubAsyncRepo repo; auto reg = makeInversionRegistry(); controller::DatasetDetailController c(repo, reg); c.loadTab("dsA", "dd_inversion_data", 0); StubDetailLoad* a = repo.last; c.loadTab("dsB", "dd_inversion_data", 0); // 同槽位替换 EXPECT_TRUE(a->aborted); // 旧句柄被 abort } TEST(DatasetDetailController, DropsLateSignalFromAbortedLoad) { StubAsyncRepo repo; auto reg = makeInversionRegistry(); controller::DatasetDetailController c(repo, reg); QSignalSpy spy(&c, &controller::DatasetDetailController::tabReady); c.loadTab("dsA", "dd_inversion_data", 0); StubDetailLoad* a = repo.last; c.loadTab("dsB", "dd_inversion_data", 0); // 同槽位替换 StubDetailLoad* b = repo.last; a->fireDone(); // 旧句柄迟到 → 身份比对丢弃 EXPECT_EQ(spy.count(), 0); b->fireDone(); // 当前句柄 → 正常 EXPECT_EQ(spy.count(), 1); } TEST(DatasetDetailController, FocusEmitsFocusRequested) { StubAsyncRepo repo; auto reg = makeInversionRegistry(); controller::DatasetDetailController c(repo, reg); QSignalSpy spy(&c, &controller::DatasetDetailController::focusRequested); c.focusDataset("ds1"); ASSERT_EQ(spy.count(), 1); EXPECT_EQ(spy.takeFirst().at(0).toString(), QStringLiteral("ds1")); }