diff --git a/tests/plugins/Redial/basic/sketch.ino b/tests/plugins/Redial/basic/sketch.ino
new file mode 100644
index 00000000..f6ba3301
--- /dev/null
+++ b/tests/plugins/Redial/basic/sketch.ino
@@ -0,0 +1,52 @@
+/* -*- mode: c++ -*-
+ * Copyright (C) 2020 Keyboard.io, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+
+#include
+#include
+
+// *INDENT-OFF*
+KEYMAPS(
+ [0] = KEYMAP_STACKED
+ (
+ Key_NoKey, Key_1, Key_2, Key_3, Key_4, Key_5, Key_NoKey,
+ Key_Backtick, Key_Q, Key_W, Key_E, Key_R, Key_T, Key_Tab,
+ Key_PageUp, Key_A, Key_S, Key_D, Key_F, Key_G,
+ Key_PageDown, Key_Z, Key_X, Key_C, Key_V, Key_B, Key_Escape,
+
+ Key_LeftControl, Key_Backspace, Key_LeftGui, Key_LeftShift,
+ XXX,
+
+ Key_Redial, Key_6, Key_7, Key_8, Key_9, Key_0, Key_skip,
+ Key_Enter, Key_Y, Key_U, Key_I, Key_O, Key_P, Key_Equals,
+ Key_H, Key_J, Key_K, Key_L, Key_Semicolon, Key_Quote,
+ Key_skip, Key_N, Key_M, Key_Comma, Key_Period, Key_Slash, Key_Minus,
+
+ Key_RightShift, Key_RightAlt, Key_Spacebar, Key_RightControl,
+ XXX
+ ),
+)
+// *INDENT-ON*
+
+// Use Redial
+KALEIDOSCOPE_INIT_PLUGINS(Redial);
+
+void setup() {
+ Kaleidoscope.setup();
+}
+
+void loop() {
+ Kaleidoscope.loop();
+}
diff --git a/tests/plugins/Redial/basic/test/testcase.cpp b/tests/plugins/Redial/basic/test/testcase.cpp
new file mode 100644
index 00000000..279b9607
--- /dev/null
+++ b/tests/plugins/Redial/basic/test/testcase.cpp
@@ -0,0 +1,170 @@
+/* -*- mode: c++ -*-
+ * Copyright (C) 2020 Keyboard.io, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+
+#include "testing/setup-googletest.h"
+
+SETUP_GOOGLETEST();
+
+namespace kaleidoscope {
+namespace testing {
+namespace {
+
+constexpr KeyAddr key_addr_Redial{0, 9};
+constexpr KeyAddr key_addr_A{2, 1};
+constexpr KeyAddr key_addr_X{3, 2};
+
+class RedialBasic : public VirtualDeviceTest {
+ protected:
+ std::set expected_keycodes_ = {};
+ std::unique_ptr state_ = nullptr;
+};
+
+TEST_F(RedialBasic, RedialFirst) {
+
+ // Press redial key
+ sim_.Press(key_addr_Redial);
+
+ state_ = RunCycle();
+
+ ASSERT_EQ(state_->HIDReports()->Keyboard().size(), 0)
+ << "There should be no HID report for a redial key without anything pressed first";
+
+ // Release redial key
+ sim_.Release(key_addr_Redial);
+ sim_.RunCycle();
+}
+
+TEST_F(RedialBasic, RedialFirstKey) {
+
+ // Press `A`
+ sim_.Press(key_addr_A);
+ state_ = RunCycle();
+
+ ASSERT_EQ(state_->HIDReports()->Keyboard().size(), 1)
+ << "There should be one report after letter key press";
+
+ expected_keycodes_.insert(Key_A.getKeyCode());
+ EXPECT_THAT(state_->HIDReports()->Keyboard(0).ActiveKeycodes(),
+ ::testing::ElementsAreArray(expected_keycodes_))
+ << "The report should include only `A`";
+
+ // Release `A`
+ sim_.Release(key_addr_A);
+ state_ = RunCycle();
+
+ ASSERT_EQ(state_->HIDReports()->Keyboard().size(), 1)
+ << "There should be one report after letter key release";
+
+ expected_keycodes_.erase(Key_A.getKeyCode());
+ EXPECT_THAT(state_->HIDReports()->Keyboard(0).ActiveKeycodes(),
+ ::testing::ElementsAreArray(expected_keycodes_))
+ << "The report should be empty";
+
+ sim_.RunCycle();
+
+ // Press redial key
+ sim_.Press(key_addr_Redial);
+
+ state_ = RunCycle();
+
+ ASSERT_EQ(state_->HIDReports()->Keyboard().size(), 1)
+ << "There should now be a redial-key report";
+
+ expected_keycodes_.insert(Key_A.getKeyCode());
+ EXPECT_THAT(state_->HIDReports()->Keyboard(0).ActiveKeycodes(),
+ ::testing::ElementsAreArray(expected_keycodes_))
+ << "The report should include only `A`";
+
+ // Release redial key
+ sim_.Release(key_addr_Redial);
+ state_ = RunCycle();
+
+ ASSERT_EQ(state_->HIDReports()->Keyboard().size(), 1)
+ << "There should be one report after redial-key release";
+
+ expected_keycodes_.erase(Key_A.getKeyCode());
+ EXPECT_THAT(state_->HIDReports()->Keyboard(0).ActiveKeycodes(),
+ ::testing::ElementsAreArray(expected_keycodes_))
+ << "The report should be empty";
+
+ sim_.RunCycle();
+
+ state_ = RunCycle();
+ ASSERT_EQ(state_->HIDReports()->Keyboard().size(), 0);
+}
+
+TEST_F(RedialBasic, RedialNextKey) {
+
+ // Press `X`
+ sim_.Press(key_addr_X);
+ state_ = RunCycle();
+
+ ASSERT_EQ(state_->HIDReports()->Keyboard().size(), 1)
+ << "There should be one report after letter key press";
+
+ expected_keycodes_.insert(Key_X.getKeyCode());
+ EXPECT_THAT(state_->HIDReports()->Keyboard(0).ActiveKeycodes(),
+ ::testing::ElementsAreArray(expected_keycodes_))
+ << "The report should include only `X`";
+
+ // Release `X`
+ sim_.Release(key_addr_X);
+ state_ = RunCycle();
+
+ ASSERT_EQ(state_->HIDReports()->Keyboard().size(), 1)
+ << "There should be one report after letter key release";
+
+ expected_keycodes_.erase(Key_X.getKeyCode());
+ EXPECT_THAT(state_->HIDReports()->Keyboard(0).ActiveKeycodes(),
+ ::testing::ElementsAreArray(expected_keycodes_))
+ << "The report should be empty";
+
+ sim_.RunCycle();
+
+ // Press redial key
+ sim_.Press(key_addr_Redial);
+
+ state_ = RunCycle();
+
+ ASSERT_EQ(state_->HIDReports()->Keyboard().size(), 1)
+ << "There should now be a redial-key report";
+
+ expected_keycodes_.insert(Key_X.getKeyCode());
+ EXPECT_THAT(state_->HIDReports()->Keyboard(0).ActiveKeycodes(),
+ ::testing::ElementsAreArray(expected_keycodes_))
+ << "The report should include only `X`";
+
+ // Release redial key
+ sim_.Release(key_addr_Redial);
+ state_ = RunCycle();
+
+ ASSERT_EQ(state_->HIDReports()->Keyboard().size(), 1)
+ << "There should be one report after redial-key release";
+
+ expected_keycodes_.erase(Key_X.getKeyCode());
+ EXPECT_THAT(state_->HIDReports()->Keyboard(0).ActiveKeycodes(),
+ ::testing::ElementsAreArray(expected_keycodes_))
+ << "The report should be empty";
+
+ sim_.RunCycle();
+
+ state_ = RunCycle();
+ ASSERT_EQ(state_->HIDReports()->Keyboard().size(), 0);
+}
+
+} // namespace
+} // namespace testing
+} // namespace kaleidoscope