mirror of
https://github.com/esphome/esphome.git
synced 2026-09-26 04:04:45 +08:00
@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
|
||||
# could be handy for archiving the generated documentation or if some version
|
||||
# control system is used.
|
||||
|
||||
PROJECT_NUMBER = 2025.9.0b3
|
||||
PROJECT_NUMBER = 2025.9.0b4
|
||||
|
||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||
# for a project that appears at the top of each page and should give viewer a
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ from enum import Enum
|
||||
|
||||
from esphome.enum import StrEnum
|
||||
|
||||
__version__ = "2025.9.0b3"
|
||||
__version__ = "2025.9.0b4"
|
||||
|
||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||
VALID_SUBSTITUTIONS_CHARACTERS = (
|
||||
|
||||
+42
-8
@@ -1,6 +1,7 @@
|
||||
import os
|
||||
import random
|
||||
import string
|
||||
from typing import Literal, NotRequired, TypedDict, Unpack
|
||||
import unicodedata
|
||||
|
||||
import voluptuous as vol
|
||||
@@ -103,11 +104,25 @@ HARDWARE_BASE_CONFIGS = {
|
||||
}
|
||||
|
||||
|
||||
def sanitize_double_quotes(value):
|
||||
def sanitize_double_quotes(value: str) -> str:
|
||||
return value.replace("\\", "\\\\").replace('"', '\\"')
|
||||
|
||||
|
||||
def wizard_file(**kwargs):
|
||||
class WizardFileKwargs(TypedDict):
|
||||
"""Keyword arguments for wizard_file function."""
|
||||
|
||||
name: str
|
||||
platform: Literal["ESP8266", "ESP32", "RP2040", "BK72XX", "LN882X", "RTL87XX"]
|
||||
board: str
|
||||
ssid: NotRequired[str]
|
||||
psk: NotRequired[str]
|
||||
password: NotRequired[str]
|
||||
ota_password: NotRequired[str]
|
||||
api_encryption_key: NotRequired[str]
|
||||
friendly_name: NotRequired[str]
|
||||
|
||||
|
||||
def wizard_file(**kwargs: Unpack[WizardFileKwargs]) -> str:
|
||||
letters = string.ascii_letters + string.digits
|
||||
ap_name_base = kwargs["name"].replace("_", " ").title()
|
||||
ap_name = f"{ap_name_base} Fallback Hotspot"
|
||||
@@ -180,7 +195,25 @@ captive_portal:
|
||||
return config
|
||||
|
||||
|
||||
def wizard_write(path, **kwargs):
|
||||
class WizardWriteKwargs(TypedDict):
|
||||
"""Keyword arguments for wizard_write function."""
|
||||
|
||||
name: str
|
||||
type: Literal["basic", "empty", "upload"]
|
||||
# Required for "basic" type
|
||||
board: NotRequired[str]
|
||||
platform: NotRequired[str]
|
||||
ssid: NotRequired[str]
|
||||
psk: NotRequired[str]
|
||||
password: NotRequired[str]
|
||||
ota_password: NotRequired[str]
|
||||
api_encryption_key: NotRequired[str]
|
||||
friendly_name: NotRequired[str]
|
||||
# Required for "upload" type
|
||||
file_text: NotRequired[str]
|
||||
|
||||
|
||||
def wizard_write(path: str, **kwargs: Unpack[WizardWriteKwargs]) -> bool:
|
||||
from esphome.components.bk72xx import boards as bk72xx_boards
|
||||
from esphome.components.esp32 import boards as esp32_boards
|
||||
from esphome.components.esp8266 import boards as esp8266_boards
|
||||
@@ -237,14 +270,14 @@ def wizard_write(path, **kwargs):
|
||||
|
||||
if get_bool_env(ENV_QUICKWIZARD):
|
||||
|
||||
def sleep(time):
|
||||
def sleep(time: float) -> None:
|
||||
pass
|
||||
|
||||
else:
|
||||
from time import sleep
|
||||
|
||||
|
||||
def safe_print_step(step, big):
|
||||
def safe_print_step(step: int, big: str) -> None:
|
||||
safe_print()
|
||||
safe_print()
|
||||
safe_print(f"============= STEP {step} =============")
|
||||
@@ -253,14 +286,14 @@ def safe_print_step(step, big):
|
||||
sleep(0.25)
|
||||
|
||||
|
||||
def default_input(text, default):
|
||||
def default_input(text: str, default: str) -> str:
|
||||
safe_print()
|
||||
safe_print(f"Press ENTER for default ({default})")
|
||||
return safe_input(text.format(default)) or default
|
||||
|
||||
|
||||
# From https://stackoverflow.com/a/518232/8924614
|
||||
def strip_accents(value):
|
||||
def strip_accents(value: str) -> str:
|
||||
return "".join(
|
||||
c
|
||||
for c in unicodedata.normalize("NFD", str(value))
|
||||
@@ -268,7 +301,7 @@ def strip_accents(value):
|
||||
)
|
||||
|
||||
|
||||
def wizard(path):
|
||||
def wizard(path: str) -> int:
|
||||
from esphome.components.bk72xx import boards as bk72xx_boards
|
||||
from esphome.components.esp32 import boards as esp32_boards
|
||||
from esphome.components.esp8266 import boards as esp8266_boards
|
||||
@@ -509,6 +542,7 @@ def wizard(path):
|
||||
ssid=ssid,
|
||||
psk=psk,
|
||||
password=password,
|
||||
type="basic",
|
||||
):
|
||||
return 1
|
||||
|
||||
|
||||
@@ -315,6 +315,19 @@ def clean_build():
|
||||
_LOGGER.info("Deleting %s", dependencies_lock)
|
||||
os.remove(dependencies_lock)
|
||||
|
||||
# Clean PlatformIO cache to resolve CMake compiler detection issues
|
||||
# This helps when toolchain paths change or get corrupted
|
||||
try:
|
||||
from platformio.project.helpers import get_project_cache_dir
|
||||
except ImportError:
|
||||
# PlatformIO is not available, skip cache cleaning
|
||||
pass
|
||||
else:
|
||||
cache_dir = get_project_cache_dir()
|
||||
if cache_dir and cache_dir.strip() and os.path.isdir(cache_dir):
|
||||
_LOGGER.info("Deleting PlatformIO cache %s", cache_dir)
|
||||
shutil.rmtree(cache_dir)
|
||||
|
||||
|
||||
GITIGNORE_CONTENT = """# Gitignore settings for ESPHome
|
||||
# This is an example and may include too much for your use-case.
|
||||
|
||||
@@ -141,3 +141,170 @@ def test_list_yaml_files_mixed_extensions(tmp_path: Path) -> None:
|
||||
str(yaml_file),
|
||||
str(yml_file),
|
||||
}
|
||||
|
||||
|
||||
def test_list_yaml_files_does_not_recurse_into_subdirectories(tmp_path: Path) -> None:
|
||||
"""Test that list_yaml_files only finds files in specified directory, not subdirectories."""
|
||||
# Create directory structure with YAML files at different depths
|
||||
root = tmp_path / "configs"
|
||||
root.mkdir()
|
||||
|
||||
# Create YAML files in the root directory
|
||||
(root / "config1.yaml").write_text("test: 1")
|
||||
(root / "config2.yml").write_text("test: 2")
|
||||
(root / "device.yaml").write_text("test: device")
|
||||
|
||||
# Create subdirectory with YAML files (should NOT be found)
|
||||
subdir = root / "subdir"
|
||||
subdir.mkdir()
|
||||
(subdir / "nested1.yaml").write_text("test: nested1")
|
||||
(subdir / "nested2.yml").write_text("test: nested2")
|
||||
|
||||
# Create deeper subdirectory (should NOT be found)
|
||||
deep_subdir = subdir / "deeper"
|
||||
deep_subdir.mkdir()
|
||||
(deep_subdir / "very_nested.yaml").write_text("test: very_nested")
|
||||
|
||||
# Test listing files from the root directory
|
||||
result = util.list_yaml_files([str(root)])
|
||||
|
||||
# Should only find the 3 files in root, not the 3 in subdirectories
|
||||
assert len(result) == 3
|
||||
|
||||
# Check that only root-level files are found
|
||||
assert str(root / "config1.yaml") in result
|
||||
assert str(root / "config2.yml") in result
|
||||
assert str(root / "device.yaml") in result
|
||||
|
||||
# Ensure nested files are NOT found
|
||||
for r in result:
|
||||
assert "subdir" not in r
|
||||
assert "deeper" not in r
|
||||
assert "nested1.yaml" not in r
|
||||
assert "nested2.yml" not in r
|
||||
assert "very_nested.yaml" not in r
|
||||
|
||||
|
||||
def test_list_yaml_files_excludes_secrets(tmp_path: Path) -> None:
|
||||
"""Test that secrets.yaml and secrets.yml are excluded."""
|
||||
root = tmp_path / "configs"
|
||||
root.mkdir()
|
||||
|
||||
# Create various YAML files including secrets
|
||||
(root / "config.yaml").write_text("test: config")
|
||||
(root / "secrets.yaml").write_text("wifi_password: secret123")
|
||||
(root / "secrets.yml").write_text("api_key: secret456")
|
||||
(root / "device.yaml").write_text("test: device")
|
||||
|
||||
result = util.list_yaml_files([str(root)])
|
||||
|
||||
# Should find 2 files (config.yaml and device.yaml), not secrets
|
||||
assert len(result) == 2
|
||||
assert str(root / "config.yaml") in result
|
||||
assert str(root / "device.yaml") in result
|
||||
assert str(root / "secrets.yaml") not in result
|
||||
assert str(root / "secrets.yml") not in result
|
||||
|
||||
|
||||
def test_list_yaml_files_excludes_hidden_files(tmp_path: Path) -> None:
|
||||
"""Test that hidden files (starting with .) are excluded."""
|
||||
root = tmp_path / "configs"
|
||||
root.mkdir()
|
||||
|
||||
# Create regular and hidden YAML files
|
||||
(root / "config.yaml").write_text("test: config")
|
||||
(root / ".hidden.yaml").write_text("test: hidden")
|
||||
(root / ".backup.yml").write_text("test: backup")
|
||||
(root / "device.yaml").write_text("test: device")
|
||||
|
||||
result = util.list_yaml_files([str(root)])
|
||||
|
||||
# Should find only non-hidden files
|
||||
assert len(result) == 2
|
||||
assert str(root / "config.yaml") in result
|
||||
assert str(root / "device.yaml") in result
|
||||
assert str(root / ".hidden.yaml") not in result
|
||||
assert str(root / ".backup.yml") not in result
|
||||
|
||||
|
||||
def test_filter_yaml_files_basic() -> None:
|
||||
"""Test filter_yaml_files function."""
|
||||
files = [
|
||||
"/path/to/config.yaml",
|
||||
"/path/to/device.yml",
|
||||
"/path/to/readme.txt",
|
||||
"/path/to/script.py",
|
||||
"/path/to/data.json",
|
||||
"/path/to/another.yaml",
|
||||
]
|
||||
|
||||
result = util.filter_yaml_files(files)
|
||||
|
||||
assert len(result) == 3
|
||||
assert "/path/to/config.yaml" in result
|
||||
assert "/path/to/device.yml" in result
|
||||
assert "/path/to/another.yaml" in result
|
||||
assert "/path/to/readme.txt" not in result
|
||||
assert "/path/to/script.py" not in result
|
||||
assert "/path/to/data.json" not in result
|
||||
|
||||
|
||||
def test_filter_yaml_files_excludes_secrets() -> None:
|
||||
"""Test that filter_yaml_files excludes secrets files."""
|
||||
files = [
|
||||
"/path/to/config.yaml",
|
||||
"/path/to/secrets.yaml",
|
||||
"/path/to/secrets.yml",
|
||||
"/path/to/device.yaml",
|
||||
"/some/dir/secrets.yaml",
|
||||
]
|
||||
|
||||
result = util.filter_yaml_files(files)
|
||||
|
||||
assert len(result) == 2
|
||||
assert "/path/to/config.yaml" in result
|
||||
assert "/path/to/device.yaml" in result
|
||||
assert "/path/to/secrets.yaml" not in result
|
||||
assert "/path/to/secrets.yml" not in result
|
||||
assert "/some/dir/secrets.yaml" not in result
|
||||
|
||||
|
||||
def test_filter_yaml_files_excludes_hidden() -> None:
|
||||
"""Test that filter_yaml_files excludes hidden files."""
|
||||
files = [
|
||||
"/path/to/config.yaml",
|
||||
"/path/to/.hidden.yaml",
|
||||
"/path/to/.backup.yml",
|
||||
"/path/to/device.yaml",
|
||||
"/some/dir/.config.yaml",
|
||||
]
|
||||
|
||||
result = util.filter_yaml_files(files)
|
||||
|
||||
assert len(result) == 2
|
||||
assert "/path/to/config.yaml" in result
|
||||
assert "/path/to/device.yaml" in result
|
||||
assert "/path/to/.hidden.yaml" not in result
|
||||
assert "/path/to/.backup.yml" not in result
|
||||
assert "/some/dir/.config.yaml" not in result
|
||||
|
||||
|
||||
def test_filter_yaml_files_case_sensitive() -> None:
|
||||
"""Test that filter_yaml_files is case-sensitive for extensions."""
|
||||
files = [
|
||||
"/path/to/config.yaml",
|
||||
"/path/to/config.YAML",
|
||||
"/path/to/config.YML",
|
||||
"/path/to/config.Yaml",
|
||||
"/path/to/config.yml",
|
||||
]
|
||||
|
||||
result = util.filter_yaml_files(files)
|
||||
|
||||
# Should only match lowercase .yaml and .yml
|
||||
assert len(result) == 2
|
||||
assert "/path/to/config.yaml" in result
|
||||
assert "/path/to/config.yml" in result
|
||||
assert "/path/to/config.YAML" not in result
|
||||
assert "/path/to/config.YML" not in result
|
||||
assert "/path/to/config.Yaml" not in result
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
"""Tests for the wizard.py file."""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
from pytest import MonkeyPatch
|
||||
|
||||
from esphome.components.bk72xx.boards import BK72XX_BOARD_PINS
|
||||
from esphome.components.esp32.boards import ESP32_BOARD_PINS
|
||||
@@ -15,7 +18,7 @@ import esphome.wizard as wz
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def default_config():
|
||||
def default_config() -> dict[str, Any]:
|
||||
return {
|
||||
"type": "basic",
|
||||
"name": "test-name",
|
||||
@@ -28,7 +31,7 @@ def default_config():
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def wizard_answers():
|
||||
def wizard_answers() -> list[str]:
|
||||
return [
|
||||
"test-node", # Name of the node
|
||||
"ESP8266", # platform
|
||||
@@ -53,7 +56,9 @@ def test_sanitize_quotes_replaces_with_escaped_char():
|
||||
assert output_str == '\\"key\\": \\"value\\"'
|
||||
|
||||
|
||||
def test_config_file_fallback_ap_includes_descriptive_name(default_config):
|
||||
def test_config_file_fallback_ap_includes_descriptive_name(
|
||||
default_config: dict[str, Any],
|
||||
):
|
||||
"""
|
||||
The fallback AP should include the node and a descriptive name
|
||||
"""
|
||||
@@ -67,7 +72,9 @@ def test_config_file_fallback_ap_includes_descriptive_name(default_config):
|
||||
assert 'ssid: "Test Node Fallback Hotspot"' in config
|
||||
|
||||
|
||||
def test_config_file_fallback_ap_name_less_than_32_chars(default_config):
|
||||
def test_config_file_fallback_ap_name_less_than_32_chars(
|
||||
default_config: dict[str, Any],
|
||||
):
|
||||
"""
|
||||
The fallback AP name must be less than 32 chars.
|
||||
Since it is composed of the node name and "Fallback Hotspot" this can be too long and needs truncating
|
||||
@@ -82,7 +89,7 @@ def test_config_file_fallback_ap_name_less_than_32_chars(default_config):
|
||||
assert 'ssid: "A Very Long Name For This Node"' in config
|
||||
|
||||
|
||||
def test_config_file_should_include_ota(default_config):
|
||||
def test_config_file_should_include_ota(default_config: dict[str, Any]):
|
||||
"""
|
||||
The Over-The-Air update should be enabled by default
|
||||
"""
|
||||
@@ -95,7 +102,9 @@ def test_config_file_should_include_ota(default_config):
|
||||
assert "ota:" in config
|
||||
|
||||
|
||||
def test_config_file_should_include_ota_when_password_set(default_config):
|
||||
def test_config_file_should_include_ota_when_password_set(
|
||||
default_config: dict[str, Any],
|
||||
):
|
||||
"""
|
||||
The Over-The-Air update should be enabled when a password is set
|
||||
"""
|
||||
@@ -109,7 +118,9 @@ def test_config_file_should_include_ota_when_password_set(default_config):
|
||||
assert "ota:" in config
|
||||
|
||||
|
||||
def test_wizard_write_sets_platform(default_config, tmp_path, monkeypatch):
|
||||
def test_wizard_write_sets_platform(
|
||||
default_config: dict[str, Any], tmp_path: Path, monkeypatch: MonkeyPatch
|
||||
):
|
||||
"""
|
||||
If the platform is not explicitly set, use "ESP8266" if the board is one of the ESP8266 boards
|
||||
"""
|
||||
@@ -126,7 +137,7 @@ def test_wizard_write_sets_platform(default_config, tmp_path, monkeypatch):
|
||||
assert "esp8266:" in generated_config
|
||||
|
||||
|
||||
def test_wizard_empty_config(tmp_path, monkeypatch):
|
||||
def test_wizard_empty_config(tmp_path: Path, monkeypatch: MonkeyPatch):
|
||||
"""
|
||||
The wizard should be able to create an empty configuration
|
||||
"""
|
||||
@@ -146,7 +157,7 @@ def test_wizard_empty_config(tmp_path, monkeypatch):
|
||||
assert generated_config == ""
|
||||
|
||||
|
||||
def test_wizard_upload_config(tmp_path, monkeypatch):
|
||||
def test_wizard_upload_config(tmp_path: Path, monkeypatch: MonkeyPatch):
|
||||
"""
|
||||
The wizard should be able to import an base64 encoded configuration
|
||||
"""
|
||||
@@ -168,7 +179,7 @@ def test_wizard_upload_config(tmp_path, monkeypatch):
|
||||
|
||||
|
||||
def test_wizard_write_defaults_platform_from_board_esp8266(
|
||||
default_config, tmp_path, monkeypatch
|
||||
default_config: dict[str, Any], tmp_path: Path, monkeypatch: MonkeyPatch
|
||||
):
|
||||
"""
|
||||
If the platform is not explicitly set, use "ESP8266" if the board is one of the ESP8266 boards
|
||||
@@ -189,7 +200,7 @@ def test_wizard_write_defaults_platform_from_board_esp8266(
|
||||
|
||||
|
||||
def test_wizard_write_defaults_platform_from_board_esp32(
|
||||
default_config, tmp_path, monkeypatch
|
||||
default_config: dict[str, Any], tmp_path: Path, monkeypatch: MonkeyPatch
|
||||
):
|
||||
"""
|
||||
If the platform is not explicitly set, use "ESP32" if the board is one of the ESP32 boards
|
||||
@@ -210,7 +221,7 @@ def test_wizard_write_defaults_platform_from_board_esp32(
|
||||
|
||||
|
||||
def test_wizard_write_defaults_platform_from_board_bk72xx(
|
||||
default_config, tmp_path, monkeypatch
|
||||
default_config: dict[str, Any], tmp_path: Path, monkeypatch: MonkeyPatch
|
||||
):
|
||||
"""
|
||||
If the platform is not explicitly set, use "BK72XX" if the board is one of BK72XX boards
|
||||
@@ -231,7 +242,7 @@ def test_wizard_write_defaults_platform_from_board_bk72xx(
|
||||
|
||||
|
||||
def test_wizard_write_defaults_platform_from_board_ln882x(
|
||||
default_config, tmp_path, monkeypatch
|
||||
default_config: dict[str, Any], tmp_path: Path, monkeypatch: MonkeyPatch
|
||||
):
|
||||
"""
|
||||
If the platform is not explicitly set, use "LN882X" if the board is one of LN882X boards
|
||||
@@ -252,7 +263,7 @@ def test_wizard_write_defaults_platform_from_board_ln882x(
|
||||
|
||||
|
||||
def test_wizard_write_defaults_platform_from_board_rtl87xx(
|
||||
default_config, tmp_path, monkeypatch
|
||||
default_config: dict[str, Any], tmp_path: Path, monkeypatch: MonkeyPatch
|
||||
):
|
||||
"""
|
||||
If the platform is not explicitly set, use "RTL87XX" if the board is one of RTL87XX boards
|
||||
@@ -272,7 +283,7 @@ def test_wizard_write_defaults_platform_from_board_rtl87xx(
|
||||
assert "rtl87xx:" in generated_config
|
||||
|
||||
|
||||
def test_safe_print_step_prints_step_number_and_description(monkeypatch):
|
||||
def test_safe_print_step_prints_step_number_and_description(monkeypatch: MonkeyPatch):
|
||||
"""
|
||||
The safe_print_step function prints the step number and the passed description
|
||||
"""
|
||||
@@ -296,7 +307,7 @@ def test_safe_print_step_prints_step_number_and_description(monkeypatch):
|
||||
assert any(f"STEP {step_num}" in arg for arg in all_args)
|
||||
|
||||
|
||||
def test_default_input_uses_default_if_no_input_supplied(monkeypatch):
|
||||
def test_default_input_uses_default_if_no_input_supplied(monkeypatch: MonkeyPatch):
|
||||
"""
|
||||
The default_input() function should return the supplied default value if the user doesn't enter anything
|
||||
"""
|
||||
@@ -312,7 +323,7 @@ def test_default_input_uses_default_if_no_input_supplied(monkeypatch):
|
||||
assert retval == default_string
|
||||
|
||||
|
||||
def test_default_input_uses_user_supplied_value(monkeypatch):
|
||||
def test_default_input_uses_user_supplied_value(monkeypatch: MonkeyPatch):
|
||||
"""
|
||||
The default_input() function should return the value that the user enters
|
||||
"""
|
||||
@@ -376,7 +387,9 @@ def test_wizard_rejects_existing_files(tmpdir):
|
||||
assert retval == 2
|
||||
|
||||
|
||||
def test_wizard_accepts_default_answers_esp8266(tmpdir, monkeypatch, wizard_answers):
|
||||
def test_wizard_accepts_default_answers_esp8266(
|
||||
tmpdir, monkeypatch: MonkeyPatch, wizard_answers: list[str]
|
||||
):
|
||||
"""
|
||||
The wizard should accept the given default answers for esp8266
|
||||
"""
|
||||
@@ -396,7 +409,9 @@ def test_wizard_accepts_default_answers_esp8266(tmpdir, monkeypatch, wizard_answ
|
||||
assert retval == 0
|
||||
|
||||
|
||||
def test_wizard_accepts_default_answers_esp32(tmpdir, monkeypatch, wizard_answers):
|
||||
def test_wizard_accepts_default_answers_esp32(
|
||||
tmpdir, monkeypatch: MonkeyPatch, wizard_answers: list[str]
|
||||
):
|
||||
"""
|
||||
The wizard should accept the given default answers for esp32
|
||||
"""
|
||||
@@ -418,7 +433,9 @@ def test_wizard_accepts_default_answers_esp32(tmpdir, monkeypatch, wizard_answer
|
||||
assert retval == 0
|
||||
|
||||
|
||||
def test_wizard_offers_better_node_name(tmpdir, monkeypatch, wizard_answers):
|
||||
def test_wizard_offers_better_node_name(
|
||||
tmpdir, monkeypatch: MonkeyPatch, wizard_answers: list[str]
|
||||
):
|
||||
"""
|
||||
When the node name does not conform, a better alternative is offered
|
||||
* Removes special chars
|
||||
@@ -449,7 +466,9 @@ def test_wizard_offers_better_node_name(tmpdir, monkeypatch, wizard_answers):
|
||||
assert wz.default_input.call_args.args[1] == expected_name
|
||||
|
||||
|
||||
def test_wizard_requires_correct_platform(tmpdir, monkeypatch, wizard_answers):
|
||||
def test_wizard_requires_correct_platform(
|
||||
tmpdir, monkeypatch: MonkeyPatch, wizard_answers: list[str]
|
||||
):
|
||||
"""
|
||||
When the platform is not either esp32 or esp8266, the wizard should reject it
|
||||
"""
|
||||
@@ -471,7 +490,9 @@ def test_wizard_requires_correct_platform(tmpdir, monkeypatch, wizard_answers):
|
||||
assert retval == 0
|
||||
|
||||
|
||||
def test_wizard_requires_correct_board(tmpdir, monkeypatch, wizard_answers):
|
||||
def test_wizard_requires_correct_board(
|
||||
tmpdir, monkeypatch: MonkeyPatch, wizard_answers: list[str]
|
||||
):
|
||||
"""
|
||||
When the board is not a valid esp8266 board, the wizard should reject it
|
||||
"""
|
||||
@@ -493,7 +514,9 @@ def test_wizard_requires_correct_board(tmpdir, monkeypatch, wizard_answers):
|
||||
assert retval == 0
|
||||
|
||||
|
||||
def test_wizard_requires_valid_ssid(tmpdir, monkeypatch, wizard_answers):
|
||||
def test_wizard_requires_valid_ssid(
|
||||
tmpdir, monkeypatch: MonkeyPatch, wizard_answers: list[str]
|
||||
):
|
||||
"""
|
||||
When the board is not a valid esp8266 board, the wizard should reject it
|
||||
"""
|
||||
@@ -515,7 +538,9 @@ def test_wizard_requires_valid_ssid(tmpdir, monkeypatch, wizard_answers):
|
||||
assert retval == 0
|
||||
|
||||
|
||||
def test_wizard_write_protects_existing_config(tmpdir, default_config, monkeypatch):
|
||||
def test_wizard_write_protects_existing_config(
|
||||
tmpdir, default_config: dict[str, Any], monkeypatch: MonkeyPatch
|
||||
):
|
||||
"""
|
||||
The wizard_write function should not overwrite existing config files and return False
|
||||
"""
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user