from engine.config import KeyBindings from engine.gamepad import DEFAULT_GAMEPAD_BINDINGS, GamepadBindings, GamepadState from engine.input import InputState def test_move_key_sets_pending_move_and_updates_facing(): input_state = InputState() input_state.handle_event({"event_type": "key_down", "key": "d"}) assert input_state.consume_move() == (1, 0, 0) assert input_state.facing == (1, 0, 0) assert input_state.consume_move() is None # consumed def test_leap_key_uses_last_facing_direction(): input_state = InputState() input_state.handle_event({"event_type": "key_down", "key": "w"}) input_state.consume_move() input_state.handle_event({"event_type": "key_down", "key": " "}) assert input_state.consume_leap() == (0, -1, 0) assert input_state.consume_leap() is None def test_leap_key_before_any_move_uses_default_facing(): input_state = InputState() input_state.handle_event({"event_type": "key_down", "key": " "}) assert input_state.consume_leap() == (0, 1, 0) def test_key_up_events_are_ignored_for_movement(): input_state = InputState() input_state.handle_event({"event_type": "key_up", "key": "d"}) assert input_state.consume_move() is None def test_held_key_contributes_to_current_move_vector(): input_state = InputState() assert input_state.current_move_vector() == (0.0, 0.0, 0.0) input_state.handle_event({"event_type": "key_down", "key": "d"}) assert input_state.current_move_vector() == (1.0, 0.0, 0.0) def test_clear_held_state_drops_held_movement_and_blocking(): input_state = InputState() input_state.handle_event({"event_type": "key_down", "key": "d"}) input_state.handle_event({"event_type": "key_down", "key": "b"}) input_state.clear_held_state() assert input_state.current_move_vector() == (0.0, 0.0, 0.0) assert input_state.is_blocking is False def test_clear_held_state_drops_pending_one_shot_actions(): input_state = InputState() input_state.handle_event({"event_type": "key_down", "key": "d"}) input_state.handle_event({"event_type": "key_down", "key": " "}) input_state.clear_held_state() assert input_state.consume_move() is None assert input_state.consume_leap() is None assert input_state.consume_hand_activation() is None def test_swim_down_key_sets_a_one_shot_flag(): input_state = InputState() assert input_state.consume_swim_down() is False input_state.handle_event({"event_type": "key_down", "key": "Control"}) assert input_state.consume_swim_down() is True assert input_state.consume_swim_down() is False # consumed def test_activate_selected_ability_key_sets_a_one_shot_flag(): input_state = InputState() assert input_state.consume_activate_ability_bar() is False input_state.handle_event({"event_type": "key_down", "key": "e"}) assert input_state.consume_activate_ability_bar() is True assert input_state.consume_activate_ability_bar() is False # consumed def test_pickup_key_sets_a_one_shot_flag(): input_state = InputState() assert input_state.consume_pickup() is False input_state.handle_event({"event_type": "key_down", "key": "f"}) assert input_state.consume_pickup() is True assert input_state.consume_pickup() is False # consumed def test_clear_held_state_lets_a_continuously_held_gamepad_button_refire(): from engine.gamepad import DEFAULT_GAMEPAD_BINDINGS, GamepadBindings, GamepadState input_state = InputState() bindings = GamepadBindings(dict(DEFAULT_GAMEPAD_BINDINGS)) held = GamepadState(buttons={"gp_left_bumper": True}) input_state.apply_gamepad_state(held, bindings) assert input_state.is_blocking is True # simulate a pause: block gets reset even though the button is still physically held input_state.clear_held_state() assert input_state.is_blocking is False # on resume, the still-held button must re-fire its down edge, not be treated as unchanged input_state.apply_gamepad_state(held, bindings) assert input_state.is_blocking is True def test_releasing_a_held_key_removes_its_contribution(): input_state = InputState() input_state.handle_event({"event_type": "key_down", "key": "d"}) input_state.handle_event({"event_type": "key_up", "key": "d"}) assert input_state.current_move_vector() == (0.0, 0.0, 0.0) def test_holding_two_keys_combines_into_a_diagonal_vector(): input_state = InputState() input_state.handle_event({"event_type": "key_down", "key": "d"}) input_state.handle_event({"event_type": "key_down", "key": "s"}) assert input_state.current_move_vector() == (1.0, 1.0, 0.0) def test_opposite_held_keys_cancel_out(): input_state = InputState() input_state.handle_event({"event_type": "key_down", "key": "d"}) input_state.handle_event({"event_type": "key_down", "key": "a"}) assert input_state.current_move_vector() == (0.0, 0.0, 0.0) def test_block_key_is_a_held_state_not_a_one_shot(): input_state = InputState() assert input_state.is_blocking is False input_state.handle_event({"event_type": "key_down", "key": "b"}) assert input_state.is_blocking is True input_state.handle_event({"event_type": "key_up", "key": "b"}) assert input_state.is_blocking is False def test_mouse_left_click_activates_right_hand(): input_state = InputState() input_state.handle_event({"event_type": "pointer_down", "button": 1, "modifiers": ()}) assert input_state.consume_hand_activation() == "right_hand" assert input_state.consume_hand_activation() is None def test_mouse_right_click_activates_left_hand(): input_state = InputState() input_state.handle_event({"event_type": "pointer_down", "button": 2, "modifiers": ()}) assert input_state.consume_hand_activation() == "left_hand" def test_shift_plus_mouse_click_activates_the_second_hand_pair(): input_state = InputState() input_state.handle_event({"event_type": "pointer_down", "button": 1, "modifiers": ("Shift",)}) assert input_state.consume_hand_activation() == "right_hand_2" def test_button_zero_is_not_a_recognized_mouse_button(): # regression guard: button 0 isn't actually reported by this project's windowing backend # for any real click (left is 1, right is 2) - see MOUSE_BUTTON_KEYS's own comment. input_state = InputState() input_state.handle_event({"event_type": "pointer_down", "button": 0, "modifiers": ()}) assert input_state.consume_hand_activation() is None def test_pointer_move_tracks_position(): input_state = InputState() assert input_state.pointer_pos is None input_state.handle_event({"event_type": "pointer_move", "x": 12.5, "y": 34.0}) assert input_state.pointer_pos == (12.5, 34.0) def test_unbound_key_is_ignored(): input_state = InputState() input_state.handle_event({"event_type": "key_down", "key": "q"}) assert input_state.consume_move() is None assert input_state.consume_leap() is None def test_custom_keybindings_are_respected(): bindings = KeyBindings({"move_up": ["j"]}) input_state = InputState(bindings) input_state.handle_event({"event_type": "key_down", "key": "j"}) assert input_state.consume_move() == (0, -1, 0) def test_numkey_1_selects_ability_bar_slot_0(): input_state = InputState() input_state.handle_event({"event_type": "key_down", "key": "1"}) assert input_state.consume_ability_select() == 0 assert input_state.consume_ability_select() is None # consumed def test_numkey_0_selects_ability_bar_slot_9(): input_state = InputState() input_state.handle_event({"event_type": "key_down", "key": "0"}) assert input_state.consume_ability_select() == 9 def test_all_ten_numkeys_map_to_distinct_slots(): input_state = InputState() seen = set() for key in "1234567890": input_state.handle_event({"event_type": "key_down", "key": key}) seen.add(input_state.consume_ability_select()) assert seen == set(range(10)) # --- gamepad ------------------------------------------------------------------------------- def test_gamepad_left_stick_drives_current_move_vector(): input_state = InputState() bindings = GamepadBindings(dict(DEFAULT_GAMEPAD_BINDINGS)) input_state.apply_gamepad_state(GamepadState(axes=[0.8, -0.5, 0, 0, 0, 0]), bindings) assert input_state.current_move_vector() == (0.8, -0.5, 0.0) def test_gamepad_stick_within_deadzone_contributes_nothing(): input_state = InputState() bindings = GamepadBindings(dict(DEFAULT_GAMEPAD_BINDINGS)) input_state.apply_gamepad_state(GamepadState(axes=[0.05, -0.05, 0, 0, 0, 0]), bindings) assert input_state.current_move_vector() == (0.0, 0.0, 0.0) def test_gamepad_stick_adds_to_held_keyboard_movement(): input_state = InputState() input_state.handle_event({"event_type": "key_down", "key": "d"}) # move_right: (1, 0, 0) bindings = GamepadBindings(dict(DEFAULT_GAMEPAD_BINDINGS)) input_state.apply_gamepad_state(GamepadState(axes=[0.5, 0, 0, 0, 0, 0]), bindings) assert input_state.current_move_vector() == (1.5, 0.0, 0.0) def test_gamepad_button_press_triggers_bound_action(): input_state = InputState() bindings = GamepadBindings(dict(DEFAULT_GAMEPAD_BINDINGS)) input_state.apply_gamepad_state(GamepadState(buttons={"gp_a": True}), bindings) # gp_a -> leap assert input_state.consume_leap() == input_state.facing def test_gamepad_block_is_held_not_one_shot(): input_state = InputState() bindings = GamepadBindings(dict(DEFAULT_GAMEPAD_BINDINGS)) input_state.apply_gamepad_state(GamepadState(buttons={"gp_left_bumper": True}), bindings) assert input_state.is_blocking is True input_state.apply_gamepad_state(GamepadState(buttons={"gp_left_bumper": False}), bindings) assert input_state.is_blocking is False def test_gamepad_trigger_press_activates_bound_hand(): input_state = InputState() bindings = GamepadBindings(dict(DEFAULT_GAMEPAD_BINDINGS)) axes = [0.0] * 6 axes[4] = 0.9 # left trigger -> gp_left_trigger -> activate_left_hand_2 input_state.apply_gamepad_state(GamepadState(axes=axes), bindings) assert input_state.consume_hand_activation() == "left_hand_2" def test_gamepad_holding_a_button_across_frames_does_not_refire(): input_state = InputState() bindings = GamepadBindings(dict(DEFAULT_GAMEPAD_BINDINGS)) input_state.apply_gamepad_state(GamepadState(buttons={"gp_a": True}), bindings) input_state.consume_leap() input_state.apply_gamepad_state(GamepadState(buttons={"gp_a": True}), bindings) # still held assert input_state.consume_leap() is None # no new leap queued on the second frame def test_gamepad_unbound_button_is_ignored(): input_state = InputState() bindings = GamepadBindings(dict(DEFAULT_GAMEPAD_BINDINGS)) input_state.apply_gamepad_state(GamepadState(buttons={"gp_guide": True}), bindings) assert input_state.consume_leap() is None assert input_state.consume_hand_activation() is None