Skip to content

Commit 122977b

Browse files
committed
test: add tests for subscription lifecycle
1 parent bf9b28d commit 122977b

File tree

2 files changed

+139
-0
lines changed

2 files changed

+139
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
- refactor: use custom value `NOT_SET = object()` instead of `None` to signal the absence of a value for the `default_value` parameter in `AutorunOptions` and internally in `Autorun` class for properties storing last selector result and last call result
66
- build: switch versioning source to `version.py`, support Python 3.14
77
- refactor: improve weakref handling in store listeners and event handlers, remove manual `weakref` handling in `SideEffectRunner`, optimize `Autorun` checks and subscription logic
8+
- test: add tests for subscription lifecycle
89

910
## Version 0.24.0
1011

tests/test_subscribe.py

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# ruff: noqa: D100, D101, D103
2+
from __future__ import annotations
3+
4+
from dataclasses import replace
5+
6+
import pytest
7+
from immutable import Immutable
8+
9+
from redux.basic_types import (
10+
BaseAction,
11+
CompleteReducerResult,
12+
FinishAction,
13+
FinishEvent,
14+
InitAction,
15+
InitializationActionError,
16+
StoreOptions,
17+
)
18+
from redux.main import Store
19+
20+
21+
class StateType(Immutable):
22+
value: int
23+
24+
25+
class IncrementAction(BaseAction): ...
26+
27+
28+
Action = IncrementAction | InitAction | FinishAction
29+
30+
31+
def reducer(
32+
state: StateType | None,
33+
action: Action,
34+
) -> StateType | CompleteReducerResult[StateType, Action, FinishEvent]:
35+
if state is None:
36+
if isinstance(action, InitAction):
37+
return StateType(value=0)
38+
raise InitializationActionError(action)
39+
40+
if isinstance(action, IncrementAction):
41+
return replace(state, value=state.value + 1)
42+
43+
return state
44+
45+
46+
StoreType = Store[StateType, Action, FinishEvent]
47+
48+
49+
@pytest.fixture
50+
def store() -> StoreType:
51+
return Store(reducer, options=StoreOptions(auto_init=True))
52+
53+
54+
def test_general(store: StoreType) -> None:
55+
times_called = 0
56+
57+
def callback(state: StateType | None) -> None:
58+
nonlocal times_called
59+
times_called += 1
60+
assert state is not None
61+
assert state.value == times_called
62+
63+
unsubscribe = store._subscribe(callback) # noqa: SLF001
64+
for _ in range(5):
65+
store.dispatch(IncrementAction())
66+
unsubscribe()
67+
for _ in range(5):
68+
store.dispatch(IncrementAction())
69+
70+
store.dispatch(FinishAction())
71+
72+
assert times_called == 5
73+
74+
75+
def test_not_keeping_ref_keeping_callback(store: StoreType) -> None:
76+
times_called = 0
77+
78+
def callback(state: StateType | None) -> None:
79+
nonlocal times_called
80+
times_called += 1
81+
assert state is not None
82+
assert state.value == times_called
83+
84+
unsubscribe = store._subscribe(callback, keep_ref=False) # noqa: SLF001
85+
for _ in range(5):
86+
store.dispatch(IncrementAction())
87+
unsubscribe()
88+
for _ in range(5):
89+
store.dispatch(IncrementAction())
90+
91+
store.dispatch(FinishAction())
92+
93+
assert times_called == 5
94+
95+
96+
def test_keeping_ref_with_callback_deletion(store: StoreType) -> None:
97+
times_called = 0
98+
99+
def callback(state: StateType | None) -> None:
100+
nonlocal times_called
101+
times_called += 1
102+
assert state is not None
103+
assert state.value == times_called
104+
105+
unsubscribe = store._subscribe(callback) # noqa: SLF001
106+
del callback
107+
108+
for _ in range(5):
109+
store.dispatch(IncrementAction())
110+
unsubscribe()
111+
for _ in range(5):
112+
store.dispatch(IncrementAction())
113+
114+
store.dispatch(FinishAction())
115+
116+
assert times_called == 5
117+
118+
119+
def test_not_keeping_ref_with_callback_deletion(store: StoreType) -> None:
120+
times_called = 0
121+
122+
def callback(state: StateType | None) -> None:
123+
nonlocal times_called
124+
times_called += 1
125+
assert state is not None
126+
assert state.value == times_called
127+
128+
store._subscribe(callback, keep_ref=False) # noqa: SLF001
129+
130+
for _ in range(5):
131+
store.dispatch(IncrementAction())
132+
del callback
133+
for _ in range(5):
134+
store.dispatch(IncrementAction())
135+
136+
store.dispatch(FinishAction())
137+
138+
assert times_called == 5

0 commit comments

Comments
 (0)