1f7d10902a7e603a47b9291664fcb9ab0b21f690

Author
Dasky <32983009+daskygit@users.noreply.github.com>
Committer
GitHub <noreply@github.com>
Date

Message

Add pointing tests (#24513)

Diff

This diff is truncated to protect this page.

   1diff --git a/builddefs/build_full_test.mk b/builddefs/build_full_test.mk
   2index 63f9fea915db61cbab3e638bc6febf7c441f66d3..5cae327c6c70a176e65594f397ea1feaa73bd199 100644
   3--- a/builddefs/build_full_test.mk
   4+++ b/builddefs/build_full_test.mk
   5@@ -21,8 +21,10 @@ $(TEST_OUTPUT)_SRC := \
   6 	$(SRC) \
   7 	$(QUANTUM_PATH)/keymap_introspection.c \
   8 	tests/test_common/matrix.c \
   9+	tests/test_common/pointing_device_driver.c \
  10 	tests/test_common/test_driver.cpp \
  11 	tests/test_common/keyboard_report_util.cpp \
  12+	tests/test_common/mouse_report_util.cpp \
  13 	tests/test_common/keycode_util.cpp \
  14 	tests/test_common/keycode_table.cpp \
  15 	tests/test_common/test_fixture.cpp \
  16diff --git a/tests/mousekeys/config.h b/tests/mousekeys/config.h
  17new file mode 100644
  18index 0000000000000000000000000000000000000000..76bbbe8daea89514bab9efe1103154f213d3779c
  19--- /dev/null
  20+++ b/tests/mousekeys/config.h
  21@@ -0,0 +1,6 @@
  22+// Copyright 2024 Dasky (@daskygit)
  23+// SPDX-License-Identifier: GPL-2.0-or-later
  24+
  25+#pragma once
  26+
  27+#include "test_common.h"
  28diff --git a/tests/mousekeys/test.mk b/tests/mousekeys/test.mk
  29new file mode 100644
  30index 0000000000000000000000000000000000000000..6c605daecf53f6a078e2fa1614efa013128f07de
  31--- /dev/null
  32+++ b/tests/mousekeys/test.mk
  33@@ -0,0 +1 @@
  34+MOUSEKEY_ENABLE = yes
  35diff --git a/tests/mousekeys/test_mousekeys.cpp b/tests/mousekeys/test_mousekeys.cpp
  36new file mode 100644
  37index 0000000000000000000000000000000000000000..a1b8e06cf7a309e5a43aa6b08b230ec57b497688
  38--- /dev/null
  39+++ b/tests/mousekeys/test_mousekeys.cpp
  40@@ -0,0 +1,109 @@
  41+// Copyright 2024 Dasky (@daskygit)
  42+// SPDX-License-Identifier: GPL-2.0-or-later
  43+
  44+#include "gtest/gtest.h"
  45+#include "mouse_report_util.hpp"
  46+#include "test_common.hpp"
  47+
  48+using testing::_;
  49+
  50+struct MouseKeyExpectations {
  51+    int16_t  x;
  52+    int16_t  y;
  53+    int16_t  h;
  54+    int16_t  v;
  55+    uint16_t button_mask;
  56+};
  57+
  58+class Mousekey : public TestFixture {};
  59+class MousekeyParametrized : public ::testing::WithParamInterface<std::pair<KeymapKey, MouseKeyExpectations>>, public Mousekey {};
  60+
  61+TEST_F(Mousekey, SendMouseNotCalledWhenNoKeyIsPressed) {
  62+    TestDriver driver;
  63+    EXPECT_NO_MOUSE_REPORT(driver);
  64+    run_one_scan_loop();
  65+}
  66+
  67+TEST_F(Mousekey, PressAndHoldCursorUpIsCorrectlyReported) {
  68+    TestDriver driver;
  69+    KeymapKey  mouse_key = KeymapKey{0, 0, 0, QK_MOUSE_CURSOR_UP};
  70+
  71+    set_keymap({mouse_key});
  72+
  73+    EXPECT_MOUSE_REPORT(driver, (0, -8, 0, 0, 0));
  74+    mouse_key.press();
  75+    run_one_scan_loop();
  76+
  77+    EXPECT_MOUSE_REPORT(driver, (0, -2, 0, 0, 0));
  78+    idle_for(MOUSEKEY_INTERVAL);
  79+
  80+    EXPECT_EMPTY_MOUSE_REPORT(driver);
  81+    mouse_key.release();
  82+    run_one_scan_loop();
  83+
  84+    VERIFY_AND_CLEAR(driver);
  85+}
  86+
  87+TEST_F(Mousekey, PressAndHoldButtonOneCorrectlyReported) {
  88+    TestDriver driver;
  89+    KeymapKey  mouse_key = KeymapKey{0, 0, 0, QK_MOUSE_BUTTON_1};
  90+
  91+    set_keymap({mouse_key});
  92+
  93+    EXPECT_MOUSE_REPORT(driver, (0, 0, 0, 0, 1));
  94+    mouse_key.press();
  95+    run_one_scan_loop();
  96+
  97+    idle_for(MOUSEKEY_INTERVAL);
  98+
  99+    EXPECT_EMPTY_MOUSE_REPORT(driver);
 100+    mouse_key.release();
 101+    run_one_scan_loop();
 102+
 103+    VERIFY_AND_CLEAR(driver);
 104+}
 105+
 106+TEST_P(MousekeyParametrized, PressAndReleaseIsCorrectlyReported) {
 107+    TestDriver           driver;
 108+    KeymapKey            mouse_key    = GetParam().first;
 109+    MouseKeyExpectations expectations = GetParam().second;
 110+
 111+    set_keymap({mouse_key});
 112+
 113+    EXPECT_MOUSE_REPORT(driver, (expectations.x, expectations.y, expectations.h, expectations.v, expectations.button_mask));
 114+    mouse_key.press();
 115+    run_one_scan_loop();
 116+
 117+    EXPECT_EMPTY_MOUSE_REPORT(driver);
 118+    mouse_key.release();
 119+    run_one_scan_loop();
 120+
 121+    EXPECT_NO_MOUSE_REPORT(driver);
 122+    run_one_scan_loop();
 123+
 124+    VERIFY_AND_CLEAR(driver);
 125+}
 126+// clang-format off
 127+INSTANTIATE_TEST_CASE_P(
 128+    Keys,
 129+    MousekeyParametrized,
 130+    ::testing::Values(
 131+        //                                Key               ,                           X, Y, H, V, Buttons Mask
 132+        std::make_pair(KeymapKey{0, 0, 0, QK_MOUSE_BUTTON_1},     MouseKeyExpectations{ 0, 0, 0, 0, 1}),
 133+        std::make_pair(KeymapKey{0, 0, 0, QK_MOUSE_BUTTON_2},     MouseKeyExpectations{ 0, 0, 0, 0, 2}),
 134+        std::make_pair(KeymapKey{0, 0, 0, QK_MOUSE_BUTTON_3},     MouseKeyExpectations{ 0, 0, 0, 0, 4}),
 135+        std::make_pair(KeymapKey{0, 0, 0, QK_MOUSE_BUTTON_4},     MouseKeyExpectations{ 0, 0, 0, 0, 8}),
 136+        std::make_pair(KeymapKey{0, 0, 0, QK_MOUSE_BUTTON_5},     MouseKeyExpectations{ 0, 0, 0, 0, 16}),
 137+        std::make_pair(KeymapKey{0, 0, 0, QK_MOUSE_BUTTON_6},     MouseKeyExpectations{ 0, 0, 0, 0, 32}),
 138+        std::make_pair(KeymapKey{0, 0, 0, QK_MOUSE_BUTTON_7},     MouseKeyExpectations{ 0, 0, 0, 0, 64}),
 139+        std::make_pair(KeymapKey{0, 0, 0, QK_MOUSE_BUTTON_8},     MouseKeyExpectations{ 0, 0, 0, 0, 128}),
 140diff --git a/tests/pointing/config.h b/tests/pointing/config.h
 141new file mode 100644
 142index 0000000000000000000000000000000000000000..76bbbe8daea89514bab9efe1103154f213d3779c
 143--- /dev/null
 144+++ b/tests/pointing/config.h
 145@@ -0,0 +1,6 @@
 146+// Copyright 2024 Dasky (@daskygit)
 147+// SPDX-License-Identifier: GPL-2.0-or-later
 148+
 149+#pragma once
 150+
 151+#include "test_common.h"
 152diff --git a/tests/pointing/invertandrotate/config.h b/tests/pointing/invertandrotate/config.h
 153new file mode 100644
 154index 0000000000000000000000000000000000000000..a3f3df7a54e8a56c1285003c32486d6dcd8da1f7
 155--- /dev/null
 156+++ b/tests/pointing/invertandrotate/config.h
 157@@ -0,0 +1,9 @@
 158+// Copyright 2024 Dasky (@daskygit)
 159+// SPDX-License-Identifier: GPL-2.0-or-later
 160+
 161+#pragma once
 162+
 163+#include "test_common.h"
 164+
 165+#define POINTING_DEVICE_INVERT_X
 166+#define POINTING_DEVICE_ROTATION_90
 167diff --git a/tests/pointing/invertandrotate/test.mk b/tests/pointing/invertandrotate/test.mk
 168new file mode 100644
 169index 0000000000000000000000000000000000000000..ba224d74f0abce7522bef05de75ede9d3552de6b
 170--- /dev/null
 171+++ b/tests/pointing/invertandrotate/test.mk
 172@@ -0,0 +1,2 @@
 173+POINTING_DEVICE_ENABLE = yes
 174+POINTING_DEVICE_DRIVER = custom
 175diff --git a/tests/pointing/invertandrotate/test_invertandrotate.cpp b/tests/pointing/invertandrotate/test_invertandrotate.cpp
 176new file mode 100644
 177index 0000000000000000000000000000000000000000..b47739f415b7cdc3043ac6416b20981f8842ae95
 178--- /dev/null
 179+++ b/tests/pointing/invertandrotate/test_invertandrotate.cpp
 180@@ -0,0 +1,59 @@
 181+// Copyright 2024 Dasky (@daskygit)
 182+// SPDX-License-Identifier: GPL-2.0-or-later
 183+
 184+#include "gtest/gtest.h"
 185+#include "mouse_report_util.hpp"
 186+#include "test_common.hpp"
 187+#include "test_pointing_device_driver.h"
 188+
 189+using testing::_;
 190+
 191+struct SimpleReport {
 192+    int16_t x;
 193+    int16_t y;
 194+    int16_t h;
 195+    int16_t v;
 196+};
 197+
 198+class Pointing : public TestFixture {};
 199+class PointingInvertAndRotateParametrized : public ::testing::WithParamInterface<std::pair<SimpleReport, SimpleReport>>, public Pointing {};
 200+
 201+TEST_P(PointingInvertAndRotateParametrized, PointingInvertAndRotateOrder) {
 202+    TestDriver   driver;
 203+    SimpleReport input        = GetParam().first;
 204+    SimpleReport expectations = GetParam().second;
 205+
 206+    pd_set_x(input.x);
 207+    pd_set_y(input.y);
 208+    pd_set_h(input.h);
 209+    pd_set_v(input.v);
 210+
 211+    EXPECT_MOUSE_REPORT(driver, (expectations.x, expectations.y, expectations.h, expectations.v, 0));
 212+    run_one_scan_loop();
 213+
 214+    // EXPECT_EMPTY_MOUSE_REPORT(driver);
 215+    pd_clear_movement();
 216+    run_one_scan_loop();
 217+
 218+    EXPECT_NO_MOUSE_REPORT(driver);
 219+    run_one_scan_loop();
 220+
 221+    VERIFY_AND_CLEAR(driver);
 222+}
 223+// clang-format off
 224+INSTANTIATE_TEST_CASE_P(
 225+    InvertAndRotate,
 226+    PointingInvertAndRotateParametrized,
 227+    ::testing::Values(
 228+        //                      Input                       Expected              // Actual Result - Rotate 90 then Invert X
 229+        std::make_pair(SimpleReport{ -1,  0, 0, 0}, SimpleReport{  0,  1, 0, 0}), // LEFT  - DOWN
 230+        std::make_pair(SimpleReport{  0, -1, 0, 0}, SimpleReport{  1,  0, 0, 0}), // UP    - RIGHT
 231+        std::make_pair(SimpleReport{  1,  0, 0, 0}, SimpleReport{  0, -1, 0, 0}), // RIGHT - UP
 232+        std::make_pair(SimpleReport{  0,  1, 0, 0}, SimpleReport{ -1,  0, 0, 0})  // DOWN  - LEFT
 233+        //                      Input                       Expected              // Invert X then Rotate 90
 234+        // std::make_pair(SimpleReport{ -1,  0, 0, 0}, SimpleReport{  0, -1, 0, 0}), // LEFT  - UP
 235+        // std::make_pair(SimpleReport{  0, -1, 0, 0}, SimpleReport{ -1,  0, 0, 0}), // UP    - LEFT
 236+        // std::make_pair(SimpleReport{  1,  0, 0, 0}, SimpleReport{  0,  1, 0, 0}), // RIGHT - DOWN
 237+        // std::make_pair(SimpleReport{  0,  1, 0, 0}, SimpleReport{  1,  0, 0, 0})  // DOWN  - RIGHT
 238+        ));
 239+// clang-format on
 240diff --git a/tests/pointing/invertxy/config.h b/tests/pointing/invertxy/config.h
 241new file mode 100644
 242index 0000000000000000000000000000000000000000..6b29185d371b32e236d36edaa607803a3738b8c2
 243--- /dev/null
 244+++ b/tests/pointing/invertxy/config.h
 245@@ -0,0 +1,9 @@
 246+// Copyright 2024 Dasky (@daskygit)
 247+// SPDX-License-Identifier: GPL-2.0-or-later
 248+
 249+#pragma once
 250+
 251+#include "test_common.h"
 252+
 253+#define POINTING_DEVICE_INVERT_X
 254+#define POINTING_DEVICE_INVERT_Y
 255diff --git a/tests/pointing/invertxy/test.mk b/tests/pointing/invertxy/test.mk
 256new file mode 100644
 257index 0000000000000000000000000000000000000000..ba224d74f0abce7522bef05de75ede9d3552de6b
 258--- /dev/null
 259+++ b/tests/pointing/invertxy/test.mk
 260@@ -0,0 +1,2 @@
 261+POINTING_DEVICE_ENABLE = yes
 262+POINTING_DEVICE_DRIVER = custom
 263diff --git a/tests/pointing/invertxy/test_invertxy.cpp b/tests/pointing/invertxy/test_invertxy.cpp
 264new file mode 100644
 265index 0000000000000000000000000000000000000000..014f6f3cd03253df630eb56b687238fd82fe1fdd
 266--- /dev/null
 267+++ b/tests/pointing/invertxy/test_invertxy.cpp
 268@@ -0,0 +1,53 @@
 269+// Copyright 2024 Dasky (@daskygit)
 270+// SPDX-License-Identifier: GPL-2.0-or-later
 271+
 272+#include "gtest/gtest.h"
 273+#include "mouse_report_util.hpp"
 274+#include "test_common.hpp"
 275+#include "test_pointing_device_driver.h"
 276+
 277+using testing::_;
 278+
 279+struct SimpleReport {
 280+    int16_t x;
 281+    int16_t y;
 282+    int16_t h;
 283+    int16_t v;
 284+};
 285+
 286+class Pointing : public TestFixture {};
 287+class PointingInvertXYParametrized : public ::testing::WithParamInterface<std::pair<SimpleReport, SimpleReport>>, public Pointing {};
 288+
 289+TEST_P(PointingInvertXYParametrized, PointingInvertXY) {
 290+    TestDriver   driver;
 291+    SimpleReport input        = GetParam().first;
 292+    SimpleReport expectations = GetParam().second;
 293+
 294+    pd_set_x(input.x);
 295+    pd_set_y(input.y);
 296+    pd_set_h(input.h);
 297+    pd_set_v(input.v);
 298+
 299+    EXPECT_MOUSE_REPORT(driver, (expectations.x, expectations.y, expectations.h, expectations.v, 0));
 300+    run_one_scan_loop();
 301+
 302+    // EXPECT_EMPTY_MOUSE_REPORT(driver);
 303+    pd_clear_movement();
 304+    run_one_scan_loop();
 305+
 306+    EXPECT_NO_MOUSE_REPORT(driver);
 307+    run_one_scan_loop();
 308+
 309+    VERIFY_AND_CLEAR(driver);
 310+}
 311+// clang-format off
 312+INSTANTIATE_TEST_CASE_P(
 313+    X_Y_XY,
 314+    PointingInvertXYParametrized,
 315+    ::testing::Values(
 316+        //                      Input                       Expected
 317+        std::make_pair(SimpleReport{ 33,    0, 0, 0}, SimpleReport{ -33,   0, 0, 0}),
 318+        std::make_pair(SimpleReport{  0, -127, 0, 0}, SimpleReport{   0, 127, 0, 0}),
 319+        std::make_pair(SimpleReport{  10, -20, 0, 0}, SimpleReport{ -10,  20, 0, 0})
 320+        ));
 321+// clang-format on
 322diff --git a/tests/pointing/rotate180/config.h b/tests/pointing/rotate180/config.h
 323new file mode 100644
 324index 0000000000000000000000000000000000000000..a80f5482fd4d9a7eb9bf6aeca84f047a2d55389f
 325--- /dev/null
 326+++ b/tests/pointing/rotate180/config.h
 327@@ -0,0 +1,8 @@
 328+// Copyright 2024 Dasky (@daskygit)
 329+// SPDX-License-Identifier: GPL-2.0-or-later
 330+
 331+#pragma once
 332+
 333+#include "test_common.h"
 334+
 335+#define POINTING_DEVICE_ROTATION_180
 336diff --git a/tests/pointing/rotate180/test.mk b/tests/pointing/rotate180/test.mk
 337new file mode 100644
 338index 0000000000000000000000000000000000000000..ba224d74f0abce7522bef05de75ede9d3552de6b
 339--- /dev/null
 340+++ b/tests/pointing/rotate180/test.mk
 341@@ -0,0 +1,2 @@
 342+POINTING_DEVICE_ENABLE = yes
 343+POINTING_DEVICE_DRIVER = custom
 344diff --git a/tests/pointing/rotate180/test_rotate180.cpp b/tests/pointing/rotate180/test_rotate180.cpp
 345new file mode 100644
 346index 0000000000000000000000000000000000000000..736cf9ea25ccd85dc0e307be9c577e50e957cb6c
 347--- /dev/null
 348+++ b/tests/pointing/rotate180/test_rotate180.cpp
 349@@ -0,0 +1,55 @@
 350+// Copyright 2024 Dasky (@daskygit)
 351+// SPDX-License-Identifier: GPL-2.0-or-later
 352+
 353+#include "gtest/gtest.h"
 354+#include "mouse_report_util.hpp"
 355+#include "test_common.hpp"
 356+#include "test_pointing_device_driver.h"
 357+
 358+using testing::_;
 359+
 360+struct SimpleReport {
 361+    int16_t x;
 362+    int16_t y;
 363+    int16_t h;
 364+    int16_t v;
 365+};
 366+
 367+class Pointing : public TestFixture {};
 368+class PointingRotateParametrized : public ::testing::WithParamInterface<std::pair<SimpleReport, SimpleReport>>, public Pointing {};
 369+
 370+TEST_P(PointingRotateParametrized, PointingRotateXY) {
 371+    TestDriver   driver;
 372+    SimpleReport input        = GetParam().first;
 373+    SimpleReport expectations = GetParam().second;
 374+
 375+    pd_set_x(input.x);
 376+    pd_set_y(input.y);
 377+    pd_set_h(input.h);
 378+    pd_set_v(input.v);
 379+
 380+    EXPECT_MOUSE_REPORT(driver, (expectations.x, expectations.y, expectations.h, expectations.v, 0));
 381+    run_one_scan_loop();
 382+
 383+    // EXPECT_EMPTY_MOUSE_REPORT(driver);
 384+    pd_clear_movement();
 385+    run_one_scan_loop();
 386+
 387+    EXPECT_NO_MOUSE_REPORT(driver);
 388+    run_one_scan_loop();
 389+
 390+    VERIFY_AND_CLEAR(driver);
 391+}
 392+// clang-format off
 393+INSTANTIATE_TEST_CASE_P(
 394+    Rotate180,
 395+    PointingRotateParametrized,
 396+    ::testing::Values(
 397+        //                      Input                       Expected
 398+        // Rotate Clockwise
 399+        std::make_pair(SimpleReport{  0,-1, 0, 0}, SimpleReport{ 0, 1, 0, 0}), // UP    - DOWN
 400+        std::make_pair(SimpleReport{  0, 1, 0, 0}, SimpleReport{ 0,-1, 0, 0}), // DOWN  - UP
 401+        std::make_pair(SimpleReport{  1, 0, 0, 0}, SimpleReport{-1, 0, 0, 0}), // RIGHT - LEFT
 402+        std::make_pair(SimpleReport{ -1, 0, 0, 0}, SimpleReport{ 1, 0, 0, 0})  // LEFT  - RIGHT
 403+        ));
 404+// clang-format on
 405diff --git a/tests/pointing/rotate270/config.h b/tests/pointing/rotate270/config.h
 406new file mode 100644
 407index 0000000000000000000000000000000000000000..3977dd95e6c36d7996049a6378957fd233252093
 408--- /dev/null
 409+++ b/tests/pointing/rotate270/config.h
 410@@ -0,0 +1,8 @@
 411+// Copyright 2024 Dasky (@daskygit)
 412+// SPDX-License-Identifier: GPL-2.0-or-later
 413+
 414+#pragma once
 415+
 416+#include "test_common.h"
 417+
 418+#define POINTING_DEVICE_ROTATION_270
 419diff --git a/tests/pointing/rotate270/test.mk b/tests/pointing/rotate270/test.mk
 420new file mode 100644
 421index 0000000000000000000000000000000000000000..ba224d74f0abce7522bef05de75ede9d3552de6b
 422--- /dev/null
 423+++ b/tests/pointing/rotate270/test.mk
 424@@ -0,0 +1,2 @@
 425+POINTING_DEVICE_ENABLE = yes
 426+POINTING_DEVICE_DRIVER = custom
 427diff --git a/tests/pointing/rotate270/test_rotate270.cpp b/tests/pointing/rotate270/test_rotate270.cpp
 428new file mode 100644
 429index 0000000000000000000000000000000000000000..6735f06248954a875155693c6d08fde73fd79e21
 430--- /dev/null
 431+++ b/tests/pointing/rotate270/test_rotate270.cpp
 432@@ -0,0 +1,60 @@
 433+// Copyright 2024 Dasky (@daskygit)
 434+// SPDX-License-Identifier: GPL-2.0-or-later
 435+
 436+#include "gtest/gtest.h"
 437+#include "mouse_report_util.hpp"
 438+#include "test_common.hpp"
 439+#include "test_pointing_device_driver.h"
 440+
 441+using testing::_;
 442+
 443+struct SimpleReport {
 444+    int16_t x;
 445+    int16_t y;
 446+    int16_t h;
 447+    int16_t v;
 448+};
 449+
 450+class Pointing : public TestFixture {};
 451+class PointingRotateParametrized : public ::testing::WithParamInterface<std::pair<SimpleReport, SimpleReport>>, public Pointing {};
 452+
 453+TEST_P(PointingRotateParametrized, PointingRotateXY) {
 454+    TestDriver   driver;
 455+    SimpleReport input        = GetParam().first;
 456+    SimpleReport expectations = GetParam().second;
 457+
 458+    pd_set_x(input.x);
 459+    pd_set_y(input.y);
 460+    pd_set_h(input.h);
 461+    pd_set_v(input.v);
 462+
 463+    EXPECT_MOUSE_REPORT(driver, (expectations.x, expectations.y, expectations.h, expectations.v, 0));
 464+    run_one_scan_loop();
 465+
 466+    // EXPECT_EMPTY_MOUSE_REPORT(driver);
 467+    pd_clear_movement();
 468+    run_one_scan_loop();
 469+
 470+    EXPECT_NO_MOUSE_REPORT(driver);
 471+    run_one_scan_loop();
 472+
 473+    VERIFY_AND_CLEAR(driver);
 474+}
 475+// clang-format off
 476+INSTANTIATE_TEST_CASE_P(
 477+    Rotate270,
 478+    PointingRotateParametrized,
 479+    ::testing::Values(
 480+        //                      Input                       Expected
 481+        // Actual Result - Rotate Anticlockwise
 482+        std::make_pair(SimpleReport{  0,-1, 0, 0}, SimpleReport{ 1, 0, 0, 0}), // UP    - RIGHT
 483+        std::make_pair(SimpleReport{  0, 1, 0, 0}, SimpleReport{-1, 0, 0, 0}), // DOWN  - LEFT
 484+        std::make_pair(SimpleReport{  1, 0, 0, 0}, SimpleReport{ 0, 1, 0, 0}), // RIGHT - DOWN
 485+        std::make_pair(SimpleReport{ -1, 0, 0, 0}, SimpleReport{ 0,-1, 0, 0})  // LEFT  - UP
 486+        // Rotate Clockwise
 487+        // std::make_pair(SimpleReport{  0,-1, 0, 0}, SimpleReport{-1, 0, 0, 0}), // UP    - LEFT
 488+        // std::make_pair(SimpleReport{  0, 1, 0, 0}, SimpleReport{ 1, 0, 0, 0}), // DOWN  - RIGHT
 489+        // std::make_pair(SimpleReport{  1, 0, 0, 0}, SimpleReport{ 0,-1, 0, 0}), // RIGHT - UP
 490+        // std::make_pair(SimpleReport{ -1, 0, 0, 0}, SimpleReport{ 0, 1, 0, 0})  // LEFT  - DOWN
 491+        ));
 492+// clang-format on
 493diff --git a/tests/pointing/rotate90/config.h b/tests/pointing/rotate90/config.h
 494new file mode 100644
 495index 0000000000000000000000000000000000000000..3a18ec92e947217a056f35741dc11db7fe578d8f
 496--- /dev/null
 497+++ b/tests/pointing/rotate90/config.h
 498@@ -0,0 +1,8 @@
 499+// Copyright 2024 Dasky (@daskygit)
 500+// SPDX-License-Identifier: GPL-2.0-or-later
 501+
 502+#pragma once
 503+
 504+#include "test_common.h"
 505+
 506+#define POINTING_DEVICE_ROTATION_90
 507diff --git a/tests/pointing/rotate90/test.mk b/tests/pointing/rotate90/test.mk
 508new file mode 100644
 509index 0000000000000000000000000000000000000000..ba224d74f0abce7522bef05de75ede9d3552de6b
 510--- /dev/null
 511+++ b/tests/pointing/rotate90/test.mk
 512@@ -0,0 +1,2 @@
 513+POINTING_DEVICE_ENABLE = yes
 514+POINTING_DEVICE_DRIVER = custom
 515diff --git a/tests/pointing/rotate90/test_rotate90.cpp b/tests/pointing/rotate90/test_rotate90.cpp
 516new file mode 100644
 517index 0000000000000000000000000000000000000000..5c44faad0ab4bdad6a70c64f8bbeb06040778f36
 518--- /dev/null
 519+++ b/tests/pointing/rotate90/test_rotate90.cpp
 520@@ -0,0 +1,60 @@
 521+// Copyright 2024 Dasky (@daskygit)
 522+// SPDX-License-Identifier: GPL-2.0-or-later
 523+
 524+#include "gtest/gtest.h"
 525+#include "mouse_report_util.hpp"
 526+#include "test_common.hpp"
 527+#include "test_pointing_device_driver.h"
 528+
 529+using testing::_;
 530+
 531+struct SimpleReport {
 532+    int16_t x;
 533+    int16_t y;
 534+    int16_t h;
 535+    int16_t v;
 536+};
 537+
 538+class Pointing : public TestFixture {};
 539+class PointingRotateParametrized : public ::testing::WithParamInterface<std::pair<SimpleReport, SimpleReport>>, public Pointing {};
 540+
 541+TEST_P(PointingRotateParametrized, PointingRotateXY) {
 542+    TestDriver   driver;
 543+    SimpleReport input        = GetParam().first;
 544+    SimpleReport expectations = GetParam().second;
 545+
 546+    pd_set_x(input.x);
 547+    pd_set_y(input.y);
 548+    pd_set_h(input.h);
 549+    pd_set_v(input.v);
 550+
 551+    EXPECT_MOUSE_REPORT(driver, (expectations.x, expectations.y, expectations.h, expectations.v, 0));
 552+    run_one_scan_loop();
 553+
 554+    // EXPECT_EMPTY_MOUSE_REPORT(driver);
 555+    pd_clear_movement();
 556+    run_one_scan_loop();
 557+
 558+    EXPECT_NO_MOUSE_REPORT(driver);
 559+    run_one_scan_loop();
 560+
 561+    VERIFY_AND_CLEAR(driver);
 562+}
 563+// clang-format off
 564+INSTANTIATE_TEST_CASE_P(
 565+    Rotate90,
 566+    PointingRotateParametrized,
 567+    ::testing::Values(
 568+        //                      Input                       Expected
 569+        // Actual Result - Rotate Anticlockwise
 570+        std::make_pair(SimpleReport{  0,-1, 0, 0}, SimpleReport{-1, 0, 0, 0}), // UP    - LEFT
 571+        std::make_pair(SimpleReport{  0, 1, 0, 0}, SimpleReport{ 1, 0, 0, 0}), // DOWN  - RIGHT
 572+        std::make_pair(SimpleReport{  1, 0, 0, 0}, SimpleReport{ 0,-1, 0, 0}), // RIGHT - UP
 573+        std::make_pair(SimpleReport{ -1, 0, 0, 0}, SimpleReport{ 0, 1, 0, 0})  // LEFT  - DOWN
 574+        // Rotate Clockwise
 575+        // std::make_pair(SimpleReport{  0,-1, 0, 0}, SimpleReport{ 1, 0, 0, 0}), // UP    - RIGHT
 576+        // std::make_pair(SimpleReport{  0, 1, 0, 0}, SimpleReport{-1, 0, 0, 0}), // DOWN  - LEFT
 577+        // std::make_pair(SimpleReport{  1, 0, 0, 0}, SimpleReport{ 0,-1, 0, 0}), // RIGHT - DOWN
 578+        // std::make_pair(SimpleReport{ -1, 0, 0, 0}, SimpleReport{ 0, 1, 0, 0})  // LEFT  - UP
 579+        ));
 580+// clang-format on
 581diff --git a/tests/pointing/test.mk b/tests/pointing/test.mk
 582new file mode 100644
 583index 0000000000000000000000000000000000000000..95fde21cb92ccb2d79ec3c03bf32e91009d33491
 584--- /dev/null
 585+++ b/tests/pointing/test.mk
 586@@ -0,0 +1,4 @@
 587+POINTING_DEVICE_ENABLE = yes
 588+MOUSEKEY_ENABLE = no
 589+POINTING_DEVICE_DRIVER = custom
 590+
 591diff --git a/tests/pointing/test_pointing.cpp b/tests/pointing/test_pointing.cpp
 592new file mode 100644
 593index 0000000000000000000000000000000000000000..d59d014925174b231e46f69733dac422305885cd
 594--- /dev/null
 595+++ b/tests/pointing/test_pointing.cpp
 596@@ -0,0 +1,166 @@
 597+// Copyright 2024 Dasky (@daskygit)
 598+// SPDX-License-Identifier: GPL-2.0-or-later
 599+
 600+#include "gtest/gtest.h"
 601+#include "mouse_report_util.hpp"
 602+#include "test_common.hpp"
 603+#include "test_pointing_device_driver.h"
 604+#include "mousekey.h"
 605+
 606+using testing::_;
 607+
 608+class Pointing : public TestFixture {};
 609+class PointingButtonsViaMousekeysParametrized : public ::testing::WithParamInterface<std::pair<KeymapKey, uint8_t>>, public Pointing {};
 610+
 611+TEST_F(Pointing, SendMouseIsNotCalledWithNoInput) {
 612+    TestDriver driver;
 613+    EXPECT_NO_MOUSE_REPORT(driver);
 614+    run_one_scan_loop();
 615+}
 616+
 617+TEST_F(Pointing, Xnegative) {
 618+    TestDriver driver;
 619+
 620+    pd_set_x(-10);
 621+    EXPECT_MOUSE_REPORT(driver, (-10, 0, 0, 0, 0));
 622+    run_one_scan_loop();
 623+
 624+    pd_clear_movement();
 625+    // EXPECT_EMPTY_MOUSE_REPORT(driver);
 626+    run_one_scan_loop();
 627+
 628+    VERIFY_AND_CLEAR(driver);
 629+}
 630+
 631+TEST_F(Pointing, Xpositive) {
 632+    TestDriver driver;
 633+
 634+    pd_set_x(10);
 635+    EXPECT_MOUSE_REPORT(driver, (10, 0, 0, 0, 0));
 636+    run_one_scan_loop();
 637+
 638+    pd_clear_movement();
 639+    // EXPECT_EMPTY_MOUSE_REPORT(driver);
 640+    run_one_scan_loop();
 641+
 642+    VERIFY_AND_CLEAR(driver);
 643+}
 644+
 645+TEST_F(Pointing, Ynegative) {
 646+    TestDriver driver;
 647+
 648+    pd_set_y(-20);
 649+    EXPECT_MOUSE_REPORT(driver, (0, -20, 0, 0, 0));
 650+    run_one_scan_loop();
 651+
 652+    pd_clear_movement();
 653+    // EXPECT_EMPTY_MOUSE_REPORT(driver);
 654+    run_one_scan_loop();
 655+
 656+    VERIFY_AND_CLEAR(driver);
 657+}
 658+
 659+TEST_F(Pointing, Ypositive) {
 660+    TestDriver driver;
 661+
 662+    pd_set_y(20);
 663+    EXPECT_MOUSE_REPORT(driver, (0, 20, 0, 0, 0));
 664+    run_one_scan_loop();
 665+
 666+    pd_clear_movement();
 667+    // EXPECT_EMPTY_MOUSE_REPORT(driver);
 668+    run_one_scan_loop();
 669+
 670+    VERIFY_AND_CLEAR(driver);
 671+}
 672+
 673+TEST_F(Pointing, XandY) {
 674+    TestDriver driver;
 675+
 676+    pd_set_x(-50);
 677+    pd_set_y(100);
 678+    EXPECT_MOUSE_REPORT(driver, (-50, 100, 0, 0, 0));
 679+    run_one_scan_loop();
 680+
 681+    pd_clear_movement();
 682+    // EXPECT_EMPTY_MOUSE_REPORT(driver);
 683+    run_one_scan_loop();
 684+
 685+    VERIFY_AND_CLEAR(driver);
 686+}
 687+
 688+TEST_F(Pointing, CorrectButtonIsReportedWhenPressed) {
 689+    TestDriver driver;
 690+
 691+    EXPECT_MOUSE_REPORT(driver, (0, 0, 0, 0, 1));
 692+    pd_press_button(POINTING_DEVICE_BUTTON1);
 693+    run_one_scan_loop();
 694+
 695+    EXPECT_EMPTY_MOUSE_REPORT(driver);
 696diff --git a/tests/test_common/mouse_report_util.cpp b/tests/test_common/mouse_report_util.cpp
 697new file mode 100644
 698index 0000000000000000000000000000000000000000..60cdeced24f5a6fce3454713fc28f401bba2282a
 699--- /dev/null
 700+++ b/tests/test_common/mouse_report_util.cpp
 701@@ -0,0 +1,58 @@
 702+/* Copyright 2017 Fred Sundvik
 703+ *
 704+ * This program is free software: you can redistribute it and/or modify
 705+ * it under the terms of the GNU General Public License as published by
 706+ * the Free Software Foundation, either version 2 of the License, or
 707+ * (at your option) any later version.
 708+ *
 709+ * This program is distributed in the hope that it will be useful,
 710+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 711+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 712+ * GNU General Public License for more details.
 713+ *
 714+ * You should have received a copy of the GNU General Public License
 715+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 716+ */
 717+
 718+#include "mouse_report_util.hpp"
 719+#include <cstdint>
 720+#include <vector>
 721+#include <algorithm>
 722+
 723+using namespace testing;
 724+
 725+bool operator==(const report_mouse_t& lhs, const report_mouse_t& rhs) {
 726+    return lhs.x == rhs.x && lhs.y == rhs.y && lhs.h == rhs.h && lhs.v == rhs.v && lhs.buttons == rhs.buttons;
 727+}
 728+
 729+std::ostream& operator<<(std::ostream& os, const report_mouse_t& report) {
 730+    os << std::setw(10) << std::left << "mouse report: ";
 731+
 732+    if (report.x == 0 && report.y == 0 && report.h == 0 && report.v == 0 && report.buttons == 0) {
 733+        return os << "empty" << std::endl;
 734+    }
 735+
 736+    os << "(X:" << (int)report.x << ", Y:" << (int)report.y << ", H:" << (int)report.h << ", V:" << (int)report.v << ", B:" << (int)report.buttons << ")";
 737+    return os << std::endl;
 738+}
 739+
 740+MouseReportMatcher::MouseReportMatcher(int16_t x, int16_t y, int8_t h, int8_t v, uint8_t button_mask) {
 741+    memset(&m_report, 0, sizeof(report_mouse_t));
 742+    m_report.x       = x;
 743+    m_report.y       = y;
 744+    m_report.h       = h;
 745+    m_report.v       = v;
 746+    m_report.buttons = button_mask;
 747+}
 748+
 749+bool MouseReportMatcher::MatchAndExplain(report_mouse_t& report, MatchResultListener* listener) const {
 750+    return m_report == report;
 751+}
 752+
 753+void MouseReportMatcher::DescribeTo(::std::ostream* os) const {
 754+    *os << "is equal to " << m_report;
 755+}
 756+
 757+void MouseReportMatcher::DescribeNegationTo(::std::ostream* os) const {
 758+    *os << "is not equal to " << m_report;
 759+}
 760diff --git a/tests/test_common/mouse_report_util.hpp b/tests/test_common/mouse_report_util.hpp
 761new file mode 100644
 762index 0000000000000000000000000000000000000000..645f6aa58c64f8f7833ddd6a6300ba5e9e246d59
 763--- /dev/null
 764+++ b/tests/test_common/mouse_report_util.hpp
 765@@ -0,0 +1,38 @@
 766+/* Copyright 2017 Fred Sundvik
 767+ *
 768+ * This program is free software: you can redistribute it and/or modify
 769+ * it under the terms of the GNU General Public License as published by
 770+ * the Free Software Foundation, either version 2 of the License, or
 771+ * (at your option) any later version.
 772+ *
 773+ * This program is distributed in the hope that it will be useful,
 774+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 775+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 776+ * GNU General Public License for more details.
 777+ *
 778+ * You should have received a copy of the GNU General Public License
 779+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 780+ */
 781+
 782+#pragma once
 783+#include "report.h"
 784+#include <ostream>
 785+#include "gmock/gmock.h"
 786+
 787+bool          operator==(const report_mouse_t& lhs, const report_mouse_t& rhs);
 788+std::ostream& operator<<(std::ostream& stream, const report_mouse_t& value);
 789+
 790+class MouseReportMatcher : public testing::MatcherInterface<report_mouse_t&> {
 791+   public:
 792+    MouseReportMatcher(int16_t x, int16_t y, int8_t h, int8_t v, uint8_t button_mask);
 793+    virtual bool MatchAndExplain(report_mouse_t& report, testing::MatchResultListener* listener) const override;
 794+    virtual void DescribeTo(::std::ostream* os) const override;
 795+    virtual void DescribeNegationTo(::std::ostream* os) const override;
 796+
 797+   private:
 798+    report_mouse_t m_report;
 799+};
 800+
 801+inline testing::Matcher<report_mouse_t&> MouseReport(int16_t x, int16_t y, int8_t h, int8_t v, uint8_t button_mask) {
 802+    return testing::MakeMatcher(new MouseReportMatcher(x, y, h, v, button_mask));
 803+}
 804diff --git a/tests/test_common/pointing_device_driver.c b/tests/test_common/pointing_device_driver.c
 805new file mode 100644
 806index 0000000000000000000000000000000000000000..b64dbad50635f3583120f1da0ff2a6bc4ceaaf82
 807--- /dev/null
 808+++ b/tests/test_common/pointing_device_driver.c
 809@@ -0,0 +1,109 @@
 810+// Copyright 2024 Dasky (@daskygit)
 811+// SPDX-License-Identifier: GPL-2.0-or-later
 812+
 813+#include "report.h"
 814+#include "test_pointing_device_driver.h"
 815+#include <string.h>
 816+
 817+typedef struct {
 818+    bool pressed;
 819+    bool dirty;
 820+} pd_button_state_t;
 821+
 822+typedef struct {
 823+    int16_t           x;
 824+    int16_t           y;
 825+    int16_t           h;
 826+    int16_t           v;
 827+    pd_button_state_t button_state[8];
 828+    uint16_t          cpi;
 829+    bool              initiated;
 830+} pd_config_t;
 831+
 832+static pd_config_t pd_config = {0};
 833+
 834+void pointing_device_driver_init(void) {
 835+    pd_set_init(true);
 836+}
 837+
 838+report_mouse_t pointing_device_driver_get_report(report_mouse_t mouse_report) {
 839+    for (uint8_t i = 0; i < 8; i++) {
 840+        if (pd_config.button_state[i].dirty) {
 841+            pd_config.button_state[i].dirty = false;
 842+            if (pd_config.button_state[i].pressed) {
 843+                mouse_report.buttons |= 1 << (i);
 844+            } else {
 845+                mouse_report.buttons &= ~(1 << (i));
 846+            }
 847+        }
 848+    }
 849+    mouse_report.x = pd_config.x;
 850+    mouse_report.y = pd_config.y;
 851+    mouse_report.h = pd_config.h;
 852+    mouse_report.v = pd_config.v;
 853+    return mouse_report;
 854+}
 855+
 856+__attribute__((weak)) uint16_t pointing_device_driver_get_cpi(void) {
 857+    return pd_config.cpi;
 858+}
 859+
 860+__attribute__((weak)) void pointing_device_driver_set_cpi(uint16_t cpi) {
 861+    pd_config.cpi = cpi;
 862+}
 863+
 864+void pd_press_button(uint8_t btn) {
 865+    pd_config.button_state[btn].dirty   = true;
 866+    pd_config.button_state[btn].pressed = true;
 867+}
 868+void pd_release_button(uint8_t btn) {
 869+    pd_config.button_state[btn].dirty   = true;
 870+    pd_config.button_state[btn].pressed = false;
 871+}
 872+
 873+void pd_clear_all_buttons(void) {
 874+    for (uint8_t i = 0; i < 8; i++) {
 875+        pd_config.button_state[i].dirty   = true;
 876+        pd_config.button_state[i].pressed = false;
 877+    }
 878+}
 879+
 880+void pd_set_x(int16_t x) {
 881+    pd_config.x = x;
 882+}
 883+
 884+void pd_clear_x(void) {
 885+    pd_set_x(0);
 886+}
 887+
 888+void pd_set_y(int16_t y) {
 889+    pd_config.y = y;
 890+}
 891+void pd_clear_y(void) {
 892+    pd_set_y(0);
 893+}
 894+
 895+void pd_set_h(int16_t h) {
 896+    pd_config.h = h;
 897+}
 898+void pd_clear_h(void) {
 899+    pd_set_h(0);
 900+}
 901+
 902+void pd_set_v(int16_t v) {
 903+    pd_config.v = v;
 904+}
 905+void pd_clear_v(void) {
 906+    pd_set_v(0);
 907+}
 908+
 909diff --git a/tests/test_common/test_driver.cpp b/tests/test_common/test_driver.cpp
 910index d410b225f9d5a6a6bcb7e18d6fb6af37182d68da..70b920ac86aba699efa5c296f82b18752eb49e79 100644
 911--- a/tests/test_common/test_driver.cpp
 912+++ b/tests/test_common/test_driver.cpp
 913@@ -54,6 +54,7 @@ void TestDriver::send_nkro(report_nkro_t* report) {
 914 }
 915 
 916 void TestDriver::send_mouse(report_mouse_t* report) {
 917+    test_logger.trace() << std::setw(10) << std::left << "send_mouse: (X:" << (int)report->x << ", Y:" << (int)report->y << ", H:" << (int)report->h << ", V:" << (int)report->v << ", B:" << (int)report->buttons << ")" << std::endl;
 918     m_this->send_mouse_mock(*report);
 919 }
 920 
 921diff --git a/tests/test_common/test_driver.hpp b/tests/test_common/test_driver.hpp
 922index ec75d3fff2268902d2876bca046f9b4e9e9eeb37..fea8225953a8e0f98e2cd471adeee6889ddb7369 100644
 923--- a/tests/test_common/test_driver.hpp
 924+++ b/tests/test_common/test_driver.hpp
 925@@ -66,6 +66,25 @@ class TestDriver {
 926  */
 927 #define EXPECT_REPORT(driver, report) EXPECT_CALL((driver), send_keyboard_mock(KeyboardReport report))
 928 
 929+/**
 930+ * @brief Sets gmock expectation that a mouse report of `report` will be sent.
 931+ * For this macro to parse correctly, the `report` arg must be surrounded by
 932+ * parentheses ( ). For instance,
 933+ *
 934+ *   // Expect that a report of "X:-10 Y:0 H:0 V:10 BTN:1 " is sent to the host.
 935+ *   EXPECT_REPORT(driver, (-10, 0, 0, 0, 1));
 936+ *
 937+ * is shorthand for
 938+ *
 939+ *   EXPECT_CALL(driver, send_mouse_mock(MouseReport(-10, 0, 0, 0, 1)));
 940+ *
 941+ * It is possible to use .Times() and other gmock APIS with EXPECT_REPORT, for instance,
 942+ * allow only single report to be sent:
 943+ *
 944+ *    EXPECT_REPORT(driver, (-10, 0, 0, 0, 1)).Times(1);
 945+ */
 946+#define EXPECT_MOUSE_REPORT(driver, report) EXPECT_CALL((driver), send_mouse_mock(MouseReport report))
 947+
 948 /**
 949  * @brief Sets gmock expectation that Unicode `code_point` is sent with UNICODE_MODE_LINUX input
 950  * mode. For instance for U+2013,
 951@@ -87,6 +106,15 @@ class TestDriver {
 952  */
 953 #define EXPECT_EMPTY_REPORT(driver) EXPECT_REPORT(driver, ())
 954 
 955+/**
 956+ * @brief Sets gmock expectation that a empty keyboard report will be sent.
 957+ * It is possible to use .Times() and other gmock APIS with EXPECT_EMPTY_MOUSE_REPORT, for instance,
 958+ * allow any number of empty reports with:
 959+ *
 960+ *   EXPECT_EMPTY_MOUSE_REPORT(driver).Times(AnyNumber());
 961+ */
 962+#define EXPECT_EMPTY_MOUSE_REPORT(driver) EXPECT_MOUSE_REPORT(driver, (0, 0, 0, 0, 0))
 963+
 964 /**
 965  * @brief Sets gmock expectation that a keyboard report will be sent, without matching its content.
 966  * It is possible to use .Times() and other gmock APIS with EXPECT_ANY_REPORT, for instance,
 967@@ -96,11 +124,25 @@ class TestDriver {
 968  */
 969 #define EXPECT_ANY_REPORT(driver) EXPECT_CALL((driver), send_keyboard_mock(_))
 970 
 971+/**
 972+ * @brief Sets gmock expectation that a mouse report will be sent, without matching its content.
 973+ * It is possible to use .Times() and other gmock APIS with EXPECT_ANY_MOUSE_REPORT, for instance,
 974+ * allow a single arbitrary report with:
 975+ *
 976+ *   EXPECT_ANY_MOUSE_REPORT(driver).Times(1);
 977+ */
 978+#define EXPECT_ANY_MOUSE_REPORT(driver) EXPECT_CALL((driver), send_mouse_mock(_))
 979+
 980 /**
 981  * @brief Sets gmock expectation that no keyboard report will be sent at all.
 982  */
 983 #define EXPECT_NO_REPORT(driver) EXPECT_ANY_REPORT(driver).Times(0)
 984 
 985+/**
 986+ * @brief Sets gmock expectation that no keyboard report will be sent at all.
 987+ */
 988+#define EXPECT_NO_MOUSE_REPORT(driver) EXPECT_ANY_MOUSE_REPORT(driver).Times(0)
 989+
 990 /** @brief Tests whether keycode `actual` is equal to `expected`. */
 991 #define EXPECT_KEYCODE_EQ(actual, expected) EXPECT_THAT((actual), KeycodeEq((expected)))
 992 
 993diff --git a/tests/test_common/test_fixture.cpp b/tests/test_common/test_fixture.cpp
 994index 3cfb2cb4c29235c499bf2c18fbefb5d8406215bd..81806b0e6d274bc95c7f531f0a47db41ce35f04a 100644
 995--- a/tests/test_common/test_fixture.cpp
 996+++ b/tests/test_common/test_fixture.cpp
 997@@ -7,6 +7,7 @@
 998 #include "gmock/gmock.h"
 999 #include "gtest/gtest.h"
1000 #include "keyboard_report_util.hpp"
1001+#include "mouse_report_util.hpp"
1002 #include "keycode.h"
1003 #include "test_driver.hpp"
1004 #include "test_logger.hpp"
1005@@ -69,6 +70,9 @@ TestFixture::~TestFixture() {
1006     /* Reset keyboard state. */
1007     clear_all_keys();
1008 
1009+#ifdef MOUSEKEY_ENABLE
1010+    EXPECT_EMPTY_MOUSE_REPORT(driver);
1011+#endif
1012     clear_keyboard();
1013 
1014     clear_oneshot_mods();
1015diff --git a/tests/test_common/test_pointing_device_driver.h b/tests/test_common/test_pointing_device_driver.h
1016new file mode 100644
1017index 0000000000000000000000000000000000000000..ae136b21cdf0eacb4ce12465d0d1dde04b5ab1c6
1018--- /dev/null
1019+++ b/tests/test_common/test_pointing_device_driver.h
1020@@ -0,0 +1,32 @@
1021+// Copyright 2024 Dasky (@daskygit)
1022+// SPDX-License-Identifier: GPL-2.0-or-later
1023+
1024+#pragma once
1025+
1026+#ifdef __cplusplus
1027+extern "C" {
1028+#endif
1029+
1030+void pd_press_button(uint8_t btn);
1031+void pd_release_button(uint8_t btn);
1032+void pd_clear_all_buttons(void);
1033+
1034+void pd_set_x(int16_t x);
1035+void clear_x(void);
1036+
1037+void pd_set_y(int16_t y);
1038+void pd_clear_y(void);
1039+
1040+void pd_set_h(int16_t h);
1041+void pd_clear_h(void);
1042+
1043+void pd_set_v(int16_t v);
1044+void pd_clear_v(void);
1045+
1046+void pd_clear_movement(void);
1047+
1048+void pd_set_init(bool success);
1049+
1050+#ifdef __cplusplus
1051+}
1052+#endif