#include "test_support.h" #include #include #include "../../switch2_reports.h" namespace { SwitchInputState neutral_state() { SwitchInputState state{}; state.lx = SWITCH_PRO_JOYSTICK_MID; state.ly = SWITCH_PRO_JOYSTICK_MID; state.rx = SWITCH_PRO_JOYSTICK_MID; state.ry = SWITCH_PRO_JOYSTICK_MID; return state; } bool bytes_are_zero( const std::array& payload, std::size_t begin, std::size_t end) { for (std::size_t index = begin; index < end; ++index) { if (payload[index] != 0) return false; } return true; } bool switch2_neutral_reports_pack_documented_constants() { // Given: neutral normalized input and distinct counters. const SwitchInputState state = neutral_state(); // When: common and Pro reports are built. const Switch2InputReport common = switch2_build_input_report( Switch2InputReportId::Common, state, 0x78563412); const Switch2InputReport pro = switch2_build_input_report( Switch2InputReportId::Pro, state, 0x1234); // Then: IDs, counters, neutral sticks, and documented constants are exact. CHECK(static_cast(common.id) == 0x05); CHECK(static_cast(pro.id) == 0x09); CHECK(common.payload.size() + 1 == 64 && pro.payload.size() + 1 == 64); CHECK(common.payload[0] == 0x12 && common.payload[1] == 0x34); CHECK(common.payload[2] == 0x56 && common.payload[3] == 0x78); CHECK(common.payload[10] == 0xFF && common.payload[11] == 0xF7 && common.payload[12] == 0x7F); CHECK(common.payload[13] == 0xFF && common.payload[14] == 0xF7 && common.payload[15] == 0x7F); CHECK(common.payload[0x29] == 0x01); CHECK(pro.payload[0] == 0x34); CHECK(pro.payload[1] == 0x01); CHECK(pro.payload[5] == 0xFF && pro.payload[6] == 0xF7 && pro.payload[7] == 0x7F); CHECK(pro.payload[8] == 0xFF && pro.payload[9] == 0xF7 && pro.payload[10] == 0x7F); CHECK(pro.payload[11] == 0x30); return true; } bool switch2_report_counters_roll_over_deterministically() { // Given: counters at and beyond the Pro report's eight-bit boundary. const SwitchInputState state = neutral_state(); // When: reports are built at 255 and 256. const Switch2InputReport common = switch2_build_input_report( Switch2InputReportId::Common, state, 0xFFFFFFFF); const Switch2InputReport pro_255 = switch2_build_input_report( Switch2InputReportId::Pro, state, 255); const Switch2InputReport pro_256 = switch2_build_input_report( Switch2InputReportId::Pro, state, 256); // Then: common remains LE32 while Pro uses the low eight bits. CHECK(common.payload[0] == 0xFF && common.payload[1] == 0xFF); CHECK(common.payload[2] == 0xFF && common.payload[3] == 0xFF); CHECK(pro_255.payload[0] == 0xFF); CHECK(pro_256.payload[0] == 0x00); return true; } bool switch2_reports_map_every_shared_button() { struct ButtonCase { bool SwitchInputState::*field; uint8_t common_offset; uint8_t common_mask; uint8_t pro_offset; uint8_t pro_mask; }; static constexpr ButtonCase cases[] = { {&SwitchInputState::button_y, 4, 0x01, 2, 0x04}, {&SwitchInputState::button_b, 4, 0x04, 2, 0x01}, {&SwitchInputState::button_a, 4, 0x08, 2, 0x02}, {&SwitchInputState::button_x, 4, 0x02, 2, 0x08}, {&SwitchInputState::button_r, 4, 0x40, 2, 0x10}, {&SwitchInputState::button_zr, 4, 0x80, 2, 0x20}, {&SwitchInputState::button_minus, 5, 0x01, 3, 0x40}, {&SwitchInputState::button_plus, 5, 0x02, 2, 0x40}, {&SwitchInputState::button_r3, 5, 0x04, 2, 0x80}, {&SwitchInputState::button_l3, 5, 0x08, 3, 0x80}, {&SwitchInputState::button_home, 5, 0x10, 4, 0x01}, {&SwitchInputState::button_capture, 5, 0x20, 4, 0x02}, {&SwitchInputState::dpad_down, 6, 0x01, 3, 0x01}, {&SwitchInputState::dpad_up, 6, 0x02, 3, 0x08}, {&SwitchInputState::dpad_right, 6, 0x04, 3, 0x02}, {&SwitchInputState::dpad_left, 6, 0x08, 3, 0x04}, {&SwitchInputState::button_l, 6, 0x40, 3, 0x10}, {&SwitchInputState::button_zl, 6, 0x80, 3, 0x20}, }; // Given/When: every shared button is built independently in both reports. for (const ButtonCase& button : cases) { SwitchInputState state = neutral_state(); state.*(button.field) = true; const Switch2InputReport common = switch2_build_input_report( Switch2InputReportId::Common, state, 0); const Switch2InputReport pro = switch2_build_input_report( Switch2InputReportId::Pro, state, 0); // Then: only the captured byte and bit for that button is set. for (uint8_t offset = 4; offset <= 7; ++offset) { CHECK(common.payload[offset] == (offset == button.common_offset ? button.common_mask : 0)); } for (uint8_t offset = 2; offset <= 4; ++offset) { CHECK(pro.payload[offset] == (offset == button.pro_offset ? button.pro_mask : 0)); } } return true; } bool switch2_reports_pack_twelve_bit_stick_extremes() { // Given: four distinct normalized axis values. SwitchInputState state{}; state.lx = 0x0000; state.ly = 0xFFFF; state.rx = 0x1234; state.ry = 0xABCD; // When: both report formats are built. const Switch2InputReport common = switch2_build_input_report( Switch2InputReportId::Common, state, 0); const Switch2InputReport pro = switch2_build_input_report( Switch2InputReportId::Pro, state, 0); // Then: axes are reduced and packed in captured 12-bit little-endian form. const uint8_t expected_left[] = {0x00, 0xF0, 0xFF}; const uint8_t expected_right[] = {0x23, 0xC1, 0xAB}; for (uint8_t index = 0; index < 3; ++index) { CHECK(common.payload[10 + index] == expected_left[index]); CHECK(common.payload[13 + index] == expected_right[index]); CHECK(pro.payload[5 + index] == expected_left[index]); CHECK(pro.payload[8 + index] == expected_right[index]); } return true; } bool switch2_reports_zero_unknowns_and_ignore_imu() { // Given: populated IMU input that has no known Switch 2 packing. SwitchInputState state = neutral_state(); state.imu_sample_count = 3; state.imu_samples[0] = {1, 2, 3, 4, 5, 6}; state.imu_samples[1] = {7, 8, 9, 10, 11, 12}; state.imu_samples[2] = {13, 14, 15, 16, 17, 18}; // When: both reports are built twice from identical input. const Switch2InputReport common = switch2_build_input_report( Switch2InputReportId::Common, state, 7); const Switch2InputReport common_again = switch2_build_input_report( Switch2InputReportId::Common, state, 7); const Switch2InputReport pro = switch2_build_input_report( Switch2InputReportId::Pro, state, 7); // Then: unknown power/sensor/motion fields stay deterministic and zero. CHECK(common.payload == common_again.payload); CHECK(bytes_are_zero(common.payload, 8, 10)); CHECK(bytes_are_zero(common.payload, 16, 0x29)); CHECK(bytes_are_zero(common.payload, 0x2A, 63)); CHECK(bytes_are_zero(pro.payload, 12, 63)); return true; } } // namespace void run_switch2_report_tests(TestRunner& runner) { runner.run("Switch 2 neutral report constants", switch2_neutral_reports_pack_documented_constants); runner.run("Switch 2 report counter rollover", switch2_report_counters_roll_over_deterministically); runner.run("Switch 2 report button mapping", switch2_reports_map_every_shared_button); runner.run("Switch 2 report stick packing", switch2_reports_pack_twelve_bit_stick_extremes); runner.run("Switch 2 report unknown fields", switch2_reports_zero_unknowns_and_ignore_imu); }