FazBrowse GitHub Viewer
|
Trending
|
URL:
|
Home
Tools:
[Download Repo ZIP]
[View Raw Code]
[Original HTTPS Page]
pythonnative/tests/test_mutations.py at preview-api-reference-fix · andevsr/pythonnative · GitHub
andevsr
/
pythonnative
Public
forked from
pythonnative/pythonnative
Notifications
You must be signed in to change notification settings
Fork
0
Star
0
Code
Pull requests
0
Actions
Projects
Security and quality
0
Insights
Additional navigation options
Code
Pull requests
Actions
Projects
Security and quality
Insights
Expand file tree
Breadcrumbs
pythonnative
/
tests
/
test_mutations.py
Copy path
More file actions
More file actions
Latest commit
History
History
History
239 lines (164 loc) · 8.1 KB
Breadcrumbs
pythonnative
/
tests
/
test_mutations.py
Copy path
File metadata and controls
239 lines (164 loc) · 8.1 KB
Raw
Copy raw file
Download raw file
Open symbols panel
Edit and raw actions
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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
"""Tests for the batched mutation protocol between reconciler and backend.
These tests pin down the *transaction semantics* of commits: what ops a
mount/re-render/unmount emits, how they're batched into single
``apply_mutations`` calls, and the minimality guarantees (no-op renders
emit nothing, frame diffing suppresses unchanged frames, callback
identity changes never cross the bridge).
"""
from
__future__
import
annotations
from
typing
import
Any
,
List
,
Tuple
from
fake_backend
import
FakeBackend
from
pythonnative
.
element
import
Element
from
pythonnative
.
events
import
dispatch_event
from
pythonnative
.
reconciler
import
Reconciler
def
_text
(
value
:
str
,
key
:
str
|
None
=
None
,
**
props
:
Any
)
->
Element
:
return
Element
(
"Text"
, {
"text"
:
value
,
**
props
}, [],
key
=
key
)
def
_column
(
*
children
:
Element
)
->
Element
:
return
Element
(
"Column"
, {},
list
(
children
))
def
_mounted
(
el
:
Element
)
->
Tuple
[
Reconciler
,
FakeBackend
]:
backend
=
FakeBackend
()
rec
=
Reconciler
(
backend
)
rec
.
_screen_re_render
=
lambda
:
None
rec
.
mount
(
el
)
return
rec
,
backend
def
_ops_since
(
backend
:
FakeBackend
,
marker
:
int
)
->
List
[
Any
]:
return
backend
.
ops
[
marker
:]
# ======================================================================
# Mount transactions
# ======================================================================
def
test_mount_commits_single_batch
()
->
None
:
_rec
,
backend
=
_mounted
(
_column
(
_text
(
"a"
),
_text
(
"b"
)))
# One apply_mutations call for the whole tree (layout is deferred
# until the host supplies a viewport).
assert
len
(
backend
.
batches
)
==
1
kinds
=
{
op
[
0
]
for
op
in
backend
.
batches
[
0
]}
assert
kinds
==
{
"create"
,
"insert_child"
}
def
test_mount_creates_parents_before_inserting_children
()
->
None
:
_rec
,
backend
=
_mounted
(
_column
(
_text
(
"a"
),
_text
(
"b"
)))
seen_kinds
=
[
op
[
0
]
for
op
in
backend
.
ops
]
first_insert
=
seen_kinds
.
index
(
"insert_child"
)
# Every create that an insert references precedes it; FakeBackend
# would raise on unknown tags, so here we just pin the shape: 3
# creates, 2 inserts.
assert
seen_kinds
[:
first_insert
].
count
(
"create"
)
>=
2
assert
seen_kinds
.
count
(
"create"
)
==
3
assert
seen_kinds
.
count
(
"insert_child"
)
==
2
def
test_layout_pass_flushes_frames_as_followup_batch
()
->
None
:
rec
,
backend
=
_mounted
(
_column
(
_text
(
"a"
),
_text
(
"b"
)))
marker
=
len
(
backend
.
ops
)
rec
.
set_viewport_size
(
320.0
,
640.0
)
new_ops
=
_ops_since
(
backend
,
marker
)
assert
new_ops
,
"viewport arrival must trigger a layout flush"
assert
{
op
[
0
]
for
op
in
new_ops
}
==
{
"set_frame"
}
# The root's frame is host-owned and never framed by layout.
root
=
backend
.
views
[
rec
.
root_tag
()]
assert
root
.
frame
==
(
0.0
,
0.0
,
0.0
,
0.0
)
# Children got real frames (FakeBackend reports 60x16 Text intrinsics).
text
=
root
.
find_first
(
"Text"
)
assert
text
is
not
None
assert
text
.
frame
[
2
]
>
0
and
text
.
frame
[
3
]
>
0
# ======================================================================
# Update minimality
# ======================================================================
def
test_prop_change_emits_minimal_update
()
->
None
:
rec
,
backend
=
_mounted
(
_column
(
_text
(
"a"
),
_text
(
"b"
)))
marker
=
len
(
backend
.
ops
)
rec
.
reconcile
(
_column
(
_text
(
"a"
),
_text
(
"B!"
)))
new_ops
=
_ops_since
(
backend
,
marker
)
assert
new_ops
==
[(
"update"
,
"Text"
,
new_ops
[
0
][
2
], (
"text"
,))]
def
test_identical_rerender_emits_nothing
()
->
None
:
rec
,
backend
=
_mounted
(
_column
(
_text
(
"a"
),
_text
(
"b"
)))
batches_before
=
len
(
backend
.
batches
)
rec
.
reconcile
(
_column
(
_text
(
"a"
),
_text
(
"b"
)))
assert
len
(
backend
.
batches
)
==
batches_before
def
test_callback_identity_change_never_crosses_the_bridge
()
->
None
:
hits
:
List
[
str
]
=
[]
def
first
()
->
None
:
hits
.
append
(
"first"
)
def
second
()
->
None
:
hits
.
append
(
"second"
)
rec
,
backend
=
_mounted
(
Element
(
"Button"
, {
"title"
:
"Go"
,
"on_press"
:
first
}, []))
tag
=
rec
.
root_tag
()
assert
tag
is
not
None
batches_before
=
len
(
backend
.
batches
)
rec
.
reconcile
(
Element
(
"Button"
, {
"title"
:
"Go"
,
"on_press"
:
second
}, []))
# No native traffic, yet the registry now routes to the new closure.
assert
len
(
backend
.
batches
)
==
batches_before
dispatch_event
(
tag
,
"on_press"
)
assert
hits
==
[
"second"
]
def
test_removed_prop_signaled_with_none
()
->
None
:
rec
,
backend
=
_mounted
(
_text
(
"a"
,
color
=
"#ff0000"
))
marker
=
len
(
backend
.
ops
)
rec
.
reconcile
(
_text
(
"a"
))
new_ops
=
_ops_since
(
backend
,
marker
)
assert
len
(
new_ops
)
==
1
assert
new_ops
[
0
][
0
]
==
"update"
and
new_ops
[
0
][
3
]
==
(
"color"
,)
assert
backend
.
views
[
rec
.
root_tag
()].
props
[
"color"
]
is
None
def
test_frame_diffing_suppresses_unchanged_frames
()
->
None
:
rec
,
backend
=
_mounted
(
_column
(
_text
(
"a"
),
_text
(
"b"
)))
rec
.
set_viewport_size
(
320.0
,
640.0
)
marker
=
len
(
backend
.
ops
)
# FakeBackend reports the same intrinsic size for any text, so this
# re-render changes a prop but no geometry.
rec
.
reconcile
(
_column
(
_text
(
"a"
),
_text
(
"c"
)))
new_ops
=
_ops_since
(
backend
,
marker
)
assert
[
op
[
0
]
for
op
in
new_ops
]
==
[
"update"
]
# ======================================================================
# Reorders and removals
# ======================================================================
def
test_keyed_reorder_moves_views_without_recreating
()
->
None
:
rec
,
backend
=
_mounted
(
_column
(
_text
(
"a"
,
key
=
"a"
),
_text
(
"b"
,
key
=
"b"
),
_text
(
"c"
,
key
=
"c"
)))
root
=
backend
.
views
[
rec
.
root_tag
()]
ids_before
=
[
c
.
id
for
c
in
root
.
children
]
marker
=
len
(
backend
.
ops
)
rec
.
reconcile
(
_column
(
_text
(
"c"
,
key
=
"c"
),
_text
(
"a"
,
key
=
"a"
),
_text
(
"b"
,
key
=
"b"
)))
new_ops
=
_ops_since
(
backend
,
marker
)
kinds
=
{
op
[
0
]
for
op
in
new_ops
}
assert
"create"
not
in
kinds
and
"destroy"
not
in
kinds
assert
"insert_child"
in
kinds
,
"a reorder is expressed as move-aware inserts"
assert
[
c
.
id
for
c
in
root
.
children
]
==
[
ids_before
[
2
],
ids_before
[
0
],
ids_before
[
1
]]
def
test_dropping_a_child_destroys_only_that_child
()
->
None
:
rec
,
backend
=
_mounted
(
_column
(
_text
(
"a"
,
key
=
"a"
),
_text
(
"b"
,
key
=
"b"
)))
marker
=
len
(
backend
.
ops
)
rec
.
reconcile
(
_column
(
_text
(
"a"
,
key
=
"a"
)))
new_ops
=
_ops_since
(
backend
,
marker
)
assert
[
op
[
0
]
for
op
in
new_ops
].
count
(
"destroy"
)
==
1
assert
backend
.
live_view_count
()
==
2
# Column + remaining Text
def
test_subtree_replacement_destroys_children_first
()
->
None
:
inner
=
Element
(
"Row"
, {}, [
_text
(
"deep"
)])
rec
,
backend
=
_mounted
(
_column
(
inner
))
root
=
backend
.
views
[
rec
.
root_tag
()]
row
=
root
.
find_first
(
"Row"
)
deep
=
root
.
find_first
(
"Text"
)
assert
row
is
not
None
and
deep
is
not
None
marker
=
len
(
backend
.
ops
)
# Changing the element type forces a teardown of the Row subtree.
rec
.
reconcile
(
_column
(
_text
(
"flat"
)))
destroys
=
[
op
for
op
in
_ops_since
(
backend
,
marker
)
if
op
[
0
]
==
"destroy"
]
assert
[
op
[
1
]
for
op
in
destroys
]
==
[
deep
.
id
,
row
.
id
],
"children precede parents"
def
test_unmount_destroys_every_view_in_one_batch
()
->
None
:
rec
,
backend
=
_mounted
(
_column
(
_text
(
"a"
),
_text
(
"b"
)))
batches_before
=
len
(
backend
.
batches
)
rec
.
unmount
()
assert
backend
.
live_view_count
()
==
0
assert
len
(
backend
.
batches
)
==
batches_before
+
1
assert
{
op
[
0
]
for
op
in
backend
.
batches
[
-
1
]}
==
{
"destroy"
}
# ======================================================================
# State-driven local re-renders
# ======================================================================
def
test_flush_dirty_commits_one_transaction_for_many_setters
()
->
None
:
from
pythonnative
.
hooks
import
component
,
use_state
setters
:
List
[
Any
]
=
[]
@
component
def
Cell
(
label
:
str
)
->
Element
:
value
,
set_value
=
use_state
(
0
)
setters
.
append
(
set_value
)
return
_text
(
f"
{
label
}
:
{
value
}
"
)
rec
,
backend
=
_mounted
(
_column
(
Cell
(
label
=
"x"
),
Cell
(
label
=
"y"
)))
batches_before
=
len
(
backend
.
batches
)
# Two independent components dirtied, one flush => one batch.
setters
[
0
](
1
)
setters
[
1
](
1
)
rec
.
flush_dirty
()
assert
len
(
backend
.
batches
)
==
batches_before
+
1
assert
[
op
[
0
]
for
op
in
backend
.
batches
[
-
1
]]
==
[
"update"
,
"update"
]
Back
|
FazBrowse Home
|
New Git URL