Files

211 lines
11 KiB
Python

"""Help hints and the game action menus (library right-click, game page "…")."""
import re
from pathlib import Path
from types import SimpleNamespace
from frameport.ui import components as C
from frameport.ui.help import HELP
UI = Path(__file__).resolve().parents[1] / "src" / "frameport" / "ui"
def test_every_help_key_used_in_the_ui_exists():
used = set()
for f in UI.rglob("*.py"):
src = f.read_text(encoding="utf-8")
used |= set(re.findall(r'help="(\w+)"', src))
used |= set(re.findall(r'HELP\["(\w+)"\]', src))
used |= set(re.findall(r'help_icon\("(\w+)"\)', src))
used |= set(re.findall(r'with_help\([^\n]*?, "(\w+)"\)', src))
from frameport.ui.views.game import CATEGORY_TITLES
used |= {f"cat_{c}" for c in CATEGORY_TITLES}
assert used, "the pattern scan found nothing"
assert used <= set(HELP), sorted(used - set(HELP))
def test_help_icon_accepts_a_key_or_text():
assert C.help_icon("lepton").tooltip.message == HELP["lepton"]
assert C.help_icon("Some text").tooltip.message == "Some text"
plain = C.meta("x")
assert C.with_help(plain, None) is plain
def test_menu_items_dividers():
act = ("A", None, None)
items = C.menu_items([None, act, None, None, act, None])
assert [i.content is not None for i in items] == [True, False, True]
def _app(monkeypatch, game, frame_info=None, busy=None, pc=()):
from frameport.core import library
from frameport.ui.app import FramePortApp
monkeypatch.setattr(library, "game", lambda p: game if p == game["package"] else None)
app = object.__new__(FramePortApp)
app.frame_state = "connected" if frame_info is not None else "none"
app.frame_info = frame_info
app.jobs = SimpleNamespace(busy_with=lambda p: busy)
app.library_view = None
app._pc_cache = (float("inf"), {p: {} for p in pc})
return app
def _labels(actions):
return [a[0] if a else "—" for a in actions]
def test_right_click_menu_follows_install_state(monkeypatch):
g = {"package": "com.q", "title": "Q", "recipe": {"status": "works"}, "build": {"sha256": "new"}}
app = _app(monkeypatch, g, {"installed": [{"package": "com.q", "sha256": "old"}]})
installed = _labels(app.game_actions("com.q"))
assert installed[:3] == ["Open", "Play on Frame", "Update on Frame"]
expected = {"Launch test on Frame", "Game settings…", "Uninstall from Frame", "Remove from library"}
assert expected <= set(installed)
missing = _labels(_app(monkeypatch, g, {"installed": []}).game_actions("com.q"))
assert "Install on Frame" in missing and "Uninstall from Frame" not in missing and "Play on Frame" not in missing
assert "Game settings…" in missing # saved to the recipe, used when it's installed
flat = {**g, "analysis": {"extra": {"vr_kind": "none"}}} # a 2D Android app has no adapter
assert "Game settings…" not in _labels(_app(monkeypatch, flat, {"installed": []}).game_actions("com.q"))
offline = _labels(_app(monkeypatch, g).game_actions("com.q"))
assert "Connect your Frame" in offline and "Launch test on Frame" not in offline
def test_right_click_menu_while_busy_and_on_game_page(monkeypatch):
g = {"package": "rift.r", "kind": "rift", "title": "R", "recipe": {"status": "unknown"}}
busy = _labels(_app(monkeypatch, g, {"installed": []}, busy=object()).game_actions("rift.r"))
assert "Cancel" in busy and "Install on Frame" not in busy and "Check game files" not in busy
page = _labels(_app(monkeypatch, g, {"installed": []}).game_actions("rift.r", quick=False))
assert page[0] == "Change executable…" and "Open" not in page and "Install on Frame" not in page
def test_play_is_the_quick_action_when_installed(monkeypatch):
g = {"package": "com.q", "title": "Q", "recipe": {"status": "works"}, "build": {"sha256": "x"}}
app = _app(monkeypatch, g, {"installed": [{"package": "com.q", "sha256": "x"}]})
assert app.quick_action(g) == ("Play on Frame", "play")
assert _app(monkeypatch, g, {"installed": []}).quick_action(g) == ("Install on Frame", "install")
old = _app(monkeypatch, g, {"installed": [{"package": "com.q", "sha256": "older"}]})
old.pc_installs = lambda: {}
assert old.quick_action(g) == ("Play on Frame", "play") # installed (even outdated): playing comes first
assert _app(monkeypatch, g).quick_action(g) == (None, None) # no Frame: "Connect" isn't a quick action
r = {"package": "rift.r", "kind": "rift", "title": "R", "recipe": {}}
app = _app(monkeypatch, r, {"installed": []}, pc=("rift.r",))
assert [o[0] for o in app.play_options(r)] == ["Play on this PC"]
def test_menus_offer_sharing_and_diagnostics(monkeypatch):
g = {"package": "com.q", "title": "Q", "recipe": {"status": "works"}, "build": {"sha256": "x"}}
for quick in (True, False):
labels = _labels(_app(monkeypatch, g, {"installed": []}).game_actions("com.q", quick=quick))
assert {"Share working config…", "Collect logs", "Report a problem…"} <= set(labels)
assert labels[-1] == "Remove from library"
def test_install_browser_explains_known_folders():
from frameport.ui.views.files_dialog import folder_note
assert "shader cache" in folder_note("lepton-shaders") and folder_note("lepton-data")
assert folder_note("lepton-app/obb") == "" # only top-level entries
def test_untested_games_invite_sharing_their_recipe():
from frameport.ui.views.game import should_ask_to_share
g = {"package": "com.x", "recipe": {"status": "unknown", "source": "heuristics"}}
assert not should_ask_to_share(g, False) # never installed, tested or played
assert should_ask_to_share(g, True)
assert should_ask_to_share({**g, "last_test": {"verdict": "pass"}}, False)
assert not should_ask_to_share({**g, "recipe": {"status": "unknown", "source": "catalog (bundled)"}}, True)
assert not should_ask_to_share({**g, "recipe": {"status": "works", "source": "user"}}, True)
assert not should_ask_to_share({**g, "shared_config": 1.0}, True)
assert not should_ask_to_share({**g, "share_dismissed": True}, True)
assert not should_ask_to_share({**g, "kind": "rift"}, True)
def _linux_app(**extra):
return {"package": "linux.tool", "kind": "linux", "title": "Tool", "apk": None, "game_dir": "/apps/tool",
"exe": "tool", "recipe": {"status": "unknown", "as_is": True, "overport": False},
"analysis": {"engine": "Linux", "xr": "none",
"extra": {"vr_kind": "none", "appimage": False, "openxr": False, "files": None,
"candidates": ["tool"], **extra}}}
def test_linux_app_menus_have_no_conversion_actions(monkeypatch):
g = _linux_app()
on_frame = {"installed": [{"package": "linux.tool", "kind": "linux"}]}
for quick in (True, False):
labels = _labels(_app(monkeypatch, g, on_frame).game_actions("linux.tool", quick=quick))
for gone in ("Analyze again", "Rebuild only (no install)", "Check game files", "Reset to suggested recipe",
"Save as known-good recipe", "Share working config…", "Game settings…",
"Add videos and files…", "Change program…"):
assert gone not in labels, gone
assert {"Find artwork…", "Report a problem…", "Collect logs", "Screenshots"} <= set(labels)
assert labels[-1] == "Remove from library"
quick = _labels(_app(monkeypatch, g, on_frame).game_actions("linux.tool"))
assert {"Play on Frame", "Reinstall on Frame", "Launch test on Frame", "Uninstall from Frame"} <= set(quick)
several = _linux_app(candidates=["tool", "tool-helper"])
assert "Change program…" in _labels(_app(monkeypatch, several, {"installed": []}).game_actions("linux.tool"))
lone = _linux_app(candidates=["a", "b"], files=["Tool.AppImage"]) # a lone AppImage: nothing to choose
assert "Change program…" not in _labels(_app(monkeypatch, lone, {"installed": []}).game_actions("linux.tool"))
def test_linux_apps_install_on_the_frame_only(monkeypatch):
g = _linux_app()
app = _app(monkeypatch, g, {"installed": []})
assert [o[0] for o in app.install_options(g)] == ["Install on Frame"]
assert app.quick_action(g) == ("Install on Frame", "install")
assert not app.has_game_settings({**g, "analysis": {"extra": {"vr_kind": "openxr", "openxr": True}}})
installed = _app(monkeypatch, g, {"installed": [{"package": "linux.tool", "kind": "linux", "recipe": None}]})
assert C.install_state(g, installed.frame_info) == "installed" # no build/recipe record: never "outdated"
assert installed.quick_action(g) == ("Play on Frame", "play")
def test_linux_platform_badge_filter_and_tags():
from frameport.artwork.steam import steam_tags
from frameport.ui.views.library import filter_games, platform_filter_options
g, vr = _linux_app(), _linux_app(openxr=True, vr_kind="openxr")
assert C.platform(g)[0] == "Linux" and C.platform(g)[2] == "platform_linux"
assert "OpenXR" in C.platform(vr)[1]
q = {"package": "com.q", "title": "Q", "recipe": {}}
r = {"package": "rift.r", "kind": "rift", "title": "R", "recipe": {}}
assert platform_filter_options([q]) == []
assert [k for k, _ in platform_filter_options([q, g])] == ["all", "quest", "linux"]
assert [k for k, _ in platform_filter_options([q, r, g])] == ["all", "quest", "pcvr", "linux"]
games = [q, r, g]
pick = {p: [x["package"] for x in filter_games(games, {"platform": p})] for p in ("quest", "pcvr", "linux")}
assert pick == {"quest": ["com.q"], "pcvr": ["rift.r"], "linux": ["linux.tool"]}
assert steam_tags(g)[:2] == ["Linux app on Frame", "Linux"]
def test_linux_missing_libraries_from_the_frame_or_the_last_install():
g = _linux_app()
assert C.missing_libraries(g, None) == []
dep = {"package": "linux.tool", "kind": "linux", "missing_libraries": ["libwebkit2gtk-4.1.so.0"]}
assert C.missing_libraries(g, {"installed": [dep]}) == ["libwebkit2gtk-4.1.so.0"]
assert C.missing_libraries(g, {"installed": []}) == [] # not on the connected Frame (any more)
rec = {**g, "installs": {"old": {"time": 1, "result": {"missing_libraries": ["libold.so"]}},
"new": {"time": 2, "result": {"missing_libraries": ["libgtk-3.so.0"]}}}}
assert C.missing_libraries(rec, None) == ["libgtk-3.so.0"] # offline: the last install's report
def test_linux_apps_have_no_twins():
from frameport.core.titles import counterparts, twins
q = {"package": "com.tool", "title": "Tool", "recipe": {}}
g = _linux_app()
assert twins([q, g]) == set() and counterparts(g, [q, g]) == [] and counterparts(q, [q, g]) == []
def test_patch_reasons_in_plain_words():
from frameport.ui.views.game import plain_reason
assert plain_reason("overport default.") == "Standard for every game" # an older recipe's wording
assert plain_reason("Known-good recipe for Batman (tested 2026-09-28).") == "From the tested recipe for this game"
assert plain_reason("Enabled by you.") == "Turned on by you"
assert plain_reason("Applied automatically if the manifest needs it.") == "Added automatically when needed"
assert plain_reason("Hand tracking is required by the game: …") == "Suggested for this game"