mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Merge branch 'dev' into bump-1.17.0b1
This commit is contained in:
		| @@ -1,4 +1,4 @@ | ||||
| """ Tests for the binary sensor component """ | ||||
| """Tests for the binary sensor component.""" | ||||
|  | ||||
|  | ||||
| def test_binary_sensor_is_setup(generate_main): | ||||
| @@ -8,7 +8,9 @@ def test_binary_sensor_is_setup(generate_main): | ||||
|     # Given | ||||
|  | ||||
|     # When | ||||
|     main_cpp = generate_main("tests/component_tests/binary_sensor/test_binary_sensor.yaml") | ||||
|     main_cpp = generate_main( | ||||
|         "tests/component_tests/binary_sensor/test_binary_sensor.yaml" | ||||
|     ) | ||||
|  | ||||
|     # Then | ||||
|     assert "new gpio::GPIOBinarySensor();" in main_cpp | ||||
| @@ -22,10 +24,12 @@ def test_binary_sensor_sets_mandatory_fields(generate_main): | ||||
|     # Given | ||||
|  | ||||
|     # When | ||||
|     main_cpp = generate_main("tests/component_tests/binary_sensor/test_binary_sensor.yaml") | ||||
|     main_cpp = generate_main( | ||||
|         "tests/component_tests/binary_sensor/test_binary_sensor.yaml" | ||||
|     ) | ||||
|  | ||||
|     # Then | ||||
|     assert "bs_1->set_name(\"test bs1\");" in main_cpp | ||||
|     assert 'bs_1->set_name("test bs1");' in main_cpp | ||||
|     assert "bs_1->set_pin(new GPIOPin" in main_cpp | ||||
|  | ||||
|  | ||||
| @@ -36,7 +40,9 @@ def test_binary_sensor_config_value_internal_set(generate_main): | ||||
|     # Given | ||||
|  | ||||
|     # When | ||||
|     main_cpp = generate_main("tests/component_tests/binary_sensor/test_binary_sensor.yaml") | ||||
|     main_cpp = generate_main( | ||||
|         "tests/component_tests/binary_sensor/test_binary_sensor.yaml" | ||||
|     ) | ||||
|  | ||||
|     # Then | ||||
|     assert "bs_1->set_internal(true);" in main_cpp | ||||
|   | ||||
| @@ -1,4 +1,12 @@ | ||||
| """ Fixtures for component tests """ | ||||
| """Fixtures for component tests.""" | ||||
|  | ||||
| import sys | ||||
| from pathlib import Path | ||||
|  | ||||
| # Add package root to python path | ||||
| here = Path(__file__).parent | ||||
| package_root = here.parent.parent | ||||
| sys.path.insert(0, package_root.as_posix()) | ||||
|  | ||||
| import pytest | ||||
|  | ||||
| @@ -9,7 +17,7 @@ from esphome.__main__ import generate_cpp_contents | ||||
|  | ||||
| @pytest.fixture | ||||
| def generate_main(): | ||||
|     """ Generates the C++ main.cpp file and returns it in string form """ | ||||
|     """Generates the C++ main.cpp file and returns it in string form.""" | ||||
|  | ||||
|     def generator(path: str) -> str: | ||||
|         CORE.config_path = path | ||||
|   | ||||
							
								
								
									
										14
									
								
								tests/component_tests/sensor/test_sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								tests/component_tests/sensor/test_sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| """Tests for the sensor component.""" | ||||
|  | ||||
|  | ||||
| def test_sensor_device_class_set(generate_main): | ||||
|     """ | ||||
|     When the device_class of sensor is set in the yaml file, it should be registered in main | ||||
|     """ | ||||
|     # Given | ||||
|  | ||||
|     # When | ||||
|     main_cpp = generate_main("tests/component_tests/sensor/test_sensor.yaml") | ||||
|  | ||||
|     # Then | ||||
|     assert 's_1->set_device_class("voltage");' in main_cpp | ||||
							
								
								
									
										12
									
								
								tests/component_tests/sensor/test_sensor.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								tests/component_tests/sensor/test_sensor.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| esphome: | ||||
|   name: test | ||||
|   platform: ESP8266 | ||||
|   board: d1_mini_lite | ||||
|  | ||||
| sensor: | ||||
|   - platform: adc | ||||
|     pin: A0 | ||||
|     id: s_1 | ||||
|     name: "test s1" | ||||
|     update_interval: 60s | ||||
|     device_class: "voltage" | ||||
							
								
								
									
										225
									
								
								tests/test1.yaml
									
									
									
									
									
								
							
							
						
						
									
										225
									
								
								tests/test1.yaml
									
									
									
									
									
								
							| @@ -1,8 +1,12 @@ | ||||
| substitutions: | ||||
|   devicename: test1 | ||||
|   sensorname: my | ||||
|   textname: template | ||||
|   roomname: living_room | ||||
|  | ||||
| esphome: | ||||
|   name: test1 | ||||
|   name_add_mac_suffix: true | ||||
|   platform: ESP32 | ||||
|   board: nodemcu-32s | ||||
|   on_boot: | ||||
| @@ -26,6 +30,31 @@ esphome: | ||||
|           green: !lambda 'return 255;' | ||||
|           blue: 0% | ||||
|           white: 100% | ||||
|       - http_request.get: | ||||
|           url: https://esphome.io | ||||
|           headers: | ||||
|             Content-Type: application/json | ||||
|           verify_ssl: false | ||||
|       - http_request.post: | ||||
|           url: https://esphome.io | ||||
|           verify_ssl: false | ||||
|           json: | ||||
|             key: !lambda |- | ||||
|               return id(${textname}_text).state; | ||||
|             greeting: 'Hello World' | ||||
|       - http_request.send: | ||||
|           method: PUT | ||||
|           url: https://esphome.io | ||||
|           headers: | ||||
|             Content-Type: application/json | ||||
|           body: 'Some data' | ||||
|           verify_ssl: false | ||||
|           on_response: | ||||
|             then: | ||||
|               - logger.log: | ||||
|                   format: 'Response status: %d' | ||||
|                   args: | ||||
|                     - status_code | ||||
|   build_path: build/test1 | ||||
|  | ||||
| packages: | ||||
| @@ -50,6 +79,10 @@ wifi: | ||||
|   reboot_timeout: 120s | ||||
|   power_save_mode: none | ||||
|  | ||||
| http_request: | ||||
|   useragent: esphome/device | ||||
|   timeout: 10s | ||||
|  | ||||
| mqtt: | ||||
|   broker: '192.168.178.84' | ||||
|   port: 1883 | ||||
| @@ -98,7 +131,7 @@ mqtt: | ||||
|                 int data = x["my_data"]; | ||||
|                 ESP_LOGD("main", "The data is: %d", data); | ||||
|             - light.turn_on: | ||||
|                 id: living_room_lights | ||||
|                 id: ${roomname}_lights | ||||
|                 brightness: !lambda |- | ||||
|                   float brightness = 1.0; | ||||
|                   if (x.containsKey("brightness")) | ||||
| @@ -110,17 +143,21 @@ mqtt: | ||||
|                     effect = x["effect"]; | ||||
|                   return effect; | ||||
|             - light.control: | ||||
|                 id: living_room_lights | ||||
|                 brightness: !lambda 'return id(living_room_lights).current_values.get_brightness() + 0.5;' | ||||
|                 id: ${roomname}_lights | ||||
|                 brightness: !lambda 'return id(${roomname}_lights).current_values.get_brightness() + 0.5;' | ||||
|             - light.dim_relative: | ||||
|                 id: living_room_lights | ||||
|                 id: ${roomname}_lights | ||||
|                 relative_brightness: 5% | ||||
|             - uart.write: | ||||
|                 id: uart0 | ||||
|                 data: Hello World | ||||
|             - uart.write: [0x00, 0x20, 0x30] | ||||
|             - uart.write: !lambda |- | ||||
|                 return {}; | ||||
|             - uart.write: | ||||
|                 id: uart0 | ||||
|                 data: [0x00, 0x20, 0x30] | ||||
|             - uart.write: | ||||
|                 id: uart0 | ||||
|                 data: !lambda |- | ||||
|                   return {}; | ||||
|  | ||||
| i2c: | ||||
|   sda: 21 | ||||
| @@ -143,6 +180,7 @@ uart: | ||||
|     data_bits: 8 | ||||
|     stop_bits: 1 | ||||
|     rx_buffer_size: 512 | ||||
|     invert: false | ||||
|  | ||||
|   - id: adalight_uart | ||||
|     tx_pin: GPIO25 | ||||
| @@ -237,6 +275,14 @@ sensor: | ||||
|           window_size: 5 | ||||
|           send_every: 5 | ||||
|           send_first_at: 3 | ||||
|       - min: | ||||
|           window_size: 5 | ||||
|           send_every: 5 | ||||
|           send_first_at: 3 | ||||
|       - max: | ||||
|           window_size: 5 | ||||
|           send_every: 5 | ||||
|           send_first_at: 3 | ||||
|       - sliding_window_moving_average: | ||||
|           window_size: 15 | ||||
|           send_every: 15 | ||||
| @@ -256,9 +302,9 @@ sensor: | ||||
|       then: | ||||
|         - lambda: |- | ||||
|             ESP_LOGD("main", "Got value %f", x); | ||||
|             id(my_sensor).publish_state(42.0); | ||||
|             ESP_LOGI("main", "Value of my sensor: %f", id(my_sensor).state); | ||||
|             ESP_LOGI("main", "Raw Value of my sensor: %f", id(my_sensor).state); | ||||
|             id(${sensorname}_sensor).publish_state(42.0); | ||||
|             ESP_LOGI("main", "Value of my sensor: %f", id(${sensorname}_sensor).state); | ||||
|             ESP_LOGI("main", "Raw Value of my sensor: %f", id(${sensorname}_sensor).state); | ||||
|     on_value_range: | ||||
|       above: 5 | ||||
|       below: 10 | ||||
| @@ -285,7 +331,7 @@ sensor: | ||||
|   - platform: ads1115 | ||||
|     multiplexer: 'A0_A1' | ||||
|     gain: 1.024 | ||||
|     id: my_sensor | ||||
|     id: ${sensorname}_sensor | ||||
|     filters: | ||||
|     state_topic: hi/me | ||||
|     retain: false | ||||
| @@ -435,7 +481,7 @@ sensor: | ||||
|       name: 'HLW8012 Power' | ||||
|       id: hlw8012_power | ||||
|     energy: | ||||
|       name: "HLW8012 Energy" | ||||
|       name: 'HLW8012 Energy' | ||||
|       id: hlw8012_energy | ||||
|     update_interval: 15s | ||||
|     current_resistor: 0.001 ohm | ||||
| @@ -549,6 +595,7 @@ sensor: | ||||
|     reference_resistance: '430 Ω' | ||||
|     rtd_nominal_resistance: '100 Ω' | ||||
|   - platform: mhz19 | ||||
|     uart_id: uart0 | ||||
|     co2: | ||||
|       name: 'MH-Z19 CO2 Value' | ||||
|     temperature: | ||||
| @@ -586,6 +633,13 @@ sensor: | ||||
|       falling_edge: DECREMENT | ||||
|     internal_filter: 13us | ||||
|     update_interval: 15s | ||||
|   - platform: pulse_meter | ||||
|     name: 'Pulse Meter' | ||||
|     pin: GPIO12 | ||||
|     internal_filter: 100ms | ||||
|     timeout: 2 min | ||||
|     total: | ||||
|       name: 'Pulse Meter Total' | ||||
|   - platform: rotary_encoder | ||||
|     name: 'Rotary Encoder' | ||||
|     id: rotary_encoder1 | ||||
| @@ -613,10 +667,23 @@ sensor: | ||||
|   - platform: pulse_width | ||||
|     name: Pulse Width | ||||
|     pin: GPIO12 | ||||
|   - platform: senseair | ||||
|   - platform: sm300d2 | ||||
|     uart_id: uart0 | ||||
|     co2: | ||||
|       name: 'SenseAir CO2 Value' | ||||
|     update_interval: 15s | ||||
|       name: 'SM300D2 CO2 Value' | ||||
|     formaldehyde: | ||||
|       name: 'SM300D2 Formaldehyde Value' | ||||
|     tvoc: | ||||
|       name: 'SM300D2 TVOC Value' | ||||
|     pm_2_5: | ||||
|       name: 'SM300D2 PM2.5 Value' | ||||
|     pm_10_0: | ||||
|       name: 'SM300D2 PM10 Value' | ||||
|     temperature: | ||||
|       name: 'SM300D2 Temperature Value' | ||||
|     humidity: | ||||
|       name: 'SM300D2 Humidity Value' | ||||
|     update_interval: 60s | ||||
|   - platform: sht3xd | ||||
|     temperature: | ||||
|       name: 'Living Room Temperature 8' | ||||
| @@ -735,6 +802,7 @@ sensor: | ||||
|             root["key"] = id(the_sensor).state; | ||||
|             root["greeting"] = "Hello World"; | ||||
|   - platform: sds011 | ||||
|     uart_id: uart0 | ||||
|     pm_2_5: | ||||
|       name: 'SDS011 PM2.5' | ||||
|     pm_10_0: | ||||
| @@ -784,6 +852,7 @@ sensor: | ||||
|       name: 'AQI' | ||||
|       calculation_type: 'CAQI' | ||||
|   - platform: teleinfo | ||||
|     uart_id: uart0 | ||||
|     tags: | ||||
|       - tag_name: 'HCHC' | ||||
|         sensor: | ||||
| @@ -829,7 +898,7 @@ binary_sensor: | ||||
|   - platform: gpio | ||||
|     name: 'MCP23S08 Pin #1' | ||||
|     pin: | ||||
|       mcp23s08: mcp23s08_hub | ||||
|       mcp23xxx: mcp23s08_hub | ||||
|       # Use pin number 1 | ||||
|       number: 1 | ||||
|       # One of INPUT or INPUT_PULLUP | ||||
| @@ -838,12 +907,22 @@ binary_sensor: | ||||
|   - platform: gpio | ||||
|     name: 'MCP23S17 Pin #1' | ||||
|     pin: | ||||
|       mcp23s17: mcp23s17_hub | ||||
|       mcp23xxx: mcp23s17_hub | ||||
|       # Use pin number 1 | ||||
|       number: 1 | ||||
|       # One of INPUT or INPUT_PULLUP | ||||
|       mode: INPUT_PULLUP | ||||
|       inverted: False | ||||
|   - platform: gpio | ||||
|     name: 'MCP23S17 Pin #1 with interrupt' | ||||
|     pin: | ||||
|       mcp23xxx: mcp23s17_hub | ||||
|       # Use pin number 1 | ||||
|       number: 1 | ||||
|       # One of INPUT or INPUT_PULLUP | ||||
|       mode: INPUT_PULLUP | ||||
|       inverted: False | ||||
|       interrupt: FALLING | ||||
|   - platform: gpio | ||||
|     pin: GPIO9 | ||||
|     name: 'Living Room Window' | ||||
| @@ -924,12 +1003,12 @@ binary_sensor: | ||||
|     name: 'Garage Door Open' | ||||
|     id: garage_door | ||||
|     lambda: |- | ||||
|       if (isnan(id(my_sensor).state)) { | ||||
|       if (isnan(id(${sensorname}_sensor).state)) { | ||||
|         // isnan checks if the ultrasonic sensor echo | ||||
|         // has timed out, resulting in a NaN (not a number) state | ||||
|         // in that case, return {} to indicate that we don't know. | ||||
|         return {}; | ||||
|       } else if (id(my_sensor).state > 30) { | ||||
|       } else if (id(${sensorname}_sensor).state > 30) { | ||||
|         // Garage Door is open. | ||||
|         return true; | ||||
|       } else { | ||||
| @@ -947,6 +1026,7 @@ binary_sensor: | ||||
|           id: gpio_19 | ||||
|           frequency: !lambda 'return 500.0;' | ||||
|   - platform: pn532 | ||||
|     pn532_id: pn532_bs | ||||
|     uid: 74-10-37-94 | ||||
|     name: 'PN532 NFC Tag' | ||||
|   - platform: rdm6300 | ||||
| @@ -962,14 +1042,14 @@ binary_sensor: | ||||
|   - platform: gpio | ||||
|     name: 'MCP21 binary sensor' | ||||
|     pin: | ||||
|       mcp23017: mcp23017_hub | ||||
|       mcp23xxx: mcp23017_hub | ||||
|       number: 1 | ||||
|       mode: INPUT | ||||
|       inverted: True | ||||
|   - platform: gpio | ||||
|     name: 'MCP22 binary sensor' | ||||
|     pin: | ||||
|       mcp23008: mcp23008_hub | ||||
|       mcp23xxx: mcp23008_hub | ||||
|       number: 7 | ||||
|       mode: INPUT_PULLUP | ||||
|       inverted: False | ||||
| @@ -1126,14 +1206,14 @@ output: | ||||
|   - platform: gpio | ||||
|     id: id22 | ||||
|     pin: | ||||
|       mcp23017: mcp23017_hub | ||||
|       mcp23xxx: mcp23017_hub | ||||
|       number: 0 | ||||
|       mode: OUTPUT | ||||
|       inverted: False | ||||
|   - platform: gpio | ||||
|     id: id23 | ||||
|     pin: | ||||
|       mcp23008: mcp23008_hub | ||||
|       mcp23xxx: mcp23008_hub | ||||
|       number: 0 | ||||
|       mode: OUTPUT | ||||
|       inverted: False | ||||
| @@ -1173,6 +1253,8 @@ output: | ||||
|   - platform: esp32_dac | ||||
|     pin: GPIO25 | ||||
|     id: dac_output | ||||
|   - platform: mcp4725 | ||||
|     id: mcp4725_dac_output | ||||
|  | ||||
| e131: | ||||
|  | ||||
| @@ -1220,7 +1302,7 @@ light: | ||||
|               state = 0; | ||||
|   - platform: rgb | ||||
|     name: 'Living Room Lights' | ||||
|     id: living_room_lights | ||||
|     id: ${roomname}_lights | ||||
|     red: pca_0 | ||||
|     green: pca_1 | ||||
|     blue: pca_2 | ||||
| @@ -1376,14 +1458,14 @@ climate: | ||||
|     name: TCL112 Climate With Sensor | ||||
|     supports_heat: True | ||||
|     supports_cool: True | ||||
|     sensor: my_sensor | ||||
|     sensor: ${sensorname}_sensor | ||||
|   - platform: tcl112 | ||||
|     name: TCL112 Climate | ||||
|   - platform: coolix | ||||
|     name: Coolix Climate With Sensor | ||||
|     supports_heat: True | ||||
|     supports_cool: True | ||||
|     sensor: my_sensor | ||||
|     sensor: ${sensorname}_sensor | ||||
|   - platform: coolix | ||||
|     name: Coolix Climate | ||||
|   - platform: fujitsu_general | ||||
| @@ -1402,12 +1484,29 @@ climate: | ||||
|     name: Toshiba Climate | ||||
|   - platform: hitachi_ac344 | ||||
|     name: Hitachi Climate | ||||
|   - platform: midea_ac | ||||
|     visual: | ||||
|       min_temperature: 18 °C | ||||
|       max_temperature: 25 °C | ||||
|       temperature_step: 0.1 °C | ||||
|     name: "Electrolux EACS" | ||||
|     beeper: true | ||||
|     outdoor_temperature: | ||||
|       name: "Temp" | ||||
|     power_usage: | ||||
|       name: "Power" | ||||
|     humidity_setpoint: | ||||
|       name: "Hum" | ||||
|  | ||||
| midea_dongle: | ||||
|   uart_id: uart0 | ||||
|   strength_icon: true | ||||
|  | ||||
| switch: | ||||
|   - platform: gpio | ||||
|     name: 'MCP23S08 Pin #0' | ||||
|     pin: | ||||
|       mcp23s08: mcp23s08_hub | ||||
|       mcp23xxx: mcp23s08_hub | ||||
|       # Use pin number 0 | ||||
|       number: 0 | ||||
|       mode: OUTPUT | ||||
| @@ -1415,7 +1514,7 @@ switch: | ||||
|   - platform: gpio | ||||
|     name: 'MCP23S17 Pin #0' | ||||
|     pin: | ||||
|       mcp23s17: mcp23s17_hub | ||||
|       mcp23xxx: mcp23s17_hub | ||||
|       # Use pin number 0 | ||||
|       number: 1 | ||||
|       mode: OUTPUT | ||||
| @@ -1451,6 +1550,12 @@ switch: | ||||
|     turn_on_action: | ||||
|       remote_transmitter.transmit_samsung: | ||||
|         data: 0xABCDEF | ||||
|   - platform: template | ||||
|     name: Samsung36 | ||||
|     turn_on_action: | ||||
|       remote_transmitter.transmit_samsung36: | ||||
|         address: 0x0400 | ||||
|         command: 0x000E00FF | ||||
|   - platform: template | ||||
|     name: Sony | ||||
|     turn_on_action: | ||||
| @@ -1542,6 +1647,9 @@ switch: | ||||
|       - output.set_level: | ||||
|           id: dac_output | ||||
|           level: !lambda 'return 0.5;' | ||||
|       - output.set_level: | ||||
|           id: mcp4725_dac_output | ||||
|           level: !lambda 'return 0.5;' | ||||
|     turn_off_action: | ||||
|       - switch.turn_on: living_room_lights_off | ||||
|     restore_state: False | ||||
| @@ -1582,11 +1690,18 @@ switch: | ||||
|           id: my_switch | ||||
|           state: !lambda 'return false;' | ||||
|   - platform: uart | ||||
|     uart_id: uart0 | ||||
|     name: 'UART String Output' | ||||
|     data: 'DataToSend' | ||||
|   - platform: uart | ||||
|     uart_id: uart0 | ||||
|     name: 'UART Bytes Output' | ||||
|     data: [0xDE, 0xAD, 0xBE, 0xEF] | ||||
|   - platform: uart | ||||
|     uart_id: uart0 | ||||
|     name: 'UART Recurring Output' | ||||
|     data: [0xDE, 0xAD, 0xBE, 0xEF] | ||||
|     send_every: 1s | ||||
|   - platform: template | ||||
|     assumed_state: yes | ||||
|     name: Stepper Switch | ||||
| @@ -1620,13 +1735,10 @@ fan: | ||||
|     direction_output: gpio_26 | ||||
|   - platform: speed | ||||
|     output: pca_6 | ||||
|     speed_count: 10 | ||||
|     name: 'Living Room Fan 2' | ||||
|     oscillation_output: gpio_19 | ||||
|     direction_output: gpio_26 | ||||
|     speed: | ||||
|       low: 0.45 | ||||
|       medium: 0.75 | ||||
|       high: 1.0 | ||||
|     oscillation_state_topic: oscillation/state/topic | ||||
|     oscillation_command_topic: oscillation/command/topic | ||||
|     speed_state_topic: speed/state/topic | ||||
| @@ -1653,7 +1765,7 @@ interval: | ||||
| color: | ||||
|   - id: kbx_red | ||||
|     red: 100% | ||||
|     green: 1% | ||||
|     green_int: 123 | ||||
|     blue: 2% | ||||
|   - id: kbx_blue | ||||
|     red: 0% | ||||
| @@ -1690,15 +1802,16 @@ display: | ||||
|       it.print("1234"); | ||||
|   - platform: tm1637 | ||||
|     clk_pin: | ||||
|       mcp23017: mcp23017_hub | ||||
|       mcp23xxx: mcp23017_hub | ||||
|       number: 1 | ||||
|     dio_pin: | ||||
|       mcp23017: mcp23017_hub | ||||
|       mcp23xxx: mcp23017_hub | ||||
|       number: 2 | ||||
|     intensity: 3 | ||||
|     lambda: |- | ||||
|       it.print("1234"); | ||||
|   - platform: nextion | ||||
|     uart_id: uart0 | ||||
|     lambda: |- | ||||
|       it.set_component_value("gauge", 50); | ||||
|       it.set_component_text("textview", "Hello World!"); | ||||
| @@ -1730,7 +1843,7 @@ display: | ||||
|     lambda: |- | ||||
|       it.rectangle(0, 0, it.get_width(), it.get_height()); | ||||
|   - platform: ssd1322_spi | ||||
|     model: "SSD1322 256x64" | ||||
|     model: 'SSD1322 256x64' | ||||
|     cs_pin: GPIO23 | ||||
|     dc_pin: GPIO23 | ||||
|     reset_pin: GPIO23 | ||||
| @@ -1776,24 +1889,6 @@ display: | ||||
|     reset_pin: GPIO23 | ||||
|     lambda: |- | ||||
|       it.rectangle(0, 0, it.get_width(), it.get_height()); | ||||
|   - platform: waveshare_epaper | ||||
|     cs_pin: GPIO23 | ||||
|     dc_pin: GPIO23 | ||||
|     busy_pin: GPIO23 | ||||
|     reset_pin: GPIO23 | ||||
|     model: 2.90in | ||||
|     full_update_every: 30 | ||||
|     lambda: |- | ||||
|       it.rectangle(0, 0, it.get_width(), it.get_height()); | ||||
|   - platform: waveshare_epaper | ||||
|     cs_pin: GPIO23 | ||||
|     dc_pin: GPIO23 | ||||
|     busy_pin: GPIO23 | ||||
|     reset_pin: GPIO23 | ||||
|     model: 2.90inv2 | ||||
|     full_update_every: 30 | ||||
|     lambda: |- | ||||
|       it.rectangle(0, 0, it.get_width(), it.get_height()); | ||||
|   - platform: st7789v | ||||
|     cs_pin: GPIO5 | ||||
|     dc_pin: GPIO16 | ||||
| @@ -1807,10 +1902,10 @@ display: | ||||
|     dc_pin: GPIO16 | ||||
|     reset_pin: GPIO23 | ||||
|     rotation: 0 | ||||
|     devicewidth: 128 | ||||
|     deviceheight: 160 | ||||
|     colstart: 0 | ||||
|     rowstart: 0 | ||||
|     device_width: 128 | ||||
|     device_height: 160 | ||||
|     col_start: 0 | ||||
|     row_start: 0 | ||||
|     lambda: |- | ||||
|       it.rectangle(0, 0, it.get_width(), it.get_height()); | ||||
| tm1651: | ||||
| @@ -1826,6 +1921,7 @@ status_led: | ||||
|   pin: GPIO2 | ||||
|  | ||||
| pn532_spi: | ||||
|   id: pn532_bs | ||||
|   cs_pin: GPIO23 | ||||
|   update_interval: 1s | ||||
|   on_tag: | ||||
| @@ -1838,6 +1934,7 @@ pn532_spi: | ||||
| pn532_i2c: | ||||
|  | ||||
| rdm6300: | ||||
|   uart_id: uart0 | ||||
|  | ||||
| rc522_spi: | ||||
|   cs_pin: GPIO23 | ||||
| @@ -1853,6 +1950,7 @@ rc522_i2c: | ||||
|         ESP_LOGD("main", "Found tag %s", x.c_str()); | ||||
|  | ||||
| gps: | ||||
|   uart_id: uart0 | ||||
|  | ||||
| time: | ||||
|   - platform: sntp | ||||
| @@ -1875,8 +1973,7 @@ time: | ||||
|     update_interval: never | ||||
|     on_time: | ||||
|       seconds: 0 | ||||
|       then: | ||||
|         ds1307.read_time | ||||
|       then: ds1307.read_time | ||||
|  | ||||
| cover: | ||||
|   - platform: template | ||||
| @@ -1950,10 +2047,10 @@ text_sensor: | ||||
|     qos: 2 | ||||
|     on_value: | ||||
|       - text_sensor.template.publish: | ||||
|           id: template_text | ||||
|           id: ${textname}_text | ||||
|           state: Hello World | ||||
|       - text_sensor.template.publish: | ||||
|           id: template_text | ||||
|           id: ${textname}_text | ||||
|           state: |- | ||||
|             return "Hello World2"; | ||||
|       - globals.set: | ||||
| @@ -1964,7 +2061,7 @@ text_sensor: | ||||
|           data: [0x10, 0x20, 0x30] | ||||
|   - platform: template | ||||
|     name: Template Text Sensor | ||||
|     id: template_text | ||||
|     id: ${textname}_text | ||||
|   - platform: wifi_info | ||||
|     ip_address: | ||||
|       name: 'IP Address' | ||||
| @@ -2006,4 +2103,4 @@ canbus: | ||||
|               condition: | ||||
|                 lambda: 'return x[0] == 0x11;' | ||||
|               then: | ||||
|                 light.toggle: living_room_lights | ||||
|                 light.toggle: ${roomname}_lights | ||||
|   | ||||
| @@ -45,6 +45,12 @@ ota: | ||||
| logger: | ||||
|   level: DEBUG | ||||
|  | ||||
| deep_sleep: | ||||
|   run_duration: 20s | ||||
|   sleep_duration: 50s | ||||
|   wakeup_pin: GPIO39 | ||||
|   wakeup_pin_mode: INVERT_WAKEUP | ||||
|  | ||||
| as3935_i2c: | ||||
|   irq_pin: GPIO12 | ||||
|  | ||||
| @@ -64,6 +70,18 @@ sensor: | ||||
|   - platform: ble_rssi | ||||
|     service_uuid: '11223344-5566-7788-99aa-bbccddeeff00' | ||||
|     name: 'BLE Test Service 128' | ||||
|   - platform: senseair | ||||
|     id: senseair0 | ||||
|     co2: | ||||
|       name: 'SenseAir CO2 Value' | ||||
|       on_value: | ||||
|         then: | ||||
|           - senseair.background_calibration: senseair0 | ||||
|           - senseair.background_calibration_result: senseair0 | ||||
|           - senseair.abc_get_period: senseair0 | ||||
|           - senseair.abc_enable: senseair0 | ||||
|           - senseair.abc_disable: senseair0 | ||||
|     update_interval: 15s | ||||
|   - platform: ruuvitag | ||||
|     mac_address: FF:56:D3:2F:7D:E8 | ||||
|     humidity: | ||||
| @@ -181,6 +199,14 @@ sensor: | ||||
|       name: 'ATC Battery-Level' | ||||
|     battery_voltage: | ||||
|       name: 'ATC Battery-Voltage' | ||||
|   - platform: inkbird_ibsth1_mini | ||||
|     mac_address: 38:81:D7:0A:9C:11 | ||||
|     temperature: | ||||
|         name: 'Inkbird IBS-TH1 Temperature' | ||||
|     humidity: | ||||
|         name: 'Inkbird IBS-TH1 Humidity' | ||||
|     battery_level: | ||||
|         name: 'Inkbird IBS-TH1 Battery Level' | ||||
|  | ||||
| time: | ||||
|   - platform: homeassistant | ||||
| @@ -294,6 +320,8 @@ text_sensor: | ||||
|       - homeassistant.tag_scanned: | ||||
|           tag: 1234-abcd | ||||
|       - homeassistant.tag_scanned: 1234-abcd | ||||
|       - deep_sleep.enter: | ||||
|           sleep_duration: 30min | ||||
|   - platform: template | ||||
|     name: 'Template Text Sensor' | ||||
|     lambda: |- | ||||
|   | ||||
| @@ -197,10 +197,6 @@ uart: | ||||
|     rx_pin: GPIO3 | ||||
|     baud_rate: 115200 | ||||
|  | ||||
|   - id: adalight_uart | ||||
|     rx_pin: GPIO3 | ||||
|     baud_rate: 115200 | ||||
|  | ||||
| ota: | ||||
|   safe_mode: True | ||||
|   port: 3286 | ||||
| @@ -229,6 +225,8 @@ sensor: | ||||
|     name: 'VL53L0x Distance' | ||||
|     address: 0x29 | ||||
|     update_interval: 60s | ||||
|     enable_pin: GPIO13 | ||||
|     timeout: 200us | ||||
|   - platform: apds9960 | ||||
|     type: clear | ||||
|     name: APDS9960 Clear | ||||
| @@ -558,14 +556,14 @@ switch: | ||||
|   - platform: gpio | ||||
|     id: gpio_switch1 | ||||
|     pin: | ||||
|       mcp23017: mcp23017_hub | ||||
|       mcp23xxx: mcp23017_hub | ||||
|       number: 0 | ||||
|       mode: OUTPUT | ||||
|     interlock: &interlock [gpio_switch1, gpio_switch2, gpio_switch3] | ||||
|   - platform: gpio | ||||
|     id: gpio_switch2 | ||||
|     pin: | ||||
|       mcp23008: mcp23008_hub | ||||
|       mcp23xxx: mcp23008_hub | ||||
|       number: 0 | ||||
|       mode: OUTPUT | ||||
|     interlock: *interlock | ||||
| @@ -812,7 +810,6 @@ light: | ||||
|     effects: | ||||
|       - wled: | ||||
|       - adalight: | ||||
|           uart_id: adalight_uart | ||||
|       - e131: | ||||
|           universe: 1 | ||||
|   - platform: hbridge | ||||
| @@ -842,6 +839,8 @@ sim800l: | ||||
|     - sim800l.send_sms: | ||||
|         message: 'hello you' | ||||
|         recipient: '+1234' | ||||
|     - sim800l.dial: | ||||
|         recipient: '+1234' | ||||
|  | ||||
| dfplayer: | ||||
|   on_finished_playback: | ||||
| @@ -886,6 +885,25 @@ rf_bridge: | ||||
|         code: 'ABC123' | ||||
|     - rf_bridge.send_raw: | ||||
|         raw: 'AAA5070008001000ABC12355' | ||||
|     - http_request.get: | ||||
|         url: https://esphome.io | ||||
|         headers: | ||||
|           Content-Type: application/json | ||||
|         verify_ssl: false | ||||
|     - http_request.post: | ||||
|         url: https://esphome.io | ||||
|         verify_ssl: false | ||||
|         json: | ||||
|           key: !lambda |- | ||||
|             return id(version_sensor).state; | ||||
|           greeting: 'Hello World' | ||||
|     - http_request.send: | ||||
|         method: PUT | ||||
|         url: https://esphome.io | ||||
|         headers: | ||||
|           Content-Type: application/json | ||||
|         body: 'Some data' | ||||
|         verify_ssl: false | ||||
|  | ||||
| display: | ||||
|   - platform: max7219digit | ||||
| @@ -897,3 +915,7 @@ display: | ||||
|     id: my_matrix | ||||
|     lambda: |- | ||||
|       it.printdigit("hello"); | ||||
|  | ||||
| http_request: | ||||
|   useragent: esphome/device | ||||
|   timeout: 10s | ||||
|   | ||||
| @@ -99,3 +99,64 @@ switch: | ||||
|   - platform: tuya | ||||
|     id: tuya_switch | ||||
|     switch_datapoint: 1 | ||||
|  | ||||
| light: | ||||
|   - platform: fastled_clockless | ||||
|     id: led_matrix_32x8 | ||||
|     name: "led_matrix_32x8" | ||||
|     chipset: WS2812B | ||||
|     pin: GPIO15 | ||||
|     num_leds: 256 | ||||
|     rgb_order: GRB | ||||
|     default_transition_length: 0s | ||||
|     color_correct: [50%, 50%, 50%] | ||||
|  | ||||
| display: | ||||
|   - platform: addressable_light | ||||
|     id: led_matrix_32x8_display | ||||
|     addressable_light_id: led_matrix_32x8 | ||||
|     width: 32 | ||||
|     height: 8 | ||||
|     pixel_mapper: |- | ||||
|       if (x % 2 == 0) { | ||||
|         return (x * 8) + y; | ||||
|       } | ||||
|       return (x * 8) + (7 - y); | ||||
|     lambda: |- | ||||
|       Color red = Color(0xFF0000); | ||||
|       Color green = Color(0x00FF00); | ||||
|       Color blue = Color(0x0000FF); | ||||
|       it.rectangle(0, 0, it.get_width(), it.get_height(), red); | ||||
|       it.rectangle(1, 1, it.get_width()-2, it.get_height()-2, green); | ||||
|       it.rectangle(2, 2, it.get_width()-4, it.get_height()-4, blue); | ||||
|       it.rectangle(3, 3, it.get_width()-6, it.get_height()-6, red); | ||||
|     rotation: 0° | ||||
|     update_interval: 16ms | ||||
|   - platform: waveshare_epaper | ||||
|     cs_pin: GPIO23 | ||||
|     dc_pin: GPIO23 | ||||
|     busy_pin: GPIO23 | ||||
|     reset_pin: GPIO23 | ||||
|     model: 2.13in-ttgo-b1 | ||||
|     full_update_every: 30 | ||||
|     lambda: |- | ||||
|       it.rectangle(0, 0, it.get_width(), it.get_height()); | ||||
|   - platform: waveshare_epaper | ||||
|     cs_pin: GPIO23 | ||||
|     dc_pin: GPIO23 | ||||
|     busy_pin: GPIO23 | ||||
|     reset_pin: GPIO23 | ||||
|     model: 2.90in | ||||
|     full_update_every: 30 | ||||
|     lambda: |- | ||||
|       it.rectangle(0, 0, it.get_width(), it.get_height()); | ||||
|   - platform: waveshare_epaper | ||||
|     cs_pin: GPIO23 | ||||
|     dc_pin: GPIO23 | ||||
|     busy_pin: GPIO23 | ||||
|     reset_pin: GPIO23 | ||||
|     model: 2.90inv2 | ||||
|     full_update_every: 30 | ||||
|     lambda: |- | ||||
|       it.rectangle(0, 0, it.get_width(), it.get_height()); | ||||
|  | ||||
|   | ||||
| @@ -27,4 +27,3 @@ def fixture_path() -> Path: | ||||
|     Location of all fixture files. | ||||
|     """ | ||||
|     return here / "fixtures" | ||||
|  | ||||
|   | ||||
| @@ -12,4 +12,6 @@ def mac_addr_strings(): | ||||
|     This consists of six strings representing integers [0..255], | ||||
|     without zero-padding, joined by dots. | ||||
|     """ | ||||
|     return st.builds("{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}".format, *(6 * [st.integers(0, 255)])) | ||||
|     return st.builds( | ||||
|         "{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}".format, *(6 * [st.integers(0, 255)]) | ||||
|     ) | ||||
|   | ||||
| @@ -4,23 +4,75 @@ from esphome import codegen as cg | ||||
|  | ||||
|  | ||||
| # Test interface remains the same. | ||||
| @pytest.mark.parametrize("attr", ( | ||||
|     # from cpp_generator | ||||
|     "Expression", "RawExpression", "RawStatement", "TemplateArguments", | ||||
|     "StructInitializer", "ArrayInitializer", "safe_exp", "Statement", "LineComment", | ||||
|     "progmem_array", "statement", "variable", "Pvariable", "new_Pvariable", | ||||
|     "add", "add_global", "add_library", "add_build_flag", "add_define", | ||||
|     "get_variable", "get_variable_with_full_id", "process_lambda", "is_template", "templatable", "MockObj", | ||||
|     "MockObjClass", | ||||
|     # from cpp_helpers | ||||
|     "gpio_pin_expression", "register_component", "build_registry_entry", | ||||
|     "build_registry_list", "extract_registry_entry_config", "register_parented", | ||||
|     "global_ns", "void", "nullptr", "float_", "double", "bool_", "int_", "std_ns", "std_string", | ||||
|     "std_vector", "uint8", "uint16", "uint32", "int32", "const_char_ptr", "NAN", | ||||
|     "esphome_ns", "App", "Nameable", "Component", "ComponentPtr", | ||||
|     # from cpp_types | ||||
|     "PollingComponent", "Application", "optional", "arduino_json_ns", "JsonObject", | ||||
|     "JsonObjectRef", "JsonObjectConstRef", "Controller", "GPIOPin" | ||||
| )) | ||||
| @pytest.mark.parametrize( | ||||
|     "attr", | ||||
|     ( | ||||
|         # from cpp_generator | ||||
|         "Expression", | ||||
|         "RawExpression", | ||||
|         "RawStatement", | ||||
|         "TemplateArguments", | ||||
|         "StructInitializer", | ||||
|         "ArrayInitializer", | ||||
|         "safe_exp", | ||||
|         "Statement", | ||||
|         "LineComment", | ||||
|         "progmem_array", | ||||
|         "statement", | ||||
|         "variable", | ||||
|         "Pvariable", | ||||
|         "new_Pvariable", | ||||
|         "add", | ||||
|         "add_global", | ||||
|         "add_library", | ||||
|         "add_build_flag", | ||||
|         "add_define", | ||||
|         "get_variable", | ||||
|         "get_variable_with_full_id", | ||||
|         "process_lambda", | ||||
|         "is_template", | ||||
|         "templatable", | ||||
|         "MockObj", | ||||
|         "MockObjClass", | ||||
|         # from cpp_helpers | ||||
|         "gpio_pin_expression", | ||||
|         "register_component", | ||||
|         "build_registry_entry", | ||||
|         "build_registry_list", | ||||
|         "extract_registry_entry_config", | ||||
|         "register_parented", | ||||
|         "global_ns", | ||||
|         "void", | ||||
|         "nullptr", | ||||
|         "float_", | ||||
|         "double", | ||||
|         "bool_", | ||||
|         "int_", | ||||
|         "std_ns", | ||||
|         "std_string", | ||||
|         "std_vector", | ||||
|         "uint8", | ||||
|         "uint16", | ||||
|         "uint32", | ||||
|         "int32", | ||||
|         "const_char_ptr", | ||||
|         "NAN", | ||||
|         "esphome_ns", | ||||
|         "App", | ||||
|         "Nameable", | ||||
|         "Component", | ||||
|         "ComponentPtr", | ||||
|         # from cpp_types | ||||
|         "PollingComponent", | ||||
|         "Application", | ||||
|         "optional", | ||||
|         "arduino_json_ns", | ||||
|         "JsonObject", | ||||
|         "JsonObjectRef", | ||||
|         "JsonObjectConstRef", | ||||
|         "Controller", | ||||
|         "GPIOPin", | ||||
|     ), | ||||
| ) | ||||
| def test_exists(attr): | ||||
|     assert hasattr(cg, attr) | ||||
|   | ||||
| @@ -2,7 +2,7 @@ import pytest | ||||
| import string | ||||
|  | ||||
| from hypothesis import given, example | ||||
| from hypothesis.strategies import one_of, text, integers, booleans, builds | ||||
| from hypothesis.strategies import one_of, text, integers, builds | ||||
|  | ||||
| from esphome import config_validation | ||||
| from esphome.config_validation import Invalid | ||||
| @@ -24,7 +24,7 @@ def test_alphanumeric__valid(value): | ||||
| @pytest.mark.parametrize("value", ("£23", "Foo!")) | ||||
| def test_alphanumeric__invalid(value): | ||||
|     with pytest.raises(Invalid): | ||||
|         actual = config_validation.alphanumeric(value) | ||||
|         config_validation.alphanumeric(value) | ||||
|  | ||||
|  | ||||
| @given(value=text(alphabet=string.ascii_lowercase + string.digits + "_-")) | ||||
| @@ -34,9 +34,7 @@ def test_valid_name__valid(value): | ||||
|     assert actual == value | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("value", ( | ||||
|     "foo bar", "FooBar", "foo::bar" | ||||
| )) | ||||
| @pytest.mark.parametrize("value", ("foo bar", "FooBar", "foo::bar")) | ||||
| def test_valid_name__invalid(value): | ||||
|     with pytest.raises(Invalid): | ||||
|         config_validation.valid_name(value) | ||||
| @@ -49,9 +47,7 @@ def test_string__valid(value): | ||||
|     assert actual == str(value) | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("value", ( | ||||
|     {}, [], True, False, None | ||||
| )) | ||||
| @pytest.mark.parametrize("value", ({}, [], True, False, None)) | ||||
| def test_string__invalid(value): | ||||
|     with pytest.raises(Invalid): | ||||
|         config_validation.string(value) | ||||
| @@ -83,23 +79,17 @@ def test_icon__invalid(): | ||||
|         config_validation.icon("foo") | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("value", ( | ||||
|     "True", "YES", "on", "enAblE", True | ||||
| )) | ||||
| @pytest.mark.parametrize("value", ("True", "YES", "on", "enAblE", True)) | ||||
| def test_boolean__valid_true(value): | ||||
|     assert config_validation.boolean(value) is True | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("value", ( | ||||
|     "False", "NO", "off", "disAblE", False | ||||
| )) | ||||
| @pytest.mark.parametrize("value", ("False", "NO", "off", "disAblE", False)) | ||||
| def test_boolean__valid_false(value): | ||||
|     assert config_validation.boolean(value) is False | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("value", ( | ||||
|     None, 1, 0, "foo" | ||||
| )) | ||||
| @pytest.mark.parametrize("value", (None, 1, 0, "foo")) | ||||
| def test_boolean__invalid(value): | ||||
|     with pytest.raises(Invalid, match="Expected boolean value"): | ||||
|         config_validation.boolean(value) | ||||
|   | ||||
| @@ -8,13 +8,16 @@ from esphome import core, const | ||||
|  | ||||
|  | ||||
| class TestHexInt: | ||||
|     @pytest.mark.parametrize("value, expected", ( | ||||
|         (1, "0x01"), | ||||
|         (255, "0xFF"), | ||||
|         (128, "0x80"), | ||||
|         (256, "0x100"), | ||||
|         (-1, "-0x01"),  # TODO: this currently fails | ||||
|     )) | ||||
|     @pytest.mark.parametrize( | ||||
|         "value, expected", | ||||
|         ( | ||||
|             (1, "0x01"), | ||||
|             (255, "0xFF"), | ||||
|             (128, "0x80"), | ||||
|             (256, "0x100"), | ||||
|             (-1, "-0x01"),  # TODO: this currently fails | ||||
|         ), | ||||
|     ) | ||||
|     def test_str(self, value, expected): | ||||
|         target = core.HexInt(value) | ||||
|  | ||||
| @@ -68,18 +71,14 @@ class TestMACAddress: | ||||
|         assert actual.text == "0xDEADBEEF00FFULL" | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("value", ( | ||||
|     1, 2, -1, 0, 1.0, -1.0, 42.0009, -42.0009 | ||||
| )) | ||||
| @pytest.mark.parametrize("value", (1, 2, -1, 0, 1.0, -1.0, 42.0009, -42.0009)) | ||||
| def test_is_approximately_integer__in_range(value): | ||||
|     actual = core.is_approximately_integer(value) | ||||
|  | ||||
|     assert actual is True | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("value", ( | ||||
|     42.01, -42.01, 1.5 | ||||
| )) | ||||
| @pytest.mark.parametrize("value", (42.01, -42.01, 1.5)) | ||||
| def test_is_approximately_integer__not_in_range(value): | ||||
|     actual = core.is_approximately_integer(value) | ||||
|  | ||||
| @@ -87,26 +86,29 @@ def test_is_approximately_integer__not_in_range(value): | ||||
|  | ||||
|  | ||||
| class TestTimePeriod: | ||||
|     @pytest.mark.parametrize("kwargs, expected", ( | ||||
|         ({}, {}), | ||||
|         ({"microseconds": 1}, {"microseconds": 1}), | ||||
|         ({"microseconds": 1.0001}, {"microseconds": 1}), | ||||
|         ({"milliseconds": 2}, {"milliseconds": 2}), | ||||
|         ({"milliseconds": 2.0001}, {"milliseconds": 2}), | ||||
|         ({"milliseconds": 2.01}, {"milliseconds": 2, "microseconds": 10}), | ||||
|         ({"seconds": 3}, {"seconds": 3}), | ||||
|         ({"seconds": 3.0001}, {"seconds": 3}), | ||||
|         ({"seconds": 3.01}, {"seconds": 3, "milliseconds": 10}), | ||||
|         ({"minutes": 4}, {"minutes": 4}), | ||||
|         ({"minutes": 4.0001}, {"minutes": 4}), | ||||
|         ({"minutes": 4.1}, {"minutes": 4, "seconds": 6}), | ||||
|         ({"hours": 5}, {"hours": 5}), | ||||
|         ({"hours": 5.0001}, {"hours": 5}), | ||||
|         ({"hours": 5.1}, {"hours": 5, "minutes": 6}), | ||||
|         ({"days": 6}, {"days": 6}), | ||||
|         ({"days": 6.0001}, {"days": 6}), | ||||
|         ({"days": 6.1}, {"days": 6, "hours": 2, "minutes": 24}), | ||||
|     )) | ||||
|     @pytest.mark.parametrize( | ||||
|         "kwargs, expected", | ||||
|         ( | ||||
|             ({}, {}), | ||||
|             ({"microseconds": 1}, {"microseconds": 1}), | ||||
|             ({"microseconds": 1.0001}, {"microseconds": 1}), | ||||
|             ({"milliseconds": 2}, {"milliseconds": 2}), | ||||
|             ({"milliseconds": 2.0001}, {"milliseconds": 2}), | ||||
|             ({"milliseconds": 2.01}, {"milliseconds": 2, "microseconds": 10}), | ||||
|             ({"seconds": 3}, {"seconds": 3}), | ||||
|             ({"seconds": 3.0001}, {"seconds": 3}), | ||||
|             ({"seconds": 3.01}, {"seconds": 3, "milliseconds": 10}), | ||||
|             ({"minutes": 4}, {"minutes": 4}), | ||||
|             ({"minutes": 4.0001}, {"minutes": 4}), | ||||
|             ({"minutes": 4.1}, {"minutes": 4, "seconds": 6}), | ||||
|             ({"hours": 5}, {"hours": 5}), | ||||
|             ({"hours": 5.0001}, {"hours": 5}), | ||||
|             ({"hours": 5.1}, {"hours": 5, "minutes": 6}), | ||||
|             ({"days": 6}, {"days": 6}), | ||||
|             ({"days": 6.0001}, {"days": 6}), | ||||
|             ({"days": 6.1}, {"days": 6, "hours": 2, "minutes": 24}), | ||||
|         ), | ||||
|     ) | ||||
|     def test_init(self, kwargs, expected): | ||||
|         target = core.TimePeriod(**kwargs) | ||||
|  | ||||
| @@ -118,26 +120,29 @@ class TestTimePeriod: | ||||
|         with pytest.raises(ValueError, match="Maximum precision is microseconds"): | ||||
|             core.TimePeriod(microseconds=1.1) | ||||
|  | ||||
|     @pytest.mark.parametrize("kwargs, expected", ( | ||||
|         ({}, "0s"), | ||||
|         ({"microseconds": 1}, "1us"), | ||||
|         ({"microseconds": 1.0001}, "1us"), | ||||
|         ({"milliseconds": 2}, "2ms"), | ||||
|         ({"milliseconds": 2.0001}, "2ms"), | ||||
|         ({"milliseconds": 2.01}, "2010us"), | ||||
|         ({"seconds": 3}, "3s"), | ||||
|         ({"seconds": 3.0001}, "3s"), | ||||
|         ({"seconds": 3.01}, "3010ms"), | ||||
|         ({"minutes": 4}, "4min"), | ||||
|         ({"minutes": 4.0001}, "4min"), | ||||
|         ({"minutes": 4.1}, "246s"), | ||||
|         ({"hours": 5}, "5h"), | ||||
|         ({"hours": 5.0001}, "5h"), | ||||
|         ({"hours": 5.1}, "306min"), | ||||
|         ({"days": 6}, "6d"), | ||||
|         ({"days": 6.0001}, "6d"), | ||||
|         ({"days": 6.1}, "8784min"), | ||||
|     )) | ||||
|     @pytest.mark.parametrize( | ||||
|         "kwargs, expected", | ||||
|         ( | ||||
|             ({}, "0s"), | ||||
|             ({"microseconds": 1}, "1us"), | ||||
|             ({"microseconds": 1.0001}, "1us"), | ||||
|             ({"milliseconds": 2}, "2ms"), | ||||
|             ({"milliseconds": 2.0001}, "2ms"), | ||||
|             ({"milliseconds": 2.01}, "2010us"), | ||||
|             ({"seconds": 3}, "3s"), | ||||
|             ({"seconds": 3.0001}, "3s"), | ||||
|             ({"seconds": 3.01}, "3010ms"), | ||||
|             ({"minutes": 4}, "4min"), | ||||
|             ({"minutes": 4.0001}, "4min"), | ||||
|             ({"minutes": 4.1}, "246s"), | ||||
|             ({"hours": 5}, "5h"), | ||||
|             ({"hours": 5.0001}, "5h"), | ||||
|             ({"hours": 5.1}, "306min"), | ||||
|             ({"days": 6}, "6d"), | ||||
|             ({"days": 6.0001}, "6d"), | ||||
|             ({"days": 6.1}, "8784min"), | ||||
|         ), | ||||
|     ) | ||||
|     def test_str(self, kwargs, expected): | ||||
|         target = core.TimePeriod(**kwargs) | ||||
|  | ||||
| @@ -145,61 +150,59 @@ class TestTimePeriod: | ||||
|  | ||||
|         assert actual == expected | ||||
|  | ||||
|     @pytest.mark.parametrize("comparison, other, expected", ( | ||||
|         ("__eq__", core.TimePeriod(microseconds=900), False), | ||||
|         ("__eq__", core.TimePeriod(milliseconds=1), True), | ||||
|         ("__eq__", core.TimePeriod(microseconds=1100), False), | ||||
|         ("__eq__", 1000, NotImplemented), | ||||
|         ("__eq__", "1000", NotImplemented), | ||||
|         ("__eq__", True, NotImplemented), | ||||
|         ("__eq__", object(), NotImplemented), | ||||
|         ("__eq__", None, NotImplemented), | ||||
|  | ||||
|         ("__ne__", core.TimePeriod(microseconds=900), True), | ||||
|         ("__ne__", core.TimePeriod(milliseconds=1), False), | ||||
|         ("__ne__", core.TimePeriod(microseconds=1100), True), | ||||
|         ("__ne__", 1000, NotImplemented), | ||||
|         ("__ne__", "1000", NotImplemented), | ||||
|         ("__ne__", True, NotImplemented), | ||||
|         ("__ne__", object(), NotImplemented), | ||||
|         ("__ne__", None, NotImplemented), | ||||
|  | ||||
|         ("__lt__", core.TimePeriod(microseconds=900), False), | ||||
|         ("__lt__", core.TimePeriod(milliseconds=1), False), | ||||
|         ("__lt__", core.TimePeriod(microseconds=1100), True), | ||||
|         ("__lt__", 1000, NotImplemented), | ||||
|         ("__lt__", "1000", NotImplemented), | ||||
|         ("__lt__", True, NotImplemented), | ||||
|         ("__lt__", object(), NotImplemented), | ||||
|         ("__lt__", None, NotImplemented), | ||||
|  | ||||
|         ("__gt__", core.TimePeriod(microseconds=900), True), | ||||
|         ("__gt__", core.TimePeriod(milliseconds=1), False), | ||||
|         ("__gt__", core.TimePeriod(microseconds=1100), False), | ||||
|         ("__gt__", 1000, NotImplemented), | ||||
|         ("__gt__", "1000", NotImplemented), | ||||
|         ("__gt__", True, NotImplemented), | ||||
|         ("__gt__", object(), NotImplemented), | ||||
|         ("__gt__", None, NotImplemented), | ||||
|  | ||||
|         ("__le__", core.TimePeriod(microseconds=900), False), | ||||
|         ("__le__", core.TimePeriod(milliseconds=1), True), | ||||
|         ("__le__", core.TimePeriod(microseconds=1100), True), | ||||
|         ("__le__", 1000, NotImplemented), | ||||
|         ("__le__", "1000", NotImplemented), | ||||
|         ("__le__", True, NotImplemented), | ||||
|         ("__le__", object(), NotImplemented), | ||||
|         ("__le__", None, NotImplemented), | ||||
|  | ||||
|         ("__ge__", core.TimePeriod(microseconds=900), True), | ||||
|         ("__ge__", core.TimePeriod(milliseconds=1), True), | ||||
|         ("__ge__", core.TimePeriod(microseconds=1100), False), | ||||
|         ("__ge__", 1000, NotImplemented), | ||||
|         ("__ge__", "1000", NotImplemented), | ||||
|         ("__ge__", True, NotImplemented), | ||||
|         ("__ge__", object(), NotImplemented), | ||||
|         ("__ge__", None, NotImplemented), | ||||
|     )) | ||||
|     @pytest.mark.parametrize( | ||||
|         "comparison, other, expected", | ||||
|         ( | ||||
|             ("__eq__", core.TimePeriod(microseconds=900), False), | ||||
|             ("__eq__", core.TimePeriod(milliseconds=1), True), | ||||
|             ("__eq__", core.TimePeriod(microseconds=1100), False), | ||||
|             ("__eq__", 1000, NotImplemented), | ||||
|             ("__eq__", "1000", NotImplemented), | ||||
|             ("__eq__", True, NotImplemented), | ||||
|             ("__eq__", object(), NotImplemented), | ||||
|             ("__eq__", None, NotImplemented), | ||||
|             ("__ne__", core.TimePeriod(microseconds=900), True), | ||||
|             ("__ne__", core.TimePeriod(milliseconds=1), False), | ||||
|             ("__ne__", core.TimePeriod(microseconds=1100), True), | ||||
|             ("__ne__", 1000, NotImplemented), | ||||
|             ("__ne__", "1000", NotImplemented), | ||||
|             ("__ne__", True, NotImplemented), | ||||
|             ("__ne__", object(), NotImplemented), | ||||
|             ("__ne__", None, NotImplemented), | ||||
|             ("__lt__", core.TimePeriod(microseconds=900), False), | ||||
|             ("__lt__", core.TimePeriod(milliseconds=1), False), | ||||
|             ("__lt__", core.TimePeriod(microseconds=1100), True), | ||||
|             ("__lt__", 1000, NotImplemented), | ||||
|             ("__lt__", "1000", NotImplemented), | ||||
|             ("__lt__", True, NotImplemented), | ||||
|             ("__lt__", object(), NotImplemented), | ||||
|             ("__lt__", None, NotImplemented), | ||||
|             ("__gt__", core.TimePeriod(microseconds=900), True), | ||||
|             ("__gt__", core.TimePeriod(milliseconds=1), False), | ||||
|             ("__gt__", core.TimePeriod(microseconds=1100), False), | ||||
|             ("__gt__", 1000, NotImplemented), | ||||
|             ("__gt__", "1000", NotImplemented), | ||||
|             ("__gt__", True, NotImplemented), | ||||
|             ("__gt__", object(), NotImplemented), | ||||
|             ("__gt__", None, NotImplemented), | ||||
|             ("__le__", core.TimePeriod(microseconds=900), False), | ||||
|             ("__le__", core.TimePeriod(milliseconds=1), True), | ||||
|             ("__le__", core.TimePeriod(microseconds=1100), True), | ||||
|             ("__le__", 1000, NotImplemented), | ||||
|             ("__le__", "1000", NotImplemented), | ||||
|             ("__le__", True, NotImplemented), | ||||
|             ("__le__", object(), NotImplemented), | ||||
|             ("__le__", None, NotImplemented), | ||||
|             ("__ge__", core.TimePeriod(microseconds=900), True), | ||||
|             ("__ge__", core.TimePeriod(milliseconds=1), True), | ||||
|             ("__ge__", core.TimePeriod(microseconds=1100), False), | ||||
|             ("__ge__", 1000, NotImplemented), | ||||
|             ("__ge__", "1000", NotImplemented), | ||||
|             ("__ge__", True, NotImplemented), | ||||
|             ("__ge__", object(), NotImplemented), | ||||
|             ("__ge__", None, NotImplemented), | ||||
|         ), | ||||
|     ) | ||||
|     def test_comparison(self, comparison, other, expected): | ||||
|         target = core.TimePeriod(microseconds=1000) | ||||
|  | ||||
| @@ -238,19 +241,19 @@ class TestLambda: | ||||
|             "it.strftime(64, 0, ", | ||||
|             "my_font", | ||||
|             "", | ||||
|             ", TextAlign::TOP_CENTER, \"%H:%M:%S\", ", | ||||
|             ', TextAlign::TOP_CENTER, "%H:%M:%S", ', | ||||
|             "esptime", | ||||
|             ".", | ||||
|             "now());\nit.printf(64, 16, ", | ||||
|             "my_font2", | ||||
|             "", | ||||
|             ", TextAlign::TOP_CENTER, \"%.1f°C (%.1f%%)\", ", | ||||
|             ', TextAlign::TOP_CENTER, "%.1f°C (%.1f%%)", ', | ||||
|             "office_tmp", | ||||
|             ".", | ||||
|             "state, ", | ||||
|             "office_hmd", | ||||
|             ".", | ||||
|             "state);\n \nint x = 4; " | ||||
|             "state);\n \nint x = 4; ", | ||||
|         ] | ||||
|  | ||||
|     def test_requires_ids(self): | ||||
| @@ -296,24 +299,33 @@ class TestID: | ||||
|     def target(self): | ||||
|         return core.ID(None, is_declaration=True, type="binary_sensor::Example") | ||||
|  | ||||
|     @pytest.mark.parametrize("id, is_manual, expected", ( | ||||
|         ("foo", None, True), | ||||
|         (None, None, False), | ||||
|         ("foo", True, True), | ||||
|         ("foo", False, False), | ||||
|         (None, True, True), | ||||
|     )) | ||||
|     @pytest.mark.parametrize( | ||||
|         "id, is_manual, expected", | ||||
|         ( | ||||
|             ("foo", None, True), | ||||
|             (None, None, False), | ||||
|             ("foo", True, True), | ||||
|             ("foo", False, False), | ||||
|             (None, True, True), | ||||
|         ), | ||||
|     ) | ||||
|     def test_init__resolve_is_manual(self, id, is_manual, expected): | ||||
|         target = core.ID(id, is_manual=is_manual) | ||||
|  | ||||
|         assert target.is_manual == expected | ||||
|  | ||||
|     @pytest.mark.parametrize("registered_ids, expected", ( | ||||
|         ([], "binary_sensor_example"), | ||||
|         (["binary_sensor_example"], "binary_sensor_example_2"), | ||||
|         (["foo"], "binary_sensor_example"), | ||||
|         (["binary_sensor_example", "foo", "binary_sensor_example_2"], "binary_sensor_example_3"), | ||||
|     )) | ||||
|     @pytest.mark.parametrize( | ||||
|         "registered_ids, expected", | ||||
|         ( | ||||
|             ([], "binary_sensor_example"), | ||||
|             (["binary_sensor_example"], "binary_sensor_example_2"), | ||||
|             (["foo"], "binary_sensor_example"), | ||||
|             ( | ||||
|                 ["binary_sensor_example", "foo", "binary_sensor_example_2"], | ||||
|                 "binary_sensor_example_3", | ||||
|             ), | ||||
|         ), | ||||
|     ) | ||||
|     def test_resolve(self, target, registered_ids, expected): | ||||
|         actual = target.resolve(registered_ids) | ||||
|  | ||||
| @@ -326,18 +338,23 @@ class TestID: | ||||
|         actual = target.copy() | ||||
|  | ||||
|         assert actual is not target | ||||
|         assert all(getattr(actual, n) == getattr(target, n) | ||||
|                    for n in ("id", "is_declaration", "type", "is_manual")) | ||||
|         assert all( | ||||
|             getattr(actual, n) == getattr(target, n) | ||||
|             for n in ("id", "is_declaration", "type", "is_manual") | ||||
|         ) | ||||
|  | ||||
|     @pytest.mark.parametrize("comparison, other, expected", ( | ||||
|         ("__eq__", core.ID(id="foo"), True), | ||||
|         ("__eq__", core.ID(id="bar"), False), | ||||
|         ("__eq__", 1000, NotImplemented), | ||||
|         ("__eq__", "1000", NotImplemented), | ||||
|         ("__eq__", True, NotImplemented), | ||||
|         ("__eq__", object(), NotImplemented), | ||||
|         ("__eq__", None, NotImplemented), | ||||
|     )) | ||||
|     @pytest.mark.parametrize( | ||||
|         "comparison, other, expected", | ||||
|         ( | ||||
|             ("__eq__", core.ID(id="foo"), True), | ||||
|             ("__eq__", core.ID(id="bar"), False), | ||||
|             ("__eq__", 1000, NotImplemented), | ||||
|             ("__eq__", "1000", NotImplemented), | ||||
|             ("__eq__", True, NotImplemented), | ||||
|             ("__eq__", object(), NotImplemented), | ||||
|             ("__eq__", None, NotImplemented), | ||||
|         ), | ||||
|     ) | ||||
|     def test_comparison(self, comparison, other, expected): | ||||
|         target = core.ID(id="foo") | ||||
|  | ||||
| @@ -384,14 +401,17 @@ class TestDocumentRange: | ||||
|  | ||||
|  | ||||
| class TestDefine: | ||||
|     @pytest.mark.parametrize("name, value, prop, expected", ( | ||||
|         ("ANSWER", None, "as_build_flag", "-DANSWER"), | ||||
|         ("ANSWER", None, "as_macro", "#define ANSWER"), | ||||
|         ("ANSWER", None, "as_tuple", ("ANSWER", None)), | ||||
|         ("ANSWER", 42, "as_build_flag", "-DANSWER=42"), | ||||
|         ("ANSWER", 42, "as_macro", "#define ANSWER 42"), | ||||
|         ("ANSWER", 42, "as_tuple", ("ANSWER", 42)), | ||||
|     )) | ||||
|     @pytest.mark.parametrize( | ||||
|         "name, value, prop, expected", | ||||
|         ( | ||||
|             ("ANSWER", None, "as_build_flag", "-DANSWER"), | ||||
|             ("ANSWER", None, "as_macro", "#define ANSWER"), | ||||
|             ("ANSWER", None, "as_tuple", ("ANSWER", None)), | ||||
|             ("ANSWER", 42, "as_build_flag", "-DANSWER=42"), | ||||
|             ("ANSWER", 42, "as_macro", "#define ANSWER 42"), | ||||
|             ("ANSWER", 42, "as_tuple", ("ANSWER", 42)), | ||||
|         ), | ||||
|     ) | ||||
|     def test_properties(self, name, value, prop, expected): | ||||
|         target = core.Define(name, value) | ||||
|  | ||||
| @@ -399,18 +419,21 @@ class TestDefine: | ||||
|  | ||||
|         assert actual == expected | ||||
|  | ||||
|     @pytest.mark.parametrize("comparison, other, expected", ( | ||||
|         ("__eq__", core.Define(name="FOO", value=42), True), | ||||
|         ("__eq__", core.Define(name="FOO", value=13), False), | ||||
|         ("__eq__", core.Define(name="FOO"), False), | ||||
|         ("__eq__", core.Define(name="BAR", value=42), False), | ||||
|         ("__eq__", core.Define(name="BAR"), False), | ||||
|         ("__eq__", 1000, NotImplemented), | ||||
|         ("__eq__", "1000", NotImplemented), | ||||
|         ("__eq__", True, NotImplemented), | ||||
|         ("__eq__", object(), NotImplemented), | ||||
|         ("__eq__", None, NotImplemented), | ||||
|     )) | ||||
|     @pytest.mark.parametrize( | ||||
|         "comparison, other, expected", | ||||
|         ( | ||||
|             ("__eq__", core.Define(name="FOO", value=42), True), | ||||
|             ("__eq__", core.Define(name="FOO", value=13), False), | ||||
|             ("__eq__", core.Define(name="FOO"), False), | ||||
|             ("__eq__", core.Define(name="BAR", value=42), False), | ||||
|             ("__eq__", core.Define(name="BAR"), False), | ||||
|             ("__eq__", 1000, NotImplemented), | ||||
|             ("__eq__", "1000", NotImplemented), | ||||
|             ("__eq__", True, NotImplemented), | ||||
|             ("__eq__", object(), NotImplemented), | ||||
|             ("__eq__", None, NotImplemented), | ||||
|         ), | ||||
|     ) | ||||
|     def test_comparison(self, comparison, other, expected): | ||||
|         target = core.Define(name="FOO", value=42) | ||||
|  | ||||
| @@ -420,12 +443,15 @@ class TestDefine: | ||||
|  | ||||
|  | ||||
| class TestLibrary: | ||||
|     @pytest.mark.parametrize("name, value, prop, expected", ( | ||||
|         ("mylib", None, "as_lib_dep", "mylib"), | ||||
|         ("mylib", None, "as_tuple", ("mylib", None)), | ||||
|         ("mylib", "1.2.3", "as_lib_dep", "mylib@1.2.3"), | ||||
|         ("mylib", "1.2.3", "as_tuple", ("mylib", "1.2.3")), | ||||
|     )) | ||||
|     @pytest.mark.parametrize( | ||||
|         "name, value, prop, expected", | ||||
|         ( | ||||
|             ("mylib", None, "as_lib_dep", "mylib"), | ||||
|             ("mylib", None, "as_tuple", ("mylib", None)), | ||||
|             ("mylib", "1.2.3", "as_lib_dep", "mylib@1.2.3"), | ||||
|             ("mylib", "1.2.3", "as_tuple", ("mylib", "1.2.3")), | ||||
|         ), | ||||
|     ) | ||||
|     def test_properties(self, name, value, prop, expected): | ||||
|         target = core.Library(name, value) | ||||
|  | ||||
| @@ -433,16 +459,19 @@ class TestLibrary: | ||||
|  | ||||
|         assert actual == expected | ||||
|  | ||||
|     @pytest.mark.parametrize("comparison, other, expected", ( | ||||
|         ("__eq__", core.Library(name="libfoo", version="1.2.3"), True), | ||||
|         ("__eq__", core.Library(name="libfoo", version="1.2.4"), False), | ||||
|         ("__eq__", core.Library(name="libbar", version="1.2.3"), False), | ||||
|         ("__eq__", 1000, NotImplemented), | ||||
|         ("__eq__", "1000", NotImplemented), | ||||
|         ("__eq__", True, NotImplemented), | ||||
|         ("__eq__", object(), NotImplemented), | ||||
|         ("__eq__", None, NotImplemented), | ||||
|     )) | ||||
|     @pytest.mark.parametrize( | ||||
|         "comparison, other, expected", | ||||
|         ( | ||||
|             ("__eq__", core.Library(name="libfoo", version="1.2.3"), True), | ||||
|             ("__eq__", core.Library(name="libfoo", version="1.2.4"), False), | ||||
|             ("__eq__", core.Library(name="libbar", version="1.2.3"), False), | ||||
|             ("__eq__", 1000, NotImplemented), | ||||
|             ("__eq__", "1000", NotImplemented), | ||||
|             ("__eq__", True, NotImplemented), | ||||
|             ("__eq__", object(), NotImplemented), | ||||
|             ("__eq__", None, NotImplemented), | ||||
|         ), | ||||
|     ) | ||||
|     def test_comparison(self, comparison, other, expected): | ||||
|         target = core.Library(name="libfoo", version="1.2.3") | ||||
|  | ||||
|   | ||||
| @@ -9,18 +9,18 @@ from esphome import cpp_types as ct | ||||
|  | ||||
|  | ||||
| class TestExpressions: | ||||
|     @pytest.mark.parametrize("target, expected", ( | ||||
|         (cg.RawExpression("foo && bar"), "foo && bar"), | ||||
|  | ||||
|         (cg.AssignmentExpression(None, None, "foo", "bar", None), 'foo = "bar"'), | ||||
|         (cg.AssignmentExpression(ct.float_, "*", "foo", 1, None), 'float *foo = 1'), | ||||
|         (cg.AssignmentExpression(ct.float_, "", "foo", 1, None), 'float foo = 1'), | ||||
|  | ||||
|         (cg.VariableDeclarationExpression(ct.int32, "*", "foo"), "int32_t *foo"), | ||||
|         (cg.VariableDeclarationExpression(ct.int32, "", "foo"), "int32_t foo"), | ||||
|  | ||||
|         (cg.ParameterExpression(ct.std_string, "foo"), "std::string foo"), | ||||
|     )) | ||||
|     @pytest.mark.parametrize( | ||||
|         "target, expected", | ||||
|         ( | ||||
|             (cg.RawExpression("foo && bar"), "foo && bar"), | ||||
|             (cg.AssignmentExpression(None, None, "foo", "bar", None), 'foo = "bar"'), | ||||
|             (cg.AssignmentExpression(ct.float_, "*", "foo", 1, None), "float *foo = 1"), | ||||
|             (cg.AssignmentExpression(ct.float_, "", "foo", 1, None), "float foo = 1"), | ||||
|             (cg.VariableDeclarationExpression(ct.int32, "*", "foo"), "int32_t *foo"), | ||||
|             (cg.VariableDeclarationExpression(ct.int32, "", "foo"), "int32_t foo"), | ||||
|             (cg.ParameterExpression(ct.std_string, "foo"), "std::string foo"), | ||||
|         ), | ||||
|     ) | ||||
|     def test_str__simple(self, target: cg.Expression, expected: str): | ||||
|         actual = str(target) | ||||
|  | ||||
| @@ -67,10 +67,7 @@ class TestTemplateArguments: | ||||
|  | ||||
| class TestCallExpression: | ||||
|     def test_str__no_template_args(self): | ||||
|         target = cg.CallExpression( | ||||
|             cg.RawExpression("my_function"), | ||||
|             1, "2", False | ||||
|         ) | ||||
|         target = cg.CallExpression(cg.RawExpression("my_function"), 1, "2", False) | ||||
|  | ||||
|         actual = str(target) | ||||
|  | ||||
| @@ -80,7 +77,9 @@ class TestCallExpression: | ||||
|         target = cg.CallExpression( | ||||
|             cg.RawExpression("my_function"), | ||||
|             cg.TemplateArguments(int, float), | ||||
|             1, "2", False | ||||
|             1, | ||||
|             "2", | ||||
|             False, | ||||
|         ) | ||||
|  | ||||
|         actual = str(target) | ||||
| @@ -100,36 +99,32 @@ class TestStructInitializer: | ||||
|  | ||||
|         actual = str(target) | ||||
|  | ||||
|         assert actual == 'foo::MyStruct{\n' \ | ||||
|                          '  .state = "on",\n' \ | ||||
|                          '  .min_length = 1,\n' \ | ||||
|                          '  .max_length = 5,\n' \ | ||||
|                          '}' | ||||
|         assert ( | ||||
|             actual == "foo::MyStruct{\n" | ||||
|             '  .state = "on",\n' | ||||
|             "  .min_length = 1,\n" | ||||
|             "  .max_length = 5,\n" | ||||
|             "}" | ||||
|         ) | ||||
|  | ||||
|  | ||||
| class TestArrayInitializer: | ||||
|     def test_str__empty(self): | ||||
|         target = cg.ArrayInitializer( | ||||
|             None, None | ||||
|         ) | ||||
|         target = cg.ArrayInitializer(None, None) | ||||
|  | ||||
|         actual = str(target) | ||||
|  | ||||
|         assert actual == "{}" | ||||
|  | ||||
|     def test_str__not_multiline(self): | ||||
|         target = cg.ArrayInitializer( | ||||
|             1, 2, 3, 4 | ||||
|         ) | ||||
|         target = cg.ArrayInitializer(1, 2, 3, 4) | ||||
|  | ||||
|         actual = str(target) | ||||
|  | ||||
|         assert actual == "{1, 2, 3, 4}" | ||||
|  | ||||
|     def test_str__multiline(self): | ||||
|         target = cg.ArrayInitializer( | ||||
|             1, 2, 3, 4, multiline=True | ||||
|         ) | ||||
|         target = cg.ArrayInitializer(1, 2, 3, 4, multiline=True) | ||||
|  | ||||
|         actual = str(target) | ||||
|  | ||||
| @@ -169,7 +164,7 @@ class TestLambdaExpression: | ||||
|  | ||||
|     def test_str__with_return(self): | ||||
|         target = cg.LambdaExpression( | ||||
|             ("return (foo == 5) && (bar < 10));", ), | ||||
|             ("return (foo == 5) && (bar < 10));",), | ||||
|             cg.ParameterListExpression((int, "foo"), (float, "bar")), | ||||
|             "=", | ||||
|             bool, | ||||
| @@ -185,27 +180,26 @@ class TestLambdaExpression: | ||||
|  | ||||
|  | ||||
| class TestLiterals: | ||||
|     @pytest.mark.parametrize("target, expected", ( | ||||
|         (cg.StringLiteral("foo"), '"foo"'), | ||||
|  | ||||
|         (cg.IntLiteral(0), "0"), | ||||
|         (cg.IntLiteral(42), "42"), | ||||
|         (cg.IntLiteral(4304967295), "4304967295ULL"), | ||||
|         (cg.IntLiteral(2150483647), "2150483647UL"), | ||||
|         (cg.IntLiteral(-2150083647), "-2150083647LL"), | ||||
|  | ||||
|         (cg.BoolLiteral(True), "true"), | ||||
|         (cg.BoolLiteral(False), "false"), | ||||
|  | ||||
|         (cg.HexIntLiteral(0), "0x00"), | ||||
|         (cg.HexIntLiteral(42), "0x2A"), | ||||
|         (cg.HexIntLiteral(682), "0x2AA"), | ||||
|  | ||||
|         (cg.FloatLiteral(0.0), "0.0f"), | ||||
|         (cg.FloatLiteral(4.2), "4.2f"), | ||||
|         (cg.FloatLiteral(1.23456789), "1.23456789f"), | ||||
|         (cg.FloatLiteral(math.nan), "NAN"), | ||||
|     )) | ||||
|     @pytest.mark.parametrize( | ||||
|         "target, expected", | ||||
|         ( | ||||
|             (cg.StringLiteral("foo"), '"foo"'), | ||||
|             (cg.IntLiteral(0), "0"), | ||||
|             (cg.IntLiteral(42), "42"), | ||||
|             (cg.IntLiteral(4304967295), "4304967295ULL"), | ||||
|             (cg.IntLiteral(2150483647), "2150483647UL"), | ||||
|             (cg.IntLiteral(-2150083647), "-2150083647LL"), | ||||
|             (cg.BoolLiteral(True), "true"), | ||||
|             (cg.BoolLiteral(False), "false"), | ||||
|             (cg.HexIntLiteral(0), "0x00"), | ||||
|             (cg.HexIntLiteral(42), "0x2A"), | ||||
|             (cg.HexIntLiteral(682), "0x2AA"), | ||||
|             (cg.FloatLiteral(0.0), "0.0f"), | ||||
|             (cg.FloatLiteral(4.2), "4.2f"), | ||||
|             (cg.FloatLiteral(1.23456789), "1.23456789f"), | ||||
|             (cg.FloatLiteral(math.nan), "NAN"), | ||||
|         ), | ||||
|     ) | ||||
|     def test_str__simple(self, target: cg.Literal, expected: str): | ||||
|         actual = str(target) | ||||
|  | ||||
| @@ -216,7 +210,9 @@ FAKE_ENUM_VALUE = cg.EnumValue() | ||||
| FAKE_ENUM_VALUE.enum_value = "foo" | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("obj, expected_type", ( | ||||
| @pytest.mark.parametrize( | ||||
|     "obj, expected_type", | ||||
|     ( | ||||
|         (cg.RawExpression("foo"), cg.RawExpression), | ||||
|         (FAKE_ENUM_VALUE, cg.StringLiteral), | ||||
|         (True, cg.BoolLiteral), | ||||
| @@ -230,49 +226,59 @@ FAKE_ENUM_VALUE.enum_value = "foo" | ||||
|         (cg.TimePeriodMinutes(minutes=42), cg.IntLiteral), | ||||
|         ((1, 2, 3), cg.ArrayInitializer), | ||||
|         ([1, 2, 3], cg.ArrayInitializer), | ||||
| )) | ||||
|     ), | ||||
| ) | ||||
| def test_safe_exp__allowed_values(obj, expected_type): | ||||
|     actual = cg.safe_exp(obj) | ||||
|  | ||||
|     assert isinstance(actual, expected_type) | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("obj, expected_type", ( | ||||
| @pytest.mark.parametrize( | ||||
|     "obj, expected_type", | ||||
|     ( | ||||
|         (bool, ct.bool_), | ||||
|         (int, ct.int32), | ||||
|         (float, ct.float_), | ||||
| )) | ||||
|     ), | ||||
| ) | ||||
| def test_safe_exp__allowed_types(obj, expected_type): | ||||
|     actual = cg.safe_exp(obj) | ||||
|  | ||||
|     assert actual is expected_type | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("obj, expected_error", ( | ||||
| @pytest.mark.parametrize( | ||||
|     "obj, expected_error", | ||||
|     ( | ||||
|         (cg.ID("foo"), "Object foo is an ID."), | ||||
|         ((x for x in "foo"), r"Object <.*> is a coroutine."), | ||||
|         (None, "Object is not an expression"), | ||||
| )) | ||||
|     ), | ||||
| ) | ||||
| def test_safe_exp__invalid_values(obj, expected_error): | ||||
|     with pytest.raises(ValueError, match=expected_error): | ||||
|         cg.safe_exp(obj) | ||||
|  | ||||
|  | ||||
| class TestStatements: | ||||
|     @pytest.mark.parametrize("target, expected", ( | ||||
|         (cg.RawStatement("foo && bar"), "foo && bar"), | ||||
|  | ||||
|         (cg.ExpressionStatement("foo"), '"foo";'), | ||||
|         (cg.ExpressionStatement(42), '42;'), | ||||
|  | ||||
|         (cg.LineComment("The point of foo is..."), "// The point of foo is..."), | ||||
|         (cg.LineComment("Help help\nI'm being repressed"), "// Help help\n// I'm being repressed"), | ||||
|  | ||||
|     @pytest.mark.parametrize( | ||||
|         "target, expected", | ||||
|         ( | ||||
|             cg.ProgmemAssignmentExpression(ct.uint16, "foo", "bar", None), | ||||
|             'static const uint16_t foo[] PROGMEM = "bar"' | ||||
|         ) | ||||
|     )) | ||||
|             (cg.RawStatement("foo && bar"), "foo && bar"), | ||||
|             (cg.ExpressionStatement("foo"), '"foo";'), | ||||
|             (cg.ExpressionStatement(42), "42;"), | ||||
|             (cg.LineComment("The point of foo is..."), "// The point of foo is..."), | ||||
|             ( | ||||
|                 cg.LineComment("Help help\nI'm being repressed"), | ||||
|                 "// Help help\n// I'm being repressed", | ||||
|             ), | ||||
|             ( | ||||
|                 cg.ProgmemAssignmentExpression(ct.uint16, "foo", "bar", None), | ||||
|                 'static const uint16_t foo[] PROGMEM = "bar"', | ||||
|             ), | ||||
|         ), | ||||
|     ) | ||||
|     def test_str__simple(self, target: cg.Statement, expected: str): | ||||
|         actual = str(target) | ||||
|  | ||||
|   | ||||
| @@ -15,11 +15,9 @@ def test_gpio_pin_expression__conf_is_none(monkeypatch): | ||||
|  | ||||
|  | ||||
| def test_gpio_pin_expression__new_pin(monkeypatch): | ||||
|     target = ch.gpio_pin_expression({ | ||||
|         const.CONF_NUMBER: 42, | ||||
|         const.CONF_MODE: "input", | ||||
|         const.CONF_INVERTED: False | ||||
|     }) | ||||
|     target = ch.gpio_pin_expression( | ||||
|         {const.CONF_NUMBER: 42, const.CONF_MODE: "input", const.CONF_INVERTED: False} | ||||
|     ) | ||||
|  | ||||
|     actual = next(target) | ||||
|  | ||||
| @@ -71,10 +69,13 @@ def test_register_component__with_setup_priority(monkeypatch): | ||||
|     add_mock = Mock() | ||||
|     monkeypatch.setattr(ch, "add", add_mock) | ||||
|  | ||||
|     target = ch.register_component(var, { | ||||
|         const.CONF_SETUP_PRIORITY: "123", | ||||
|         const.CONF_UPDATE_INTERVAL: "456", | ||||
|     }) | ||||
|     target = ch.register_component( | ||||
|         var, | ||||
|         { | ||||
|             const.CONF_SETUP_PRIORITY: "123", | ||||
|             const.CONF_UPDATE_INTERVAL: "456", | ||||
|         }, | ||||
|     ) | ||||
|  | ||||
|     actual = next(target) | ||||
|  | ||||
|   | ||||
| @@ -6,69 +6,89 @@ from hypothesis.provisional import ip_addresses | ||||
| from esphome import helpers | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("preferred_string, current_strings, expected", ( | ||||
|     ("foo", [], "foo"), | ||||
|     # TODO: Should this actually start at 1? | ||||
|     ("foo", ["foo"], "foo_2"), | ||||
|     ("foo", ("foo",), "foo_2"), | ||||
|     ("foo", ("foo", "foo_2"), "foo_3"), | ||||
|     ("foo", ("foo", "foo_2", "foo_2"), "foo_3"), | ||||
| )) | ||||
| @pytest.mark.parametrize( | ||||
|     "preferred_string, current_strings, expected", | ||||
|     ( | ||||
|         ("foo", [], "foo"), | ||||
|         # TODO: Should this actually start at 1? | ||||
|         ("foo", ["foo"], "foo_2"), | ||||
|         ("foo", ("foo",), "foo_2"), | ||||
|         ("foo", ("foo", "foo_2"), "foo_3"), | ||||
|         ("foo", ("foo", "foo_2", "foo_2"), "foo_3"), | ||||
|     ), | ||||
| ) | ||||
| def test_ensure_unique_string(preferred_string, current_strings, expected): | ||||
|     actual = helpers.ensure_unique_string(preferred_string, current_strings) | ||||
|  | ||||
|     assert actual == expected | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("text, expected", ( | ||||
|     ("foo", "foo"), | ||||
|     ("foo\nbar", "foo\nbar"), | ||||
|     ("foo\nbar\neek", "foo\n  bar\neek"), | ||||
| )) | ||||
| @pytest.mark.parametrize( | ||||
|     "text, expected", | ||||
|     ( | ||||
|         ("foo", "foo"), | ||||
|         ("foo\nbar", "foo\nbar"), | ||||
|         ("foo\nbar\neek", "foo\n  bar\neek"), | ||||
|     ), | ||||
| ) | ||||
| def test_indent_all_but_first_and_last(text, expected): | ||||
|     actual = helpers.indent_all_but_first_and_last(text) | ||||
|  | ||||
|     assert actual == expected | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("text, expected", ( | ||||
|     ("foo", ["  foo"]), | ||||
|     ("foo\nbar", ["  foo", "  bar"]), | ||||
|     ("foo\nbar\neek", ["  foo", "  bar", "  eek"]), | ||||
| )) | ||||
| @pytest.mark.parametrize( | ||||
|     "text, expected", | ||||
|     ( | ||||
|         ("foo", ["  foo"]), | ||||
|         ("foo\nbar", ["  foo", "  bar"]), | ||||
|         ("foo\nbar\neek", ["  foo", "  bar", "  eek"]), | ||||
|     ), | ||||
| ) | ||||
| def test_indent_list(text, expected): | ||||
|     actual = helpers.indent_list(text) | ||||
|  | ||||
|     assert actual == expected | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("text, expected", ( | ||||
|     ("foo", "  foo"), | ||||
|     ("foo\nbar", "  foo\n  bar"), | ||||
|     ("foo\nbar\neek", "  foo\n  bar\n  eek"), | ||||
| )) | ||||
| @pytest.mark.parametrize( | ||||
|     "text, expected", | ||||
|     ( | ||||
|         ("foo", "  foo"), | ||||
|         ("foo\nbar", "  foo\n  bar"), | ||||
|         ("foo\nbar\neek", "  foo\n  bar\n  eek"), | ||||
|     ), | ||||
| ) | ||||
| def test_indent(text, expected): | ||||
|     actual = helpers.indent(text) | ||||
|  | ||||
|     assert actual == expected | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("string, expected", ( | ||||
|     ("foo", '"foo"'), | ||||
|     ("foo\nbar", '"foo\\012bar"'), | ||||
|     ("foo\\bar", '"foo\\134bar"'), | ||||
|     ('foo "bar"', '"foo \\042bar\\042"'), | ||||
|     ('foo 🐍', '"foo \\360\\237\\220\\215"'), | ||||
| )) | ||||
| @pytest.mark.parametrize( | ||||
|     "string, expected", | ||||
|     ( | ||||
|         ("foo", '"foo"'), | ||||
|         ("foo\nbar", '"foo\\012bar"'), | ||||
|         ("foo\\bar", '"foo\\134bar"'), | ||||
|         ('foo "bar"', '"foo \\042bar\\042"'), | ||||
|         ("foo 🐍", '"foo \\360\\237\\220\\215"'), | ||||
|     ), | ||||
| ) | ||||
| def test_cpp_string_escape(string, expected): | ||||
|     actual = helpers.cpp_string_escape(string) | ||||
|  | ||||
|     assert actual == expected | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("host", ( | ||||
|     "127.0.0", "localhost", "127.0.0.b", | ||||
| )) | ||||
| @pytest.mark.parametrize( | ||||
|     "host", | ||||
|     ( | ||||
|         "127.0.0", | ||||
|         "localhost", | ||||
|         "127.0.0.b", | ||||
|     ), | ||||
| ) | ||||
| def test_is_ip_address__invalid(host): | ||||
|     actual = helpers.is_ip_address(host) | ||||
|  | ||||
| @@ -82,13 +102,16 @@ def test_is_ip_address__valid(value): | ||||
|     assert actual is True | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("var, value, default, expected", ( | ||||
|     ("FOO", None, False, False), | ||||
|     ("FOO", None, True, True), | ||||
|     ("FOO", "", False, False), | ||||
|     ("FOO", "Yes", False, True), | ||||
|     ("FOO", "123", False, True), | ||||
| )) | ||||
| @pytest.mark.parametrize( | ||||
|     "var, value, default, expected", | ||||
|     ( | ||||
|         ("FOO", None, False, False), | ||||
|         ("FOO", None, True, True), | ||||
|         ("FOO", "", False, False), | ||||
|         ("FOO", "Yes", False, True), | ||||
|         ("FOO", "123", False, True), | ||||
|     ), | ||||
| ) | ||||
| def test_get_bool_env(monkeypatch, var, value, default, expected): | ||||
|     if value is None: | ||||
|         monkeypatch.delenv(var, raising=False) | ||||
| @@ -100,10 +123,7 @@ def test_get_bool_env(monkeypatch, var, value, default, expected): | ||||
|     assert actual == expected | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("value, expected", ( | ||||
|     (None, False), | ||||
|     ("Yes", True) | ||||
| )) | ||||
| @pytest.mark.parametrize("value, expected", ((None, False), ("Yes", True))) | ||||
| def test_is_hassio(monkeypatch, value, expected): | ||||
|     if value is None: | ||||
|         monkeypatch.delenv("ESPHOME_IS_HASSIO", raising=False) | ||||
| @@ -185,20 +205,23 @@ class Test_copy_file_if_changed: | ||||
|         assert src.read_text() == dst.read_text() | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("file1, file2, expected", ( | ||||
|     # Same file | ||||
|     ("file-a.txt", "file-a.txt", True), | ||||
|     # Different files, different size | ||||
|     ("file-a.txt", "file-b_1.txt", False), | ||||
|     # Different files, same size | ||||
|     ("file-a.txt", "file-c.txt", False), | ||||
|     # Same files | ||||
|     ("file-b_1.txt", "file-b_2.txt", True), | ||||
|     # Not a file | ||||
|     ("file-a.txt", "", False), | ||||
|     # File doesn't exist | ||||
|     ("file-a.txt", "file-d.txt", False), | ||||
| )) | ||||
| @pytest.mark.parametrize( | ||||
|     "file1, file2, expected", | ||||
|     ( | ||||
|         # Same file | ||||
|         ("file-a.txt", "file-a.txt", True), | ||||
|         # Different files, different size | ||||
|         ("file-a.txt", "file-b_1.txt", False), | ||||
|         # Different files, same size | ||||
|         ("file-a.txt", "file-c.txt", False), | ||||
|         # Same files | ||||
|         ("file-b_1.txt", "file-b_2.txt", True), | ||||
|         # Not a file | ||||
|         ("file-a.txt", "", False), | ||||
|         # File doesn't exist | ||||
|         ("file-a.txt", "file-d.txt", False), | ||||
|     ), | ||||
| ) | ||||
| def test_file_compare(fixture_path, file1, file2, expected): | ||||
|     path1 = fixture_path / "helpers" / file1 | ||||
|     path2 = fixture_path / "helpers" / file2 | ||||
|   | ||||
| @@ -15,12 +15,12 @@ from esphome import pins | ||||
|  | ||||
|  | ||||
| MOCK_ESP8266_BOARD_ID = "_mock_esp8266" | ||||
| MOCK_ESP8266_PINS = {'X0': 16, 'X1': 5, 'X2': 4, 'LED': 2} | ||||
| MOCK_ESP8266_PINS = {"X0": 16, "X1": 5, "X2": 4, "LED": 2} | ||||
| MOCK_ESP8266_BOARD_ALIAS_ID = "_mock_esp8266_alias" | ||||
| MOCK_ESP8266_FLASH_SIZE = pins.FLASH_SIZE_2_MB | ||||
|  | ||||
| MOCK_ESP32_BOARD_ID = "_mock_esp32" | ||||
| MOCK_ESP32_PINS = {'Y0': 12, 'Y1': 8, 'Y2': 3, 'LED': 9, "A0": 8} | ||||
| MOCK_ESP32_PINS = {"Y0": 12, "Y1": 8, "Y2": 3, "LED": 9, "A0": 8} | ||||
| MOCK_ESP32_BOARD_ALIAS_ID = "_mock_esp32_alias" | ||||
|  | ||||
| UNKNOWN_PLATFORM = "STM32" | ||||
| @@ -68,10 +68,13 @@ def core_esp32(core): | ||||
|  | ||||
|  | ||||
| class Test_lookup_pin: | ||||
|     @pytest.mark.parametrize("value, expected", ( | ||||
|     @pytest.mark.parametrize( | ||||
|         "value, expected", | ||||
|         ( | ||||
|             ("X1", 5), | ||||
|             ("MOSI", 13), | ||||
|     )) | ||||
|         ), | ||||
|     ) | ||||
|     def test_valid_esp8266_pin(self, core_esp8266, value, expected): | ||||
|         actual = pins._lookup_pin(value) | ||||
|  | ||||
| @@ -84,11 +87,14 @@ class Test_lookup_pin: | ||||
|  | ||||
|         assert actual == 4 | ||||
|  | ||||
|     @pytest.mark.parametrize("value, expected", ( | ||||
|     @pytest.mark.parametrize( | ||||
|         "value, expected", | ||||
|         ( | ||||
|             ("Y1", 8), | ||||
|             ("A0", 8), | ||||
|             ("MOSI", 23), | ||||
|     )) | ||||
|         ), | ||||
|     ) | ||||
|     def test_valid_esp32_pin(self, core_esp32, value, expected): | ||||
|         actual = pins._lookup_pin(value) | ||||
|  | ||||
| @@ -102,7 +108,9 @@ class Test_lookup_pin: | ||||
|         assert actual == 3 | ||||
|  | ||||
|     def test_invalid_pin(self, core_esp8266): | ||||
|         with pytest.raises(Invalid, match="Cannot resolve pin name 'X42' for board _mock_esp8266."): | ||||
|         with pytest.raises( | ||||
|             Invalid, match="Cannot resolve pin name 'X42' for board _mock_esp8266." | ||||
|         ): | ||||
|             pins._lookup_pin("X42") | ||||
|  | ||||
|     def test_unsupported_platform(self, core): | ||||
| @@ -113,13 +121,16 @@ class Test_lookup_pin: | ||||
|  | ||||
|  | ||||
| class Test_translate_pin: | ||||
|     @pytest.mark.parametrize("value, expected", ( | ||||
|     @pytest.mark.parametrize( | ||||
|         "value, expected", | ||||
|         ( | ||||
|             (2, 2), | ||||
|             ("3", 3), | ||||
|             ("GPIO4", 4), | ||||
|             ("TX", 1), | ||||
|             ("Y0", 12), | ||||
|     )) | ||||
|         ), | ||||
|     ) | ||||
|     def test_valid_values(self, core_esp32, value, expected): | ||||
|         actual = pins._translate_pin(value) | ||||
|  | ||||
| @@ -137,7 +148,9 @@ class Test_validate_gpio_pin: | ||||
|  | ||||
|         assert actual == 22 | ||||
|  | ||||
|     @pytest.mark.parametrize("value, match", ( | ||||
|     @pytest.mark.parametrize( | ||||
|         "value, match", | ||||
|         ( | ||||
|             (-1, "ESP32: Invalid pin number: -1"), | ||||
|             (40, "ESP32: Invalid pin number: 40"), | ||||
|             (6, "This pin cannot be used on ESP32s and"), | ||||
| @@ -150,7 +163,8 @@ class Test_validate_gpio_pin: | ||||
|             (29, "The pin GPIO29 is not usable on ESP32s"), | ||||
|             (30, "The pin GPIO30 is not usable on ESP32s"), | ||||
|             (31, "The pin GPIO31 is not usable on ESP32s"), | ||||
|     )) | ||||
|         ), | ||||
|     ) | ||||
|     def test_esp32_invalid_pin(self, core_esp32, value, match): | ||||
|         with pytest.raises(Invalid, match=match): | ||||
|             pins.validate_gpio_pin(value) | ||||
| @@ -168,14 +182,17 @@ class Test_validate_gpio_pin: | ||||
|  | ||||
|         assert actual == 12 | ||||
|  | ||||
|     @pytest.mark.parametrize("value, match", ( | ||||
|     @pytest.mark.parametrize( | ||||
|         "value, match", | ||||
|         ( | ||||
|             (-1, "ESP8266: Invalid pin number: -1"), | ||||
|             (18, "ESP8266: Invalid pin number: 18"), | ||||
|             (6, "This pin cannot be used on ESP8266s and"), | ||||
|             (7, "This pin cannot be used on ESP8266s and"), | ||||
|             (8, "This pin cannot be used on ESP8266s and"), | ||||
|             (11, "This pin cannot be used on ESP8266s and"), | ||||
|     )) | ||||
|         ), | ||||
|     ) | ||||
|     def test_esp8266_invalid_pin(self, core_esp8266, value, match): | ||||
|         with pytest.raises(Invalid, match=match): | ||||
|             pins.validate_gpio_pin(value) | ||||
| @@ -196,18 +213,19 @@ class Test_validate_gpio_pin: | ||||
|  | ||||
|  | ||||
| class Test_input_pin: | ||||
|     @pytest.mark.parametrize("value, expected", ( | ||||
|             ("X0", 16), | ||||
|     )) | ||||
|     @pytest.mark.parametrize("value, expected", (("X0", 16),)) | ||||
|     def test_valid_esp8266_values(self, core_esp8266, value, expected): | ||||
|         actual = pins.input_pin(value) | ||||
|  | ||||
|         assert actual == expected | ||||
|  | ||||
|     @pytest.mark.parametrize("value, expected", ( | ||||
|     @pytest.mark.parametrize( | ||||
|         "value, expected", | ||||
|         ( | ||||
|             ("Y0", 12), | ||||
|             (17, 17), | ||||
|     )) | ||||
|         ), | ||||
|     ) | ||||
|     def test_valid_esp32_values(self, core_esp32, value, expected): | ||||
|         actual = pins.input_pin(value) | ||||
|  | ||||
| @@ -226,18 +244,19 @@ class Test_input_pin: | ||||
|  | ||||
|  | ||||
| class Test_input_pullup_pin: | ||||
|     @pytest.mark.parametrize("value, expected", ( | ||||
|             ("X0", 16), | ||||
|     )) | ||||
|     @pytest.mark.parametrize("value, expected", (("X0", 16),)) | ||||
|     def test_valid_esp8266_values(self, core_esp8266, value, expected): | ||||
|         actual = pins.input_pullup_pin(value) | ||||
|  | ||||
|         assert actual == expected | ||||
|  | ||||
|     @pytest.mark.parametrize("value, expected", ( | ||||
|     @pytest.mark.parametrize( | ||||
|         "value, expected", | ||||
|         ( | ||||
|             ("Y0", 12), | ||||
|             (17, 17), | ||||
|     )) | ||||
|         ), | ||||
|     ) | ||||
|     def test_valid_esp32_values(self, core_esp32, value, expected): | ||||
|         actual = pins.input_pullup_pin(value) | ||||
|  | ||||
| @@ -256,18 +275,19 @@ class Test_input_pullup_pin: | ||||
|  | ||||
|  | ||||
| class Test_output_pin: | ||||
|     @pytest.mark.parametrize("value, expected", ( | ||||
|             ("X0", 16), | ||||
|     )) | ||||
|     @pytest.mark.parametrize("value, expected", (("X0", 16),)) | ||||
|     def test_valid_esp8266_values(self, core_esp8266, value, expected): | ||||
|         actual = pins.output_pin(value) | ||||
|  | ||||
|         assert actual == expected | ||||
|  | ||||
|     @pytest.mark.parametrize("value, expected", ( | ||||
|     @pytest.mark.parametrize( | ||||
|         "value, expected", | ||||
|         ( | ||||
|             ("Y0", 12), | ||||
|             (17, 17), | ||||
|     )) | ||||
|         ), | ||||
|     ) | ||||
|     def test_valid_esp32_values(self, core_esp32, value, expected): | ||||
|         actual = pins.output_pin(value) | ||||
|  | ||||
| @@ -291,18 +311,19 @@ class Test_output_pin: | ||||
|  | ||||
|  | ||||
| class Test_analog_pin: | ||||
|     @pytest.mark.parametrize("value, expected", ( | ||||
|             (17, 17), | ||||
|     )) | ||||
|     @pytest.mark.parametrize("value, expected", ((17, 17),)) | ||||
|     def test_valid_esp8266_values(self, core_esp8266, value, expected): | ||||
|         actual = pins.analog_pin(value) | ||||
|  | ||||
|         assert actual == expected | ||||
|  | ||||
|     @pytest.mark.parametrize("value, expected", ( | ||||
|     @pytest.mark.parametrize( | ||||
|         "value, expected", | ||||
|         ( | ||||
|             (32, 32), | ||||
|             (39, 39), | ||||
|     )) | ||||
|         ), | ||||
|     ) | ||||
|     def test_valid_esp32_values(self, core_esp32, value, expected): | ||||
|         actual = pins.analog_pin(value) | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| """ Tests for the wizard.py file """ | ||||
| """Tests for the wizard.py file.""" | ||||
|  | ||||
| import esphome.wizard as wz | ||||
| import pytest | ||||
| @@ -14,7 +14,7 @@ def default_config(): | ||||
|         "board": "test_board", | ||||
|         "ssid": "test_ssid", | ||||
|         "psk": "test_psk", | ||||
|         "password": "" | ||||
|         "password": "", | ||||
|     } | ||||
|  | ||||
|  | ||||
| @@ -35,13 +35,13 @@ def test_sanitize_quotes_replaces_with_escaped_char(): | ||||
|     The sanitize_quotes function should replace double quotes with their escaped equivalents | ||||
|     """ | ||||
|     # Given | ||||
|     input_str = "\"key\": \"value\"" | ||||
|     input_str = '"key": "value"' | ||||
|  | ||||
|     # When | ||||
|     output_str = wz.sanitize_double_quotes(input_str) | ||||
|  | ||||
|     # Then | ||||
|     assert output_str == "\\\"key\\\": \\\"value\\\"" | ||||
|     assert output_str == '\\"key\\": \\"value\\"' | ||||
|  | ||||
|  | ||||
| def test_config_file_fallback_ap_includes_descriptive_name(default_config): | ||||
| @@ -55,7 +55,7 @@ def test_config_file_fallback_ap_includes_descriptive_name(default_config): | ||||
|     config = wz.wizard_file(**default_config) | ||||
|  | ||||
|     # Then | ||||
|     assert f"ssid: \"Test Node Fallback Hotspot\"" in config | ||||
|     assert 'ssid: "Test Node Fallback Hotspot"' in config | ||||
|  | ||||
|  | ||||
| def test_config_file_fallback_ap_name_less_than_32_chars(default_config): | ||||
| @@ -70,7 +70,7 @@ def test_config_file_fallback_ap_name_less_than_32_chars(default_config): | ||||
|     config = wz.wizard_file(**default_config) | ||||
|  | ||||
|     # Then | ||||
|     assert f"ssid: \"A Very Long Name For This Node\"" in config | ||||
|     assert 'ssid: "A Very Long Name For This Node"' in config | ||||
|  | ||||
|  | ||||
| def test_config_file_should_include_ota(default_config): | ||||
| @@ -115,7 +115,9 @@ def test_wizard_write_sets_platform(default_config, tmp_path, monkeypatch): | ||||
|     assert f"platform: {default_config['platform']}" in generated_config | ||||
|  | ||||
|  | ||||
| def test_wizard_write_defaults_platform_from_board_esp8266(default_config, tmp_path, monkeypatch): | ||||
| def test_wizard_write_defaults_platform_from_board_esp8266( | ||||
|     default_config, tmp_path, monkeypatch | ||||
| ): | ||||
|     """ | ||||
|     If the platform is not explicitly set, use "ESP8266" if the board is one of the ESP8266 boards | ||||
|     """ | ||||
| @@ -133,7 +135,9 @@ def test_wizard_write_defaults_platform_from_board_esp8266(default_config, tmp_p | ||||
|     assert "platform: ESP8266" in generated_config | ||||
|  | ||||
|  | ||||
| def test_wizard_write_defaults_platform_from_board_esp32(default_config, tmp_path, monkeypatch): | ||||
| def test_wizard_write_defaults_platform_from_board_esp32( | ||||
|     default_config, tmp_path, monkeypatch | ||||
| ): | ||||
|     """ | ||||
|     If the platform is not explicitly set, use "ESP32" if the board is not one of the ESP8266 boards | ||||
|     """ | ||||
| @@ -167,7 +171,9 @@ def test_safe_print_step_prints_step_number_and_description(monkeypatch): | ||||
|  | ||||
|     # Then | ||||
|     # Collect arguments to all safe_print() calls (substituting "" for any empty ones) | ||||
|     all_args = [call.args[0] if len(call.args) else "" for call in wz.safe_print.call_args_list] | ||||
|     all_args = [ | ||||
|         call.args[0] if len(call.args) else "" for call in wz.safe_print.call_args_list | ||||
|     ] | ||||
|  | ||||
|     assert any(step_desc == arg for arg in all_args) | ||||
|     assert any(f"STEP {step_num}" in arg for arg in all_args) | ||||
| @@ -212,7 +218,7 @@ def test_strip_accents_removes_diacritics(): | ||||
|     """ | ||||
|  | ||||
|     # Given | ||||
|     input_str = u"Kühne" | ||||
|     input_str = "Kühne" | ||||
|     expected_str = "Kuhne" | ||||
|  | ||||
|     # When | ||||
| @@ -264,7 +270,7 @@ def test_wizard_accepts_default_answers_esp8266(tmpdir, monkeypatch, wizard_answ | ||||
|     monkeypatch.setattr("builtins.input", input_mock) | ||||
|     monkeypatch.setattr(wz, "safe_print", lambda t=None: 0) | ||||
|     monkeypatch.setattr(wz, "sleep", lambda _: 0) | ||||
|     monkeypatch.setattr(wz, "wizard_write",  MagicMock()) | ||||
|     monkeypatch.setattr(wz, "wizard_write", MagicMock()) | ||||
|  | ||||
|     # When | ||||
|     retval = wz.wizard(str(config_file)) | ||||
| @@ -286,7 +292,7 @@ def test_wizard_accepts_default_answers_esp32(tmpdir, monkeypatch, wizard_answer | ||||
|     monkeypatch.setattr("builtins.input", input_mock) | ||||
|     monkeypatch.setattr(wz, "safe_print", lambda t=None: 0) | ||||
|     monkeypatch.setattr(wz, "sleep", lambda _: 0) | ||||
|     monkeypatch.setattr(wz, "wizard_write",  MagicMock()) | ||||
|     monkeypatch.setattr(wz, "wizard_write", MagicMock()) | ||||
|  | ||||
|     # When | ||||
|     retval = wz.wizard(str(config_file)) | ||||
| @@ -306,14 +312,16 @@ def test_wizard_offers_better_node_name(tmpdir, monkeypatch, wizard_answers): | ||||
|     # Given | ||||
|     wizard_answers[0] = "Küche #2" | ||||
|     expected_name = "kuche_2" | ||||
|     monkeypatch.setattr(wz, "default_input", MagicMock(side_effect=lambda _, default: default)) | ||||
|     monkeypatch.setattr( | ||||
|         wz, "default_input", MagicMock(side_effect=lambda _, default: default) | ||||
|     ) | ||||
|  | ||||
|     config_file = tmpdir.join("test.yaml") | ||||
|     input_mock = MagicMock(side_effect=wizard_answers) | ||||
|     monkeypatch.setattr("builtins.input", input_mock) | ||||
|     monkeypatch.setattr(wz, "safe_print", lambda t=None: 0) | ||||
|     monkeypatch.setattr(wz, "sleep", lambda _: 0) | ||||
|     monkeypatch.setattr(wz, "wizard_write",  MagicMock()) | ||||
|     monkeypatch.setattr(wz, "wizard_write", MagicMock()) | ||||
|  | ||||
|     # When | ||||
|     retval = wz.wizard(str(config_file)) | ||||
| @@ -336,7 +344,7 @@ def test_wizard_requires_correct_platform(tmpdir, monkeypatch, wizard_answers): | ||||
|     monkeypatch.setattr("builtins.input", input_mock) | ||||
|     monkeypatch.setattr(wz, "safe_print", lambda t=None: 0) | ||||
|     monkeypatch.setattr(wz, "sleep", lambda _: 0) | ||||
|     monkeypatch.setattr(wz, "wizard_write",  MagicMock()) | ||||
|     monkeypatch.setattr(wz, "wizard_write", MagicMock()) | ||||
|  | ||||
|     # When | ||||
|     retval = wz.wizard(str(config_file)) | ||||
| @@ -358,7 +366,7 @@ def test_wizard_requires_correct_board(tmpdir, monkeypatch, wizard_answers): | ||||
|     monkeypatch.setattr("builtins.input", input_mock) | ||||
|     monkeypatch.setattr(wz, "safe_print", lambda t=None: 0) | ||||
|     monkeypatch.setattr(wz, "sleep", lambda _: 0) | ||||
|     monkeypatch.setattr(wz, "wizard_write",  MagicMock()) | ||||
|     monkeypatch.setattr(wz, "wizard_write", MagicMock()) | ||||
|  | ||||
|     # When | ||||
|     retval = wz.wizard(str(config_file)) | ||||
| @@ -380,7 +388,7 @@ def test_wizard_requires_valid_ssid(tmpdir, monkeypatch, wizard_answers): | ||||
|     monkeypatch.setattr("builtins.input", input_mock) | ||||
|     monkeypatch.setattr(wz, "safe_print", lambda t=None: 0) | ||||
|     monkeypatch.setattr(wz, "sleep", lambda _: 0) | ||||
|     monkeypatch.setattr(wz, "wizard_write",  MagicMock()) | ||||
|     monkeypatch.setattr(wz, "wizard_write", MagicMock()) | ||||
|  | ||||
|     # When | ||||
|     retval = wz.wizard(str(config_file)) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user