6
6
import json
7
7
import os
8
8
from collections import defaultdict
9
- from typing import TYPE_CHECKING , Any , cast
9
+ from pathlib import Path
10
+ from typing import TYPE_CHECKING , Any , Callable , Generic , cast
10
11
11
12
import pytest
12
13
13
- from redux .basic_types import FinishEvent
14
+ from redux .basic_types import FinishEvent , State
14
15
15
16
if TYPE_CHECKING :
16
- from pathlib import Path
17
-
18
17
from _pytest .fixtures import SubRequest
19
18
20
19
from redux .main import Store
21
20
22
21
23
- class StoreSnapshot :
22
+ class StoreSnapshot ( Generic [ State ]) :
24
23
"""Context object for tests taking snapshots of the store."""
25
24
26
25
def __init__ (
@@ -32,11 +31,14 @@ def __init__(
32
31
store : Store ,
33
32
) -> None :
34
33
"""Create a new store snapshot context."""
34
+ self ._is_failed = False
35
35
self ._is_closed = False
36
36
self .override = override
37
37
self .test_counter : dict [str | None , int ] = defaultdict (int )
38
38
file = path .with_suffix ('' ).name
39
- self .results_dir = path .parent / 'results' / file / test_id .split ('::' )[- 1 ][5 :]
39
+ self .results_dir = Path (
40
+ path .parent / 'results' / file / test_id .split ('::' )[- 1 ][5 :],
41
+ )
40
42
if self .results_dir .exists ():
41
43
for file in self .results_dir .glob (
42
44
'store-*.jsonc' if override else 'store-*.mismatch.jsonc' ,
@@ -47,12 +49,15 @@ def __init__(
47
49
self .store = store
48
50
store .subscribe_event (FinishEvent , self .close )
49
51
50
- @property
51
- def json_snapshot (self : StoreSnapshot ) -> str :
52
+ def json_snapshot (
53
+ self : StoreSnapshot [State ],
54
+ * ,
55
+ selector : Callable [[State ], Any ] = lambda state : state ,
56
+ ) -> str :
52
57
"""Return the snapshot of the current state of the store."""
53
58
return (
54
59
json .dumps (
55
- self .store .snapshot ,
60
+ self .store .serialize_value ( selector ( self . store . _state )), # noqa: SLF001
56
61
indent = 2 ,
57
62
sort_keys = True ,
58
63
ensure_ascii = False ,
@@ -61,13 +66,18 @@ def json_snapshot(self: StoreSnapshot) -> str:
61
66
else ''
62
67
)
63
68
64
- def get_filename (self : StoreSnapshot , title : str | None ) -> str :
69
+ def get_filename (self : StoreSnapshot [ State ] , title : str | None ) -> str :
65
70
"""Get the filename for the snapshot."""
66
71
if title :
67
72
return f"""store-{ title } -{ self .test_counter [title ]:03d} """
68
73
return f"""store-{ self .test_counter [title ]:03d} """
69
74
70
- def take (self : StoreSnapshot , * , title : str | None = None ) -> None :
75
+ def take (
76
+ self : StoreSnapshot [State ],
77
+ * ,
78
+ title : str | None = None ,
79
+ selector : Callable [[State ], Any ] = lambda state : state ,
80
+ ) -> None :
71
81
"""Take a snapshot of the current window."""
72
82
if self ._is_closed :
73
83
msg = (
@@ -81,29 +91,39 @@ def take(self: StoreSnapshot, *, title: str | None = None) -> None:
81
91
json_path = path .with_suffix ('.jsonc' )
82
92
mismatch_path = path .with_suffix ('.mismatch.jsonc' )
83
93
84
- new_snapshot = self .json_snapshot
94
+ new_snapshot = self .json_snapshot ( selector = selector )
85
95
if self .override :
86
96
json_path .write_text (f'// { filename } \n { new_snapshot } \n ' ) # pragma: no cover
87
97
else :
88
98
old_snapshot = None
89
99
if json_path .exists ():
90
100
old_snapshot = json_path .read_text ().split ('\n ' , 1 )[1 ][:- 1 ]
91
101
if old_snapshot != new_snapshot :
102
+ self ._is_failed = True
92
103
mismatch_path .write_text ( # pragma: no cover
93
104
f'// MISMATCH: { filename } \n { new_snapshot } \n ' ,
94
105
)
95
106
assert new_snapshot == old_snapshot , f'Store snapshot mismatch - { filename } '
96
107
97
108
self .test_counter [title ] += 1
98
109
99
- def close (self : StoreSnapshot ) -> None :
110
+ def monitor (self : StoreSnapshot [State ], selector : Callable [[State ], Any ]) -> None :
111
+ """Monitor the state of the store and take snapshots."""
112
+
113
+ @self .store .autorun (selector = selector )
114
+ def _ (state : State ) -> None :
115
+ self .take (selector = lambda _ : state )
116
+
117
+ def close (self : StoreSnapshot [State ]) -> None :
100
118
"""Close the snapshot context."""
119
+ self ._is_closed = True
120
+ if self ._is_failed :
121
+ return
101
122
for title in self .test_counter :
102
123
filename = self .get_filename (title )
103
124
json_path = (self .results_dir / filename ).with_suffix ('.jsonc' )
104
125
105
126
assert not json_path .exists (), f'Snapshot { filename } not taken'
106
- self ._is_closed = True
107
127
108
128
109
129
@pytest .fixture ()
0 commit comments