mirror of
https://github.com/esphome/esphome.git
synced 2026-10-07 03:16:37 +00:00
Add integration test for deprecated ClimateTraits compat layer
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
"""Legacy climate component — tests deprecated ClimateTraits setters backward compat."""
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
"""Legacy climate platform that uses deprecated ClimateTraits setters."""
|
||||||
|
|
||||||
|
import esphome.codegen as cg
|
||||||
|
from esphome.components import climate
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
|
||||||
|
legacy_climate_ns = cg.esphome_ns.namespace("legacy_climate_test")
|
||||||
|
LegacyClimate = legacy_climate_ns.class_("LegacyClimate", climate.Climate, cg.Component)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = climate.climate_schema(LegacyClimate).extend(cv.COMPONENT_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = await climate.new_climate(config)
|
||||||
|
await cg.register_component(var, config)
|
||||||
+59
@@ -0,0 +1,59 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/components/climate/climate.h"
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace legacy_climate_test {
|
||||||
|
|
||||||
|
/// Test climate that uses the DEPRECATED ClimateTraits setters for custom modes.
|
||||||
|
/// This validates backward compatibility for external components that haven't migrated.
|
||||||
|
class LegacyClimate : public climate::Climate, public Component {
|
||||||
|
public:
|
||||||
|
void setup() override {
|
||||||
|
this->mode = climate::CLIMATE_MODE_OFF;
|
||||||
|
this->target_temperature = 22.0f;
|
||||||
|
this->current_temperature = 20.0f;
|
||||||
|
this->publish_state();
|
||||||
|
}
|
||||||
|
|
||||||
|
float get_setup_priority() const override { return setup_priority::LATE; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
climate::ClimateTraits traits() override {
|
||||||
|
auto traits = climate::ClimateTraits();
|
||||||
|
traits.add_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE);
|
||||||
|
traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT, climate::CLIMATE_MODE_COOL});
|
||||||
|
traits.set_visual_min_temperature(16.0f);
|
||||||
|
traits.set_visual_max_temperature(30.0f);
|
||||||
|
traits.set_visual_temperature_step(0.5f);
|
||||||
|
|
||||||
|
// DEPRECATED API: setting custom modes directly on ClimateTraits.
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||||
|
traits.set_supported_custom_fan_modes({"Turbo", "Silent", "Auto"});
|
||||||
|
traits.set_supported_custom_presets({"Eco Mode", "Night Mode"});
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
|
|
||||||
|
return traits;
|
||||||
|
}
|
||||||
|
|
||||||
|
void control(const climate::ClimateCall &call) override {
|
||||||
|
if (call.get_mode().has_value()) {
|
||||||
|
this->mode = *call.get_mode();
|
||||||
|
}
|
||||||
|
if (call.get_target_temperature().has_value()) {
|
||||||
|
this->target_temperature = *call.get_target_temperature();
|
||||||
|
}
|
||||||
|
if (call.get_custom_fan_mode() != nullptr) {
|
||||||
|
this->set_custom_fan_mode_(call.get_custom_fan_mode());
|
||||||
|
}
|
||||||
|
if (call.get_custom_preset() != nullptr) {
|
||||||
|
this->set_custom_preset_(call.get_custom_preset());
|
||||||
|
}
|
||||||
|
this->publish_state();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace legacy_climate_test
|
||||||
|
} // namespace esphome
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
esphome:
|
||||||
|
name: legacy-climate-compat
|
||||||
|
platformio_options:
|
||||||
|
build_flags:
|
||||||
|
- "-DUSE_HOST"
|
||||||
|
|
||||||
|
host:
|
||||||
|
api:
|
||||||
|
logger:
|
||||||
|
level: DEBUG
|
||||||
|
|
||||||
|
external_components:
|
||||||
|
- source:
|
||||||
|
type: local
|
||||||
|
path: EXTERNAL_COMPONENT_PATH
|
||||||
|
components: [legacy_climate_component]
|
||||||
|
|
||||||
|
climate:
|
||||||
|
- platform: legacy_climate_component
|
||||||
|
name: "Legacy Climate"
|
||||||
|
id: legacy_climate
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
"""Integration test for backward compatibility of deprecated ClimateTraits setters.
|
||||||
|
|
||||||
|
Verifies that external components using the old traits.set_supported_custom_fan_modes()
|
||||||
|
and traits.set_supported_custom_presets() API still work correctly during the
|
||||||
|
deprecation period (removed in 2026.11.0).
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import aioesphomeapi
|
||||||
|
from aioesphomeapi import ClimateInfo
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from .state_utils import InitialStateHelper
|
||||||
|
from .types import APIClientConnectedFactory, RunCompiledFunction
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_legacy_climate_compat(
|
||||||
|
yaml_config: str,
|
||||||
|
run_compiled: RunCompiledFunction,
|
||||||
|
api_client_connected: APIClientConnectedFactory,
|
||||||
|
) -> None:
|
||||||
|
"""Test that deprecated ClimateTraits custom mode setters still work end-to-end."""
|
||||||
|
external_components_path = str(
|
||||||
|
Path(__file__).parent / "fixtures" / "external_components"
|
||||||
|
)
|
||||||
|
yaml_config = yaml_config.replace(
|
||||||
|
"EXTERNAL_COMPONENT_PATH", external_components_path
|
||||||
|
)
|
||||||
|
|
||||||
|
loop = asyncio.get_running_loop()
|
||||||
|
|
||||||
|
async with run_compiled(yaml_config), api_client_connected() as client:
|
||||||
|
entities, services = await client.list_entities_services()
|
||||||
|
initial_state_helper = InitialStateHelper(entities)
|
||||||
|
|
||||||
|
climate_infos = [e for e in entities if isinstance(e, ClimateInfo)]
|
||||||
|
assert len(climate_infos) == 1, (
|
||||||
|
f"Expected 1 climate entity, got {len(climate_infos)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
test_climate = climate_infos[0]
|
||||||
|
|
||||||
|
# Verify custom fan modes set via deprecated ClimateTraits setter are exposed
|
||||||
|
assert set(test_climate.supported_custom_fan_modes) == {
|
||||||
|
"Turbo",
|
||||||
|
"Silent",
|
||||||
|
"Auto",
|
||||||
|
}, (
|
||||||
|
f"Expected custom fan modes {{Turbo, Silent, Auto}}, "
|
||||||
|
f"got {test_climate.supported_custom_fan_modes}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify custom presets set via deprecated ClimateTraits setter are exposed
|
||||||
|
assert set(test_climate.supported_custom_presets) == {
|
||||||
|
"Eco Mode",
|
||||||
|
"Night Mode",
|
||||||
|
}, (
|
||||||
|
f"Expected custom presets {{Eco Mode, Night Mode}}, "
|
||||||
|
f"got {test_climate.supported_custom_presets}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set up state tracking with InitialStateHelper
|
||||||
|
turbo_future: asyncio.Future[aioesphomeapi.ClimateState] = loop.create_future()
|
||||||
|
|
||||||
|
def on_state(state: aioesphomeapi.EntityState) -> None:
|
||||||
|
if (
|
||||||
|
isinstance(state, aioesphomeapi.ClimateState)
|
||||||
|
and state.custom_fan_mode == "Turbo"
|
||||||
|
and not turbo_future.done()
|
||||||
|
):
|
||||||
|
turbo_future.set_result(state)
|
||||||
|
|
||||||
|
client.subscribe_states(initial_state_helper.on_state_wrapper(on_state))
|
||||||
|
|
||||||
|
try:
|
||||||
|
await initial_state_helper.wait_for_initial_states()
|
||||||
|
except TimeoutError:
|
||||||
|
pytest.fail("Timeout waiting for initial states")
|
||||||
|
|
||||||
|
# Verify we can set a custom fan mode via API (tests find_custom_fan_mode_ compat path)
|
||||||
|
client.climate_command(test_climate.key, custom_fan_mode="Turbo")
|
||||||
|
|
||||||
|
try:
|
||||||
|
turbo_state = await asyncio.wait_for(turbo_future, timeout=5.0)
|
||||||
|
except TimeoutError:
|
||||||
|
pytest.fail("Custom fan mode 'Turbo' not received within 5 seconds")
|
||||||
|
|
||||||
|
assert turbo_state.custom_fan_mode == "Turbo"
|
||||||
Reference in New Issue
Block a user