@@ -31,7 +31,7 @@ def test_migrate_v1_config_adds_profile_apps_and_gesture_defaults(self):
3131
3232 migrated = config ._migrate (legacy )
3333
34- self .assertEqual (migrated ["version" ], 10 )
34+ self .assertEqual (migrated ["version" ], 12 )
3535 self .assertEqual (migrated ["profiles" ]["default" ]["apps" ], [])
3636 self .assertFalse (migrated ["settings" ]["invert_hscroll" ])
3737 self .assertFalse (migrated ["settings" ]["invert_vscroll" ])
@@ -73,7 +73,7 @@ def test_migrate_updates_media_player_profile_apps(self):
7373
7474 migrated = config ._migrate (cfg )
7575
76- self .assertEqual (migrated ["version" ], 10 )
76+ self .assertEqual (migrated ["version" ], 12 )
7777 self .assertEqual (
7878 migrated ["profiles" ]["media" ]["apps" ],
7979 ["Microsoft.Media.Player.exe" , "VLC.exe" ],
@@ -112,8 +112,10 @@ def test_load_config_merges_missing_defaults_from_disk(self):
112112 ):
113113 loaded = config .load_config ()
114114
115- self .assertEqual (loaded ["version" ], 10 )
115+ self .assertEqual (loaded ["version" ], 12 )
116116 self .assertEqual (loaded ["settings" ]["dpi" ], 800 )
117+ self .assertEqual (loaded ["settings" ]["action_haptic" ], [])
118+ self .assertTrue (loaded ["settings" ]["haptic_enabled" ])
117119 self .assertFalse (loaded ["settings" ]["start_at_login" ])
118120 self .assertEqual (loaded ["settings" ]["gesture_threshold" ], 50 )
119121 self .assertEqual (loaded ["settings" ]["appearance_mode" ], "system" )
@@ -136,7 +138,7 @@ def test_migrate_renames_start_with_windows_to_start_at_login(self):
136138
137139 migrated = config ._migrate (legacy )
138140
139- self .assertEqual (migrated ["version" ], 10 )
141+ self .assertEqual (migrated ["version" ], 12 )
140142 self .assertTrue (migrated ["settings" ]["start_at_login" ])
141143 self .assertEqual (
142144 migrated ["profiles" ]["default" ]["mappings" ]["mode_shift" ],
@@ -162,7 +164,7 @@ def test_migrate_v8_to_v9_adds_actions_ring_and_haptic(self):
162164
163165 migrated = config ._migrate (v8_cfg )
164166
165- self .assertEqual (migrated ["version" ], 10 )
167+ self .assertEqual (migrated ["version" ], 12 )
166168 self .assertEqual (
167169 migrated ["profiles" ]["default" ]["mappings" ]["actions_ring" ], "none"
168170 )
@@ -190,10 +192,60 @@ def test_migrate_v9_to_v10_adds_button_haptic(self):
190192
191193 migrated = config ._migrate (v9_cfg )
192194
193- self .assertEqual (migrated ["version" ], 10 )
195+ self .assertEqual (migrated ["version" ], 12 )
194196 self .assertEqual (migrated ["profiles" ]["default" ]["button_haptic" ], {})
195197 self .assertEqual (migrated ["profiles" ]["work" ]["button_haptic" ], {})
196198
199+ def test_migrate_v11_to_v12_adds_action_haptic_list (self ):
200+ v11_cfg = {
201+ "version" : 11 ,
202+ "active_profile" : "default" ,
203+ "profiles" : {
204+ "default" : {
205+ "label" : "Default" ,
206+ "apps" : [],
207+ "mappings" : {"middle" : "none" , "actions_ring" : "none" },
208+ }
209+ },
210+ "settings" : {"dpi" : 1000 , "haptic_level" : 2 , "haptic_enabled" : True },
211+ }
212+
213+ migrated = config ._migrate (v11_cfg )
214+
215+ self .assertEqual (migrated ["version" ], 12 )
216+ self .assertEqual (migrated ["settings" ]["action_haptic" ], [])
217+ # existing haptic settings preserved
218+ self .assertTrue (migrated ["settings" ]["haptic_enabled" ])
219+ self .assertEqual (migrated ["settings" ]["haptic_level" ], 2 )
220+
221+ def test_action_haptic_enabled_returns_false_when_missing (self ):
222+ cfg = {"settings" : {"action_haptic" : ["cycle_dpi" ]}}
223+ self .assertTrue (config .action_haptic_enabled (cfg , "cycle_dpi" ))
224+ self .assertFalse (config .action_haptic_enabled (cfg , "alt_tab" ))
225+
226+ def test_set_action_haptic_adds_and_removes (self ):
227+ cfg = {"settings" : {"action_haptic" : []}}
228+
229+ with patch .object (config , "save_config" , lambda c : c ):
230+ cfg = config .set_action_haptic (cfg , "cycle_dpi" , True )
231+ self .assertEqual (cfg ["settings" ]["action_haptic" ], ["cycle_dpi" ])
232+
233+ # Adding the same action twice is a no-op
234+ cfg = config .set_action_haptic (cfg , "cycle_dpi" , True )
235+ self .assertEqual (cfg ["settings" ]["action_haptic" ], ["cycle_dpi" ])
236+
237+ cfg = config .set_action_haptic (cfg , "volume_mute" , True )
238+ self .assertEqual (
239+ cfg ["settings" ]["action_haptic" ], ["cycle_dpi" , "volume_mute" ]
240+ )
241+
242+ cfg = config .set_action_haptic (cfg , "cycle_dpi" , False )
243+ self .assertEqual (cfg ["settings" ]["action_haptic" ], ["volume_mute" ])
244+
245+ # Removing a non-present action is a no-op
246+ cfg = config .set_action_haptic (cfg , "cycle_dpi" , False )
247+ self .assertEqual (cfg ["settings" ]["action_haptic" ], ["volume_mute" ])
248+
197249 def test_get_profile_for_app_matches_aliases (self ):
198250 cfg = {
199251 "app_overrides" : {},
0 commit comments