From b254ad2b14a9740f34dca5471f65d65e518493bc Mon Sep 17 00:00:00 2001 From: Richard Date: Sun, 7 Jun 2026 19:19:00 -0700 Subject: [PATCH] Remove T9 search text-entry option; default to multitap keypad entry Name Search offered an optional `t9_search` mode (press each key once; match object names against keypad-digit sequences) alongside the default multitap entry (press a key repeatedly to cycle through its letters). This removes the option entirely, so name search always uses multitap entry plus substring text search. - Remove the `t9_search` config key and the "T9 Search" Settings menu item - Drop the t9_search_enabled branch in UITextEntry; always use search_by_text - Remove the now-unused Catalogs.search_by_t9 machinery (keypad digit maps, _t9_cache, helpers) and its dedicated test - Fix the User Pref submenu index shift in the web settings test (Language moves from index 6 to 5) - Update the user guide and ax/catalog/ui docs to drop T9 references Translation catalogs still contain an orphaned "T9 Search" entry; it will be removed by the next `nox -s babel` sync (run periodically, not per-PR). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../docs/references/product-knowledge-base.md | 4 +- default_config.json | 1 - docs/ax/catalog.md | 40 ++-- docs/ax/catalog/CONTEXT.md | 8 +- docs/ax/ui.md | 2 +- docs/source/user_guide.rst | 4 +- python/PiFinder/catalogs.py | 86 --------- python/PiFinder/ui/menu_structure.py | 16 -- python/PiFinder/ui/textentry.py | 20 +- python/tests/test_t9_search.py | 176 ------------------ .../tests/website/test_web_remote_settings.py | 15 +- 11 files changed, 28 insertions(+), 344 deletions(-) delete mode 100644 python/tests/test_t9_search.py diff --git a/.claude/skills/docs/references/product-knowledge-base.md b/.claude/skills/docs/references/product-knowledge-base.md index 0638e876e..cb8242d8b 100644 --- a/.claude/skills/docs/references/product-knowledge-base.md +++ b/.claude/skills/docs/references/product-knowledge-base.md @@ -237,7 +237,7 @@ The PiFinder has a digital alignment system that maps where within its 10° fiel - **Physical alignment vs digital alignment**: When a customer reports alignment problems, first verify they understand the alignment procedure (especially the SQUARE button press). If the camera is physically pointed too far from the telescope's optical axis (>5 degrees), no amount of digital alignment will help — the star won't be in the camera's FOV at all. ### Finding Objects & Push-To Guidance -- Access objects via main menu → **Objects** → choose **By Catalog**, **All Filtered**, **Recent** (session history), or **Name Search** (T9-style text entry). +- Access objects via main menu → **Objects** → choose **By Catalog**, **All Filtered**, **Recent** (session history), or **Name Search** (keypad text entry). - In object lists, pressing **SQUARE** cycles through info displays: catalog designation → common names → magnitude/size with observation checkmarks. - **Push-to guidance** (from Object Details screen): - **Top number**: Rotational direction (CW/CCW) and degrees to move @@ -403,7 +403,7 @@ The PiFinder connects to SkySafari (and other planetarium apps) via WiFi using t - PiFinder initially sends 0° RA/DEC until the first plate solve completes - Works with **SkySafari 5 Plus, 6, and 7** (7 is most reliable) - SkySafari can lock the view to the scope's position -- SkySafari can send objects to PiFinder's observing list (useful alternative to T9 keypad entry) +- SkySafari can send objects to PiFinder's observing list (useful alternative to keypad text entry) - **Single connection limit**: Only one device/app can connect to the PiFinder's LX200 server at a time. If SkySafari is connected on an iPhone, it must be disconnected before connecting from an iPad (or vice versa). - **SkySafari cannot connect to PiFinder and a GoTo mount simultaneously** — choose one - **Sleep mode warning**: If PiFinder enters sleep mode, it stops sending position updates. Extend or disable the sleep timer if using SkySafari continuously. diff --git a/default_config.json b/default_config.json index 6b1883399..7137404f0 100644 --- a/default_config.json +++ b/default_config.json @@ -6,7 +6,6 @@ "auto_exposure_zero_star_handler": "sweep", "menu_anim_speed": 0.1, "text_scroll_speed": "Med", - "t9_search": false, "screen_direction": "right", "mount_type": "Alt/Az", "solver_debug": 0, diff --git a/docs/ax/catalog.md b/docs/ax/catalog.md index 595afdb99..e469b29ba 100644 --- a/docs/ax/catalog.md +++ b/docs/ax/catalog.md @@ -36,7 +36,6 @@ At a high level: └─► Catalogs object (single instance shared across the app) │ ├── CatalogFilter (one shared instance set on every catalog) - ├── T9 / text search caches └── Iterates as List[Catalog], each holding List[CompositeObject] ``` @@ -119,7 +118,7 @@ External code is expected to read through `get_objects()` (returns a ### 2.5 `Catalogs` A container that holds a `List[Catalog]` plus the singleton -`CatalogFilter` and the T9 search cache. It exposes: +`CatalogFilter`. It exposes: - `filter_catalogs()` — runs `filter_objects()` on every catalog. - `set_catalog_filter(filter)` — installs one filter object on every @@ -131,10 +130,9 @@ A container that holds a `List[Catalog]` plus the singleton - `get_catalog_by_code(code)` / `get_object(code, sequence)` — direct lookup. - `search_by_text(s)` — substring match against all names (selected or - not, filtered or not). -- `search_by_t9(digits)` — see §5. + not, filtered or not). See §5. - `add(catalog)` / `remove(code)` / `set(catalogs)` — mutate the - collection and invalidate the T9 cache. + collection. - `is_loading()` — reports whether the background loader thread is still alive (UI uses this to show a "still loading" indicator). - `__iter__` — yields only selected catalogs. @@ -254,31 +252,17 @@ filter is restored on app start. ## 5. Search -### 5.1 Text search - `Catalogs.search_by_text(s)` does a substring lower-case match against each name on each object. Returns a `List[CompositeObject]`. No indexing -— it's O(n_names) per call but fine in practice. - -### 5.2 T9 (keypad) search - -PiFinder's hardware keypad uses a non-standard digit-to-letter mapping -(`KEYPAD_DIGIT_TO_CHARS` at the top of `catalogs.py` — note `7→abc`, -`1→tuv`, `3→'-+/`, etc.). `Catalogs.search_by_t9(digits)`: - -1. Translates every object name to its digit-form via a - `str.maketrans` table. -2. Filters out characters that aren't valid T9 digits. -3. Caches `(catalog_code, sequence) → list[digit_string]` in - `_t9_cache`, invalidated when catalogs are added/removed/replaced. -4. Returns any object whose digit string contains the search pattern as - a substring (skipping objects whose digit string is shorter than the - query, since the substring couldn't match). - -The cache is rebuilt lazily by `_ensure_t9_cache(objs)` only if the -dirty flag is set **or** the set of object keys has changed since the -last rebuild — so dynamic catalogs (PL, comets) updating their -positions don't trigger a rebuild as long as their key set is stable. +— it's O(n_names) per call but fine in practice. It is the only catalog +search. + +On the hardware keypad, `UITextEntry` builds the search string with +multitap entry: each number key cycles through its letters (the keypad +uses a non-standard layout — `7→abc`, `1→tuv`, `3→'-+/`, etc.), and the +resulting text is passed to `search_by_text`. (An earlier `t9_search` +option that matched names against keypad-digit sequences has been +removed.) --- diff --git a/docs/ax/catalog/CONTEXT.md b/docs/ax/catalog/CONTEXT.md index 77a17fff5..4312847d9 100644 --- a/docs/ax/catalog/CONTEXT.md +++ b/docs/ax/catalog/CONTEXT.md @@ -59,7 +59,7 @@ A `CatalogBase` (object list + sequence/id indices) plus filtering: holds the sh _Avoid_: catalog object, catalog instance — use `Catalog` when the class is meant. **Catalogs** (the catalog collection): -The container of `Catalog`s. Owns the singleton `CatalogFilter` and the T9/text search caches; this is the runtime API used by the UI and web. In prose, say "the catalog collection"; reserve `Catalogs` (code-style) for the type itself. +The container of `Catalog`s. Owns the singleton `CatalogFilter`; this is the runtime API used by the UI and web. In prose, say "the catalog collection"; reserve `Catalogs` (code-style) for the type itself. _Avoid_: catalog list, catalog set, the catalogs object. ### Filtering @@ -149,13 +149,9 @@ _Avoid_: catalog state (the enum is `CatalogState`; the wrapper is `CatalogStatu ### Search **Text search** (`search_by_text`): -Lower-case substring match against every name in every catalog. Selection and filter state are ignored. +Lower-case substring match against every name in every catalog. Selection and filter state are ignored. This is the only catalog search; the on-screen keypad in `UITextEntry` uses multitap (cycle through a key's letters) to build the search string. _Avoid_: name search, full-text search. -**T9 search** (`search_by_t9`): -Substring search after translating names to PiFinder's non-standard keypad-digit form (`KEYPAD_DIGIT_TO_CHARS` — `7→abc`, `1→tuv`, …). Backed by `_t9_cache` keyed on `(catalog_code, sequence)`. -_Avoid_: keypad search, digit search (use T9 search for the public concept). - ### UI helpers **CatalogDesignator**: diff --git a/docs/ax/ui.md b/docs/ax/ui.md index 9cf49f5c7..c09af830c 100644 --- a/docs/ax/ui.md +++ b/docs/ax/ui.md @@ -364,7 +364,7 @@ hardware, or network dependency at construct or update time — are: | `UIObjectList` | `object_list.py:108` | Reads `catalogs` heavily (filtered/by-code lists). Loads marker PNGs from `PiFinder/markers/`. Constructor mutates the passed `item_definition` (`select`/`items`). | | `UIObjectDetails` | `object_details.py:56`, `:77` | Requires `item_definition["object"]` and `["object_list"]`. Opens an `ObservationsDatabase` (SQLite, `~/PiFinder_data/observations.db`). Reads `catalogs`, `shared_state`. | | `UILog` | `log.py:30`, `:44` | Requires `item_definition["object"]`. Opens `ObservationsDatabase`. | -| `UITextEntry` | `textentry.py:104` | In catalog-search mode opens `ObjectsDatabase` (the bundled `pifinder_objects.db`) and calls `catalogs.search_by_t9` / `search_by_text`. | +| `UITextEntry` | `textentry.py:104` | In catalog-search mode opens `ObjectsDatabase` (the bundled `pifinder_objects.db`) and calls `catalogs.search_by_text`. | | `UISQM` | `sqm.py:54` | `update()` calls `self.camera_image.copy()` — needs a real `camera_image` (a `PIL.Image`, **not** `None`). Reads `shared_state.sqm()`, sends camera commands. | | `UIPreview` | `preview.py:213` | Also `self.camera_image.copy()` — needs a real camera image; reads `shared_state.last_image_metadata()`. | | `UISoftware` | `software.py:55`–`:74` | Constructor `open()`s `version.txt` and `wifi_status.txt` from `utils.pifinder_dir` (must exist). Update path does `requests.get(...)` to GitHub — network. | diff --git a/docs/source/user_guide.rst b/docs/source/user_guide.rst index a6cbcbdea..97ae076f9 100644 --- a/docs/source/user_guide.rst +++ b/docs/source/user_guide.rst @@ -347,7 +347,7 @@ four options: for observing projects and finding the nearest objects in a particular catalog. - **Recent**: Starts empty and builds a history of the objects you've checked out during the current session. -- **Name Search**: Using the number keypad and T9-style text entry, search for objects by +- **Name Search**: Using the number keypad and keypad text entry, search for objects by name. The Snowball planetary? Cat's Eye? This is the way to find them. However you build the list, it always displays the same information and offers the same @@ -490,7 +490,7 @@ screen, select it from the Objects menu: .. image:: images/user_guide/name_search_01.png -It uses T9-style text input, like the cellphones from the dawn of text messaging. The +It uses keypad text input, like the cellphones from the dawn of text messaging. The on-screen keypad shows the letters available by pressing each number key several times in a row. diff --git a/python/PiFinder/catalogs.py b/python/PiFinder/catalogs.py index a34c71b2c..db0c8008b 100644 --- a/python/PiFinder/catalogs.py +++ b/python/PiFinder/catalogs.py @@ -1,6 +1,5 @@ # mypy: ignore-errors import logging -import re import time import datetime import pytz @@ -28,31 +27,6 @@ logger = logging.getLogger("Catalog") -# Mapping from keypad numbers to characters (non-conventional layout) -KEYPAD_DIGIT_TO_CHARS = { - "7": "abc", - "8": "def", - "9": "ghi", - "4": "jkl", - "5": "mno", - "6": "pqrs", - "1": "tuv", - "2": "wxyz", - "3": "'-+/", -} - -LETTER_TO_DIGIT_MAP: dict[str, str] = {} -for _digit, _chars in KEYPAD_DIGIT_TO_CHARS.items(): - # Map the digit to itself so numbers in names still match - LETTER_TO_DIGIT_MAP[_digit] = _digit - for _char in _chars: - LETTER_TO_DIGIT_MAP[_char] = _digit - LETTER_TO_DIGIT_MAP[_char.upper()] = _digit - -translator = str.maketrans(LETTER_TO_DIGIT_MAP) -VALID_T9_DIGITS = "".join(KEYPAD_DIGIT_TO_CHARS.keys()) -INVALID_T9_DIGITS_RE = re.compile(f"[^{VALID_T9_DIGITS}]") - # collection of all catalog-related classes # CatalogBase : just the CompositeObjects (imported from catalog_base) @@ -366,8 +340,6 @@ class Catalogs: def __init__(self, catalogs: List[Catalog]): self.__catalogs: List[Catalog] = catalogs self.catalog_filter: Union[CatalogFilter, None] = None - self._t9_cache: dict[tuple[str, int], list[str]] = {} - self._t9_cache_dirty = True def filter_catalogs(self): """ @@ -421,61 +393,6 @@ def get_object(self, catalog_code: str, sequence: int) -> Optional[CompositeObje if catalog: return catalog.get_object_by_sequence(sequence) - # this is memory efficient and doesn't hit the sdcard, but could be faster - # also, it could be cached - def _name_to_t9_digits(self, name: str) -> str: - translated_name = name.translate(translator) - return INVALID_T9_DIGITS_RE.sub("", translated_name) - - def _object_cache_key(self, obj: CompositeObject) -> tuple[str, int]: - return (obj.catalog_code, obj.sequence) - - def _invalidate_t9_cache(self) -> None: - self._t9_cache_dirty = True - - def _rebuild_t9_cache(self, objs: list[CompositeObject]) -> None: - self._t9_cache = {} - for obj in objs: - self._t9_cache[self._object_cache_key(obj)] = [ - self._name_to_t9_digits(name) for name in obj.names - ] - self._t9_cache_dirty = False - - def _ensure_t9_cache(self, objs: list[CompositeObject]) -> None: - current_keys = {self._object_cache_key(obj) for obj in objs} - if self._t9_cache_dirty or current_keys != set(self._t9_cache.keys()): - self._rebuild_t9_cache(objs) - - def search_by_t9(self, search_digits: str) -> List[CompositeObject]: - """Search catalog objects using keypad digits. - - Uses the existing keypad letter mapping (including its non-conventional - layout) to convert object names to their digit representation and - returns all objects whose digit string contains the search pattern. - """ - - objs = self.get_objects(only_selected=False, filtered=False) - result: list[CompositeObject] = [] - if not search_digits: - return result - - self._ensure_t9_cache(objs) - - for obj in objs: - for digits in self._t9_cache.get(self._object_cache_key(obj), []): - if len(digits) < len(search_digits): - continue - if search_digits in digits: - result.append(obj) - logger.debug( - "Found %s in %s %i via T9", - digits, - obj.catalog_code, - obj.sequence, - ) - break - return result - def search_by_text(self, search_text: str) -> List[CompositeObject]: objs = self.get_objects(only_selected=False, filtered=False) result = [] @@ -495,14 +412,12 @@ def search_by_text(self, search_text: str) -> List[CompositeObject]: def set(self, catalogs: List[Catalog]): self.__catalogs = catalogs self.select_all_catalogs() - self._invalidate_t9_cache() def add(self, catalog: Catalog, select: bool = False): if catalog.catalog_code not in [x.catalog_code for x in self.__catalogs]: if select: self.catalog_filter.selected_catalogs.add(catalog.catalog_code) self.__catalogs.append(catalog) - self._invalidate_t9_cache() else: logger.warning( "Catalog %s already exists, not replaced (in Catalogs.add)", @@ -513,7 +428,6 @@ def remove(self, catalog_code: str): for catalog in self.__catalogs: if catalog.catalog_code == catalog_code: self.__catalogs.remove(catalog) - self._invalidate_t9_cache() return logger.warning("Catalog %s does not exist, cannot remove", catalog_code) diff --git a/python/PiFinder/ui/menu_structure.py b/python/PiFinder/ui/menu_structure.py index 96b349db5..483be2f28 100644 --- a/python/PiFinder/ui/menu_structure.py +++ b/python/PiFinder/ui/menu_structure.py @@ -702,22 +702,6 @@ def _(key: str) -> Any: }, ], }, - { - "name": _("T9 Search"), - "class": UITextMenu, - "select": "single", - "config_option": "t9_search", - "items": [ - { - "name": _("Off"), - "value": False, - }, - { - "name": _("On"), - "value": True, - }, - ], - }, { "name": _("Az Arrows"), "class": UITextMenu, diff --git a/python/PiFinder/ui/textentry.py b/python/PiFinder/ui/textentry.py index 037a50753..42edce5ca 100644 --- a/python/PiFinder/ui/textentry.py +++ b/python/PiFinder/ui/textentry.py @@ -124,10 +124,6 @@ def __init__(self, *args, **kwargs) -> None: self._results_updated = False # Flag to trigger UI refresh self.SEARCH_DEBOUNCE_MS = 250 # milliseconds - @property - def t9_search_enabled(self) -> bool: - return bool(self.config_object.get_option("t9_search", False)) - def draw_text_entry(self): line_text_y = self.text_y + self.bold.height + 2 self.draw.line( @@ -175,7 +171,7 @@ def draw_text_entry(self): ) def draw_keypad(self): - # 3-column x 4-row T9 grid filling the width below the text line; key + # 3-column x 4-row keypad grid filling the width below the text line; key # size derives from the screen so it scales (38x23 on the 128 panel). start_x = self.text_x start_y = self.text_y + self.bold.height @@ -286,10 +282,7 @@ def _perform_search(self, search_text, search_version): # Priority catalogs (NGC, IC, M) are loaded first, WDS loads in background # So search will work immediately with those, WDS results appear when loading completes logger.info(f"Starting search for '{search_text}'") - if self.t9_search_enabled: - results = self.catalogs.search_by_t9(search_text) - else: - results = self.catalogs.search_by_text(search_text) + results = self.catalogs.search_by_text(search_text) logger.info(f"Search for '{search_text}' found {len(results)} results") # Only update if this search is still current (not superseded by newer search) @@ -353,15 +346,6 @@ def key_long_minus(self): def key_number(self, number): current_time = time.time() number_key = str(number) - if not self.text_entry_mode and self.t9_search_enabled: - # In T9 mode we simply append the pressed digit - self.last_key_press_time = current_time - self.last_key = number - if number_key in self.keys: - self.char_index = 0 - self.add_char(number_key) - return - # Check if the same key is pressed within a short time if self.last_key == number and self.within_keypress_window(current_time): self.char_index = (self.char_index + 1) % self.keys.get_nr_entries( diff --git a/python/tests/test_t9_search.py b/python/tests/test_t9_search.py deleted file mode 100644 index e5be2baca..000000000 --- a/python/tests/test_t9_search.py +++ /dev/null @@ -1,176 +0,0 @@ -import sys -import types - -import pytest - - -@pytest.fixture() -def catalogs_api(monkeypatch): - """Provide catalog helpers while isolating the calc_utils stub.""" - - # Avoid expensive ephemeris downloads triggered during PiFinder.calc_utils import - stub_calc_utils = types.ModuleType("PiFinder.calc_utils") - stub_calc_utils.FastAltAz = None - stub_calc_utils.sf_utils = None - monkeypatch.setitem(sys.modules, "PiFinder.calc_utils", stub_calc_utils) - - # Avoid optional timezone dependency required by the catalogs module - stub_pytz = types.ModuleType("pytz") - stub_pytz.timezone = lambda name: name - stub_pytz.utc = "UTC" - monkeypatch.setitem(sys.modules, "pytz", stub_pytz) - - # Avoid optional dataclasses JSON dependency required by config/equipment imports - stub_dataclasses_json = types.ModuleType("dataclasses_json") - - def dataclass_json(cls=None, **_kwargs): - def decorator(inner_cls): - return inner_cls - - return decorator(cls) if cls is not None else decorator - - stub_dataclasses_json.dataclass_json = dataclass_json - monkeypatch.setitem(sys.modules, "dataclasses_json", stub_dataclasses_json) - - # Avoid optional numpy dependency pulled in via CompositeObject - stub_numpy = types.ModuleType("numpy") - stub_numpy.array = lambda *args, **kwargs: None - monkeypatch.setitem(sys.modules, "numpy", stub_numpy) - - # Avoid timezone lookup dependency required by SharedState - stub_timezonefinder = types.ModuleType("timezonefinder") - - class _TimezoneFinder: - def timezone_at(self, **_kwargs): - return "UTC" - - stub_timezonefinder.TimezoneFinder = _TimezoneFinder - monkeypatch.setitem(sys.modules, "timezonefinder", stub_timezonefinder) - - # Avoid skyfield dependency pulled in by comets module - stub_skyfield = types.ModuleType("skyfield") - stub_skyfield_data = types.ModuleType("skyfield.data") - stub_skyfield_constants = types.ModuleType("skyfield.constants") - stub_skyfield_data.mpc = types.SimpleNamespace(COMET_URL="") - stub_skyfield_constants.GM_SUN_Pitjeva_2005_km3_s2 = 0 - monkeypatch.setitem(sys.modules, "skyfield", stub_skyfield) - monkeypatch.setitem(sys.modules, "skyfield.data", stub_skyfield_data) - monkeypatch.setitem(sys.modules, "skyfield.constants", stub_skyfield_constants) - - from PiFinder import catalogs as catalogs_module - - return ( - catalogs_module.Catalogs, - catalogs_module.KEYPAD_DIGIT_TO_CHARS, - catalogs_module.LETTER_TO_DIGIT_MAP, - ) - - -class DummyObject: - def __init__(self, names, catalog_code="TST", sequence=1): - self.names = names - self.catalog_code = catalog_code - self.sequence = sequence - - -class DummyCatalog: - def __init__(self, catalog_code, objects): - self.catalog_code = catalog_code - self._objects = objects - - def is_selected(self): - return True - - def get_objects(self): - return self._objects - - -@pytest.mark.unit -def test_letter_mapping_uses_keypad_layout(catalogs_api): - _, KEYPAD_DIGIT_TO_CHARS, LETTER_TO_DIGIT_MAP = catalogs_api - # spot-check the non-conventional keypad mapping - assert LETTER_TO_DIGIT_MAP["t"] == "1" - assert LETTER_TO_DIGIT_MAP["v"] == "1" - assert LETTER_TO_DIGIT_MAP["m"] == "5" - assert LETTER_TO_DIGIT_MAP["'"] == "3" - # ensure every keypad character is represented in the mapping - for digit, chars in KEYPAD_DIGIT_TO_CHARS.items(): - for char in chars: - assert LETTER_TO_DIGIT_MAP[char] == digit - - -@pytest.mark.unit -def test_search_by_t9_matches_objects(catalogs_api): - Catalogs, _, _ = catalogs_api - objects = [ - DummyObject(["Vega"], sequence=1), - DummyObject(["M31", "Andromeda"], sequence=2), - DummyObject(["Polaris"], sequence=3), - ] - catalogs = Catalogs([DummyCatalog("TST", objects)]) - - # Vega -> v(1)e(8)g(9)a(7) - vega_results = catalogs.search_by_t9("1897") - assert len(vega_results) == 1 - assert vega_results[0].sequence == 1 - - # M31 -> m(5)3(3)1(1) - m31_results = catalogs.search_by_t9("531") - assert len(m31_results) == 1 - assert m31_results[0].sequence == 2 - - # No matches should return an empty list - assert catalogs.search_by_t9("9999") == [] - - -@pytest.mark.unit -def test_search_by_t9_uses_cached_digits(monkeypatch, catalogs_api): - Catalogs, _, _ = catalogs_api - objects = [DummyObject(["Vega"], sequence=1)] - catalogs = Catalogs([DummyCatalog("TST", objects)]) - - call_count = 0 - original = Catalogs._name_to_t9_digits - - def counting(self, name): - nonlocal call_count - call_count += 1 - return original(self, name) - - monkeypatch.setattr(Catalogs, "_name_to_t9_digits", counting) - - catalogs.search_by_t9("1") - first_count = call_count - - # Subsequent searches should use cached digit strings - catalogs.search_by_t9("18") - assert call_count == first_count - - -@pytest.mark.unit -def test_search_by_t9_cache_invalidation_on_catalog_change(monkeypatch, catalogs_api): - Catalogs, _, _ = catalogs_api - objects = [DummyObject(["Vega"], sequence=1)] - dummy_catalog = DummyCatalog("TST", objects) - catalogs = Catalogs([dummy_catalog]) - - catalogs.search_by_t9("1897") - - new_object = DummyObject(["Deneb"], sequence=2) - dummy_catalog._objects.append(new_object) - - # Update tracker to ensure cache rebuild triggers conversion for new object - call_count = 0 - original = Catalogs._name_to_t9_digits - - def counting(self, name): - nonlocal call_count - call_count += 1 - return original(self, name) - - monkeypatch.setattr(Catalogs, "_name_to_t9_digits", counting) - - results = catalogs.search_by_t9("88587") - - assert any(obj.sequence == 2 for obj in results) - assert call_count > 0 diff --git a/python/tests/website/test_web_remote_settings.py b/python/tests/website/test_web_remote_settings.py index 2515e18ac..dc171fc3e 100644 --- a/python/tests/website/test_web_remote_settings.py +++ b/python/tests/website/test_web_remote_settings.py @@ -29,9 +29,8 @@ 1: Sleep Time (not tested: hardware display) 2: Menu Anim (not tested: animation param) 3: Scroll Speed (not tested: animation param) - 4: T9 Search (not tested: input method) - 5: Az Arrows (not tested: hardware keypad) - 6: Language (testable: software language switch) + 4: Az Arrows (not tested: hardware keypad) + 5: Language (testable: software language switch) Language submenu items (0-indexed, values depend on translation): English (en), German (de), French (fr), Spanish (es), Chinese (zh) @@ -40,7 +39,7 @@ DD → highlight Settings (index 4, 2 downs from Objects=2) R → enter Settings submenu (now at User Pref, index 0) R → enter User Pref submenu (now at Key Bright, index 0) - DDDDDD → navigate to Language (index 6, 6 downs from Key Bright) + DDDDD → navigate to Language (index 5, 5 downs from Key Bright) R → enter Language submenu """ @@ -101,10 +100,10 @@ def test_settings_language_submenu_entry(driver): navigate_to_root_menu(driver) # DDR = enter Settings; R = enter User Pref at Key Bright (0) - # DDDDDD = navigate to Language (index 6); R = enter Language submenu + # DDDDD = navigate to Language (index 5); R = enter Language submenu press_keys_and_validate( driver, - "DDRRDDDDDDR", + "DDRRDDDDDR", { "ui_type": "UITextMenu", "title": "Language", @@ -123,7 +122,7 @@ def test_settings_language_has_english_default(driver): # Navigate to Language submenu press_keys_and_validate( driver, - "DDRRDDDDDDR", + "DDRRDDDDDR", { "ui_type": "UITextMenu", "title": "Language", @@ -160,7 +159,7 @@ def test_settings_language_select_german_and_restore(driver): # Navigate to Language submenu press_keys_and_validate( driver, - "DDRRDDDDDDR", + "DDRRDDDDDR", {"ui_type": "UITextMenu", "title": "Language"}, )