forked from cleveragents/cleveragents-core
fix(tui): guard Throbber DOM queries against unmounted state
- Import NoMatches from textual.css.query - Add _is_mounted guard in _start_animation() to prevent timer creation outside a Textual app context (eliminates RuntimeWarning for unawaited Timer coroutines) - Wrap query_one() calls in _tick_rainbow() and _rotate_quote() with try/except NoMatches so they gracefully no-op when the widget's #throbber-content child is not in the DOM - Broadened _start_animation() exception handler to catch both RuntimeError and NoMatches Fixes the unit_tests CI failure on BDD scenario 'Throbber activates with quotes style' (features/tui_mainscreen.feature:72) which raised textual.css.query.NoMatches when _rotate_quote() called query_one() on an unmounted widget.
This commit is contained in:
@@ -13,6 +13,7 @@ import random
|
||||
from enum import Enum
|
||||
|
||||
from textual.app import ComposeResult
|
||||
from textual.css.query import NoMatches
|
||||
from textual.reactive import reactive
|
||||
from textual.timer import Timer
|
||||
from textual.widget import Widget
|
||||
@@ -95,6 +96,8 @@ class Throbber(Widget):
|
||||
|
||||
def _start_animation(self) -> None:
|
||||
self._stop_animation()
|
||||
if not self._is_mounted:
|
||||
return
|
||||
try:
|
||||
if self.style_mode == ThrobberStyle.RAINBOW:
|
||||
self._timer = self.set_interval(_RAINBOW_FPS, self._tick_rainbow)
|
||||
@@ -103,8 +106,9 @@ class Throbber(Widget):
|
||||
self._quote_timer = self.set_interval(
|
||||
_QUOTE_INTERVAL, self._rotate_quote
|
||||
)
|
||||
except RuntimeError:
|
||||
# No event loop — running outside of a Textual app context
|
||||
except (RuntimeError, NoMatches):
|
||||
# No event loop or widget not mounted — running outside of a
|
||||
# Textual app context (e.g. unit/BDD tests).
|
||||
pass
|
||||
|
||||
def _stop_animation(self) -> None:
|
||||
@@ -117,6 +121,10 @@ class Throbber(Widget):
|
||||
|
||||
def _tick_rainbow(self) -> None:
|
||||
"""Advance the rainbow gradient by one step."""
|
||||
try:
|
||||
content = self.query_one("#throbber-content", Static)
|
||||
except NoMatches:
|
||||
return
|
||||
width = self.size.width or 40
|
||||
gradient_len = len(RAINBOW_GRADIENT)
|
||||
segments: list[str] = []
|
||||
@@ -124,7 +132,6 @@ class Throbber(Widget):
|
||||
color_idx = (i + self._offset) % gradient_len
|
||||
color = RAINBOW_GRADIENT[color_idx]
|
||||
segments.append(f"[{color}]{_GRADIENT_CHAR}[/]")
|
||||
content = self.query_one("#throbber-content", Static)
|
||||
content.update("".join(segments))
|
||||
self._offset += 1
|
||||
|
||||
@@ -136,5 +143,8 @@ class Throbber(Widget):
|
||||
self._quote_index % len(self._shuffled_quotes)
|
||||
]
|
||||
self._quote_index += 1
|
||||
content = self.query_one("#throbber-content", Static)
|
||||
try:
|
||||
content = self.query_one("#throbber-content", Static)
|
||||
except NoMatches:
|
||||
return
|
||||
content.update(f"[italic]{self._current_quote}[/italic]")
|
||||
|
||||
Reference in New Issue
Block a user