From 184a9d409adcf570fa45e1f2653271eff733fe31 Mon Sep 17 00:00:00 2001 From: vincent178 Date: Wed, 22 Jul 2026 06:08:53 +0800 Subject: [PATCH 1/2] Fix remove_all_listeners leaving stale empty event key remove_all_listeners("event") replaced the event's OrderedDict with an empty one instead of deleting the key, so the event remained present in _events and event_names() still reported it as registered. Delete the key outright (guarded so removing a never-registered event is a no-op rather than a KeyError) and add regression tests covering both the single-event and clear-all paths. Co-Authored-By: Claude --- pyee/base.py | 3 ++- tests/test_sync.py | 60 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/pyee/base.py b/pyee/base.py index 21ca96d..9f2bbce 100644 --- a/pyee/base.py +++ b/pyee/base.py @@ -275,7 +275,8 @@ def remove_all_listeners(self: Self, event: Optional[str] = None) -> None: """ with self._lock: if event is not None: - self._events[event] = OrderedDict() + if event in self._events: + del self._events[event] else: self._events = dict() diff --git a/tests/test_sync.py b/tests/test_sync.py index 17a5f1e..39a31aa 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -131,7 +131,65 @@ def fourth(): assert ee._events["event"] == OrderedDict([(third, third), (fourth, fourth)]) ee.remove_all_listeners("event") - assert "event" not in ee._events["event"] + assert "event" not in ee._events + assert ee.event_names() == set() + + +def test_remove_all_listeners_single_event_cleans_key(): + """`remove_all_listeners("event")` fully removes the event key rather + than leaving an empty mapping behind. + + Regression test: a previous implementation replaced the entry with an + empty ``OrderedDict``, which kept the key present in ``_events`` and + caused ``event_names()`` to wrongly report the event as still + registered. + """ + + ee = EventEmitter() + + @ee.on("event") + def event_handler(): + pass + + @ee.on("other") + def other_handler(): + pass + + assert ee.event_names() == {"event", "other"} + + ee.remove_all_listeners("event") + + # The key must be gone entirely, not just emptied. + assert "event" not in ee._events + # event_names() must reflect the removal. + assert ee.event_names() == {"other"} + # The other event's listeners are untouched. + assert ee.listeners("other") == [other_handler] + # Emitting the removed event reports no handlers. + assert ee.emit("event") is False + + +def test_remove_all_listeners_clears_every_event(): + """`remove_all_listeners()` with no argument removes every event.""" + + ee = EventEmitter() + + @ee.on("a") + def a_handler(): + pass + + @ee.on("b") + def b_handler(): + pass + + assert ee.event_names() == {"a", "b"} + + ee.remove_all_listeners() + + assert ee._events == dict() + assert ee.event_names() == set() + assert ee.emit("a") is False + assert ee.emit("b") is False def test_listener_removal_on_emit(): From 4a7034e99d07847dbd23901816e093e79d5baead Mon Sep 17 00:00:00 2001 From: vincent178 Date: Wed, 22 Jul 2026 06:10:30 +0800 Subject: [PATCH 2/2] Remove redundant remove_all_listeners single-event regression test The single-event path is already covered by the fixed assertion at the end of test_listener_removal. Drop the dedicated test_remove_all_listeners_single_event_cleans_key to avoid duplication; keep the clear-all regression test. Co-Authored-By: Claude --- tests/test_sync.py | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/tests/test_sync.py b/tests/test_sync.py index 39a31aa..af2712f 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -135,40 +135,6 @@ def fourth(): assert ee.event_names() == set() -def test_remove_all_listeners_single_event_cleans_key(): - """`remove_all_listeners("event")` fully removes the event key rather - than leaving an empty mapping behind. - - Regression test: a previous implementation replaced the entry with an - empty ``OrderedDict``, which kept the key present in ``_events`` and - caused ``event_names()`` to wrongly report the event as still - registered. - """ - - ee = EventEmitter() - - @ee.on("event") - def event_handler(): - pass - - @ee.on("other") - def other_handler(): - pass - - assert ee.event_names() == {"event", "other"} - - ee.remove_all_listeners("event") - - # The key must be gone entirely, not just emptied. - assert "event" not in ee._events - # event_names() must reflect the removal. - assert ee.event_names() == {"other"} - # The other event's listeners are untouched. - assert ee.listeners("other") == [other_handler] - # Emitting the removed event reports no handlers. - assert ee.emit("event") is False - - def test_remove_all_listeners_clears_every_event(): """`remove_all_listeners()` with no argument removes every event."""