diff --git a/tests/features/layers/activation-order/sketch.ino b/tests/features/layers/activation-order/sketch.ino
new file mode 100644
index 00000000..cf592980
--- /dev/null
+++ b/tests/features/layers/activation-order/sketch.ino
@@ -0,0 +1,98 @@
+/* -*- 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 "Kaleidoscope.h"
+
+#include "Kaleidoscope-Macros.h"
+
+// *INDENT-OFF*
+
+KEYMAPS(
+ [0] = KEYMAP_STACKED
+ (
+ Key_A ,Key_A ,Key_A ,Key_A ,Key_A ,Key_A ,Key_A
+ ,Key_A ,Key_A ,Key_A ,Key_A ,Key_A ,Key_A ,Key_A
+ ,Key_A ,Key_A ,Key_A ,Key_A ,Key_A ,Key_A
+ ,Key_A ,Key_A ,Key_A ,Key_A ,Key_A ,Key_A ,Key_A
+ ,Key_A ,Key_A ,Key_A ,M(0)
+ ,ShiftToLayer(1)
+
+ ,Key_A ,Key_A ,Key_A ,Key_A ,Key_A ,Key_A ,Key_A
+ ,Key_A ,Key_A ,Key_A ,Key_A ,Key_A ,Key_A ,Key_A
+ ,Key_A ,Key_A ,Key_A ,Key_A ,Key_A ,Key_A
+ ,Key_A ,Key_A ,Key_A ,Key_A ,Key_A ,Key_A ,Key_A
+ ,Key_A ,Key_A ,Key_A ,Key_A
+ ,ShiftToLayer(2)
+ ),
+
+ [1] = KEYMAP_STACKED
+ (
+ Key_B ,Key_B ,Key_B ,Key_B ,Key_B ,Key_B ,Key_B
+ ,Key_B ,Key_B ,Key_B ,Key_B ,Key_B ,Key_B ,Key_B
+ ,Key_B ,Key_B ,Key_B ,Key_B ,Key_B ,Key_B
+ ,Key_B ,Key_B ,Key_B ,Key_B ,Key_B ,Key_B ,Key_B
+ ,Key_B ,Key_B ,Key_B ,Key_B
+ ,___
+
+ ,Key_B ,Key_B ,Key_B ,Key_B ,Key_B ,Key_B ,Key_B
+ ,Key_B ,Key_B ,Key_B ,Key_B ,Key_B ,Key_B ,Key_B
+ ,Key_B ,Key_B ,Key_B ,Key_B ,Key_B ,Key_B
+ ,Key_B ,Key_B ,Key_B ,Key_B ,Key_B ,Key_B ,Key_B
+ ,Key_B ,Key_B ,Key_B ,Key_B
+ ,___
+ ),
+
+ [2] = KEYMAP_STACKED
+ (
+ Key_C ,Key_C ,Key_C ,Key_C ,Key_C ,Key_C ,Key_C
+ ,Key_C ,Key_C ,Key_C ,Key_C ,Key_C ,Key_C ,Key_C
+ ,Key_C ,Key_C ,Key_C ,Key_C ,Key_C ,Key_C
+ ,Key_C ,Key_C ,Key_C ,Key_C ,Key_C ,Key_C ,Key_C
+ ,Key_C ,Key_C ,Key_C ,Key_C
+ ,___
+
+ ,Key_C ,Key_C ,Key_C ,Key_C ,Key_C ,Key_C ,Key_C
+ ,Key_C ,Key_C ,Key_C ,Key_C ,Key_C ,Key_C ,Key_C
+ ,Key_C ,Key_C ,Key_C ,Key_C ,Key_C ,Key_C
+ ,Key_C ,Key_C ,Key_C ,Key_C ,Key_C ,Key_C ,Key_C
+ ,Key_C ,Key_C ,Key_C ,Key_C
+ ,___
+ )
+) // KEYMAPS(
+
+// *INDENT-ON*
+
+KALEIDOSCOPE_INIT_PLUGINS(Macros);
+
+const macro_t *macroAction(uint8_t macroIndex, uint8_t keyState) {
+ switch (macroIndex) {
+ case 0:
+ if (keyToggledOn(keyState))
+ Layer.deactivate(0);
+ else
+ Layer.activate(0);
+ break;
+ }
+ return MACRO_NONE;
+}
+
+void setup() {
+ Kaleidoscope.setup();
+}
+
+void loop() {
+ Kaleidoscope.loop();
+}
diff --git a/tests/features/layers/activation-order/test/testcase.cpp b/tests/features/layers/activation-order/test/testcase.cpp
new file mode 100644
index 00000000..31ab26c1
--- /dev/null
+++ b/tests/features/layers/activation-order/test/testcase.cpp
@@ -0,0 +1,177 @@
+/* -*- 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 {
+
+using ::testing::IsEmpty;
+
+class LayerActivationOrder : public VirtualDeviceTest {
+ public:
+ void SingleKeyTest(Key k) {
+ sim_.Press(0, 0); // k
+ auto state = RunCycle();
+
+ ASSERT_EQ(state->HIDReports()->Keyboard().size(), 1);
+ EXPECT_THAT(
+ state->HIDReports()->Keyboard(0).ActiveKeycodes(),
+ Contains(k));
+
+ sim_.Release(0, 0); // k
+ state = RunCycle();
+
+ ASSERT_EQ(state->HIDReports()->Keyboard().size(), 1);
+ EXPECT_THAT(
+ state->HIDReports()->Keyboard(0).ActiveKeycodes(),
+ IsEmpty());
+
+ state = RunCycle();
+
+ // 2 cycles after releasing k
+ EXPECT_EQ(state->HIDReports()->Keyboard().size(), 0);
+ }
+};
+
+TEST_F(LayerActivationOrder, BaseLayerHasNotRegressed) {
+ SingleKeyTest(Key_A);
+}
+
+TEST_F(LayerActivationOrder, ShifToLayerOne) {
+ // Pressing (3,6) shifts to Layer 1, and we stay there until release.
+
+ sim_.Press(3, 6); // ShiftToLayer(1)
+ auto state = RunCycle();
+
+ SingleKeyTest(Key_B);
+
+ // Releasing (3,6) gets us back to the base layer
+ sim_.Release(3, 6); // ShiftToLayer(1)
+ state = RunCycle();
+
+ SingleKeyTest(Key_A);
+}
+
+TEST_F(LayerActivationOrder, ShiftingWithCaching) {
+ // Pressing (0, 0) will activate A
+ sim_.Press(0, 0);
+ auto state = RunCycle();
+
+ ASSERT_EQ(state->HIDReports()->Keyboard().size(), 1);
+ EXPECT_THAT(
+ state->HIDReports()->Keyboard(0).ActiveKeycodes(),
+ Contains(Key_A));
+
+ // Pressing (3, 6) will switch to Layer 1
+ sim_.Press(3, 6);
+ state = RunCycle();
+
+ // ...since we're still pressing (0, 0), and there was no change in the HID
+ // states, we shouldn't emit a report.
+ ASSERT_EQ(state->HIDReports()->Keyboard().size(), 0);
+
+ // Pressing (0, 1), the report shall contain 'A' _and_ 'B'.
+ sim_.Press(0, 1);
+ state = RunCycle();
+
+ ASSERT_EQ(state->HIDReports()->Keyboard().size(), 1);
+ EXPECT_THAT(
+ state->HIDReports()->Keyboard(0).ActiveKeycodes(),
+ Contains(Key_A));
+ EXPECT_THAT(
+ state->HIDReports()->Keyboard(0).ActiveKeycodes(),
+ Contains(Key_B));
+
+ // Releasing (0, 0), the report should now contain B only
+ sim_.Release(0, 0);
+ state = RunCycle();
+
+ ASSERT_EQ(state->HIDReports()->Keyboard().size(), 1);
+ EXPECT_THAT(
+ state->HIDReports()->Keyboard(0).ActiveKeycodes(),
+ Contains(Key_B));
+
+ // Release (0, 1)
+ sim_.Release(0, 1);
+ state = RunCycle();
+
+ // Test B in isolation again
+ SingleKeyTest(Key_B);
+
+ // Release the layer key as well.
+ sim_.Release(3, 6);
+ state = RunCycle();
+
+ ASSERT_EQ(state->HIDReports()->Keyboard().size(), 0);
+}
+
+TEST_F(LayerActivationOrder, Ordering) {
+ // Pressing (3, 9) will switch to Layer 2
+ sim_.Press(3, 9);
+ auto state = RunCycle();
+
+ // Pressing (0, 0) will activate 'C'
+ sim_.Press(0, 0);
+ state = RunCycle();
+
+ ASSERT_EQ(state->HIDReports()->Keyboard().size(), 1);
+ EXPECT_THAT(
+ state->HIDReports()->Keyboard(0).ActiveKeycodes(),
+ Contains(Key_C));
+
+ // Pressing (3, 6) will activate Layer 1
+ sim_.Press(3, 6);
+ state = RunCycle();
+
+ // Pressing (0, 1) will activate 'B' now, due to activation ordering.
+ sim_.Press(0, 1);
+ state = RunCycle();
+
+ ASSERT_EQ(state->HIDReports()->Keyboard().size(), 1);
+ EXPECT_THAT(
+ state->HIDReports()->Keyboard(0).ActiveKeycodes(),
+ Contains(Key_C));
+ EXPECT_THAT(
+ state->HIDReports()->Keyboard(0).ActiveKeycodes(),
+ Contains(Key_B));
+
+ sim_.Release(0, 0);
+ sim_.Release(0, 1);
+ sim_.Release(3, 6);
+ sim_.Release(3, 9);
+ state = RunCycle();
+}
+
+TEST_F(LayerActivationOrder, LayerZero) {
+ sim_.Press(3, 7); // Macro#0: Layer.deactivate(0)
+ auto state = RunCycle();
+
+ sim_.Press(0, 0); // A
+ state = RunCycle();
+
+ ASSERT_EQ(state->HIDReports()->Keyboard().size(), 1);
+ EXPECT_THAT(
+ state->HIDReports()->Keyboard(0).ActiveKeycodes(),
+ Contains(Key_A));
+}
+
+} // namespace
+} // namespace testing
+} // namespace kaleidoscope