#include #include #include "esphome/components/climate/climate.h" namespace esphome::climate::testing { // Minimal concrete Climate that offers a fixed set of modes, so the restore path can be exercised // without any hardware or platform component. class TestClimate : public Climate { public: ClimateTraits traits() override { auto traits = ClimateTraits(); traits.set_supported_modes({CLIMATE_MODE_OFF, CLIMATE_MODE_COOL}); traits.set_supported_fan_modes({CLIMATE_FAN_LOW, CLIMATE_FAN_HIGH}); return traits; } protected: void control(const ClimateCall &call) override {} }; TEST(ClimateRestoreStateTest, RestoresASupportedMode) { TestClimate climate; // Value-initialized: several members (mode, swing_mode, the temperature union) have no default // member initializer, so leaving the {} off would read indeterminate values. ClimateDeviceRestoreState state{}; state.mode = CLIMATE_MODE_COOL; state.apply(&climate); EXPECT_EQ(climate.mode, CLIMATE_MODE_COOL); } TEST(ClimateRestoreStateTest, DoesNotRestoreAnUnsupportedMode) { TestClimate climate; ClimateDeviceRestoreState state{}; state.mode = CLIMATE_MODE_HEAT; state.apply(&climate); // The device never advertised HEAT, so the mode stays where it was. EXPECT_EQ(climate.mode, CLIMATE_MODE_OFF); } TEST(ClimateRestoreStateTest, LeavesTheCurrentModeAloneRatherThanForcingOff) { TestClimate climate; // apply() is public and nothing restricts it to setup(), so the entity is not necessarily off // when an unsupported mode is dropped. It keeps what it had rather than being forced to OFF. climate.mode = CLIMATE_MODE_COOL; ClimateDeviceRestoreState state{}; state.mode = CLIMATE_MODE_HEAT; state.apply(&climate); EXPECT_EQ(climate.mode, CLIMATE_MODE_COOL); } TEST(ClimateRestoreStateTest, KeepsRestoringTheOtherFieldsWhenTheModeIsDropped) { TestClimate climate; ClimateDeviceRestoreState state{}; state.mode = CLIMATE_MODE_HEAT; state.target_temperature = 21.0f; state.uses_custom_fan_mode = false; state.fan_mode = CLIMATE_FAN_HIGH; state.apply(&climate); EXPECT_EQ(climate.mode, CLIMATE_MODE_OFF); EXPECT_FLOAT_EQ(climate.target_temperature, 21.0f); // Compared as an optional: this asserts both that the fan mode was restored and what it holds. EXPECT_EQ(climate.fan_mode, CLIMATE_FAN_HIGH); } static constexpr const char *const FAN_TABLE[] = {"Turbo", "Silent"}; TEST(ClimateCustomModesTest, StaticTableIsViewedNotCopied) { TestClimate climate; climate.set_supported_custom_fan_modes_static(FAN_TABLE, 2); auto traits = climate.get_traits(); ASSERT_EQ(traits.get_supported_custom_fan_modes().size(), 2u); EXPECT_EQ(traits.get_supported_custom_fan_modes().data(), FAN_TABLE); EXPECT_TRUE(traits.get_supports_fan_modes()); EXPECT_TRUE(traits.supports_custom_fan_mode("Silent")); EXPECT_FALSE(traits.supports_custom_fan_mode("Quiet")); } TEST(ClimateCustomModesTest, RuntimeListsAreCopiedAndReplaced) { TestClimate climate; std::vector presets{"Eco", "Sleep"}; climate.set_supported_custom_presets(presets); presets[0] = "Changed"; // the climate keeps its own copy EXPECT_STREQ(climate.get_traits().get_supported_custom_presets()[0], "Eco"); climate.set_supported_custom_presets({"Night"}); // frees the previous copy ASSERT_EQ(climate.get_traits().get_supported_custom_presets().size(), 1u); EXPECT_STREQ(climate.get_traits().get_supported_custom_presets()[0], "Night"); } TEST(ClimateCustomModesTest, TraitsWithoutAClimateHaveEmptyLists) { ClimateTraits traits; EXPECT_TRUE(traits.get_supported_custom_fan_modes().empty()); EXPECT_TRUE(traits.get_supported_custom_presets().empty()); EXPECT_FALSE(traits.supports_custom_preset("Eco")); } } // namespace esphome::climate::testing