Fix TUI widgets color bleedthru

This commit is contained in:
Javanaut
2026-04-13 12:00:38 +02:00
parent cbf43e5d6c
commit d9639561ce
8 changed files with 582 additions and 241 deletions

View File

@@ -23,6 +23,21 @@ class StaticConfig:
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 ScreenSupportTests(unittest.TestCase):
def make_context(self):
return {
@@ -81,6 +96,32 @@ class ScreenSupportTests(unittest.TestCase):
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"],
)
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,325 @@
from __future__ import annotations
from pathlib import Path
import sys
import unittest
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.audio_layout import AudioLayout # noqa: E402
from ffx.iso_language import IsoLanguage # noqa: E402
from ffx.logging_utils import get_ffx_logger # noqa: E402
from ffx.media_details_screen import MediaDetailsScreen # noqa: E402
from ffx.pattern_details_screen import PatternDetailsScreen # noqa: E402
from ffx.show_descriptor import ShowDescriptor # noqa: E402
from ffx.show_details_screen import ShowDetailsScreen # noqa: E402
from ffx.shows_screen import ShowsScreen # noqa: E402
from ffx.track_codec import TrackCodec # noqa: E402
from ffx.track_descriptor import TrackDescriptor # noqa: E402
from ffx.track_details_screen import TrackDetailsScreen # noqa: E402
from ffx.track_type import TrackType # noqa: E402
class FakeTagTable:
def __init__(self):
self.rows = {}
self.cursor_coordinate = (0, 0)
self._selected_row_key = None
self._next_index = 0
self._row_order = []
def clear(self):
self.rows.clear()
self._selected_row_key = None
self._row_order.clear()
def add_row(self, *values):
row_key = f"row-{self._next_index}"
self._next_index += 1
self.rows[row_key] = tuple(values)
self._row_order.append(row_key)
if self._selected_row_key is None:
self._selected_row_key = row_key
return row_key
def coordinate_to_cell_key(self, _coordinate):
return self._selected_row_key, None
def select_row(self, row_key):
self._selected_row_key = row_key
def get_row_index(self, row_key):
return self._row_order.index(row_key)
def remove_row(self, row_key):
self.rows.pop(row_key, None)
if row_key in self._row_order:
self._row_order.remove(row_key)
if self._selected_row_key == row_key:
self._selected_row_key = self._row_order[0] if self._row_order else None
def update_cell(self, row_key, column_key, value):
row = list(self.rows[row_key])
row[int(column_key)] = value
self.rows[row_key] = tuple(row)
class FakeMediaDescriptor:
def __init__(self, track_descriptors):
self._track_descriptors = list(track_descriptors)
def getTrackDescriptors(self):
return list(self._track_descriptors)
class FakeValueWidget:
def __init__(self, value):
self.value = value
class FakeInputWidget:
def __init__(self, value):
self.value = value
class FakeSelectionListWidget:
def __init__(self, selected):
self.selected = selected
def make_track_descriptor(index, sub_index, track_type):
return TrackDescriptor(
index=index,
sub_index=sub_index,
track_type=track_type,
codec_name=TrackCodec.UNKNOWN,
audio_layout=AudioLayout.LAYOUT_UNDEFINED,
)
def make_show_descriptor(show_id, name="Show", year=2000):
return ShowDescriptor(
id=show_id,
name=name,
year=year,
)
class TagTableScreenStateTests(unittest.TestCase):
def test_track_details_screen_reads_selected_tag_from_raw_row_mapping(self):
screen = object.__new__(TrackDetailsScreen)
screen.trackTagsTable = FakeTagTable()
screen._TrackDetailsScreen__draftTrackTags = {
"BPS": "4835",
"KEEP_ME": "plain",
}
screen._TrackDetailsScreen__ignoreTrackKeys = ["KEEP_ME"]
screen._TrackDetailsScreen__removeTrackKeys = ["BPS"]
screen._TrackDetailsScreen__tagRowData = {}
screen.updateTags()
self.assertEqual(
("[red]BPS[/red]", "[red]4835[/red]"),
screen.trackTagsTable.rows["row-0"],
)
self.assertEqual(
("BPS", "4835"),
screen.getSelectedTag(),
)
def test_track_details_screen_reads_select_values_from_widget_state(self):
screen = object.__new__(TrackDetailsScreen)
screen.context = {"logger": get_ffx_logger()}
screen._TrackDetailsScreen__trackDescriptor = None
screen._TrackDetailsScreen__patternId = 5
screen._TrackDetailsScreen__index = 2
screen._TrackDetailsScreen__subIndex = 0
screen._TrackDetailsScreen__trackCodec = TrackCodec.UNKNOWN
screen._TrackDetailsScreen__draftTrackTags = {"KEEP": "value"}
widgets = {
"#type_select": FakeValueWidget(TrackType.AUDIO),
"#audio_layout_select": FakeValueWidget(AudioLayout.LAYOUT_STEREO),
"#language_select": FakeValueWidget(IsoLanguage.GERMAN),
"#title_input": FakeInputWidget("German Audio"),
"#dispositions_selection_list": FakeSelectionListWidget({0, 6}),
}
screen.query_one = lambda selector, _widget_type=None: widgets[selector]
descriptor = screen.getTrackDescriptorFromInput()
self.assertEqual(TrackType.AUDIO, descriptor.getType())
self.assertEqual(AudioLayout.LAYOUT_STEREO, descriptor.getAudioLayout())
self.assertEqual("deu", descriptor.getTags()["language"])
self.assertEqual("German Audio", descriptor.getTitle())
self.assertEqual("value", descriptor.getTags()["KEEP"])
def test_pattern_details_screen_reads_selected_track_from_row_mapping(self):
first_track = make_track_descriptor(0, 0, TrackType.VIDEO)
second_track = make_track_descriptor(1, 0, TrackType.SUBTITLE)
screen = object.__new__(PatternDetailsScreen)
screen.tracksTable = FakeTagTable()
screen._PatternDetailsScreen__draftTracks = [first_track, second_track]
screen._PatternDetailsScreen__pattern = None
screen._PatternDetailsScreen__trackRowData = {}
screen.updateTracks()
screen.tracksTable.select_row("row-1")
self.assertIs(second_track, screen.getSelectedTrackDescriptor())
def test_pattern_details_screen_reads_selected_tag_from_raw_row_mapping(self):
screen = object.__new__(PatternDetailsScreen)
screen.tagsTable = FakeTagTable()
screen._PatternDetailsScreen__pattern = None
screen._PatternDetailsScreen__draftTags = {
"BPS": "4835",
"TITLE": "Deutsch [FN]",
}
screen._PatternDetailsScreen__ignoreGlobalKeys = ["TITLE"]
screen._PatternDetailsScreen__removeGlobalKeys = ["BPS"]
screen._PatternDetailsScreen__tagRowData = {}
screen.updateTags()
self.assertEqual(
("[red]BPS[/red]", "[red]4835[/red]"),
screen.tagsTable.rows["row-0"],
)
self.assertEqual(
("BPS", "4835"),
screen.getSelectedTag(),
)
def test_media_details_screen_reads_selected_track_from_row_mapping(self):
first_track = make_track_descriptor(0, 0, TrackType.VIDEO)
second_track = make_track_descriptor(1, 0, TrackType.SUBTITLE)
screen = object.__new__(MediaDetailsScreen)
screen.tracksTable = FakeTagTable()
screen._MediaDetailsScreen__sourceMediaDescriptor = FakeMediaDescriptor(
[first_track, second_track]
)
screen._MediaDetailsScreen__trackRowData = {}
screen.updateTracks()
screen.tracksTable.select_row("row-1")
self.assertIs(second_track, screen.getSelectedTrackDescriptor())
def test_pattern_details_screen_reads_selected_shifted_season_from_row_mapping(self):
screen = object.__new__(PatternDetailsScreen)
screen.shiftedSeasonsTable = FakeTagTable()
screen._PatternDetailsScreen__pattern = object()
screen._PatternDetailsScreen__shiftedSeasonRowData = {}
row_key = screen.shiftedSeasonsTable.add_row("9", "1", "3", "1", "0")
screen._PatternDetailsScreen__shiftedSeasonRowData[row_key] = {
"id": 44,
"original_season": 9,
"first_episode": 1,
"last_episode": 3,
"season_offset": 1,
"episode_offset": 0,
}
screen.shiftedSeasonsTable.rows[row_key] = ("broken", "ui", "values", "!", "?")
self.assertEqual(
{
"id": 44,
"original_season": 9,
"first_episode": 1,
"last_episode": 3,
"season_offset": 1,
"episode_offset": 0,
},
screen.getSelectedShiftedSeasonObjFromInput(),
)
def test_show_details_screen_reads_selected_pattern_from_row_mapping(self):
screen = object.__new__(ShowDetailsScreen)
screen.patternTable = FakeTagTable()
screen._ShowDetailsScreen__showDescriptor = make_show_descriptor(7, "Demo", 1999)
screen._ShowDetailsScreen__patternRowData = {}
row_key = screen._add_pattern_row(pattern_id=11, pattern_text=r"^demo_(s[0-9]+e[0-9]+)\.mkv$")
screen.patternTable.rows[row_key] = ("display text changed",)
self.assertEqual(
{
"id": 11,
"show_id": 7,
"pattern": r"^demo_(s[0-9]+e[0-9]+)\.mkv$",
},
screen.getSelectedPatternDescriptor(),
)
def test_show_details_screen_reads_selected_shifted_season_from_row_mapping(self):
screen = object.__new__(ShowDetailsScreen)
screen.shiftedSeasonsTable = FakeTagTable()
screen._ShowDetailsScreen__shiftedSeasonRowData = {}
row_key = screen.shiftedSeasonsTable.add_row("1", "", "", "0", "0")
screen._ShowDetailsScreen__shiftedSeasonRowData[row_key] = {
"id": 3,
"original_season": 1,
"first_episode": -1,
"last_episode": -1,
"season_offset": 0,
"episode_offset": 0,
}
screen.shiftedSeasonsTable.rows[row_key] = ("bad", "visible", "data", "x", "y")
self.assertEqual(
{
"id": 3,
"original_season": 1,
"first_episode": -1,
"last_episode": -1,
"season_offset": 0,
"episode_offset": 0,
},
screen.getSelectedShiftedSeasonObjFromInput(),
)
def test_shows_screen_reads_selected_show_id_from_row_mapping(self):
screen = object.__new__(ShowsScreen)
screen.table = FakeTagTable()
screen._ShowsScreen__showRowData = {}
row_key = screen._add_show_row(make_show_descriptor(4, "Mapped", 2011))
screen.table.rows[row_key] = ("999", "Visible", "2099")
self.assertEqual(4, screen.getSelectedShowId())
def test_media_details_screen_reads_selected_show_from_row_mapping(self):
screen = object.__new__(MediaDetailsScreen)
screen.showsTable = FakeTagTable()
screen._MediaDetailsScreen__showRowData = {}
placeholder_key = screen._add_show_row(None)
show_key = screen._add_show_row(make_show_descriptor(8, "Real Show", 2020))
screen.showsTable.select_row(show_key)
screen.showsTable.rows[show_key] = ("oops", "display", "changed")
selected_show = screen.getSelectedShowDescriptor()
self.assertIsInstance(selected_show, ShowDescriptor)
self.assertEqual(8, selected_show.getId())
self.assertEqual(0, screen.getRowIndexFromShowId(-1))
self.assertEqual(1, screen.getRowIndexFromShowId(8))
screen.removeShow(-1)
self.assertNotIn(placeholder_key, screen._MediaDetailsScreen__showRowData)
self.assertEqual(0, screen.getRowIndexFromShowId(8))
if __name__ == "__main__":
unittest.main()