Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added artifacts/coverage.txt
Binary file not shown.
Binary file added artifacts/pytest-summary.txt
Binary file not shown.
23 changes: 23 additions & 0 deletions tests/local/test_modlistwalker_focus_guard.py
Original file line number Diff line number Diff line change
@@ -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
39 changes: 32 additions & 7 deletions zulipterminal/ui_tools/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Loading