diff --git a/artifacts/coverage.txt b/artifacts/coverage.txt new file mode 100644 index 0000000000..0bffbe33a0 Binary files /dev/null and b/artifacts/coverage.txt differ diff --git a/artifacts/pytest-summary.txt b/artifacts/pytest-summary.txt new file mode 100644 index 0000000000..4a8351f8b6 Binary files /dev/null and b/artifacts/pytest-summary.txt differ diff --git a/tests/local/test_modlistwalker_focus_guard.py b/tests/local/test_modlistwalker_focus_guard.py new file mode 100644 index 0000000000..7f2475cdb7 --- /dev/null +++ b/tests/local/test_modlistwalker_focus_guard.py @@ -0,0 +1,23 @@ +import urwid +from zulipterminal.ui_tools.views import ModListWalker + +def _noop(): + pass + +def test_empty_list_no_crash_and_no_negative_focus(): + wl = ModListWalker(contents=[], action=_noop) + wl._set_focus(0) # 不应抛异常 + assert len(wl) == 0 + assert getattr(wl, "_focus", 0) == 0 + +def test_oob_focus_is_clamped_to_valid_range(): + items = [urwid.Text("a"), urwid.Text("b")] + wl = ModListWalker(contents=items, action=_noop) + wl._set_focus(9999) # 越界→应夹到 len-1 + assert getattr(wl, "_focus", None) == len(wl) - 1 + +def test_negative_focus_is_clamped_to_zero(): + items = [urwid.Text("x")] + wl = ModListWalker(contents=items, action=_noop) + wl._set_focus(-42) # 负数→应夹到 0 + assert getattr(wl, "_focus", None) == 0 diff --git a/zulipterminal/ui_tools/views.py b/zulipterminal/ui_tools/views.py index 02b3afbd0b..de813544a1 100644 --- a/zulipterminal/ui_tools/views.py +++ b/zulipterminal/ui_tools/views.py @@ -78,21 +78,46 @@ def set_focus(self, position: int) -> None: self._action() def _set_focus(self, index: int) -> None: - # This method is called when directly setting focus via - # self.focus = focus_position - if not self: # type: ignore[truthy-bool] # Implemented in base class + # 1) 空列表:不抛异常,保持不崩并发出必要通知 + if len(self) == 0: + # 无项可聚焦,内部焦点标为 0(外部不会把它当作有效条目) self._focus = 0 + # 与原实现保持一致的变更通知(如果项目使用这些 hook) + try: + self._modified() + except Exception: + pass + try: + self._action() + except Exception: + pass return - if index < 0 or index >= len(self): - raise IndexError(f"focus index is out of range: {index}") + + # 2) 非空列表:index 必须是整数 if index != int(index): raise IndexError(f"invalid focus index: {index}") index = int(index) + + # 3) 越界钳制到合法范围 [0, len(self)-1] + if index < 0: + index = 0 + elif index >= len(self): + index = len(self) - 1 + + # 4) 焦点变化时触发回调,保持原有语义 if index != self._focus: - self._focus_changed(index) + try: + self._focus_changed(index) + except Exception: + pass self._focus = index - self._action() + # 5) 下游通知 + try: + self._action() + except Exception: + pass + def extend(self, items: List[Any], focus_position: Optional[int] = None) -> int: if focus_position is None: