#include #include #include #include #include #include extern "C" { #include #include } using namespace Config::Lua; namespace { class CLuaState { public: CLuaState() : m_lua(luaL_newstate()) { luaL_openlibs(m_lua); } ~CLuaState() { if (m_lua) lua_close(m_lua); } lua_State* get() const { return m_lua; } private: lua_State* m_lua = nullptr; }; int getGlobalInt(lua_State* L, const char* name) { lua_getglobal(L, name); const int v = sc(lua_tointeger(L, -1)); lua_pop(L, 1); return v; } bool getGlobalBool(lua_State* L, const char* name) { lua_getglobal(L, name); const bool v = lua_toboolean(L, -1) != 0; lua_pop(L, 1); return v; } bool isGlobalNil(lua_State* L, const char* name) { lua_getglobal(L, name); const bool isnil = lua_isnil(L, -1); lua_pop(L, 1); return isnil; } } TEST(ConfigLuaObjects, keybindCanToggleEnabledFromLua) { CLuaState S; const auto L = S.get(); Objects::CLuaKeybind{}.setup(L); auto keybind = makeShared(); keybind->enabled = true; Objects::CLuaKeybind::push(L, keybind); lua_setglobal(L, "kb"); ASSERT_EQ(luaL_dostring(L, R"( assert(kb:is_enabled() == true) kb:set_enabled(false) assert(kb:is_enabled() == false) )"), LUA_OK); EXPECT_FALSE(keybind->enabled); } TEST(ConfigLuaObjects, keybindExposesMetadataAndRemoveMethods) { CLuaState S; const auto L = S.get(); Objects::CLuaKeybind{}.setup(L); auto keybind = makeShared(); keybind->enabled = true; keybind->description = "Close active window"; keybind->hasDescription = true; keybind->displayKey = "SUPER + Q"; keybind->submap.name = "default"; keybind->handler = "exec"; keybind->arg = "kitty"; keybind->modmask = HL_MODIFIER_META; keybind->key = "Q"; keybind->keycode = 24; keybind->repeat = true; keybind->locked = true; keybind->release = false; keybind->nonConsuming = true; keybind->transparent = false; keybind->ignoreMods = false; keybind->longPress = false; keybind->dontInhibit = true; keybind->click = false; keybind->drag = false; keybind->submapUniversal = false; keybind->deviceInclusive = true; keybind->devices = {"kbd-a", "kbd-b"}; Objects::CLuaKeybind::push(L, keybind); lua_setglobal(L, "kb"); ASSERT_EQ(luaL_dostring(L, R"( assert(kb.enabled == true) assert(kb.description == "Close active window") assert(kb.display_key == "SUPER + Q") assert(kb.submap == "default") assert(kb.handler == "exec") assert(kb.arg == "kitty") assert(kb.modmask ~= nil) assert(kb.key == "Q") assert(kb.keycode == 24) assert(kb.repeating == true) assert(kb.locked == true) assert(kb.non_consuming == true) assert(kb.dont_inhibit == true) assert(type(kb.devices) == "table") kb:remove() kb:unbind() )"), LUA_OK); } TEST(ConfigLuaObjects, objectsAreReadOnlyFromLua) { CLuaState S; const auto L = S.get(); Objects::CLuaKeybind{}.setup(L); auto keybind = makeShared(); Objects::CLuaKeybind::push(L, keybind); lua_setglobal(L, "kb"); luaL_dostring(L, "kb.foo = 1"); luaL_dostring(L, "x = kb.foo"); EXPECT_TRUE(isGlobalNil(L, "x")); } TEST(ConfigLuaObjects, keybindSupportsEqAndToString) { CLuaState S; const auto L = S.get(); Objects::CLuaKeybind{}.setup(L); auto keybindA = makeShared(); auto keybindB = makeShared(); Objects::CLuaKeybind::push(L, keybindA); lua_setglobal(L, "kb1"); Objects::CLuaKeybind::push(L, keybindA); lua_setglobal(L, "kb2"); Objects::CLuaKeybind::push(L, keybindB); lua_setglobal(L, "kb3"); ASSERT_EQ(luaL_dostring(L, R"( eq12 = (kb1 == kb2) neq13 = (kb1 ~= kb3) local s = tostring(kb1) hasPrefix = type(s) == "string" and string.sub(s, 1, 11) == "HL.Keybind(" )"), LUA_OK); EXPECT_TRUE(getGlobalBool(L, "eq12")); EXPECT_TRUE(getGlobalBool(L, "neq13")); EXPECT_TRUE(getGlobalBool(L, "hasPrefix")); } TEST(ConfigLuaObjects, eventSubscriptionRemoveAndIsActive) { CLuaState S; const auto L = S.get(); Objects::CLuaEventSubscription{}.setup(L); CLuaEventHandler handler(L); ASSERT_EQ(luaL_dostring(L, R"( count = 0 function onReload() count = count + 1 end )"), LUA_OK); lua_getglobal(L, "onReload"); ASSERT_TRUE(lua_isfunction(L, -1)); const int ref = luaL_ref(L, LUA_REGISTRYINDEX); const auto handle = handler.registerEvent("config.reloaded", ref); ASSERT_TRUE(handle.has_value()); Objects::CLuaEventSubscription::push(L, &handler, *handle); lua_setglobal(L, "sub"); Event::bus()->m_events.config.reloaded.emit(); EXPECT_EQ(getGlobalInt(L, "count"), 1); ASSERT_EQ(luaL_dostring(L, R"( assert(sub:is_active() == true) sub:remove() assert(sub:is_active() == false) sub:remove() )"), LUA_OK); Event::bus()->m_events.config.reloaded.emit(); EXPECT_EQ(getGlobalInt(L, "count"), 1); } TEST(ConfigLuaObjects, eventSubscriptionSupportsEqAndToString) { CLuaState S; const auto L = S.get(); Objects::CLuaEventSubscription{}.setup(L); CLuaEventHandler handler(L); ASSERT_EQ(luaL_dostring(L, R"( function cbA() end function cbB() end )"), LUA_OK); lua_getglobal(L, "cbA"); ASSERT_TRUE(lua_isfunction(L, -1)); const int refA = luaL_ref(L, LUA_REGISTRYINDEX); lua_getglobal(L, "cbB"); ASSERT_TRUE(lua_isfunction(L, -1)); const int refB = luaL_ref(L, LUA_REGISTRYINDEX); const auto handleA = handler.registerEvent("config.reloaded", refA); const auto handleB = handler.registerEvent("config.reloaded", refB); ASSERT_TRUE(handleA.has_value()); ASSERT_TRUE(handleB.has_value()); Objects::CLuaEventSubscription::push(L, &handler, *handleA); lua_setglobal(L, "sub1"); Objects::CLuaEventSubscription::push(L, &handler, *handleA); lua_setglobal(L, "sub2"); Objects::CLuaEventSubscription::push(L, &handler, *handleB); lua_setglobal(L, "sub3"); ASSERT_EQ(luaL_dostring(L, R"( eq12 = (sub1 == sub2) neq13 = (sub1 ~= sub3) local s = tostring(sub1) hasPrefix = type(s) == "string" and string.sub(s, 1, 21) == "HL.EventSubscription(" )"), LUA_OK); EXPECT_TRUE(getGlobalBool(L, "eq12")); EXPECT_TRUE(getGlobalBool(L, "neq13")); EXPECT_TRUE(getGlobalBool(L, "hasPrefix")); }