From 97917db59369b66ef412a87f90cfdbda3ad55225 Mon Sep 17 00:00:00 2001 From: Higor Prado Date: Sat, 2 May 2026 20:25:43 -0300 Subject: [PATCH] test(hyprland): expose dispatch internals for unit tests Move buildLuaDispatch and isLuaProtocol from private to protected/public so IPCTestHelper can access them. Add 7 tests covering all buildLuaDispatch branches, dispatch error path, and isLuaProtocol cache behavior. --- include/modules/hyprland/backend.hpp | 14 ++--- test/hyprland/backend.cpp | 79 ++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 7 deletions(-) diff --git a/include/modules/hyprland/backend.hpp b/include/modules/hyprland/backend.hpp index 4e16299b..7c6369da 100644 --- a/include/modules/hyprland/backend.hpp +++ b/include/modules/hyprland/backend.hpp @@ -40,22 +40,22 @@ class IPC { /// (legacy text or Lua-based) depending on the running Hyprland version. static std::string dispatch(const std::string& dispatcher, const std::string& arg); + /// Build a Lua-format dispatch command string. + static std::string buildLuaDispatch(const std::string& dispatcher, const std::string& arg); + protected: static std::filesystem::path socketFolder_; - private: - void socketListener(); - void parseIPC(const std::string&); - /// Detect whether the running Hyprland uses the Lua-based IPC protocol. /// Returns true for Hyprland >= 0.54 (Lua config), false for older versions. static bool isLuaProtocol(); - /// Build a Lua-format dispatch command string. - static std::string buildLuaDispatch(const std::string& dispatcher, const std::string& arg); - static std::optional s_luaProtocolDetected_; // cached detection result + private: + void socketListener(); + void parseIPC(const std::string&); + std::thread ipcThread_; std::mutex callbackMutex_; std::mutex socketMutex_; diff --git a/test/hyprland/backend.cpp b/test/hyprland/backend.cpp index ccc2da65..62d23ae4 100644 --- a/test/hyprland/backend.cpp +++ b/test/hyprland/backend.cpp @@ -15,6 +15,10 @@ namespace { class IPCTestHelper : public hyprland::IPC { public: static void resetSocketFolder() { socketFolder_.clear(); } + static void resetLuaProtocolDetection() { s_luaProtocolDetected_.reset(); } + static void setLuaProtocolDetected(bool value) { s_luaProtocolDetected_ = value; } + using hyprland::IPC::buildLuaDispatch; + using hyprland::IPC::isLuaProtocol; }; std::size_t countOpenFds() { @@ -133,3 +137,78 @@ TEST_CASE("getSocket1Reply failure paths do not leak fds", "[getSocket1Reply][fd REQUIRE(after_connect_failures == baseline); } #endif + +// --- Tests for new Lua IPC dispatch functions --- + +TEST_CASE("buildLuaDispatch workspace", "[buildLuaDispatch]") { + SECTION("numeric workspace") { + auto result = IPCTestHelper::buildLuaDispatch("workspace", "1"); + REQUIRE(result == "/dispatch hl.dsp.focus({ workspace = \"1\" })"); + } + SECTION("named workspace") { + auto result = IPCTestHelper::buildLuaDispatch("workspace", "name:term"); + REQUIRE(result == "/dispatch hl.dsp.focus({ workspace = \"name:term\" })"); + } + SECTION("relative workspace") { + auto result = IPCTestHelper::buildLuaDispatch("workspace", "e+1"); + REQUIRE(result == "/dispatch hl.dsp.focus({ workspace = \"e+1\" })"); + } +} + +TEST_CASE("buildLuaDispatch focusworkspaceoncurrentmonitor", "[buildLuaDispatch]") { + auto result = + IPCTestHelper::buildLuaDispatch("focusworkspaceoncurrentmonitor", "3"); + REQUIRE( + result == + "/dispatch hl.dsp.focus({ workspace = \"3\", monitor = \"current\" })"); +} + +TEST_CASE("buildLuaDispatch togglespecialworkspace", "[buildLuaDispatch]") { + SECTION("with name") { + auto result = + IPCTestHelper::buildLuaDispatch("togglespecialworkspace", "scratchpad"); + REQUIRE(result == + "/dispatch hl.dsp.workspace.toggle_special(\"scratchpad\")"); + } + SECTION("empty arg") { + auto result = + IPCTestHelper::buildLuaDispatch("togglespecialworkspace", ""); + REQUIRE(result == "/dispatch hl.dsp.workspace.toggle_special()"); + } +} + +TEST_CASE("buildLuaDispatch unknown dispatcher fallback", "[buildLuaDispatch]") { + auto result = + IPCTestHelper::buildLuaDispatch("unknown_dispatcher", "some_arg"); + REQUIRE(result == + "/dispatch hl.dsp.unknown_dispatcher(\"some_arg\")"); +} + +TEST_CASE("dispatch throws when Hyprland is not running", "[dispatch]") { + unsetenv("HYPRLAND_INSTANCE_SIGNATURE"); + IPCTestHelper::resetSocketFolder(); + IPCTestHelper::resetLuaProtocolDetection(); + + CHECK_THROWS(hyprland::IPC::dispatch("workspace", "1")); +} + +TEST_CASE("isLuaProtocol uses cached value and avoids socket call", + "[isLuaProtocol]") { + unsetenv("HYPRLAND_INSTANCE_SIGNATURE"); + IPCTestHelper::resetSocketFolder(); + + SECTION("cached false") { + IPCTestHelper::setLuaProtocolDetected(false); + // Should return false without throwing (no socket call needed) + REQUIRE(IPCTestHelper::isLuaProtocol() == false); + } + + SECTION("cached true") { + IPCTestHelper::setLuaProtocolDetected(true); + // Should return true without throwing (no socket call needed) + REQUIRE(IPCTestHelper::isLuaProtocol() == true); + } + + // Cleanup: reset detection so other tests aren't affected + IPCTestHelper::resetLuaProtocolDetection(); +}