1- from  pathlib  import  Path 
2- import  sys 
3- from  collections .abc  import  Iterator , Sequence 
4- 
51import  pytest 
2+ from  wzrdbrain .wzrdbrain  import  Trick , generate_combo , DIRECTIONS , MOVES , STANCES , only_first 
3+ 
4+ 
5+ def  test_trick_creation_with_validation ():
6+     """Test that creating a Trick with invalid data raises a ValueError.""" 
7+     with  pytest .raises (ValueError , match = "Invalid move" ):
8+         Trick (move = "invalid_move" )
9+     with  pytest .raises (ValueError , match = "Invalid direction" ):
10+         Trick (direction = "invalid_direction" )
11+     with  pytest .raises (ValueError , match = "Invalid stance" ):
12+         Trick (stance = "invalid_stance" )
13+ 
14+ 
15+ def  test_trick_default_creation ():
16+     """Test creating a Trick with no arguments uses random defaults.""" 
17+     trick  =  Trick ()
18+     assert  isinstance (trick , Trick )
19+     assert  trick .move  in  MOVES 
20+     assert  trick .direction  in  DIRECTIONS 
21+     if  trick .move  not  in "predator" , "predator one" }:
22+         # Stance should be None for predator moves, otherwise it should be set 
23+         if  trick .stance :
24+             assert  trick .stance  in  STANCES 
25+ 
26+ 
27+ def  test_trick_str_representation ():
28+     """Test the string formatting of a Trick, including the 'fakie' logic.""" 
29+     trick1  =  Trick (direction = "front" , stance = "open" , move = "gazelle" )
30+     assert  str (trick1 ) ==  "front open gazelle" 
31+ 
32+     trick2  =  Trick (direction = "back" , move = "360" )
33+     assert  str (trick2 ) ==  "fakie 360" 
34+ 
35+     trick3  =  Trick (direction = "front" , move = "soul slide" )
36+     assert  str (trick3 ) ==  "forward soul slide" 
37+ 
38+ 
39+ def  test_trick_to_dict ():
40+     """Test the to_dict method includes the 'name' key.""" 
41+     trick  =  Trick (direction = "front" , stance = "open" , move = "gazelle" )
42+     trick_dict  =  trick .to_dict ()
43+     assert  isinstance (trick_dict , dict )
44+     assert  "name"  in  trick_dict 
45+     assert  trick_dict ["name" ] ==  "front open gazelle" 
46+     assert  trick_dict ["move" ] ==  "gazelle" 
47+ 
48+ 
49+ def  test_generate_combo_returns_list_of_dicts ():
50+     """Test that generate_combo returns a list of trick dictionaries.""" 
51+     combo  =  generate_combo (3 )
52+     assert  isinstance (combo , list )
53+     assert  len (combo ) ==  3 
54+     for  trick_dict  in  combo :
55+         assert  isinstance (trick_dict , dict )
56+         assert  "name"  in  trick_dict 
57+         assert  "move"  in  trick_dict 
58+ 
59+ 
60+ def  test_generate_combo_linking ():
61+     """Test that tricks in a combo are linked by their exit/enter directions.""" 
62+     # Generate a long combo to increase the chance of seeing rotation 
63+     combo  =  generate_combo (10 )
64+     for  i  in  range (len (combo ) -  1 ):
65+         current_trick  =  combo [i ]
66+         next_trick  =  combo [i  +  1 ]
67+         assert  current_trick ["exit_from_trick" ] ==  next_trick ["enter_into_trick" ]
68+ 
669
7- # Ensure src is on path so we can import the module under test 
8- ROOT  =  Path (__file__ ).resolve ().parents [1 ]
9- SRC  =  ROOT  /  "src" 
10- sys .path .insert (0 , str (SRC ))
11- 
12- import  wzrdbrain .wzrdbrain  as  wb   # noqa: E402 
13- 
14- 
15- def  test_generate_trick_exclude_stance (monkeypatch : pytest .MonkeyPatch ) ->  None :
16-     # Force choices so selected_move is in exclude_stance ('predator') 
17-     def  fake_choice (seq : Sequence [str ]) ->  str :
18-         if  seq  is  wb .move :
19-             return  "predator" 
20-         if  seq  is  wb .direction :
21-             return  "front" 
22-         if  seq  is  wb .stance :
23-             return  "open" 
24-         raise  RuntimeError ("unexpected seq" )
25- 
26-     # patch the module-level random.choice by target string to avoid mypy attr warnings 
27-     monkeypatch .setattr ("wzrdbrain.wzrdbrain.random.choice" , fake_choice )
28-     trick  =  wb .generate_trick ()
29-     assert  trick  ==  ["front" , "predator" ]
30- 
31- 
32- def  test_generate_trick_with_stance (monkeypatch : pytest .MonkeyPatch ) ->  None :
33-     # Force choices so selected_move is NOT in exclude_stance ('parallel') 
34-     def  fake_choice (seq : Sequence [str ]) ->  str :
35-         if  seq  is  wb .move :
36-             return  "parallel" 
37-         if  seq  is  wb .direction :
38-             return  "back" 
39-         if  seq  is  wb .stance :
40-             return  "open" 
41-         raise  RuntimeError ("unexpected seq" )
42- 
43-     monkeypatch .setattr ("wzrdbrain.wzrdbrain.random.choice" , fake_choice )
44-     trick  =  wb .generate_trick ()
45-     assert  trick  ==  ["back" , "open" , "parallel" ]
46- 
47- 
48- def  test_generate_combo_fakie_conversion (monkeypatch : pytest .MonkeyPatch ) ->  None :
49-     # Provide deterministic successive generate_trick outputs to test fakie/forward conversion 
50-     seq : Iterator [list [str ]] =  iter (
51-         [
52-             ["front" , "parallel" ],  # not in use_fakie -> unchanged 
53-             ["back" , "toe press" ],  # in use_fakie -> back -> fakie (since it's subsequent) 
54-             ["front" , "360" ],  # in use_fakie -> front -> forward 
55-         ]
56-     )
57- 
58-     def  fake_generate_trick () ->  list [str ]:
59-         return  next (seq )
60- 
61-     monkeypatch .setattr (wb , "generate_trick" , fake_generate_trick )
62-     line  =  wb .generate_combo (3 )
63-     assert  line  ==  [
64-         "front parallel" ,
65-         "fakie toe press" ,
66-         "forward 360" ,
67-     ]
68- 
69- 
70- def  test_generate_combo_default_length () ->  None :
71-     # Default should produce between 1 and 5 tricks 
72-     line  =  wb .generate_combo ()
73-     assert  1  <=  len (line ) <=  5 
74-     for  item  in  line :
75-         assert  isinstance (item , str )
70+ def  test_generate_combo_only_first_rule ():
71+     """Test that moves in 'only_first' do not appear after the first trick.""" 
72+     # Run multiple times to ensure rule is consistently applied 
73+     for  _  in  range (10 ):
74+         combo  =  generate_combo (5 )
75+         # Check all tricks after the first one 
76+         for  trick_dict  in  combo [1 :]:
77+             assert  trick_dict ["move" ] not  in only_first 
0 commit comments