mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-06-10 14:02:47 +00:00
feat(automation): add view.select dispatcher handler + tests
This commit is contained in:
@@ -101,6 +101,13 @@ public:
|
||||
// Throws AutomationError(kErrLoadFailed) when nothing loads. Header stays wx-free:
|
||||
// the concrete LoadStrategy is chosen inside WxUiBackend, not exposed here.
|
||||
virtual int open_files(const std::vector<std::string>& paths) = 0;
|
||||
|
||||
// Select a top-level view/tab by stable name (e.g. "prepare", "preview", "home",
|
||||
// "device", "project", "calibration", "multi_device") on the GUI thread. Returns
|
||||
// the resulting tab index. Throws AutomationError(kErrNotFound) when the named
|
||||
// view is unknown or not available in the current layout. The wx-specific
|
||||
// name->tab mapping lives in WxUiBackend/MainFrame, not here.
|
||||
virtual int select_view(const std::string& view) = 0;
|
||||
};
|
||||
|
||||
}}} // namespace Slic3r::GUI::Automation
|
||||
|
||||
@@ -154,6 +154,21 @@ std::vector<std::string> parse_paths(const nlohmann::json& params) {
|
||||
throw AutomationError(kInvalidParams, "'paths' is empty");
|
||||
return out;
|
||||
}
|
||||
|
||||
// "view" must be a non-empty string naming a top-level tab. The name->tab mapping
|
||||
// itself lives in the wx backend, so this only validates shape; throws kInvalidParams
|
||||
// when view is missing, not a string, or empty.
|
||||
std::string parse_view(const nlohmann::json& params) {
|
||||
if (!params.is_object() || !params.contains("view"))
|
||||
throw AutomationError(kInvalidParams, "view.select requires 'view'");
|
||||
const auto& v = params.at("view");
|
||||
if (!v.is_string())
|
||||
throw AutomationError(kInvalidParams, "'view' must be a string");
|
||||
std::string name = v.get<std::string>();
|
||||
if (name.empty())
|
||||
throw AutomationError(kInvalidParams, "'view' is empty");
|
||||
return name;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
@@ -196,7 +211,7 @@ nlohmann::json JsonRpcDispatcher::m_version(const nlohmann::json&) {
|
||||
{"capabilities", nlohmann::json::array({
|
||||
"tree.dump","tree.find","widget.get","input.click","input.type",
|
||||
"input.key","sync.wait_for","app.state","screenshot.window",
|
||||
"file.open" })} };
|
||||
"file.open","view.select" })} };
|
||||
}
|
||||
|
||||
nlohmann::json JsonRpcDispatcher::dispatch(const nlohmann::json& request) {
|
||||
@@ -222,6 +237,7 @@ nlohmann::json JsonRpcDispatcher::dispatch(const nlohmann::json& request) {
|
||||
if (method == "app.state") return make_result(id, m_app_state(params));
|
||||
if (method == "screenshot.window") return make_result(id, m_screenshot_window(params));
|
||||
if (method == "file.open") return make_result(id, m_file_open(params));
|
||||
if (method == "view.select") return make_result(id, m_view_select(params));
|
||||
return make_error(id, kMethodNotFound, "unknown method: " + method);
|
||||
} catch (const AutomationError& e) {
|
||||
return make_error(id, e.code, e.what());
|
||||
@@ -377,4 +393,10 @@ nlohmann::json JsonRpcDispatcher::m_file_open(const nlohmann::json& params) {
|
||||
return { {"ok", true}, {"loaded", loaded} };
|
||||
}
|
||||
|
||||
nlohmann::json JsonRpcDispatcher::m_view_select(const nlohmann::json& params) {
|
||||
const std::string view = parse_view(params);
|
||||
const int index = m_backend.select_view(view);
|
||||
return { {"ok", true}, {"view", view}, {"index", index} };
|
||||
}
|
||||
|
||||
}}} // namespace
|
||||
|
||||
@@ -49,6 +49,7 @@ private:
|
||||
nlohmann::json m_app_state(const nlohmann::json& params);
|
||||
nlohmann::json m_screenshot_window(const nlohmann::json& params);
|
||||
nlohmann::json m_file_open(const nlohmann::json& params);
|
||||
nlohmann::json m_view_select(const nlohmann::json& params);
|
||||
|
||||
// Resolve a unique, actionable (enabled+visible) node from params["target"].
|
||||
// Throws kErrNotFound (missing/ambiguous) or kErrNotActionable (disabled/hidden).
|
||||
|
||||
@@ -20,6 +20,7 @@ public:
|
||||
std::vector<std::vector<KeyChord>> sent_keys;
|
||||
int screenshot_window_count = 0;
|
||||
std::vector<std::vector<std::string>> opened_paths; // paths of each open_files()
|
||||
std::vector<std::string> selected_views; // view of each select_view()
|
||||
|
||||
// Canned outputs (set by tests).
|
||||
UiNode tree; // default tree for dump_tree
|
||||
@@ -28,6 +29,8 @@ public:
|
||||
bool click_result = true;
|
||||
int open_return_count = 0; // value open_files() returns
|
||||
bool open_should_fail = false; // when true, open_files() throws kErrLoadFailed
|
||||
int select_view_index = 0; // value select_view() returns
|
||||
bool select_view_should_fail = false; // when true, select_view() throws kErrNotFound
|
||||
|
||||
// Optional: per-call tree provider (overrides `tree` when set).
|
||||
std::function<UiNode(int /*call_index*/)> tree_provider;
|
||||
@@ -59,6 +62,12 @@ public:
|
||||
throw AutomationError(kErrLoadFailed, "mock load failed");
|
||||
return open_return_count;
|
||||
}
|
||||
int select_view(const std::string& view) override {
|
||||
selected_views.push_back(view);
|
||||
if (select_view_should_fail)
|
||||
throw AutomationError(kErrNotFound, "mock view not found");
|
||||
return select_view_index;
|
||||
}
|
||||
};
|
||||
|
||||
}}} // namespace
|
||||
|
||||
@@ -267,3 +267,62 @@ TEST_CASE("automation.version capabilities include file.open", "[automation][rpc
|
||||
for (const auto& c : caps) if (c == "file.open") found = true;
|
||||
CHECK(found);
|
||||
}
|
||||
|
||||
TEST_CASE("view.select routes the view name to the backend", "[automation][rpc]") {
|
||||
MockUiBackend mock;
|
||||
mock.select_view_index = 1;
|
||||
JsonRpcDispatcher d(mock);
|
||||
const json resp = d.dispatch({{"jsonrpc","2.0"},{"id",1},{"method","view.select"},
|
||||
{"params",{{"view","prepare"}}}});
|
||||
CHECK(resp.at("result").at("ok") == true);
|
||||
CHECK(resp.at("result").at("view") == "prepare");
|
||||
CHECK(resp.at("result").at("index") == 1);
|
||||
REQUIRE(mock.selected_views.size() == 1);
|
||||
CHECK(mock.selected_views[0] == "prepare");
|
||||
}
|
||||
|
||||
TEST_CASE("view.select with missing view -> invalid params", "[automation][rpc]") {
|
||||
MockUiBackend mock;
|
||||
JsonRpcDispatcher d(mock);
|
||||
const json resp = d.dispatch({{"jsonrpc","2.0"},{"id",2},{"method","view.select"},
|
||||
{"params", json::object()}});
|
||||
CHECK(resp.at("error").at("code") == kInvalidParams);
|
||||
CHECK(mock.selected_views.empty());
|
||||
}
|
||||
|
||||
TEST_CASE("view.select with non-string view -> invalid params", "[automation][rpc]") {
|
||||
MockUiBackend mock;
|
||||
JsonRpcDispatcher d(mock);
|
||||
const json resp = d.dispatch({{"jsonrpc","2.0"},{"id",3},{"method","view.select"},
|
||||
{"params",{{"view", 42}}}});
|
||||
CHECK(resp.at("error").at("code") == kInvalidParams);
|
||||
CHECK(mock.selected_views.empty());
|
||||
}
|
||||
|
||||
TEST_CASE("view.select with empty view -> invalid params", "[automation][rpc]") {
|
||||
MockUiBackend mock;
|
||||
JsonRpcDispatcher d(mock);
|
||||
const json resp = d.dispatch({{"jsonrpc","2.0"},{"id",4},{"method","view.select"},
|
||||
{"params",{{"view",""}}}});
|
||||
CHECK(resp.at("error").at("code") == kInvalidParams);
|
||||
CHECK(mock.selected_views.empty());
|
||||
}
|
||||
|
||||
TEST_CASE("view.select unavailable view -> not found (1001)", "[automation][rpc]") {
|
||||
MockUiBackend mock;
|
||||
mock.select_view_should_fail = true;
|
||||
JsonRpcDispatcher d(mock);
|
||||
const json resp = d.dispatch({{"jsonrpc","2.0"},{"id",5},{"method","view.select"},
|
||||
{"params",{{"view","calibration"}}}});
|
||||
CHECK(resp.at("error").at("code") == kErrNotFound);
|
||||
}
|
||||
|
||||
TEST_CASE("automation.version capabilities include view.select", "[automation][rpc]") {
|
||||
MockUiBackend mock;
|
||||
JsonRpcDispatcher d(mock);
|
||||
const json resp = d.dispatch({{"jsonrpc","2.0"},{"id",6},{"method","automation.version"}});
|
||||
const auto& caps = resp.at("result").at("capabilities");
|
||||
bool found = false;
|
||||
for (const auto& c : caps) if (c == "view.select") found = true;
|
||||
CHECK(found);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user