Files
voculinux/tests/test_ibus_active_engine_guard.py
T
Jatin K MalikandGitHub 6bb6d8802e fix(ibus): require a real IM engine on Wayland (#478) (#491)
* fix(ibus): require a real IM engine on Wayland (#478)

On Wayland a bare xkb:* IBus engine is a layout passthrough that never
delivers commit_text() to applications, so selecting the IBus backend
there silently no-ops (niri/sway, and GNOME/Mutter via the env-var
branch). Require a real IM engine before treating IBus as active on
Wayland, regardless of how it was detected; X11/XWayland (XIM) is
unchanged. Complements #486's compositor denylist.

* test(ibus): bind #478 patches to the module under test

Patch get_current_engine/is_ibus_daemon_running via patch.object on a
held ibus_engine reference and call is_ibus_active_input_method through
it, so the patched functions and the function under test always share
one module object. Fixes flakiness in the full CI suite where other test
files re-import ibus_engine and the dotted-string patch bound a
different module object.
2026-06-30 20:19:01 -07:00

92 lines
4.3 KiB
Python

"""Regression tests for issue #478.
On Wayland, IBus only delivers commit_text() to applications through a *real*
IM engine. A bare ``xkb:*`` engine (or none) is a layout passthrough, so
selecting the IBus backend there is a silent no-op. is_ibus_active_input_method()
must require a real engine on Wayland regardless of how IBus was detected, while
leaving X11/XWayland (XIM) behavior unchanged.
Note: we patch via ``patch.object`` on a held module reference (and call the
function through that same reference) rather than by dotted string, so the
patched ``get_current_engine`` / ``is_ibus_daemon_running`` and the function
under test always share one module object even when other test files re-import
``ibus_engine`` during a full-suite run.
"""
import sys
import unittest
from unittest.mock import MagicMock, patch
# Mock GI so importing ibus_engine does not require a real IBus/GTK stack.
_mock_gi = MagicMock()
_mock_gi_repo = MagicMock()
_mock_gi_repo.IBus = MagicMock()
_mock_gi_repo.GLib = MagicMock()
_mock_gi_repo.GObject = MagicMock()
_mock_gi_repo.IBus.Engine = MagicMock
_mock_gi_repo.GLib.MainLoop = MagicMock
sys.modules["gi"] = _mock_gi
sys.modules["gi.repository"] = _mock_gi_repo
for _key in list(sys.modules.keys()):
if "vocalinux" in _key and "ibus_engine" in _key:
del sys.modules[_key]
import vocalinux.text_injection.ibus_engine as ibus_engine # noqa: E402
class TestIsRealIBusEngine(unittest.TestCase):
def test_real_engines(self):
for engine in ("anthy", "libpinyin", "hangul", "mozc-jp"):
self.assertTrue(ibus_engine._is_real_ibus_engine(engine), engine)
def test_layout_only_or_missing_engines(self):
for engine in ("xkb:us::eng", "xkb:de::ger", "xkb:gb::eng", "", None, "No global engine"):
self.assertFalse(ibus_engine._is_real_ibus_engine(engine), repr(engine))
class TestWaylandRequiresRealEngine(unittest.TestCase):
@patch.object(ibus_engine, "get_current_engine", return_value="xkb:us::eng")
@patch.object(ibus_engine, "is_ibus_daemon_running", return_value=True)
@patch.dict("os.environ", {"XDG_SESSION_TYPE": "wayland", "QT_IM_MODULE": "ibus"}, clear=True)
def test_bare_xkb_engine_inactive_even_with_env_var(self, *_):
# GNOME/Mutter: QT_IM_MODULE=ibus but only a bare xkb engine -> no-op.
self.assertFalse(ibus_engine.is_ibus_active_input_method())
@patch.object(ibus_engine, "get_current_engine", return_value="anthy")
@patch.object(ibus_engine, "is_ibus_daemon_running", return_value=True)
@patch.dict("os.environ", {"XDG_SESSION_TYPE": "wayland"}, clear=True)
def test_real_engine_is_active(self, *_):
self.assertTrue(ibus_engine.is_ibus_active_input_method())
@patch.object(ibus_engine, "get_current_engine", return_value=None)
@patch.object(ibus_engine, "is_ibus_daemon_running", return_value=True)
@patch.dict("os.environ", {"XDG_SESSION_TYPE": "wayland", "GTK_IM_MODULE": "ibus"}, clear=True)
def test_no_engine_is_inactive(self, *_):
self.assertFalse(ibus_engine.is_ibus_active_input_method())
class TestX11BehaviorUnchanged(unittest.TestCase):
@patch.object(ibus_engine, "get_current_engine", return_value="xkb:us::eng")
@patch.object(ibus_engine, "is_ibus_daemon_running", return_value=True)
@patch.dict("os.environ", {"XDG_SESSION_TYPE": "x11"}, clear=True)
def test_daemon_with_xkb_engine_still_active_on_x11(self, *_):
# XIM delivers even with a bare xkb engine, so the daemon fallback stands.
self.assertTrue(ibus_engine.is_ibus_active_input_method())
@patch.object(ibus_engine, "get_current_engine", return_value=None)
@patch.object(ibus_engine, "is_ibus_daemon_running", return_value=False)
@patch.dict("os.environ", {"XDG_SESSION_TYPE": "x11", "GTK_IM_MODULE": "ibus"}, clear=True)
def test_env_var_is_active_on_x11(self, *_):
self.assertTrue(ibus_engine.is_ibus_active_input_method())
@patch.object(ibus_engine, "get_current_engine", return_value=None)
@patch.object(ibus_engine, "is_ibus_daemon_running", return_value=False)
@patch.dict("os.environ", {"XDG_SESSION_TYPE": "x11", "GTK_IM_MODULE": "fcitx"}, clear=True)
def test_other_im_configured_is_inactive(self, *_):
self.assertFalse(ibus_engine.is_ibus_active_input_method())
if __name__ == "__main__":
unittest.main()