Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/build_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,8 @@ jobs:
with:
test_path: 'test/unittests'
install_extras: 'test'
# ovos-gui-api-client is not yet on PyPI (lives on its feat/packaging branch,
# PR OpenVoiceOS/ovos-gui-api-client#2). Install it from git so CI can resolve
# the new skill-side GUIInterface. Revert to the released version (or @dev) once
# that PR merges and an alpha is published.
pre_install_pip: 'git+https://github.com/OpenVoiceOS/ovos-gui-api-client@dev'
4 changes: 4 additions & 0 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ jobs:
sudo apt-get update
sudo apt install python3-dev
python -m pip install build wheel
- name: Install unreleased ovos-gui-api-client (until PyPI release)
# PR OpenVoiceOS/ovos-gui-api-client#2 — drop once published
run: |
pip install git+https://github.com/OpenVoiceOS/ovos-gui-api-client@dev
- name: Install repo
run: |
pip install -e .
Expand Down
2 changes: 1 addition & 1 deletion ovos_workshop/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from ovos_config.locations import get_xdg_config_save_path
from ovos_bus_client.util import get_mycroft_bus
from ovos_utils.lang import standardize_lang_tag
from ovos_bus_client.apis.gui import GUIInterface
from ovos_gui_api_client import GUIInterface
from ovos_bus_client.client.client import MessageBusClient
from ovos_workshop.resource_files import locate_lang_directories
from ovos_workshop.skills.ovos import OVOSSkill
Expand Down
10 changes: 3 additions & 7 deletions ovos_workshop/skills/ovos.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
from ovos_bus_client import MessageBusClient
from ovos_bus_client.apis.enclosure import EnclosureAPI
from ovos_bus_client.apis.events import EventSchedulerInterface
from ovos_bus_client.apis.gui import GUIInterface
from ovos_gui_api_client import GUIInterface
from ovos_bus_client.apis.ocp import OCPInterface
from ovos_bus_client.handler import HandlerLifecycle
from ovos_bus_client.message import Message, dig_for_message
Expand All @@ -50,7 +50,6 @@
from ovos_utils.dialog import MustacheDialogRenderer
from ovos_utils.events import EventContainer, get_handler_name, create_wrapper
from ovos_utils.file_utils import FileWatcher
from ovos_utils.gui import get_ui_directories
from ovos_utils.json_helper import merge_dict
from ovos_utils.lang import standardize_lang_tag
from ovos_utils.log import LOG
Expand Down Expand Up @@ -845,7 +844,6 @@ def _init_skill_gui(self):
Set up the SkillGUI for this skill and connect relevant bus events.
"""
self.gui = SkillGUI(self)
self.gui.setup_default_handlers()

def register_homescreen_app(self, icon: str, name: str, event: str):
"""the icon file MUST be located under 'gui' subfolder"""
Expand Down Expand Up @@ -2547,15 +2545,13 @@ def __init__(self, skill: OVOSSkill):
Initialize a SkillGUI that connects a skill to the GUI framework.

Parameters:
skill (OVOSSkill): The skill instance whose GUI should be managed. The constructor initializes the underlying GUIInterface using the skill's id, message bus, GUI configuration, and UI directories.
skill (OVOSSkill): The skill instance whose GUI should be managed. The constructor initializes the underlying GUIInterface using the skill's id, message bus, and GUI configuration.
"""
self._skill = skill
skill_id = skill.skill_id
bus = skill.bus
config = skill.config_core.get('gui')
ui_directories = get_ui_directories(skill.root_dir)
GUIInterface.__init__(self, skill_id=skill_id, bus=bus, config=config,
ui_directories=ui_directories)
GUIInterface.__init__(self, skill_id=skill_id, bus=bus, config=config)



Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ requires-python = ">=3.9"
dependencies = [
"ovos-utils>= 0.13.3a2,<1.0.0", # FakeBus folds before handlers (ovos-utils#396) — keeps in-place add_context mutations alive so intent-layer gating works
"ovos_bus_client>=2.6.2a2,<3.0.0", # HandlerLifecycle done-signal util (#246) + singleton-registry SessionManager (2.6.2a2)
"ovos-gui-api-client>=0.0.1,<1.0.0",
"ovos-config>=0.0.12,<3.0.0",
"ovos-spec-tools>=1.2.3a1", # canonical SessionManager registry + OVOS-INTENT-4 intent-definition primitives
"ovos-yes-no-plugin>=0.3.0,<1.0.0",
Expand Down
65 changes: 65 additions & 0 deletions test/unittests/test_gui_rebind.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""Tests for binding OVOSSkill.gui to the standalone ovos-gui-api-client.

The skill-side GUIInterface now lives in `ovos_gui_api_client` (extracted from
ovos-bus-client). SkillGUI must subclass it and construct without the obsolete
`ui_directories` argument (skills no longer ship QML).
"""
import unittest
from unittest.mock import MagicMock

from ovos_utils.fakebus import FakeBus


class TestGUIRebind(unittest.TestCase):
def test_skillgui_subclasses_api_client_interface(self):
from ovos_gui_api_client import GUIInterface
from ovos_workshop.skills.ovos import SkillGUI, GUIInterface as wGUI
# workshop must import the api-client interface, not the legacy one
self.assertIs(wGUI, GUIInterface)
self.assertTrue(issubclass(SkillGUI, GUIInterface))

def test_app_imports_api_client_interface(self):
from ovos_gui_api_client import GUIInterface
import ovos_workshop.app as app
self.assertIs(app.GUIInterface, GUIInterface)

def _make_skillgui(self, bus):
from ovos_workshop.skills.ovos import SkillGUI
skill = MagicMock()
skill.skill_id = "test.skill"
skill.bus = bus
skill.config_core = {"gui": {}}
return SkillGUI(skill)

def test_skillgui_constructs_without_ui_directories(self):
# regression: new GUIInterface ctor has no ui_directories param
gui = self._make_skillgui(FakeBus())
self.assertEqual(gui.skill_id, "test.skill")

def test_show_template_emits_page_show(self):
bus = FakeBus()
seen = []
bus.on("gui.page.show", lambda m: seen.append(m))
gui = self._make_skillgui(bus)
gui.show_text("hello world", "a title")
self.assertTrue(seen, "show_text should emit gui.page.show")
page_names = seen[-1].data.get("page_names") or seen[-1].data.get("page")
self.assertIn("SYSTEM_text", str(page_names))

def test_show_without_bus_raises(self):
# contract: a skill always has a bus; calling a template with no bus
# set is a misconfiguration and surfaces a clear error (it is NOT a
# silent no-op — the headless/no-adapter no-op guarantee lives in the
# ovos-gui service + adapter layer, not the skill-side interface).
from ovos_workshop.skills.ovos import SkillGUI
skill = MagicMock()
skill.skill_id = "test.skill"
skill.bus = None
skill.config_core = {"gui": {}}
gui = SkillGUI(skill)
with self.assertRaises(RuntimeError):
gui.show_text("needs a bus")


if __name__ == "__main__":
unittest.main()
Loading