test(config_flow,media_player,remote): digit-delay/send-enter options (store + window/error validation), IME-first text entry with ADB fallback tier, icon column parse/prefill/store (P5-2)
This commit is contained in:
1 parent
3d026ae60e
commit
a3b397920e
7 files changed
+320
-18
No files matched your search
@@ -374,17 +374,116 @@ def _cmd_player(modules):
|
||||
return player, None, None
|
||||
|
||||
|
||||
class TestAppIconColumn:
|
||||
"""P5-2: the editor's optional icon column (package=name|icon)."""
|
||||
|
||||
async def test_two_column_line_stores_the_icon(self, tmp_path):
|
||||
module = _form_module()
|
||||
entry = SimpleNamespace(options={}, title="Shield")
|
||||
flow = module.NvidiaShieldOptionsFlow(entry)
|
||||
flow.hass = FakeHass(str(tmp_path))
|
||||
flow.async_show_form = lambda **kwargs: {"type": "form", **kwargs}
|
||||
flow.async_create_entry = lambda **kwargs: {"type": "create_entry", **kwargs}
|
||||
|
||||
await flow.async_step_init({"app_name_map": "ar.tvplayer.tv=TiviMate|http://i.local/t.png"})
|
||||
|
||||
assert flow._options["app_name_map"] == {"ar.tvplayer.tv": "TiviMate"}
|
||||
assert flow._options["app_icon_map"] == {"ar.tvplayer.tv": "http://i.local/t.png"}
|
||||
|
||||
async def test_single_column_map_stays_valid(self, tmp_path):
|
||||
module = _form_module()
|
||||
entry = SimpleNamespace(options={}, title="Shield")
|
||||
flow = module.NvidiaShieldOptionsFlow(entry)
|
||||
flow.hass = FakeHass(str(tmp_path))
|
||||
flow.async_show_form = lambda **kwargs: {"type": "form", **kwargs}
|
||||
flow.async_create_entry = lambda **kwargs: {"type": "create_entry", **kwargs}
|
||||
|
||||
await flow.async_step_init({"app_name_map": "ar.tvplayer.tv=TiviMate"})
|
||||
|
||||
assert flow._options["app_name_map"] == {"ar.tvplayer.tv": "TiviMate"}
|
||||
assert flow._options["app_icon_map"] == {} # blank-safe (no icons)
|
||||
|
||||
async def test_prefill_round_trips_the_icon_column(self):
|
||||
module = _form_module()
|
||||
|
||||
# the serializer writes the second column through on prefill
|
||||
text = module._app_map_to_text(
|
||||
{"ar.tvplayer.tv": "TiviMate"},
|
||||
{"ar.tvplayer.tv": "http://i.local/t.png"},
|
||||
)
|
||||
assert text == "ar.tvplayer.tv=TiviMate|http://i.local/t.png"
|
||||
|
||||
# and the parser round-trips it back
|
||||
icons: dict[str, str] = {}
|
||||
app_map = module._app_map_from_text(text, icons)
|
||||
assert app_map == {"ar.tvplayer.tv": "TiviMate"}
|
||||
assert icons == {"ar.tvplayer.tv": "http://i.local/t.png"}
|
||||
|
||||
async def test_malformed_icon_invalidates_the_row(self, tmp_path):
|
||||
module = _form_module()
|
||||
entry = SimpleNamespace(options={}, title="Shield")
|
||||
flow = module.NvidiaShieldOptionsFlow(entry)
|
||||
flow.hass = FakeHass(str(tmp_path))
|
||||
flow.async_show_form = lambda **kwargs: {"type": "form", **kwargs}
|
||||
flow.async_create_entry = lambda **kwargs: {"type": "create_entry", **kwargs}
|
||||
|
||||
result = await flow.async_step_init({"app_name_map": "p=Name|has spaces"})
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["errors"] == {"base": "invalid_app_map"}
|
||||
|
||||
|
||||
class TestChannelEntryOptions:
|
||||
"""P5-2: digit-delay + optional send-enter on the options page."""
|
||||
|
||||
def _options_flow(self, tmp_path, options):
|
||||
from types import SimpleNamespace as DefaultEntry
|
||||
|
||||
module = _form_module()
|
||||
entry = DefaultEntry(options=dict(options) if options else {})
|
||||
flow = module.NvidiaShieldOptionsFlow(entry)
|
||||
hass = FakeHass(str(tmp_path))
|
||||
flow.hass = hass
|
||||
flow._options = dict(options) if options else {}
|
||||
return module, flow
|
||||
|
||||
async def test_digit_delay_and_send_enter_store(self, tmp_path):
|
||||
module, flow = self._options_flow(tmp_path, options={})
|
||||
|
||||
flow.async_show_form = lambda **kwargs: {"type": "form", **kwargs}
|
||||
flow.async_create_entry = lambda **kwargs: {"type": "create_entry", **kwargs}
|
||||
|
||||
await flow.async_step_init({"channel_digit_delay": "0.3", "channel_send_enter": True})
|
||||
|
||||
assert flow._options["channel_digit_delay"] == 0.3
|
||||
assert flow._options["channel_send_enter"] is True
|
||||
|
||||
async def test_digit_delay_out_of_window_rejects(self, tmp_path):
|
||||
module, flow = self._options_flow(tmp_path, options={})
|
||||
|
||||
flow.async_show_form = lambda **kwargs: {"type": "form", **kwargs}
|
||||
flow.async_create_entry = lambda **kwargs: {"type": "create_entry", **kwargs}
|
||||
|
||||
result = await flow.async_step_init({"channel_digit_delay": "2.0"})
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["errors"] == {"base": "invalid_digit_delay"}
|
||||
|
||||
async def test_digit_delay_not_a_number_rejects(self, tmp_path):
|
||||
module, flow = self._options_flow(tmp_path, options={})
|
||||
|
||||
flow.async_show_form = lambda **kwargs: {"type": "form", **kwargs}
|
||||
flow.async_create_entry = lambda **kwargs: {"type": "create_entry", **kwargs}
|
||||
|
||||
result = await flow.async_step_init({"channel_digit_delay": "slow"})
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["errors"] == {"base": "invalid_digit_delay"}
|
||||
|
||||
|
||||
class TestLegacyUniqueIdMigration:
|
||||
"""P5-1: one-time legacy androidtv-entity-id unique_id migration."""
|
||||
|
||||
def _entry_module(self):
|
||||
import entity_stublib
|
||||
|
||||
entity_stublib.install()
|
||||
if "nsh.media_player" not in sys.modules:
|
||||
entity_stublib.load_entity_modules()
|
||||
return sys.modules["nsh.media_player"]
|
||||
|
||||
async def test_migrates_to_entry_id_without_protocol(self, tmp_path):
|
||||
import entity_stublib
|
||||
|
||||
|
||||
Reference in new issue
Block a user