#include #include #include "hal.h" #include "mock_hal.h" #include "blink_app.h" using ::testing::_; using ::testing::AnyNumber; using ::testing::Return; using ::testing::InSequence; using ::testing::HasSubstr; // ============================================================================ // Unit Tests -- verify exact HAL interactions // ============================================================================ class BlinkAppUnitTest : public ::testing::Test { protected: void SetUp() override { // Allow timing and serial calls by default so tests can focus // on the specific interactions they care about. ON_CALL(mock_, millis()).WillByDefault(Return(0)); ON_CALL(mock_, digitalRead(_)).WillByDefault(Return(HIGH)); EXPECT_CALL(mock_, serialBegin(_)).Times(AnyNumber()); EXPECT_CALL(mock_, serialPrintln(_)).Times(AnyNumber()); EXPECT_CALL(mock_, millis()).Times(AnyNumber()); } ::testing::NiceMock mock_; }; TEST_F(BlinkAppUnitTest, BeginConfiguresPins) { BlinkApp app(&mock_, 13, 2); EXPECT_CALL(mock_, pinMode(13, OUTPUT)).Times(1); EXPECT_CALL(mock_, pinMode(2, INPUT_PULLUP)).Times(1); EXPECT_CALL(mock_, serialBegin(115200)).Times(1); app.begin(); } TEST_F(BlinkAppUnitTest, StartsInSlowMode) { BlinkApp app(&mock_); app.begin(); EXPECT_FALSE(app.fastMode()); EXPECT_EQ(app.interval(), BlinkApp::SLOW_INTERVAL_MS); } TEST_F(BlinkAppUnitTest, TogglesLedAfterInterval) { BlinkApp app(&mock_); // begin() at t=0 ON_CALL(mock_, millis()).WillByDefault(Return(0)); app.begin(); // update() at t=500 should toggle LED ON_CALL(mock_, millis()).WillByDefault(Return(500)); EXPECT_CALL(mock_, digitalWrite(13, _)).Times(1); app.update(); } TEST_F(BlinkAppUnitTest, DoesNotToggleBeforeInterval) { BlinkApp app(&mock_); ON_CALL(mock_, millis()).WillByDefault(Return(0)); app.begin(); // update() at t=499 -- not enough time, no toggle ON_CALL(mock_, millis()).WillByDefault(Return(499)); EXPECT_CALL(mock_, digitalWrite(_, _)).Times(0); app.update(); } TEST_F(BlinkAppUnitTest, ButtonPressSwitchesToFastMode) { BlinkApp app(&mock_, 13, 2); ON_CALL(mock_, millis()).WillByDefault(Return(0)); ON_CALL(mock_, digitalRead(2)).WillByDefault(Return(HIGH)); app.begin(); // Simulate button press (falling edge: HIGH -> LOW) ON_CALL(mock_, digitalRead(2)).WillByDefault(Return(LOW)); EXPECT_CALL(mock_, serialPrintln(HasSubstr("FAST"))).Times(1); app.update(); EXPECT_TRUE(app.fastMode()); EXPECT_EQ(app.interval(), BlinkApp::FAST_INTERVAL_MS); } TEST_F(BlinkAppUnitTest, SecondButtonPressReturnsToSlowMode) { BlinkApp app(&mock_, 13, 2); ON_CALL(mock_, millis()).WillByDefault(Return(0)); ON_CALL(mock_, digitalRead(2)).WillByDefault(Return(HIGH)); app.begin(); // First press: fast mode ON_CALL(mock_, digitalRead(2)).WillByDefault(Return(LOW)); app.update(); EXPECT_TRUE(app.fastMode()); // Release ON_CALL(mock_, digitalRead(2)).WillByDefault(Return(HIGH)); app.update(); // Second press: back to slow ON_CALL(mock_, digitalRead(2)).WillByDefault(Return(LOW)); EXPECT_CALL(mock_, serialPrintln(HasSubstr("SLOW"))).Times(1); app.update(); EXPECT_FALSE(app.fastMode()); } TEST_F(BlinkAppUnitTest, HoldingButtonDoesNotRepeatToggle) { BlinkApp app(&mock_, 13, 2); ON_CALL(mock_, millis()).WillByDefault(Return(0)); ON_CALL(mock_, digitalRead(2)).WillByDefault(Return(HIGH)); app.begin(); // Press and hold ON_CALL(mock_, digitalRead(2)).WillByDefault(Return(LOW)); app.update(); EXPECT_TRUE(app.fastMode()); // Still held -- should NOT toggle again app.update(); app.update(); EXPECT_TRUE(app.fastMode()); // still fast, not toggled back }