55
66from __future__ import annotations
77
8+ import itertools
89from collections .abc import Callable
910from pathlib import Path
1011from typing import Any
2223from mhfu_studio .shell .workspace import Dock
2324from mhfu_studio .ui import kit , theme
2425from mhfu_studio .ui .testing import FakeWorkspace
25- from PySide6 .QtCore import QPoint , QPointF , Qt
26+ from PySide6 .QtCore import QPoint , QPointF , QRectF , Qt
2627from PySide6 .QtTest import QTest
2728from PySide6 .QtWidgets import QComboBox , QDockWidget , QToolButton
2829
@@ -178,6 +179,76 @@ def test_the_window_opens_the_dock(
178179 assert dock .height () > 2 * strip .height () and dock .geometry ().bottom () < strip .geometry ().top ()
179180
180181
182+ def open_hosted (
183+ make_window : Callable [..., Any ], qtbot : Any , port_doc : PortDocument
184+ ) -> tuple [MonsterWorkspace , BehaviourPanel ]:
185+ """The dock in a real `Window`, which syncs a turn after a change, over the `graph` port."""
186+ own = MonsterWorkspace ()
187+ own .doc = port_doc
188+ port_doc .edit (graph )
189+ host = Hosted (own )
190+ w = make_window (host )
191+ host .focus = "Behaviour"
192+ w .studio .changed ()
193+ w .sync ()
194+ qtbot .wait (60 )
195+ dock = w .findChild (QDockWidget , "monster/Behaviour" )
196+ assert dock is not None and isinstance (dock .widget (), BehaviourPanel )
197+ return own , dock .widget ()
198+
199+
200+ def applies (p : BehaviourPanel , node : str , name : str , monkeypatch : pytest .MonkeyPatch ) -> list [Any ]:
201+ """Every value of `node.name` the view is told to draw, in order."""
202+ seen : list [Any ] = []
203+ real = p .view ._apply
204+
205+ def apply (nodes : Any , links : Any ) -> None :
206+ seen .extend (n .values [name ] for n in nodes if n .id == node )
207+ real (nodes , links )
208+
209+ monkeypatch .setattr (p .view , "_apply" , apply )
210+ return seen
211+
212+
213+ def test_an_accepted_edit_applies_once_in_the_window (
214+ make_window : Callable [..., Any ],
215+ qtbot : Any ,
216+ port_doc : PortDocument ,
217+ monkeypatch : pytest .MonkeyPatch ,
218+ ) -> None :
219+ own , p = open_hosted (make_window , qtbot , port_doc )
220+ seen = applies (p , "b1" , "frames" , monkeypatch )
221+ field (p , "b1" , "frames" )._ctl .setValue (45 )
222+ qtbot .wait (80 )
223+ assert doc (own ).manifest .behaviour .blocks ["b1" ].params == {"frames" : 45 }
224+ assert seen == [45 ] and field (p , "b1" , "frames" ).get_value () == 45
225+
226+
227+ def test_a_refused_edit_reverts_once_in_the_window (
228+ make_window : Callable [..., Any ],
229+ qtbot : Any ,
230+ port_doc : PortDocument ,
231+ monkeypatch : pytest .MonkeyPatch ,
232+ ) -> None :
233+ blk = Block ("host_state" , (0.0 , 0.0 ), {"mains" : [0 ]})
234+ port_doc .edit (lambda m : m .behaviour .blocks .__setitem__ ("b4" , blk ))
235+ own , p = open_hosted (make_window , qtbot , port_doc )
236+ before = doc (own ).manifest
237+ seen = applies (p , "b4" , "mains" , monkeypatch )
238+ field (p , "b4" , "mains" )._ctl .menu ().actions ()[0 ].trigger () # unticks the only main state
239+ qtbot .wait (80 )
240+ assert doc (own ).manifest is before and "main states" in own .message
241+ assert seen == [[0 ]] and field (p , "b4" , "mains" ).get_value () == (0 ,)
242+
243+
244+ def test_a_block_made_in_the_window_is_selected (
245+ make_window : Callable [..., Any ], qtbot : Any , port_doc : PortDocument
246+ ) -> None :
247+ own , p = open_hosted (make_window , qtbot , port_doc )
248+ create (qtbot , p , KINDS ["on_noticed" ].title , QPoint (300 , 120 ))
249+ assert "b4" in doc (own ).manifest .behaviour .blocks and p .view .selected () == ["b4" ]
250+
251+
181252def test_empty (qtbot : Any ) -> None :
182253 p = make (qtbot , MonsterWorkspace ())
183254 assert p .pages .currentWidget () is p .no_scene
@@ -247,6 +318,22 @@ def test_the_zinogre_opens(
247318 assert field (p , "b2" , "hi" ).get_value () == 1000.0
248319
249320
321+ def test_a_new_name_keeps_every_node_size_on_the_zinogre (
322+ qtbot : Any , ws : MonsterWorkspace , zinogre_toml : Path , synthetic_pac : bytes
323+ ) -> None :
324+ from mhfu_studio .monster .core .scene import Scene
325+
326+ zin = PortDocument .open (zinogre_toml )
327+ ws .load (Scene .from_bytes (synthetic_pac , "z" , path = zinogre_toml .with_suffix (".bin" )), zin )
328+ p = make (qtbot , ws )
329+ p .view .grab () # a paint: the fitted graph is small, and NodeGraphQt hides small nodes' widgets
330+ sizes = {i : (s .view .width , s .view .height ) for i , s in p .view ._shown .items ()}
331+ flag = next (i for i , b in zin .manifest .behaviour .blocks .items () if "flag" in b .params )
332+ type_name (qtbot , p , flag , "flag" , "brand_new" )
333+ assert zin .manifest .behaviour .blocks [flag ].params ["flag" ] == "brand_new"
334+ assert {i : (s .view .width , s .view .height ) for i , s in p .view ._shown .items ()} == sizes
335+
336+
250337def test_spread_out_is_one_step (panel : BehaviourPanel , ws : MonsterWorkspace ) -> None :
251338 before = doc (ws ).manifest
252339 panel .spread .click ()
@@ -256,6 +343,21 @@ def test_spread_out_is_one_step(panel: BehaviourPanel, ws: MonsterWorkspace) ->
256343 assert doc (ws ).manifest == before
257344
258345
346+ def test_no_two_nodes_of_the_zinogre_overlap (
347+ qtbot : Any , ws : MonsterWorkspace , zinogre_toml : Path , synthetic_pac : bytes
348+ ) -> None :
349+ from mhfu_studio .monster .core .scene import Scene
350+
351+ zin = PortDocument .open (zinogre_toml )
352+ ws .load (Scene .from_bytes (synthetic_pac , "z" , path = zinogre_toml .with_suffix (".bin" )), zin )
353+ p = make (qtbot , ws )
354+ rects = {
355+ i : QRectF (* n .view .xy_pos , n .view .width , n .view .height ) for i , n in p .view ._shown .items ()
356+ }
357+ clash = [(a , b ) for a , b in itertools .combinations (rects , 2 ) if rects [a ].intersects (rects [b ])]
358+ assert clash == []
359+
360+
259361def test_part_and_main_options (panel : BehaviourPanel , ws : MonsterWorkspace ) -> None :
260362 doc (ws ).edit (lambda m : m .behaviour .blocks .__setitem__ ("b4" , Block ("on_flinch" , (0.0 , 0.0 ))))
261363 panel .sync ()
0 commit comments