#include #include #include #include #include #include #include #include #include #include #include #include i16 GenerateAxisValueWithinThreshold(u16 threshold) { return rand() % threshold; } i16 GenerateAxisValueOutsideThreshold(u16 threshold) { return threshold + (rand() % (RAND_MAX - threshold - 1)) + 1; } spec("input") { describe("joystick") { describe("Initialize") { static IZ_JoystickState state[IZ_PLAYERS]; after_each() { mock_reset(IZ_memcpy); } after_each() { mock_reset(SDL_NumJoysticks); } after_each() { mock_reset(SDL_JoystickOpen); } after_each() { mock_reset(IZ_ConfigSave); } after_each() { mock_reset(IZ_ConfigInitialize); } it("sets initial state") { IZ_JoystickInitialize(&state, IZ_CONFIG_GAME_PATH, 0, NULL); check(mock_is_called(IZ_memcpy), "Initial state not loaded."); check(mock_is_called(SDL_NumJoysticks), "Connected joysticks not checked."); } it("calls load method") { IZ_JoystickInitialize(&state, IZ_CONFIG_GAME_PATH, 0, NULL); check(mock_is_called(IZ_ConfigInitialize), "Config load function not called."); } it("calls save method") { IZ_JoystickInitialize(&state, IZ_CONFIG_GAME_PATH, 0, NULL); check(mock_is_called(IZ_ConfigSave), "Config save function not called."); } it("opens device handles") { mock_set_expected_calls(SDL_JoystickOpen, MOCK_OPEN_JOYSTICKS); IZ_JoystickInitialize(&state, IZ_CONFIG_GAME_PATH, 0, NULL); check( mock_get_expected_calls(SDL_JoystickOpen) == mock_get_actual_calls(SDL_JoystickOpen), "Call count mismatch for SDL_JoystickOpen() (expected %u, received %u).", mock_get_expected_calls(SDL_JoystickOpen), mock_get_actual_calls(SDL_JoystickOpen) ); } } describe("HandleEvents") { static SDL_Event e; static IZ_JoystickState state[IZ_PLAYERS] = {}; static IZ_Action action[IZ_PLAYERS] = {}; u8 p; for (p = 0; p < IZ_PLAYERS; p += 1) { describe("on player %u", p) { describe("on axis motion events") { before_each() { e.type = SDL_JOYAXISMOTION; state[p].config.axis_threshold = 8000u; } describe("on primary horizontal direction") { before_each() { e.jaxis.axis = IZ_JOY_AXIS_DIRECTION_HORIZONTAL1; } it("handles positive motion") { e.jaxis.value = GenerateAxisValueOutsideThreshold(state[p].config.axis_threshold); action[p] = 0; printf("(axis value: %d) ", e.jaxis.value); IZ_JoystickHandleEvents(&state, &action, e); check( action[p] == (0x1 << IZ_ACTION_INDEX_RIGHT), "Action not set." ); } it("handles negative motion") { e.jaxis.value = -GenerateAxisValueOutsideThreshold(state[p].config.axis_threshold); action[p] = 0; printf("(axis value: %d) ", e.jaxis.value); IZ_JoystickHandleEvents(&state, &action, e); check( action[p] == (0x1 << IZ_ACTION_INDEX_LEFT), "Action not set." ); } it("handles neutral motion") { e.jaxis.value = GenerateAxisValueWithinThreshold(state[p].config.axis_threshold); action[p] = 0; printf("(axis value: %d) ", e.jaxis.value); IZ_JoystickHandleEvents(&state, &action, e); check( action[p] == 0, "Action not set." ); } } describe("on secondary horizontal direction") { before_each() { e.jaxis.axis = IZ_JOY_AXIS_DIRECTION_HORIZONTAL2; } it("handles positive motion") { e.jaxis.value = GenerateAxisValueOutsideThreshold(state[p].config.axis_threshold); action[p] = 0; printf("(axis value: %d) ", e.jaxis.value); IZ_JoystickHandleEvents(&state, &action, e); check( action[p] == (0x1 << IZ_ACTION_INDEX_RIGHT), "Action not set." ); } it("handles negative motion") { e.jaxis.value = -GenerateAxisValueOutsideThreshold(state[p].config.axis_threshold); action[p] = 0; printf("(axis value: %d) ", e.jaxis.value); IZ_JoystickHandleEvents(&state, &action, e); check( action[p] == (0x1 << IZ_ACTION_INDEX_LEFT), "Action not set." ); } it("handles neutral motion") { e.jaxis.value = GenerateAxisValueWithinThreshold(state[p].config.axis_threshold);; action[p] = 0; printf("(axis value: %d) ", e.jaxis.value); IZ_JoystickHandleEvents(&state, &action, e); check( action[p] == 0, "Action not set." ); } } describe("on primary vertical direction") { before_each() { e.jaxis.axis = IZ_JOY_AXIS_DIRECTION_VERTICAL1; } it("handles positive motion") { e.jaxis.value = GenerateAxisValueOutsideThreshold(state[p].config.axis_threshold); action[p] = 0; printf("(axis value: %d) ", e.jaxis.value); IZ_JoystickHandleEvents(&state, &action, e); check( action[p] == (0x1 << IZ_ACTION_INDEX_DOWN), "Action not set." ); } it("handles negative motion") { e.jaxis.value = -GenerateAxisValueOutsideThreshold(state[p].config.axis_threshold); action[p] = 0; printf("(axis value: %d) ", e.jaxis.value); IZ_JoystickHandleEvents(&state, &action, e); check( action[p] == (0x1 << IZ_ACTION_INDEX_UP), "Action not set." ); } it("handles neutral motion") { e.jaxis.value = GenerateAxisValueWithinThreshold(state[p].config.axis_threshold);; action[p] = 0; printf("(axis value: %d) ", e.jaxis.value); IZ_JoystickHandleEvents(&state, &action, e); check( action[p] == 0, "Action not set." ); } } describe("on secondary vertical direction") { before_each() { e.jaxis.axis = IZ_JOY_AXIS_DIRECTION_VERTICAL2; } it("handles positive motion") { e.jaxis.value = GenerateAxisValueOutsideThreshold(state[p].config.axis_threshold); action[p] = 0; printf("(axis value: %d) ", e.jaxis.value); IZ_JoystickHandleEvents(&state, &action, e); check( action[p] == (0x1 << IZ_ACTION_INDEX_DOWN), "Action not set." ); } it("handles negative motion") { e.jaxis.value = -GenerateAxisValueOutsideThreshold(state[p].config.axis_threshold); action[p] = 0; printf("(axis value: %d) ", e.jaxis.value); IZ_JoystickHandleEvents(&state, &action, e); check( action[p] == (0x1 << IZ_ACTION_INDEX_UP), "Action not set." ); } it("handles neutral motion") { e.jaxis.value = GenerateAxisValueWithinThreshold(state[p].config.axis_threshold);; action[p] = 0; printf("(axis value: %d) ", e.jaxis.value); IZ_JoystickHandleEvents(&state, &action, e); check( action[p] == 0, "Action not set." ); } } } describe("on hat motion events") { before_each() { e.type = SDL_JOYHATMOTION; } for (u8 i = 0; i < 4; i += 1) { it("handles motion for %s action", IZ_ACTION_NAMES[i]) { e.jhat.value = (0x1u << i); action[p] = 0; IZ_JoystickHandleEvents(&state, &action, e); check( action[p] == (0x1u << i), "Action not set." ); } it("handles motion for %s deactivation", IZ_ACTION_NAMES[i]) { e.jhat.value = 0; action[p] = ~0; IZ_JoystickHandleEvents(&state, &action, e); check( !(action[p] & (0x1 << i)), "Action not unset." ); } } } describe("on button events") { for (u8 i = 4; i < IZ_CONTROLS; i += 1) { it("handles %s action activation", IZ_ACTION_NAMES[i]) { e.type = SDL_JOYBUTTONDOWN; e.jbutton.button = IZ_JOYSTICK_DEFAULT_STATE[p].config.control_mapping[i]; state[p].config.control_mapping[i] = IZ_JOYSTICK_DEFAULT_STATE[p].config.control_mapping[i]; action[p] = 0; IZ_JoystickHandleEvents(&state, &action, e); check( action[p] == (0x1u << i), "Action not set." ); } it("handles %s action deactivation", IZ_ACTION_NAMES[i]) { e.type = SDL_JOYBUTTONUP; e.jbutton.button = IZ_JOYSTICK_DEFAULT_STATE[p].config.control_mapping[i]; state[p].config.control_mapping[i] = IZ_JOYSTICK_DEFAULT_STATE[p].config.control_mapping[i]; action[p] = ~0; IZ_JoystickHandleEvents(&state, &action, e); check( !(action[p] & (0x1 << i)), "Action not unset." ); } } } } } } describe("SaveConfig") { static IZ_JoystickState state[IZ_PLAYERS]; after_each() { mock_reset(IZ_ConfigSave); } before_each() { for (u8 p = 0; p < IZ_PLAYERS; p += 1) { for (u8 i = 0; i < IZ_CONTROLS; i += 1) { state[p].config.control_mapping[i] = IZ_JOYSTICK_DEFAULT_STATE[p].config.control_mapping[i]; } } } it("calls save method") { IZ_JoystickSaveConfig(&state, IZ_CONFIG_GAME_PATH); check(mock_is_called(IZ_ConfigSave), "Config save function not called."); } } describe("Teardown") { static void* device = (void*) 1; static IZ_JoystickState state[IZ_PLAYERS] = {}; before_each() { for (u8 p = 0; p < IZ_PLAYERS; p += 1) { state[p].device = device; } } after_each() { mock_reset(SDL_JoystickClose); } it("closes opened devices") { mock_set_expected_calls(SDL_JoystickClose, IZ_PLAYERS); IZ_JoystickTeardown(&state); check( mock_get_expected_calls(SDL_JoystickClose) == mock_get_actual_calls(SDL_JoystickClose), "Call count mismatch for SDL_JoystickClose() (expected %u, received %u).", mock_get_expected_calls(SDL_JoystickClose), mock_get_actual_calls(SDL_JoystickClose) ); } } } describe("keyboard") { describe("Initialize") { static IZ_KeyboardState state[IZ_PLAYERS] = {}; after_each() { mock_reset(IZ_memcpy); } after_each() { mock_reset(IZ_ConfigInitialize); } after_each() { mock_reset(IZ_ConfigSave); } before_each() { for (u8 p = 0; p < IZ_PLAYERS; p += 1) { for (u8 i = 0; i < IZ_CONTROLS; i += 1) { state[p].config.control_mapping[i] = IZ_KEYBOARD_DEFAULT_STATE[p].config.control_mapping[i]; } } } it("sets initial state") { IZ_KeyboardInitialize(&state, IZ_CONFIG_GAME_PATH, 0, NULL); check(mock_is_called(IZ_memcpy), "Initial state not loaded."); } it("calls load method") { IZ_KeyboardInitialize(&state, IZ_CONFIG_GAME_PATH, 0, NULL); check(mock_is_called(IZ_ConfigInitialize), "Config load function not called."); } it("calls save method") { IZ_KeyboardInitialize(&state, IZ_CONFIG_GAME_PATH, 0, NULL); check(mock_is_called(IZ_ConfigSave), "Config save function not called."); } } describe("HandleEvents") { static SDL_Event e; static IZ_KeyboardState state[IZ_PLAYERS] = {}; static IZ_Action action[IZ_PLAYERS] = {}; for (u8 p = 0; p < IZ_PLAYERS; p += 1) { describe("on player %u", p) { for (u8 i = 0; i < IZ_CONTROLS; i += 1) { it("handles %s action activation", IZ_ACTION_NAMES[i]) { e.type = SDL_KEYDOWN; e.key.keysym.sym = IZ_KEYBOARD_DEFAULT_STATE[p].config.control_mapping[i]; state[p].config.control_mapping[i] = IZ_KEYBOARD_DEFAULT_STATE[p].config.control_mapping[i]; action[p] = 0; IZ_KeyboardHandleEvents(&state, &action, e); check( action[p] == (0x1 << i), "Action not set." ); } it("handles %s action deactivation", IZ_ACTION_NAMES[i]) { e.type = SDL_KEYUP; e.key.keysym.sym = IZ_KEYBOARD_DEFAULT_STATE[p].config.control_mapping[i]; state[p].config.control_mapping[i] = IZ_KEYBOARD_DEFAULT_STATE[p].config.control_mapping[i]; action[p] = ~0; IZ_KeyboardHandleEvents(&state, &action, e); check( !(action[p] & (0x1 << i)), "Action not unset." ); } } } } } describe("SaveConfig") { static IZ_KeyboardState state[IZ_PLAYERS] = {}; after_each() { mock_reset(IZ_ConfigSave); } before_each() { for (u8 p = 0; p < IZ_PLAYERS; p += 1) { for (u8 i = 0; i < IZ_CONTROLS; i += 1) { state[p].config.control_mapping[i] = IZ_KEYBOARD_DEFAULT_STATE[p].config.control_mapping[i]; } } } it("calls save method") { IZ_KeyboardSaveConfig(&state, IZ_CONFIG_GAME_PATH); check(mock_is_called(IZ_ConfigSave), "Config save function not called."); } } } describe("midi") { describe("Initialize") { static IZ_MIDIInputState state[IZ_PLAYERS]; after_each() { mock_reset(IZ_memcpy); } after_each() { mock_reset(Pm_CountDevices); } after_each() { mock_reset(Pm_OpenInput); } after_each() { mock_reset(IZ_ConfigSave); } after_each() { mock_reset(IZ_ConfigInitialize); } after_each() { mock_reset(IZ_ConfigSave); } after_each() { mock_reset(IZ_ConfigInitialize); } it("sets initial state") { IZ_MIDIInputInitialize(&state, IZ_CONFIG_GAME_PATH, 0, NULL); check(mock_is_called(IZ_memcpy), "Initial state not loaded."); check(mock_is_called(Pm_CountDevices), "Connected MIDI devices not checked."); } it("calls load method") { IZ_MIDIInputInitialize(&state, IZ_CONFIG_GAME_PATH, 0, NULL); check(mock_is_called(IZ_ConfigInitialize), "Config load function not called."); } it("calls save method") { IZ_MIDIInputInitialize(&state, IZ_CONFIG_GAME_PATH, 0, NULL); check(mock_is_called(IZ_ConfigSave), "Config save function not called."); } it("opens device handles") { mock_set_expected_calls(Pm_OpenInput, MOCK_OPEN_JOYSTICKS); IZ_MIDIInputInitialize(&state, IZ_CONFIG_GAME_PATH, 0, NULL); check( mock_get_expected_calls(Pm_OpenInput) == mock_get_actual_calls(Pm_OpenInput), "Call count mismatch for Pm_OpenInput() (expected %u, received %u).", mock_get_expected_calls(Pm_OpenInput), mock_get_actual_calls(Pm_OpenInput) ); } } describe("SaveConfig") { static IZ_MIDIInputState state[IZ_PLAYERS]; after_each() { mock_reset(IZ_ConfigSave); } after_each() { mock_reset(IZ_ConfigSave); } it("calls save method") { IZ_MIDIInputSaveConfig(&state, IZ_CONFIG_GAME_PATH); check(mock_is_called(IZ_ConfigSave), "Config save function not called."); } } describe("HandleEvents") { static PmEvent e; static IZ_MIDIInputState state[IZ_PLAYERS] = {}; static IZ_Action action[IZ_PLAYERS] = {}; for (u8 p = 0; p < IZ_PLAYERS; p += 1) { describe("on player %u", p) { for (u8 i = 0; i < IZ_CONTROLS; i += 1) { it("handles %s action activation", IZ_ACTION_NAMES[i]) { e.message = IZ_MIDI_NOTE_ON | (IZ_MIDI_INPUT_DEFAULT_STATE[p].config.control_mapping[i] << 8); state[p].config.control_mapping[i] = IZ_MIDI_INPUT_DEFAULT_STATE[p].config.control_mapping[i]; action[p] = 0; IZ_MIDIInputHandleEvents(&state, &action, e); check( action[p] == (0x1 << i), "Action not set." ); } it("handles %s action deactivation", IZ_ACTION_NAMES[i]) { e.message = IZ_MIDI_NOTE_OFF | (IZ_MIDI_INPUT_DEFAULT_STATE[p].config.control_mapping[i] << 8); state[p].config.control_mapping[i] = IZ_MIDI_INPUT_DEFAULT_STATE[p].config.control_mapping[i]; action[p] = ~0; IZ_MIDIInputHandleEvents(&state, &action, e); check( !(action[p] & (0x1 << i)), "Action not unset." ); } } } } } describe("Teardown") { static PmStream* stream; static IZ_MIDIInputState state[IZ_PLAYERS] = {}; before_each() { for (u8 p = 0; p < IZ_PLAYERS; p += 1) { state[p].stream = &stream; } } after_each() { mock_reset(Pm_Close); } it("closes opened devices") { mock_set_expected_calls(Pm_Close, IZ_PLAYERS); IZ_MIDIInputTeardown(&state); check( mock_get_expected_calls(Pm_Close) == mock_get_actual_calls(Pm_Close), "Call count mismatch for Pm_Close() (expected %u, received %u).", mock_get_expected_calls(Pm_Close), mock_get_actual_calls(Pm_Close) ); } } } }