-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathtest_todo.py
More file actions
131 lines (105 loc) · 3.37 KB
/
test_todo.py
File metadata and controls
131 lines (105 loc) · 3.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# ruff: noqa: D100, D101, D103
from __future__ import annotations
import logging
import time
import uuid
from dataclasses import replace
from typing import TYPE_CHECKING
import pytest
from immutable import Immutable
from redux import (
BaseAction,
BaseEvent,
CompleteReducerResult,
FinishAction,
InitAction,
InitializationActionError,
ReducerResult,
Store,
StoreOptions,
)
from redux.basic_types import AutorunOptions
if TYPE_CHECKING:
from collections.abc import Sequence
from redux_pytest.fixtures import StoreSnapshot
# state:
class TodoItem(Immutable):
id: str
content: str
timestamp: float
class TodoState(Immutable):
items: Sequence[TodoItem]
# actions:
class AddTodoItemAction(BaseAction):
content: str
timestamp: float
class RemoveTodoItemAction(BaseAction):
id: str
# events:
class CallApiEvent(BaseEvent):
parameters: object
@pytest.fixture
def store() -> Store:
# reducer:
def reducer(
state: TodoState | None,
action: BaseAction,
) -> ReducerResult[TodoState, BaseAction, CallApiEvent]:
if state is None:
match action:
case InitAction():
return TodoState(
items=[
TodoItem(
id='first-item',
content='Initial Item',
timestamp=time.time(),
),
],
)
case _:
raise InitializationActionError(action)
match action:
case AddTodoItemAction():
return replace(
state,
items=[
*state.items,
TodoItem(
id=uuid.uuid4().hex,
content=action.content,
timestamp=action.timestamp,
),
],
)
case RemoveTodoItemAction():
return CompleteReducerResult(
state=replace(
state,
items=[item for item in state.items if item.id != action.id],
),
events=[CallApiEvent(parameters={})],
)
case _:
return state
return Store(reducer, options=StoreOptions(auto_init=True))
def test_todo(store_snapshot: StoreSnapshot, store: Store) -> None:
# subscription:
dummy_render = logging.getLogger().info
store._subscribe(dummy_render) # noqa: SLF001
# autorun:
@store.autorun(
lambda state: state.items[0].content if len(state.items) > 0 else None,
options=AutorunOptions(initial_call=False),
)
def reaction(_: str | None) -> None:
store_snapshot.take()
_ = reaction
# event listener, note that this will run async in a separate thread, so it can
# include async operations like API calls, etc:
dummy_api_call = logging.getLogger().info
store.subscribe_event(CallApiEvent, lambda event: dummy_api_call(event.parameters))
# dispatch:
store.dispatch(AddTodoItemAction(content='New Item', timestamp=time.time()))
store.dispatch(RemoveTodoItemAction(id='first-item'))
store.dispatch(FinishAction())