When dismissing a message, it should be removed from the list and the tree view.
This commit is contained in:
@@ -139,6 +139,32 @@ class NotificationPresenter:
|
|||||||
self._notifications = []
|
self._notifications = []
|
||||||
self._current_index = -1
|
self._current_index = -1
|
||||||
|
|
||||||
|
def remove_entry(self, entry: Optional[NotificationEntry]) -> bool:
|
||||||
|
"""Removes entry from the notification history."""
|
||||||
|
|
||||||
|
if entry is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
index = self._notifications.index(entry)
|
||||||
|
except ValueError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
del self._notifications[index]
|
||||||
|
if not self._notifications:
|
||||||
|
self._current_index = -1
|
||||||
|
return True
|
||||||
|
|
||||||
|
if self._current_index == -1:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if index < self._current_index:
|
||||||
|
self._current_index -= 1
|
||||||
|
elif index == self._current_index and self._current_index >= len(self._notifications):
|
||||||
|
self._current_index = -1
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def refresh_live_notifications(self) -> bool:
|
def refresh_live_notifications(self) -> bool:
|
||||||
"""Refreshes live mako state without announcing new notifications."""
|
"""Refreshes live mako state without announcing new notifications."""
|
||||||
|
|
||||||
@@ -207,6 +233,8 @@ class NotificationPresenter:
|
|||||||
|
|
||||||
result = self._mako_monitor.dismiss_notification(entry.notification_id)
|
result = self._mako_monitor.dismiss_notification(entry.notification_id)
|
||||||
if result:
|
if result:
|
||||||
|
entry.live = False
|
||||||
|
self.remove_entry(entry)
|
||||||
script.presentMessage(messages.NOTIFICATION_DISMISSED)
|
script.presentMessage(messages.NOTIFICATION_DISMISSED)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -669,9 +697,50 @@ class NotificationListGUI:
|
|||||||
|
|
||||||
if not self._presenter.dismiss_entry(self._script, entry):
|
if not self._presenter.dismiss_entry(self._script, entry):
|
||||||
self._script.presentMessage(messages.NOTIFICATION_UNAVAILABLE)
|
self._script.presentMessage(messages.NOTIFICATION_UNAVAILABLE)
|
||||||
|
else:
|
||||||
|
self._remove_selected_row()
|
||||||
|
|
||||||
self._update_action_buttons()
|
self._update_action_buttons()
|
||||||
|
|
||||||
|
def _remove_selected_row(self) -> None:
|
||||||
|
if self._selection is None or self._model is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
model, paths = self._selection.get_selected_rows()
|
||||||
|
if not paths:
|
||||||
|
return
|
||||||
|
|
||||||
|
row_iter = model.get_iter(paths[0])
|
||||||
|
if row_iter is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
model.remove(row_iter)
|
||||||
|
if self._model.iter_n_children(None) == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
row_index = self._path_to_index(paths[0])
|
||||||
|
if row_index is None:
|
||||||
|
self._selection.select_path(0)
|
||||||
|
return
|
||||||
|
|
||||||
|
next_index = min(row_index, self._model.iter_n_children(None) - 1)
|
||||||
|
self._selection.select_path(next_index)
|
||||||
|
|
||||||
|
def _path_to_index(self, path: Any) -> Optional[int]:
|
||||||
|
if isinstance(path, int):
|
||||||
|
return path
|
||||||
|
|
||||||
|
get_indices = getattr(path, "get_indices", None)
|
||||||
|
if callable(get_indices):
|
||||||
|
indices = get_indices()
|
||||||
|
if indices:
|
||||||
|
return indices[0]
|
||||||
|
|
||||||
|
try:
|
||||||
|
return path[0]
|
||||||
|
except (TypeError, IndexError, KeyError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
_presenter = None
|
_presenter = None
|
||||||
|
|
||||||
|
|||||||
@@ -81,6 +81,41 @@ class _FakeLabel:
|
|||||||
self.visible = value
|
self.visible = value
|
||||||
|
|
||||||
|
|
||||||
|
class _FakeListStore:
|
||||||
|
def __init__(self, rows=None):
|
||||||
|
self.rows = list(rows or [])
|
||||||
|
|
||||||
|
def iter_n_children(self, _parent):
|
||||||
|
return len(self.rows)
|
||||||
|
|
||||||
|
def get_iter(self, path):
|
||||||
|
index = path if isinstance(path, int) else path[0]
|
||||||
|
if 0 <= index < len(self.rows):
|
||||||
|
return index
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_value(self, row_iter, column):
|
||||||
|
return self.rows[row_iter][column]
|
||||||
|
|
||||||
|
def remove(self, row_iter):
|
||||||
|
del self.rows[row_iter]
|
||||||
|
return row_iter < len(self.rows)
|
||||||
|
|
||||||
|
|
||||||
|
class _FakeSelection:
|
||||||
|
def __init__(self, model=None, selected_path=0):
|
||||||
|
self.model = model
|
||||||
|
self.selected_path = selected_path
|
||||||
|
|
||||||
|
def get_selected_rows(self):
|
||||||
|
if self.selected_path is None:
|
||||||
|
return self.model, []
|
||||||
|
return self.model, [self.selected_path]
|
||||||
|
|
||||||
|
def select_path(self, path):
|
||||||
|
self.selected_path = path
|
||||||
|
|
||||||
|
|
||||||
class NotificationPresenterMakoTests(unittest.TestCase):
|
class NotificationPresenterMakoTests(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.presenter = NotificationPresenter()
|
self.presenter = NotificationPresenter()
|
||||||
@@ -163,13 +198,104 @@ class NotificationPresenterMakoTests(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertTrue(self.presenter.dismiss_entry(script, entry))
|
self.assertTrue(self.presenter.dismiss_entry(script, entry))
|
||||||
self.assertEqual(self.monitor.dismissed, [6])
|
self.assertEqual(self.monitor.dismissed, [6])
|
||||||
|
self.assertEqual(self.presenter._notifications, [])
|
||||||
script.presentMessage.assert_called_with(messages.NOTIFICATION_DISMISSED)
|
script.presentMessage.assert_called_with(messages.NOTIFICATION_DISMISSED)
|
||||||
|
|
||||||
script.reset_mock()
|
script.reset_mock()
|
||||||
|
entry = self.presenter.save_notification(
|
||||||
|
"Notification current",
|
||||||
|
source="mako",
|
||||||
|
source_generation=2,
|
||||||
|
notification_id=6,
|
||||||
|
live=True,
|
||||||
|
actions={"default": "View"},
|
||||||
|
)
|
||||||
self.assertTrue(self.presenter.invoke_action_for_entry(script, entry, "default"))
|
self.assertTrue(self.presenter.invoke_action_for_entry(script, entry, "default"))
|
||||||
self.assertEqual(self.monitor.invoked, [(6, "default")])
|
self.assertEqual(self.monitor.invoked, [(6, "default")])
|
||||||
script.presentMessage.assert_called_with(messages.NOTIFICATION_ACTION_INVOKED)
|
script.presentMessage.assert_called_with(messages.NOTIFICATION_ACTION_INVOKED)
|
||||||
|
|
||||||
|
def test_notification_list_dismiss_removes_selected_entry_from_history_and_model(self):
|
||||||
|
entry = self.presenter.save_notification(
|
||||||
|
"Notification current",
|
||||||
|
source="mako",
|
||||||
|
source_generation=2,
|
||||||
|
notification_id=6,
|
||||||
|
live=True,
|
||||||
|
actions={"default": "View"},
|
||||||
|
)
|
||||||
|
remaining = self.presenter.save_notification(
|
||||||
|
"Notification stale",
|
||||||
|
source="mako",
|
||||||
|
source_generation=1,
|
||||||
|
notification_id=7,
|
||||||
|
live=False,
|
||||||
|
actions={},
|
||||||
|
)
|
||||||
|
|
||||||
|
gui = notification_presenter.NotificationListGUI.__new__(
|
||||||
|
notification_presenter.NotificationListGUI
|
||||||
|
)
|
||||||
|
gui._script = mock.Mock()
|
||||||
|
gui._presenter = self.presenter
|
||||||
|
gui._dismiss_button = _FakeButton()
|
||||||
|
gui._actions_box = _FakeBox()
|
||||||
|
gui._actions_status_label = _FakeLabel()
|
||||||
|
gui._model = _FakeListStore(
|
||||||
|
[
|
||||||
|
[entry.message, "now", entry],
|
||||||
|
[remaining.message, "later", remaining],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
gui._selection = _FakeSelection(model=gui._model, selected_path=0)
|
||||||
|
gui._update_action_buttons = mock.Mock()
|
||||||
|
|
||||||
|
gui._dismiss_selected_notification()
|
||||||
|
|
||||||
|
self.assertEqual(self.monitor.dismissed, [6])
|
||||||
|
self.assertEqual(self.presenter._notifications, [remaining])
|
||||||
|
self.assertEqual(gui._model.rows, [[remaining.message, "later", remaining]])
|
||||||
|
gui._script.presentMessage.assert_called_with(messages.NOTIFICATION_DISMISSED)
|
||||||
|
|
||||||
|
def test_notification_list_dismiss_reselects_remaining_row_after_removing_last_selection(self):
|
||||||
|
remaining = self.presenter.save_notification(
|
||||||
|
"Notification stale",
|
||||||
|
source="mako",
|
||||||
|
source_generation=1,
|
||||||
|
notification_id=7,
|
||||||
|
live=False,
|
||||||
|
actions={},
|
||||||
|
)
|
||||||
|
entry = self.presenter.save_notification(
|
||||||
|
"Notification current",
|
||||||
|
source="mako",
|
||||||
|
source_generation=2,
|
||||||
|
notification_id=6,
|
||||||
|
live=True,
|
||||||
|
actions={"default": "View"},
|
||||||
|
)
|
||||||
|
|
||||||
|
gui = notification_presenter.NotificationListGUI.__new__(
|
||||||
|
notification_presenter.NotificationListGUI
|
||||||
|
)
|
||||||
|
gui._script = mock.Mock()
|
||||||
|
gui._presenter = self.presenter
|
||||||
|
gui._dismiss_button = _FakeButton()
|
||||||
|
gui._actions_box = _FakeBox()
|
||||||
|
gui._actions_status_label = _FakeLabel()
|
||||||
|
gui._model = _FakeListStore(
|
||||||
|
[
|
||||||
|
[remaining.message, "later", remaining],
|
||||||
|
[entry.message, "now", entry],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
gui._selection = _FakeSelection(model=gui._model, selected_path=1)
|
||||||
|
gui._update_action_buttons = mock.Mock()
|
||||||
|
|
||||||
|
gui._dismiss_selected_notification()
|
||||||
|
|
||||||
|
self.assertEqual(gui._model.rows, [[remaining.message, "later", remaining]])
|
||||||
|
self.assertEqual(gui._selection.selected_path, 0)
|
||||||
|
|
||||||
def test_notification_list_builds_inline_action_buttons_in_reported_order(self):
|
def test_notification_list_builds_inline_action_buttons_in_reported_order(self):
|
||||||
entry = self.presenter.save_notification(
|
entry = self.presenter.save_notification(
|
||||||
"Notification current",
|
"Notification current",
|
||||||
|
|||||||
Reference in New Issue
Block a user