feat(data+controller): ApiDatasetRepository 改异步 + DatasetDetailController abort-and-replace+句柄身份比对+loadStarted(移除 busy_/catch)+ 回灌防护测试
This commit is contained in:
parent
8cdd6679a9
commit
e57985c057
|
|
@ -1,60 +1,62 @@
|
||||||
#include "DatasetDetailController.hpp"
|
#include "DatasetDetailController.hpp"
|
||||||
#include <stdexcept>
|
#include "repo/IAsyncDatasetRepository.hpp"
|
||||||
#include "repo/IDatasetRepository.hpp"
|
#include "api/DatasetLoadHandles.hpp"
|
||||||
namespace geopro::controller {
|
namespace geopro::controller {
|
||||||
|
|
||||||
DatasetDetailController::DatasetDetailController(data::IDatasetRepository& repo, QObject* parent)
|
DatasetDetailController::DatasetDetailController(data::IAsyncDatasetRepository& repo, QObject* parent)
|
||||||
: QObject(parent), repo_(repo) {}
|
: QObject(parent), repo_(repo) {}
|
||||||
|
|
||||||
void DatasetDetailController::openDataset(const QString& dsId, const QString& ddCode) {
|
void DatasetDetailController::openDataset(const QString& dsId, const QString& ddCode) {
|
||||||
if (busy_) return; // 防重入(同步网络期间 QEventLoop 可重入)
|
|
||||||
busy_ = true;
|
|
||||||
if (ddCode != QLatin1String("dd_inversion_data")) { // 首版仅支持 ERT 反演
|
if (ddCode != QLatin1String("dd_inversion_data")) { // 首版仅支持 ERT 反演
|
||||||
busy_ = false;
|
|
||||||
emit loadFailed(dsId, QStringLiteral("暂不支持该数据类型的预览"));
|
emit loadFailed(dsId, QStringLiteral("暂不支持该数据类型的预览"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const std::string id = dsId.toStdString();
|
if (chartLoad_) chartLoad_->abort(); // abort-and-replace
|
||||||
try {
|
data::ChartLoad* load = repo_.loadChartAsync(dsId.toStdString());
|
||||||
|
chartLoad_ = load;
|
||||||
|
emit loadStarted(dsId, LoadPhase::Chart);
|
||||||
|
QObject::connect(load, &data::ChartLoad::done, this,
|
||||||
|
[this, load, dsId, ddCode](const data::ChartParts& parts) {
|
||||||
|
if (load != chartLoad_) return; // §5.0 句柄身份比对:丢弃迟到信号
|
||||||
|
chartLoad_.clear();
|
||||||
ChartData d;
|
ChartData d;
|
||||||
d.dsId = dsId;
|
d.dsId = dsId;
|
||||||
d.ddCode = ddCode;
|
d.ddCode = ddCode;
|
||||||
// 严格对齐原版「原数据」页:只拉 scatter + 散点色阶(type1) 两个。
|
d.scatter = parts.scatter;
|
||||||
// 网格数据(inversion/rows)与异常(queryException)随「网格数据」页按需懒加载。
|
d.scatterScale = parts.scatterScale;
|
||||||
d.scatter = repo_.loadScatter(id);
|
|
||||||
d.scatterScale = repo_.loadScatterColorScale(id);
|
|
||||||
busy_ = false;
|
|
||||||
emit chartReady(d);
|
emit chartReady(d);
|
||||||
} catch (const std::exception& e) {
|
});
|
||||||
busy_ = false;
|
QObject::connect(load, &data::ChartLoad::failed, this,
|
||||||
emit loadFailed(dsId, QString::fromStdString(e.what()));
|
[this, load, dsId](const QString& msg) {
|
||||||
} catch (...) { // 非 std 异常(VTK/Qt)也必须复位 busy_,否则永久拒绝后续加载
|
if (load != chartLoad_) return;
|
||||||
busy_ = false;
|
chartLoad_.clear();
|
||||||
emit loadFailed(dsId, QStringLiteral("未知错误"));
|
emit loadFailed(dsId, msg);
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void DatasetDetailController::loadGridData(const QString& dsId, const QString& ddCode) {
|
void DatasetDetailController::loadGridData(const QString& dsId, const QString& ddCode) {
|
||||||
if (busy_) return; // 防重入(同步网络期间 QEventLoop 可重入)
|
|
||||||
if (ddCode != QLatin1String("dd_inversion_data")) return; // 仅 ERT 反演有网格数据
|
if (ddCode != QLatin1String("dd_inversion_data")) return; // 仅 ERT 反演有网格数据
|
||||||
busy_ = true;
|
if (gridLoad_) gridLoad_->abort(); // abort-and-replace
|
||||||
const std::string id = dsId.toStdString();
|
data::GridLoad* load = repo_.loadGridAsync(dsId.toStdString());
|
||||||
try {
|
gridLoad_ = load;
|
||||||
|
emit loadStarted(dsId, LoadPhase::Grid);
|
||||||
|
QObject::connect(load, &data::GridLoad::done, this,
|
||||||
|
[this, load, dsId](const data::GridParts& parts) {
|
||||||
|
if (load != gridLoad_) return; // §5.0 句柄身份比对:丢弃迟到信号
|
||||||
|
gridLoad_.clear();
|
||||||
GridData d;
|
GridData d;
|
||||||
d.dsId = dsId;
|
d.dsId = dsId;
|
||||||
// 网格数据:rows(服务端网格化,慢) + 色阶 type2 + 异常 queryException。
|
d.grid = parts.grid;
|
||||||
d.grid = repo_.loadGrid(id);
|
d.gridScale = parts.gridScale;
|
||||||
d.gridScale = repo_.loadColorScale(id);
|
d.anomalies = parts.anomalies;
|
||||||
d.anomalies = repo_.loadAnomalies(id);
|
|
||||||
busy_ = false;
|
|
||||||
emit gridReady(d);
|
emit gridReady(d);
|
||||||
} catch (const std::exception& e) {
|
});
|
||||||
busy_ = false;
|
QObject::connect(load, &data::GridLoad::failed, this,
|
||||||
emit loadFailed(dsId, QString::fromStdString(e.what()));
|
[this, load, dsId](const QString& msg) {
|
||||||
} catch (...) { // 非 std 异常(VTK/Qt)也必须复位 busy_,否则永久拒绝后续加载
|
if (load != gridLoad_) return;
|
||||||
busy_ = false;
|
gridLoad_.clear();
|
||||||
emit loadFailed(dsId, QStringLiteral("未知错误"));
|
emit loadFailed(dsId, msg);
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void DatasetDetailController::focusDataset(const QString& dsId) { emit focusRequested(dsId); }
|
void DatasetDetailController::focusDataset(const QString& dsId) { emit focusRequested(dsId); }
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,21 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QPointer>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include "model/Field.hpp"
|
#include "model/Field.hpp"
|
||||||
#include "model/ColorScale.hpp"
|
#include "model/ColorScale.hpp"
|
||||||
#include "model/Anomaly.hpp"
|
#include "model/Anomaly.hpp"
|
||||||
namespace geopro::data { class IDatasetRepository; }
|
namespace geopro::data { class IAsyncDatasetRepository; class ChartLoad; class GridLoad; }
|
||||||
namespace geopro::controller {
|
namespace geopro::controller {
|
||||||
|
|
||||||
// 数据详情编排:单击/双击数据集 → 拉 散点/网格/色阶/异常 → 发信号给详情面板。被动视图。
|
// 数据详情编排:双击/网格页签 → 异步拉 散点/网格/色阶/异常 → 发信号给详情面板。被动视图。
|
||||||
// 仅服务图表,不与 WorkbenchNavController(项目/结构导航)耦合。
|
|
||||||
class DatasetDetailController : public QObject {
|
class DatasetDetailController : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
|
enum class LoadPhase { Chart, Grid };
|
||||||
|
Q_ENUM(LoadPhase)
|
||||||
|
|
||||||
struct ChartData {
|
struct ChartData {
|
||||||
QString dsId, ddCode;
|
QString dsId, ddCode;
|
||||||
geopro::core::ScatterField scatter;
|
geopro::core::ScatterField scatter;
|
||||||
|
|
@ -29,19 +32,20 @@ public:
|
||||||
std::vector<geopro::core::Anomaly> anomalies;
|
std::vector<geopro::core::Anomaly> anomalies;
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit DatasetDetailController(data::IDatasetRepository& repo, QObject* parent = nullptr);
|
explicit DatasetDetailController(data::IAsyncDatasetRepository& repo, QObject* parent = nullptr);
|
||||||
public slots:
|
public slots:
|
||||||
void openDataset(const QString& dsId, const QString& ddCode); // 双击=新建/聚焦页
|
void openDataset(const QString& dsId, const QString& ddCode);
|
||||||
void focusDataset(const QString& dsId); // 单击=聚焦已开页
|
void focusDataset(const QString& dsId);
|
||||||
// 网格数据懒加载:网格页首次激活时调用(rows 服务端网格化 1-4s,故不随 openDataset 拉)。
|
|
||||||
void loadGridData(const QString& dsId, const QString& ddCode);
|
void loadGridData(const QString& dsId, const QString& ddCode);
|
||||||
signals:
|
signals:
|
||||||
|
void loadStarted(const QString& dsId, LoadPhase phase);
|
||||||
void chartReady(const ChartData& data);
|
void chartReady(const ChartData& data);
|
||||||
void gridReady(const GridData& data);
|
void gridReady(const GridData& data);
|
||||||
void focusRequested(const QString& dsId);
|
void focusRequested(const QString& dsId);
|
||||||
void loadFailed(const QString& dsId, const QString& message);
|
void loadFailed(const QString& dsId, const QString& message);
|
||||||
private:
|
private:
|
||||||
data::IDatasetRepository& repo_;
|
data::IAsyncDatasetRepository& repo_;
|
||||||
bool busy_ = false;
|
QPointer<data::ChartLoad> chartLoad_;
|
||||||
|
QPointer<data::GridLoad> gridLoad_;
|
||||||
};
|
};
|
||||||
} // namespace geopro::controller
|
} // namespace geopro::controller
|
||||||
|
|
|
||||||
|
|
@ -1,51 +1,62 @@
|
||||||
#include "api/ApiDatasetRepository.hpp"
|
#include "api/ApiDatasetRepository.hpp"
|
||||||
#include <stdexcept>
|
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include "ApiClient.hpp"
|
#include "ApiClient.hpp"
|
||||||
|
#include "ApiBatch.hpp"
|
||||||
|
#include "api/DatasetLoadHandles.hpp"
|
||||||
#include "dto/DatasetChartDto.hpp"
|
#include "dto/DatasetChartDto.hpp"
|
||||||
|
|
||||||
namespace geopro::data {
|
namespace geopro::data {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
QString enc(const std::string& s) {
|
QString enc(const std::string& s) {
|
||||||
return QString::fromUtf8(QUrl::toPercentEncoding(QString::fromStdString(s)));
|
return QString::fromUtf8(QUrl::toPercentEncoding(QString::fromStdString(s)));
|
||||||
}
|
}
|
||||||
void must(const net::ApiResponse& r, const char* what) {
|
|
||||||
if (r.code != 200) throw std::runtime_error(std::string(what) + " failed: " +
|
// 失败判定(原 must() 口径):业务码 != 200 或传输错误。
|
||||||
(r.msg.isEmpty() ? r.rawError.toStdString() : r.msg.toStdString()));
|
bool isFailure(const geopro::net::ApiResponse& r) {
|
||||||
|
return r.code != 200 || !r.rawError.isEmpty();
|
||||||
}
|
}
|
||||||
geopro::core::ColorScale colorScale(net::ApiClient& api, const std::string& dsId, int type) {
|
|
||||||
QJsonObject body{{"dsObjectId", QString::fromStdString(dsId)}, {"businessCode", ""}, {"type", type}};
|
QJsonObject colorBody(const std::string& dsId, int type) {
|
||||||
const net::ApiResponse r = api.postJson(QStringLiteral("/business/lvl/colorGradation/getDetail"), body);
|
return QJsonObject{{"dsObjectId", QString::fromStdString(dsId)}, {"businessCode", ""}, {"type", type}};
|
||||||
must(r, "colorGradation");
|
|
||||||
return dto::parseColorBar(r.data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
ApiDatasetRepository::ApiDatasetRepository(net::ApiClient& api) : api_(api) {}
|
ApiDatasetRepository::ApiDatasetRepository(net::ApiClient& api) : api_(api) {}
|
||||||
|
|
||||||
geopro::core::Grid ApiDatasetRepository::loadGrid(const std::string& dsId) {
|
ChartLoad* ApiDatasetRepository::loadChartAsync(const std::string& dsId) {
|
||||||
const net::ApiResponse r = api_.get(
|
// index 0 = scatter(GET),index 1 = 散点色阶 type1(POST)
|
||||||
QStringLiteral("/business/dd/ert/inversion/rows/%1").arg(enc(dsId)));
|
QList<net::IApiCall*> calls{
|
||||||
must(r, "inversion/rows");
|
api_.getAsync(QStringLiteral("/business/dd/ert/inversion/getErtRawDataScatterGraph/%1").arg(enc(dsId))),
|
||||||
return dto::parseInversionGrid(r.data);
|
api_.postJsonAsync(QStringLiteral("/business/lvl/colorGradation/getDetail"), colorBody(dsId, 1)),
|
||||||
|
};
|
||||||
|
auto* batch = new net::ApiBatch(calls, &isFailure);
|
||||||
|
return new ApiChartLoad(batch, [](const QList<net::ApiResponse>& r) {
|
||||||
|
ChartParts p;
|
||||||
|
p.scatter = dto::parseScatterGraph(r[0].data);
|
||||||
|
p.scatterScale = dto::parseColorBar(r[1].data);
|
||||||
|
return p;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
geopro::core::ScatterField ApiDatasetRepository::loadScatter(const std::string& dsId) {
|
|
||||||
const net::ApiResponse r = api_.get(
|
GridLoad* ApiDatasetRepository::loadGridAsync(const std::string& dsId) {
|
||||||
QStringLiteral("/business/dd/ert/inversion/getErtRawDataScatterGraph/%1").arg(enc(dsId)));
|
// index 0 = rows(GET,慢),1 = 色阶 type2(POST),2 = 异常(GET)
|
||||||
must(r, "scatterGraph");
|
QList<net::IApiCall*> calls{
|
||||||
return dto::parseScatterGraph(r.data);
|
api_.getAsync(QStringLiteral("/business/dd/ert/inversion/rows/%1").arg(enc(dsId))),
|
||||||
}
|
api_.postJsonAsync(QStringLiteral("/business/lvl/colorGradation/getDetail"), colorBody(dsId, 2)),
|
||||||
geopro::core::ColorScale ApiDatasetRepository::loadColorScale(const std::string& dsId) {
|
api_.getAsync(QStringLiteral("/business/exception/queryException/%1").arg(enc(dsId))),
|
||||||
return colorScale(api_, dsId, 2);
|
};
|
||||||
}
|
auto* batch = new net::ApiBatch(calls, &isFailure);
|
||||||
geopro::core::ColorScale ApiDatasetRepository::loadScatterColorScale(const std::string& dsId) {
|
return new ApiGridLoad(batch, [](const QList<net::ApiResponse>& r) {
|
||||||
return colorScale(api_, dsId, 1);
|
GridParts p;
|
||||||
}
|
p.grid = dto::parseInversionGrid(r[0].data);
|
||||||
std::vector<geopro::core::Anomaly> ApiDatasetRepository::loadAnomalies(const std::string& dsId) {
|
p.gridScale = dto::parseColorBar(r[1].data);
|
||||||
const net::ApiResponse r = api_.get(
|
p.anomalies = dto::parseDatasetAnomalies(r[2].data.value(QStringLiteral("value")).toArray());
|
||||||
QStringLiteral("/business/exception/queryException/%1").arg(enc(dsId)));
|
return p;
|
||||||
must(r, "queryException");
|
});
|
||||||
return dto::parseDatasetAnomalies(r.data.value(QStringLiteral("value")).toArray());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace geopro::data
|
} // namespace geopro::data
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,14 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "repo/IDatasetRepository.hpp"
|
#include "repo/IAsyncDatasetRepository.hpp"
|
||||||
namespace geopro::net { class ApiClient; }
|
namespace geopro::net { class ApiClient; }
|
||||||
namespace geopro::data {
|
namespace geopro::data {
|
||||||
|
|
||||||
// 真实 API 实现 IDatasetRepository(ERT 反演相关)。失败抛 std::runtime_error。
|
// 真实 API 实现 IAsyncDatasetRepository(ERT 反演)。每次加载返回自管理句柄。
|
||||||
class ApiDatasetRepository : public IDatasetRepository {
|
class ApiDatasetRepository : public IAsyncDatasetRepository {
|
||||||
public:
|
public:
|
||||||
explicit ApiDatasetRepository(net::ApiClient& api);
|
explicit ApiDatasetRepository(net::ApiClient& api);
|
||||||
std::vector<GsNode> loadStructure() override { return {}; } // 不经此仓储取结构
|
ChartLoad* loadChartAsync(const std::string& dsId) override;
|
||||||
geopro::core::Grid loadGrid(const std::string& dsId) override;
|
GridLoad* loadGridAsync(const std::string& dsId) override;
|
||||||
geopro::core::ScatterField loadScatter(const std::string& dsId) override;
|
|
||||||
geopro::core::ColorScale loadColorScale(const std::string& dsId) override; // type2 网格
|
|
||||||
geopro::core::ColorScale loadScatterColorScale(const std::string& dsId) override; // type1 原数据
|
|
||||||
std::vector<geopro::core::Anomaly> loadAnomalies(const std::string& dsId) override;
|
|
||||||
private:
|
private:
|
||||||
net::ApiClient& api_;
|
net::ApiClient& api_;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,81 @@
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
#include <QSignalSpy>
|
#include <QSignalSpy>
|
||||||
#include "DatasetDetailController.hpp"
|
#include "DatasetDetailController.hpp"
|
||||||
#include "repo/IDatasetRepository.hpp"
|
#include "repo/IAsyncDatasetRepository.hpp"
|
||||||
|
#include "api/DatasetLoadHandles.hpp"
|
||||||
using namespace geopro;
|
using namespace geopro;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
struct StubRepo : data::IDatasetRepository {
|
// 桩句柄:不声明 Q_OBJECT —— 发射继承自 data::ChartLoad/GridLoad 的信号、override abort。
|
||||||
bool fail = false;
|
struct StubChartLoad : data::ChartLoad {
|
||||||
std::vector<data::GsNode> loadStructure() override { return {}; }
|
bool aborted = false;
|
||||||
core::Grid loadGrid(const std::string&) override { core::Grid g(2,2); g.x={0,1}; g.y={0,1}; return g; }
|
void abort() override { aborted = true; }
|
||||||
// openDataset 现只拉 scatter/scatterScale/anomalies(网格懒加载),失败路径由 loadScatter 抛出触发。
|
void fireDone() { emit done(data::ChartParts{}); }
|
||||||
core::ScatterField loadScatter(const std::string&) override { if (fail) throw std::runtime_error("x"); return {}; }
|
void fireFailed() { emit failed(QStringLiteral("x")); }
|
||||||
core::ColorScale loadColorScale(const std::string&) override { return {}; }
|
};
|
||||||
core::ColorScale loadScatterColorScale(const std::string&) override { return {}; }
|
struct StubGridLoad : data::GridLoad {
|
||||||
std::vector<core::Anomaly> loadAnomalies(const std::string&) override { return {}; }
|
bool aborted = false;
|
||||||
|
void abort() override { aborted = true; }
|
||||||
|
void fireDone() { emit done(data::GridParts{}); }
|
||||||
|
};
|
||||||
|
struct StubAsyncRepo : data::IAsyncDatasetRepository {
|
||||||
|
StubChartLoad* lastChart = nullptr;
|
||||||
|
StubGridLoad* lastGrid = nullptr;
|
||||||
|
data::ChartLoad* loadChartAsync(const std::string&) override {
|
||||||
|
lastChart = new StubChartLoad; return lastChart;
|
||||||
|
}
|
||||||
|
data::GridLoad* loadGridAsync(const std::string&) override {
|
||||||
|
lastGrid = new StubGridLoad; return lastGrid;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
TEST(DatasetDetailController, EmitsChartReadyOnSuccess) {
|
|
||||||
StubRepo repo;
|
TEST(DatasetDetailController, EmitsChartReadyOnDone) {
|
||||||
|
StubAsyncRepo repo;
|
||||||
controller::DatasetDetailController c(repo);
|
controller::DatasetDetailController c(repo);
|
||||||
QSignalSpy spy(&c, &controller::DatasetDetailController::chartReady);
|
QSignalSpy spy(&c, &controller::DatasetDetailController::chartReady);
|
||||||
c.openDataset("ds1", "dd_inversion_data");
|
c.openDataset("ds1", "dd_inversion_data");
|
||||||
|
repo.lastChart->fireDone();
|
||||||
EXPECT_EQ(spy.count(), 1);
|
EXPECT_EQ(spy.count(), 1);
|
||||||
}
|
}
|
||||||
TEST(DatasetDetailController, EmitsLoadFailedOnThrow) {
|
|
||||||
StubRepo repo; repo.fail = true;
|
TEST(DatasetDetailController, EmitsLoadFailedOnFailed) {
|
||||||
|
StubAsyncRepo repo;
|
||||||
controller::DatasetDetailController c(repo);
|
controller::DatasetDetailController c(repo);
|
||||||
QSignalSpy spy(&c, &controller::DatasetDetailController::loadFailed);
|
QSignalSpy spy(&c, &controller::DatasetDetailController::loadFailed);
|
||||||
c.openDataset("ds1", "dd_inversion_data");
|
c.openDataset("ds1", "dd_inversion_data");
|
||||||
|
repo.lastChart->fireFailed();
|
||||||
|
EXPECT_EQ(spy.count(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DatasetDetailController, UnsupportedTypeFailsImmediately) {
|
||||||
|
StubAsyncRepo repo;
|
||||||
|
controller::DatasetDetailController c(repo);
|
||||||
|
QSignalSpy spy(&c, &controller::DatasetDetailController::loadFailed);
|
||||||
|
c.openDataset("ds1", "dd_other");
|
||||||
|
EXPECT_EQ(spy.count(), 1);
|
||||||
|
EXPECT_EQ(repo.lastChart, nullptr); // 未发起加载
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DatasetDetailController, AbortsPreviousOnReopen) {
|
||||||
|
StubAsyncRepo repo;
|
||||||
|
controller::DatasetDetailController c(repo);
|
||||||
|
c.openDataset("dsA", "dd_inversion_data");
|
||||||
|
StubChartLoad* a = repo.lastChart;
|
||||||
|
c.openDataset("dsB", "dd_inversion_data"); // 替换
|
||||||
|
EXPECT_TRUE(a->aborted); // 旧句柄被 abort
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DatasetDetailController, DropsLateSignalFromAbortedLoad) {
|
||||||
|
StubAsyncRepo repo;
|
||||||
|
controller::DatasetDetailController c(repo);
|
||||||
|
QSignalSpy spy(&c, &controller::DatasetDetailController::chartReady);
|
||||||
|
c.openDataset("dsA", "dd_inversion_data");
|
||||||
|
StubChartLoad* a = repo.lastChart;
|
||||||
|
c.openDataset("dsB", "dd_inversion_data");
|
||||||
|
StubChartLoad* b = repo.lastChart;
|
||||||
|
a->fireDone(); // 旧句柄迟到 → 身份比对丢弃
|
||||||
|
EXPECT_EQ(spy.count(), 0);
|
||||||
|
b->fireDone(); // 当前句柄 → 正常
|
||||||
EXPECT_EQ(spy.count(), 1);
|
EXPECT_EQ(spy.count(), 1);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue