from __future__ import annotations from pathlib import Path import logging import sys import unittest from unittest.mock import patch SRC_ROOT = Path(__file__).resolve().parents[2] / "src" if str(SRC_ROOT) not in sys.path: sys.path.insert(0, str(SRC_ROOT)) from ffx import screen_support # noqa: E402 from ffx.i18n import set_current_language, t # noqa: E402 class StaticConfig: def __init__(self, data): self._data = data def getData(self): return self._data class FakeTagTable: def __init__(self): self.rows = {} self._next_index = 0 def clear(self): self.rows.clear() def add_row(self, *values): row_key = f"row-{self._next_index}" self._next_index += 1 self.rows[row_key] = tuple(values) return row_key class FakeApp: def __init__(self, screen_stack): self.screen_stack = list(screen_stack) self.pop_called = False self.exit_called = False def pop_screen(self): self.pop_called = True def exit(self): self.exit_called = True class FakeScreen: def __init__(self, screen_stack): self.app = FakeApp(screen_stack) class FakeRichLog: def __init__(self): self.messages = [] def write(self, message): self.messages.append(message) class FakeScreenWithLog: def __init__(self): self.log_view = FakeRichLog() def query_one(self, selector, _widget_type=None): if selector == f"#{screen_support.SCREEN_LOG_VIEW_ID}": return self.log_view raise LookupError(selector) class FakeThreadedApp: def __init__(self, screen): self.screen = screen self.calls = [] def call_from_thread(self, func, *args): self.calls.append((func, args)) return func(*args) class ScreenSupportTests(unittest.TestCase): def tearDown(self): set_current_language("de") screen_support.set_screen_log_pane_enabled(False) def make_context(self): return { "config": StaticConfig( { "metadata": { "signature": {"RECODED_WITH": "FFX"}, "remove": ["VERSION-eng"], "ignore": ["ENCODER"], "streams": { "remove": ["BPS"], "ignore": ["language"], }, } } ), "database": {"session": object()}, } def test_build_screen_bootstrap_extracts_metadata_filters(self): context = self.make_context() bootstrap = screen_support.build_screen_bootstrap(context) self.assertIs(context, bootstrap.context) self.assertEqual({"RECODED_WITH": "FFX"}, bootstrap.signature_tags) self.assertEqual(["VERSION-eng"], bootstrap.remove_global_keys) self.assertEqual(["ENCODER"], bootstrap.ignore_global_keys) self.assertEqual(["BPS"], bootstrap.remove_track_keys) self.assertEqual(["language"], bootstrap.ignore_track_keys) def test_build_screen_controllers_only_creates_requested_instances(self): context = self.make_context() with ( patch.object(screen_support, "PatternController", side_effect=lambda context: ("pattern", context)), patch.object(screen_support, "ShowController", side_effect=lambda context: ("show", context)), patch.object(screen_support, "TmdbController", side_effect=lambda: "tmdb"), patch.object(screen_support, "ShiftedSeasonController", side_effect=lambda context: ("shifted", context)), ): controllers = screen_support.build_screen_controllers( context, pattern=True, show=True, tmdb=True, shifted_season=True, ) self.assertEqual( { "pattern": ("pattern", context), "show": ("show", context), "tmdb": "tmdb", "shifted_season": ("shifted", context), }, controllers, ) def test_populate_tag_table_keeps_raw_values_outside_display_labels(self): table = FakeTagTable() row_data = screen_support.populate_tag_table( table, {"BPS": 4835, "KEEP": "plain"}, ignore_keys=["KEEP"], remove_keys=["BPS"], ) self.assertEqual( { "row-0": ("BPS", "4835"), "row-1": ("KEEP", "plain"), }, row_data, ) self.assertEqual( ("[red]BPS[/red]", "[red]4835[/red]"), table.rows["row-0"], ) self.assertEqual( ("[blue]KEEP[/blue]", "[blue]plain[/blue]"), table.rows["row-1"], ) def test_go_back_or_exit_exits_from_first_pushed_screen(self): screen = FakeScreen(screen_stack=["base", "shows"]) screen_support.go_back_or_exit(screen) self.assertFalse(screen.app.pop_called) self.assertTrue(screen.app.exit_called) def test_go_back_or_exit_pops_nested_screen(self): screen = FakeScreen(screen_stack=["base", "shows", "details"]) screen_support.go_back_or_exit(screen) self.assertTrue(screen.app.pop_called) self.assertFalse(screen.app.exit_called) def test_localized_column_width_handles_combining_character_labels(self): set_current_language("ta") translated = t("SubIndex") self.assertEqual("துணைச்சுட்டி", translated) self.assertGreater(len(translated), 8) self.assertEqual(len(translated) + 2, screen_support.localized_column_width(translated, 8)) def test_build_screen_log_pane_is_hidden_when_debug_mode_is_disabled(self): screen_support.set_screen_log_pane_enabled(False) log_pane = screen_support.build_screen_log_pane() self.assertFalse(log_pane.display) def test_build_screen_log_pane_is_collapsed_when_debug_mode_is_enabled(self): screen_support.set_screen_log_pane_enabled(True) log_pane = screen_support.build_screen_log_pane() self.assertIsInstance(log_pane, screen_support.ResizableScreenLogPane) self.assertEqual(screen_support.SCREEN_LOG_PANE_ID, log_pane.id) self.assertTrue(log_pane.collapsed) def test_resizable_screen_log_pane_clamps_height_to_minimum(self): log_pane = screen_support.ResizableScreenLogPane() log_pane.set_log_height(1) self.assertEqual(screen_support.SCREEN_LOG_MIN_HEIGHT, log_pane.get_log_height()) def test_configure_screen_log_handler_routes_logger_messages_to_active_screen(self): logger_name = "ffx-test-screen-log-handler" logger = logging.getLogger(logger_name) logger.setLevel(logging.DEBUG) logger.propagate = False for handler in list(logger.handlers): logger.removeHandler(handler) handler.close() screen = FakeScreenWithLog() app = FakeThreadedApp(screen) try: handler = screen_support.configure_screen_log_handler( logger, app, enabled=True, ) self.assertIsNotNone(handler) logger.info("hello pane") self.assertEqual(1, len(screen.log_view.messages)) self.assertRegex( screen.log_view.messages[0], r"^ffx-test-screen-log-handler\s+INFO\s+\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} \| hello pane$", ) finally: screen_support.configure_screen_log_handler(logger, app, enabled=False) for handler in list(logger.handlers): logger.removeHandler(handler) handler.close() if __name__ == "__main__": unittest.main()