mirror of
https://github.com/home-assistant/core.git
synced 2025-07-13 08:17:08 +00:00
Deprecate the map integration (#113215)
* Deprecate the map integration * Revert changes in DashboardsCollection._async_load_data * Add option to allow single word in dashboard URL * Update tests * Translate title * Add icon * Improve test coverage
This commit is contained in:
parent
fef2d7ddd4
commit
a16ea3d7bd
@ -747,7 +747,6 @@ omit =
|
|||||||
homeassistant/components/lyric/climate.py
|
homeassistant/components/lyric/climate.py
|
||||||
homeassistant/components/lyric/sensor.py
|
homeassistant/components/lyric/sensor.py
|
||||||
homeassistant/components/mailgun/notify.py
|
homeassistant/components/mailgun/notify.py
|
||||||
homeassistant/components/map/*
|
|
||||||
homeassistant/components/mastodon/notify.py
|
homeassistant/components/mastodon/notify.py
|
||||||
homeassistant/components/matrix/__init__.py
|
homeassistant/components/matrix/__init__.py
|
||||||
homeassistant/components/matrix/notify.py
|
homeassistant/components/matrix/notify.py
|
||||||
|
@ -4,7 +4,7 @@ import logging
|
|||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components import frontend, websocket_api
|
from homeassistant.components import frontend, onboarding, websocket_api
|
||||||
from homeassistant.config import (
|
from homeassistant.config import (
|
||||||
async_hass_config_yaml,
|
async_hass_config_yaml,
|
||||||
async_process_component_and_handle_errors,
|
async_process_component_and_handle_errors,
|
||||||
@ -14,11 +14,13 @@ from homeassistant.core import HomeAssistant, ServiceCall, callback
|
|||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import collection, config_validation as cv
|
from homeassistant.helpers import collection, config_validation as cv
|
||||||
from homeassistant.helpers.service import async_register_admin_service
|
from homeassistant.helpers.service import async_register_admin_service
|
||||||
|
from homeassistant.helpers.translation import async_get_translations
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
from homeassistant.loader import async_get_integration
|
from homeassistant.loader import async_get_integration
|
||||||
|
|
||||||
from . import dashboard, resources, websocket
|
from . import dashboard, resources, websocket
|
||||||
from .const import ( # noqa: F401
|
from .const import ( # noqa: F401
|
||||||
|
CONF_ALLOW_SINGLE_WORD,
|
||||||
CONF_ICON,
|
CONF_ICON,
|
||||||
CONF_REQUIRE_ADMIN,
|
CONF_REQUIRE_ADMIN,
|
||||||
CONF_SHOW_IN_SIDEBAR,
|
CONF_SHOW_IN_SIDEBAR,
|
||||||
@ -201,6 +203,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||||||
# Process storage dashboards
|
# Process storage dashboards
|
||||||
dashboards_collection = dashboard.DashboardsCollection(hass)
|
dashboards_collection = dashboard.DashboardsCollection(hass)
|
||||||
|
|
||||||
|
# This can be removed when the map integration is removed
|
||||||
|
hass.data[DOMAIN]["dashboards_collection"] = dashboards_collection
|
||||||
|
|
||||||
dashboards_collection.async_add_listener(storage_dashboard_changed)
|
dashboards_collection.async_add_listener(storage_dashboard_changed)
|
||||||
await dashboards_collection.async_load()
|
await dashboards_collection.async_load()
|
||||||
|
|
||||||
@ -212,6 +217,12 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||||||
STORAGE_DASHBOARD_UPDATE_FIELDS,
|
STORAGE_DASHBOARD_UPDATE_FIELDS,
|
||||||
).async_setup(hass, create_list=False)
|
).async_setup(hass, create_list=False)
|
||||||
|
|
||||||
|
def create_map_dashboard():
|
||||||
|
hass.async_create_task(_create_map_dashboard(hass))
|
||||||
|
|
||||||
|
if not onboarding.async_is_onboarded(hass):
|
||||||
|
onboarding.async_add_listener(hass, create_map_dashboard)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@ -249,3 +260,25 @@ def _register_panel(hass, url_path, mode, config, update):
|
|||||||
kwargs["sidebar_icon"] = config.get(CONF_ICON, DEFAULT_ICON)
|
kwargs["sidebar_icon"] = config.get(CONF_ICON, DEFAULT_ICON)
|
||||||
|
|
||||||
frontend.async_register_built_in_panel(hass, DOMAIN, **kwargs)
|
frontend.async_register_built_in_panel(hass, DOMAIN, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
async def _create_map_dashboard(hass: HomeAssistant):
|
||||||
|
translations = await async_get_translations(
|
||||||
|
hass, hass.config.language, "dashboard", {onboarding.DOMAIN}
|
||||||
|
)
|
||||||
|
title = translations["component.onboarding.dashboard.map.title"]
|
||||||
|
|
||||||
|
dashboards_collection: dashboard.DashboardsCollection = hass.data[DOMAIN][
|
||||||
|
"dashboards_collection"
|
||||||
|
]
|
||||||
|
await dashboards_collection.async_create_item(
|
||||||
|
{
|
||||||
|
CONF_ALLOW_SINGLE_WORD: True,
|
||||||
|
CONF_ICON: "mdi:map",
|
||||||
|
CONF_TITLE: title,
|
||||||
|
CONF_URL_PATH: "map",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
map_store: dashboard.LovelaceStorage = hass.data[DOMAIN]["dashboards"]["map"]
|
||||||
|
await map_store.async_save({"strategy": {"type": "map"}})
|
||||||
|
@ -24,6 +24,7 @@ MODE_STORAGE = "storage"
|
|||||||
MODE_AUTO = "auto-gen"
|
MODE_AUTO = "auto-gen"
|
||||||
|
|
||||||
LOVELACE_CONFIG_FILE = "ui-lovelace.yaml"
|
LOVELACE_CONFIG_FILE = "ui-lovelace.yaml"
|
||||||
|
CONF_ALLOW_SINGLE_WORD = "allow_single_word"
|
||||||
CONF_URL_PATH = "url_path"
|
CONF_URL_PATH = "url_path"
|
||||||
CONF_RESOURCE_TYPE_WS = "res_type"
|
CONF_RESOURCE_TYPE_WS = "res_type"
|
||||||
|
|
||||||
@ -75,6 +76,8 @@ STORAGE_DASHBOARD_CREATE_FIELDS = {
|
|||||||
# For now we write "storage" as all modes.
|
# For now we write "storage" as all modes.
|
||||||
# In future we can adjust this to be other modes.
|
# In future we can adjust this to be other modes.
|
||||||
vol.Optional(CONF_MODE, default=MODE_STORAGE): MODE_STORAGE,
|
vol.Optional(CONF_MODE, default=MODE_STORAGE): MODE_STORAGE,
|
||||||
|
# Set to allow adding dashboard without hyphen
|
||||||
|
vol.Optional(CONF_ALLOW_SINGLE_WORD): bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
STORAGE_DASHBOARD_UPDATE_FIELDS = DASHBOARD_BASE_UPDATE_FIELDS
|
STORAGE_DASHBOARD_UPDATE_FIELDS = DASHBOARD_BASE_UPDATE_FIELDS
|
||||||
|
@ -18,6 +18,7 @@ from homeassistant.helpers import collection, storage
|
|||||||
from homeassistant.util.yaml import Secrets, load_yaml_dict
|
from homeassistant.util.yaml import Secrets, load_yaml_dict
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
|
CONF_ALLOW_SINGLE_WORD,
|
||||||
CONF_ICON,
|
CONF_ICON,
|
||||||
CONF_URL_PATH,
|
CONF_URL_PATH,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
@ -234,10 +235,14 @@ class DashboardsCollection(collection.DictStorageCollection):
|
|||||||
|
|
||||||
async def _process_create_data(self, data: dict) -> dict:
|
async def _process_create_data(self, data: dict) -> dict:
|
||||||
"""Validate the config is valid."""
|
"""Validate the config is valid."""
|
||||||
if "-" not in data[CONF_URL_PATH]:
|
url_path = data[CONF_URL_PATH]
|
||||||
|
|
||||||
|
allow_single_word = data.pop(CONF_ALLOW_SINGLE_WORD, False)
|
||||||
|
|
||||||
|
if not allow_single_word and "-" not in url_path:
|
||||||
raise vol.Invalid("Url path needs to contain a hyphen (-)")
|
raise vol.Invalid("Url path needs to contain a hyphen (-)")
|
||||||
|
|
||||||
if data[CONF_URL_PATH] in self.hass.data[DATA_PANELS]:
|
if url_path in self.hass.data[DATA_PANELS]:
|
||||||
raise vol.Invalid("Panel url path needs to be unique")
|
raise vol.Invalid("Panel url path needs to be unique")
|
||||||
|
|
||||||
return self.CREATE_SCHEMA(data)
|
return self.CREATE_SCHEMA(data)
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
"domain": "lovelace",
|
"domain": "lovelace",
|
||||||
"name": "Dashboards",
|
"name": "Dashboards",
|
||||||
"codeowners": ["@home-assistant/frontend"],
|
"codeowners": ["@home-assistant/frontend"],
|
||||||
|
"dependencies": ["onboarding"],
|
||||||
"documentation": "https://www.home-assistant.io/integrations/lovelace",
|
"documentation": "https://www.home-assistant.io/integrations/lovelace",
|
||||||
"integration_type": "system",
|
"integration_type": "system",
|
||||||
"quality_scale": "internal"
|
"quality_scale": "internal"
|
||||||
|
@ -1,16 +1,52 @@
|
|||||||
"""Support for showing device locations."""
|
"""Support for showing device locations."""
|
||||||
|
from homeassistant.components import onboarding
|
||||||
from homeassistant.components import frontend
|
from homeassistant.components.lovelace import _create_map_dashboard
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
|
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
||||||
|
from homeassistant.helpers.storage import Store
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
|
|
||||||
DOMAIN = "map"
|
DOMAIN = "map"
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)
|
CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)
|
||||||
|
|
||||||
|
STORAGE_KEY = DOMAIN
|
||||||
|
STORAGE_VERSION_MAJOR = 1
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
"""Register the built-in map panel."""
|
"""Create a map panel."""
|
||||||
frontend.async_register_built_in_panel(hass, "map", "map", "hass:tooltip-account")
|
|
||||||
|
if DOMAIN in config:
|
||||||
|
async_create_issue(
|
||||||
|
hass,
|
||||||
|
HOMEASSISTANT_DOMAIN,
|
||||||
|
f"deprecated_yaml_{DOMAIN}",
|
||||||
|
breaks_in_ha_version="2024.10.0",
|
||||||
|
is_fixable=False,
|
||||||
|
is_persistent=False,
|
||||||
|
issue_domain=DOMAIN,
|
||||||
|
severity=IssueSeverity.WARNING,
|
||||||
|
translation_key="deprecated_yaml",
|
||||||
|
translation_placeholders={
|
||||||
|
"domain": DOMAIN,
|
||||||
|
"integration_title": "map",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
store: Store[dict[str, bool]] = Store(
|
||||||
|
hass,
|
||||||
|
STORAGE_VERSION_MAJOR,
|
||||||
|
STORAGE_KEY,
|
||||||
|
)
|
||||||
|
data = await store.async_load()
|
||||||
|
if data:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if onboarding.async_is_onboarded(hass):
|
||||||
|
await _create_map_dashboard(hass)
|
||||||
|
|
||||||
|
await store.async_save({"migrated": True})
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"domain": "map",
|
"domain": "map",
|
||||||
"name": "Map",
|
"name": "Map",
|
||||||
"codeowners": [],
|
"codeowners": [],
|
||||||
"dependencies": ["frontend"],
|
"dependencies": ["frontend", "lovelace"],
|
||||||
"documentation": "https://www.home-assistant.io/integrations/map",
|
"documentation": "https://www.home-assistant.io/integrations/map",
|
||||||
"integration_type": "system",
|
"integration_type": "system",
|
||||||
"quality_scale": "internal"
|
"quality_scale": "internal"
|
||||||
|
@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from collections.abc import Callable
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import TYPE_CHECKING, TypedDict
|
||||||
|
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
@ -26,15 +28,30 @@ STORAGE_VERSION = 4
|
|||||||
CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)
|
CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)
|
||||||
|
|
||||||
|
|
||||||
class OnboadingStorage(Store[dict[str, list[str]]]):
|
@dataclass
|
||||||
|
class OnboardingData:
|
||||||
|
"""Container for onboarding data."""
|
||||||
|
|
||||||
|
listeners: list[Callable[[], None]]
|
||||||
|
onboarded: bool
|
||||||
|
steps: OnboardingStoreData
|
||||||
|
|
||||||
|
|
||||||
|
class OnboardingStoreData(TypedDict):
|
||||||
|
"""Onboarding store data."""
|
||||||
|
|
||||||
|
done: list[str]
|
||||||
|
|
||||||
|
|
||||||
|
class OnboardingStorage(Store[OnboardingStoreData]):
|
||||||
"""Store onboarding data."""
|
"""Store onboarding data."""
|
||||||
|
|
||||||
async def _async_migrate_func(
|
async def _async_migrate_func(
|
||||||
self,
|
self,
|
||||||
old_major_version: int,
|
old_major_version: int,
|
||||||
old_minor_version: int,
|
old_minor_version: int,
|
||||||
old_data: dict[str, list[str]],
|
old_data: OnboardingStoreData,
|
||||||
) -> dict[str, list[str]]:
|
) -> OnboardingStoreData:
|
||||||
"""Migrate to the new version."""
|
"""Migrate to the new version."""
|
||||||
# From version 1 -> 2, we automatically mark the integration step done
|
# From version 1 -> 2, we automatically mark the integration step done
|
||||||
if old_major_version < 2:
|
if old_major_version < 2:
|
||||||
@ -50,21 +67,37 @@ class OnboadingStorage(Store[dict[str, list[str]]]):
|
|||||||
@callback
|
@callback
|
||||||
def async_is_onboarded(hass: HomeAssistant) -> bool:
|
def async_is_onboarded(hass: HomeAssistant) -> bool:
|
||||||
"""Return if Home Assistant has been onboarded."""
|
"""Return if Home Assistant has been onboarded."""
|
||||||
data = hass.data.get(DOMAIN)
|
data: OnboardingData | None = hass.data.get(DOMAIN)
|
||||||
return data is None or data is True
|
return data is None or data.onboarded is True
|
||||||
|
|
||||||
|
|
||||||
@bind_hass
|
@bind_hass
|
||||||
@callback
|
@callback
|
||||||
def async_is_user_onboarded(hass: HomeAssistant) -> bool:
|
def async_is_user_onboarded(hass: HomeAssistant) -> bool:
|
||||||
"""Return if a user has been created as part of onboarding."""
|
"""Return if a user has been created as part of onboarding."""
|
||||||
return async_is_onboarded(hass) or STEP_USER in hass.data[DOMAIN]["done"]
|
return async_is_onboarded(hass) or STEP_USER in hass.data[DOMAIN].steps["done"]
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_add_listener(hass: HomeAssistant, listener: Callable[[], None]) -> None:
|
||||||
|
"""Add a listener to be called when onboarding is complete."""
|
||||||
|
data: OnboardingData | None = hass.data.get(DOMAIN)
|
||||||
|
|
||||||
|
if not data:
|
||||||
|
# Onboarding not active
|
||||||
|
return
|
||||||
|
|
||||||
|
if data.onboarded:
|
||||||
|
listener()
|
||||||
|
return
|
||||||
|
|
||||||
|
data.listeners.append(listener)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
"""Set up the onboarding component."""
|
"""Set up the onboarding component."""
|
||||||
store = OnboadingStorage(hass, STORAGE_VERSION, STORAGE_KEY, private=True)
|
store = OnboardingStorage(hass, STORAGE_VERSION, STORAGE_KEY, private=True)
|
||||||
data: dict[str, list[str]] | None
|
data: OnboardingStoreData | None
|
||||||
if (data := await store.async_load()) is None:
|
if (data := await store.async_load()) is None:
|
||||||
data = {"done": []}
|
data = {"done": []}
|
||||||
|
|
||||||
@ -88,7 +121,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||||||
if set(data["done"]) == set(STEPS):
|
if set(data["done"]) == set(STEPS):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
hass.data[DOMAIN] = data
|
hass.data[DOMAIN] = OnboardingData([], False, data)
|
||||||
|
|
||||||
await views.async_setup(hass, data, store)
|
await views.async_setup(hass, data, store)
|
||||||
|
|
||||||
|
@ -3,5 +3,8 @@
|
|||||||
"living_room": "Living Room",
|
"living_room": "Living Room",
|
||||||
"bedroom": "Bedroom",
|
"bedroom": "Bedroom",
|
||||||
"kitchen": "Kitchen"
|
"kitchen": "Kitchen"
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"map": { "title": "Map" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ from homeassistant.setup import async_setup_component
|
|||||||
from homeassistant.util.async_ import create_eager_task
|
from homeassistant.util.async_ import create_eager_task
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from . import OnboadingStorage
|
from . import OnboardingData, OnboardingStorage, OnboardingStoreData
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
DEFAULT_AREAS,
|
DEFAULT_AREAS,
|
||||||
@ -40,7 +40,7 @@ from .const import (
|
|||||||
|
|
||||||
|
|
||||||
async def async_setup(
|
async def async_setup(
|
||||||
hass: HomeAssistant, data: dict[str, list[str]], store: OnboadingStorage
|
hass: HomeAssistant, data: OnboardingStoreData, store: OnboardingStorage
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the onboarding view."""
|
"""Set up the onboarding view."""
|
||||||
hass.http.register_view(OnboardingView(data, store))
|
hass.http.register_view(OnboardingView(data, store))
|
||||||
@ -58,7 +58,7 @@ class OnboardingView(HomeAssistantView):
|
|||||||
url = "/api/onboarding"
|
url = "/api/onboarding"
|
||||||
name = "api:onboarding"
|
name = "api:onboarding"
|
||||||
|
|
||||||
def __init__(self, data: dict[str, list[str]], store: OnboadingStorage) -> None:
|
def __init__(self, data: OnboardingStoreData, store: OnboardingStorage) -> None:
|
||||||
"""Initialize the onboarding view."""
|
"""Initialize the onboarding view."""
|
||||||
self._store = store
|
self._store = store
|
||||||
self._data = data
|
self._data = data
|
||||||
@ -77,7 +77,7 @@ class InstallationTypeOnboardingView(HomeAssistantView):
|
|||||||
url = "/api/onboarding/installation_type"
|
url = "/api/onboarding/installation_type"
|
||||||
name = "api:onboarding:installation_type"
|
name = "api:onboarding:installation_type"
|
||||||
|
|
||||||
def __init__(self, data: dict[str, list[str]]) -> None:
|
def __init__(self, data: OnboardingStoreData) -> None:
|
||||||
"""Initialize the onboarding installation type view."""
|
"""Initialize the onboarding installation type view."""
|
||||||
self._data = data
|
self._data = data
|
||||||
|
|
||||||
@ -96,7 +96,7 @@ class _BaseOnboardingView(HomeAssistantView):
|
|||||||
|
|
||||||
step: str
|
step: str
|
||||||
|
|
||||||
def __init__(self, data: dict[str, list[str]], store: OnboadingStorage) -> None:
|
def __init__(self, data: OnboardingStoreData, store: OnboardingStorage) -> None:
|
||||||
"""Initialize the onboarding view."""
|
"""Initialize the onboarding view."""
|
||||||
self._store = store
|
self._store = store
|
||||||
self._data = data
|
self._data = data
|
||||||
@ -113,7 +113,10 @@ class _BaseOnboardingView(HomeAssistantView):
|
|||||||
await self._store.async_save(self._data)
|
await self._store.async_save(self._data)
|
||||||
|
|
||||||
if set(self._data["done"]) == set(STEPS):
|
if set(self._data["done"]) == set(STEPS):
|
||||||
hass.data[DOMAIN] = True
|
data: OnboardingData = hass.data[DOMAIN]
|
||||||
|
data.onboarded = True
|
||||||
|
for listener in data.listeners:
|
||||||
|
listener()
|
||||||
|
|
||||||
|
|
||||||
class UserOnboardingView(_BaseOnboardingView):
|
class UserOnboardingView(_BaseOnboardingView):
|
||||||
|
@ -441,7 +441,10 @@ def gen_platform_strings_schema(config: Config, integration: Integration) -> vol
|
|||||||
|
|
||||||
|
|
||||||
ONBOARDING_SCHEMA = vol.Schema(
|
ONBOARDING_SCHEMA = vol.Schema(
|
||||||
{vol.Required("area"): {str: translation_value_validator}}
|
{
|
||||||
|
vol.Required("area"): {str: translation_value_validator},
|
||||||
|
vol.Required("dashboard"): {str: {"title": translation_value_validator}},
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
"""Test the Lovelace Cast platform."""
|
"""Test the Lovelace Cast platform."""
|
||||||
|
|
||||||
|
from collections.abc import Generator
|
||||||
from time import time
|
from time import time
|
||||||
from unittest.mock import patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@ -15,6 +16,19 @@ from homeassistant.setup import async_setup_component
|
|||||||
from tests.common import async_mock_service
|
from tests.common import async_mock_service
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def mock_onboarding_done() -> Generator[MagicMock, None, None]:
|
||||||
|
"""Mock that Home Assistant is currently onboarding.
|
||||||
|
|
||||||
|
Enabled to prevent creating default dashboards during test execution.
|
||||||
|
"""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.onboarding.async_is_onboarded",
|
||||||
|
return_value=True,
|
||||||
|
) as mock_onboarding:
|
||||||
|
yield mock_onboarding
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
async def mock_https_url(hass):
|
async def mock_https_url(hass):
|
||||||
"""Mock valid URL."""
|
"""Mock valid URL."""
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
"""Test the Lovelace initialization."""
|
"""Test the Lovelace initialization."""
|
||||||
|
|
||||||
|
from collections.abc import Generator
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from unittest.mock import patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@ -14,6 +15,19 @@ from tests.common import assert_setup_component, async_capture_events
|
|||||||
from tests.typing import WebSocketGenerator
|
from tests.typing import WebSocketGenerator
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def mock_onboarding_done() -> Generator[MagicMock, None, None]:
|
||||||
|
"""Mock that Home Assistant is currently onboarding.
|
||||||
|
|
||||||
|
Enabled to prevent creating default dashboards during test execution.
|
||||||
|
"""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.onboarding.async_is_onboarded",
|
||||||
|
return_value=True,
|
||||||
|
) as mock_onboarding:
|
||||||
|
yield mock_onboarding
|
||||||
|
|
||||||
|
|
||||||
async def test_lovelace_from_storage(
|
async def test_lovelace_from_storage(
|
||||||
hass: HomeAssistant, hass_ws_client, hass_storage: dict[str, Any]
|
hass: HomeAssistant, hass_ws_client, hass_storage: dict[str, Any]
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -277,7 +291,7 @@ async def test_dashboard_from_yaml(
|
|||||||
|
|
||||||
async def test_wrong_key_dashboard_from_yaml(hass: HomeAssistant) -> None:
|
async def test_wrong_key_dashboard_from_yaml(hass: HomeAssistant) -> None:
|
||||||
"""Test we don't load lovelace dashboard without hyphen config from yaml."""
|
"""Test we don't load lovelace dashboard without hyphen config from yaml."""
|
||||||
with assert_setup_component(0):
|
with assert_setup_component(0, "lovelace"):
|
||||||
assert not await async_setup_component(
|
assert not await async_setup_component(
|
||||||
hass,
|
hass,
|
||||||
"lovelace",
|
"lovelace",
|
||||||
|
98
tests/components/lovelace/test_init.py
Normal file
98
tests/components/lovelace/test_init.py
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
"""Test the Lovelace initialization."""
|
||||||
|
|
||||||
|
from collections.abc import Generator
|
||||||
|
from typing import Any
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
|
from tests.typing import WebSocketGenerator
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_onboarding_not_done() -> Generator[MagicMock, None, None]:
|
||||||
|
"""Mock that Home Assistant is currently onboarding."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.onboarding.async_is_onboarded",
|
||||||
|
return_value=False,
|
||||||
|
) as mock_onboarding:
|
||||||
|
yield mock_onboarding
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_onboarding_done() -> Generator[MagicMock, None, None]:
|
||||||
|
"""Mock that Home Assistant is currently onboarding."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.onboarding.async_is_onboarded",
|
||||||
|
return_value=True,
|
||||||
|
) as mock_onboarding:
|
||||||
|
yield mock_onboarding
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_add_onboarding_listener() -> Generator[MagicMock, None, None]:
|
||||||
|
"""Mock that Home Assistant is currently onboarding."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.onboarding.async_add_listener",
|
||||||
|
) as mock_add_onboarding_listener:
|
||||||
|
yield mock_add_onboarding_listener
|
||||||
|
|
||||||
|
|
||||||
|
async def test_create_dashboards_when_onboarded(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
hass_ws_client: WebSocketGenerator,
|
||||||
|
hass_storage: dict[str, Any],
|
||||||
|
mock_onboarding_done,
|
||||||
|
) -> None:
|
||||||
|
"""Test we don't create dashboards when onboarded."""
|
||||||
|
client = await hass_ws_client(hass)
|
||||||
|
|
||||||
|
assert await async_setup_component(hass, "lovelace", {})
|
||||||
|
|
||||||
|
# List dashboards
|
||||||
|
await client.send_json_auto_id({"type": "lovelace/dashboards/list"})
|
||||||
|
response = await client.receive_json()
|
||||||
|
assert response["success"]
|
||||||
|
assert response["result"] == []
|
||||||
|
|
||||||
|
|
||||||
|
async def test_create_dashboards_when_not_onboarded(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
hass_ws_client: WebSocketGenerator,
|
||||||
|
hass_storage: dict[str, Any],
|
||||||
|
mock_add_onboarding_listener,
|
||||||
|
mock_onboarding_not_done,
|
||||||
|
) -> None:
|
||||||
|
"""Test we automatically create dashboards when not onboarded."""
|
||||||
|
client = await hass_ws_client(hass)
|
||||||
|
|
||||||
|
assert await async_setup_component(hass, "lovelace", {})
|
||||||
|
|
||||||
|
# Call onboarding listener
|
||||||
|
mock_add_onboarding_listener.mock_calls[0][1][1]()
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# List dashboards
|
||||||
|
await client.send_json_auto_id({"type": "lovelace/dashboards/list"})
|
||||||
|
response = await client.receive_json()
|
||||||
|
assert response["success"]
|
||||||
|
assert response["result"] == [
|
||||||
|
{
|
||||||
|
"icon": "mdi:map",
|
||||||
|
"id": "map",
|
||||||
|
"mode": "storage",
|
||||||
|
"require_admin": False,
|
||||||
|
"show_in_sidebar": True,
|
||||||
|
"title": "Map",
|
||||||
|
"url_path": "map",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
# List map dashboard config
|
||||||
|
await client.send_json_auto_id({"type": "lovelace/config", "url_path": "map"})
|
||||||
|
response = await client.receive_json()
|
||||||
|
assert response["success"]
|
||||||
|
assert response["result"] == {"strategy": {"type": "map"}}
|
@ -1,7 +1,10 @@
|
|||||||
"""Tests for Lovelace system health."""
|
"""Tests for Lovelace system health."""
|
||||||
|
|
||||||
|
from collections.abc import Generator
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from unittest.mock import patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.lovelace import dashboard
|
from homeassistant.components.lovelace import dashboard
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
@ -10,6 +13,19 @@ from homeassistant.setup import async_setup_component
|
|||||||
from tests.common import get_system_health_info
|
from tests.common import get_system_health_info
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def mock_onboarding_done() -> Generator[MagicMock, None, None]:
|
||||||
|
"""Mock that Home Assistant is currently onboarding.
|
||||||
|
|
||||||
|
Enabled to prevent creating default dashboards during test execution.
|
||||||
|
"""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.onboarding.async_is_onboarded",
|
||||||
|
return_value=True,
|
||||||
|
) as mock_onboarding:
|
||||||
|
yield mock_onboarding
|
||||||
|
|
||||||
|
|
||||||
async def test_system_health_info_autogen(hass: HomeAssistant) -> None:
|
async def test_system_health_info_autogen(hass: HomeAssistant) -> None:
|
||||||
"""Test system health info endpoint."""
|
"""Test system health info endpoint."""
|
||||||
assert await async_setup_component(hass, "lovelace", {})
|
assert await async_setup_component(hass, "lovelace", {})
|
||||||
|
1
tests/components/map/__init__.py
Normal file
1
tests/components/map/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
"""Tests for Map."""
|
116
tests/components/map/test_init.py
Normal file
116
tests/components/map/test_init.py
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
"""Test the Map initialization."""
|
||||||
|
|
||||||
|
from collections.abc import Generator
|
||||||
|
from typing import Any
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant.components.map import DOMAIN
|
||||||
|
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
|
||||||
|
from homeassistant.helpers import issue_registry as ir
|
||||||
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
|
from tests.common import MockModule, mock_integration
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_onboarding_not_done() -> Generator[MagicMock, None, None]:
|
||||||
|
"""Mock that Home Assistant is currently onboarding."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.onboarding.async_is_onboarded",
|
||||||
|
return_value=False,
|
||||||
|
) as mock_onboarding:
|
||||||
|
yield mock_onboarding
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_onboarding_done() -> Generator[MagicMock, None, None]:
|
||||||
|
"""Mock that Home Assistant is currently onboarding."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.onboarding.async_is_onboarded",
|
||||||
|
return_value=True,
|
||||||
|
) as mock_onboarding:
|
||||||
|
yield mock_onboarding
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_create_map_dashboard() -> Generator[MagicMock, None, None]:
|
||||||
|
"""Mock the create map dashboard function."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.map._create_map_dashboard",
|
||||||
|
) as mock_create_map_dashboard:
|
||||||
|
yield mock_create_map_dashboard
|
||||||
|
|
||||||
|
|
||||||
|
async def test_create_dashboards_when_onboarded(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
hass_storage: dict[str, Any],
|
||||||
|
mock_onboarding_done,
|
||||||
|
mock_create_map_dashboard,
|
||||||
|
) -> None:
|
||||||
|
"""Test we create map dashboard when onboarded."""
|
||||||
|
# Mock the lovelace integration to prevent it from creating a map dashboard
|
||||||
|
mock_integration(hass, MockModule("lovelace"))
|
||||||
|
|
||||||
|
assert await async_setup_component(hass, DOMAIN, {})
|
||||||
|
|
||||||
|
mock_create_map_dashboard.assert_called_once()
|
||||||
|
assert hass_storage[DOMAIN]["data"] == {"migrated": True}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_create_dashboards_once_when_onboarded(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
hass_storage: dict[str, Any],
|
||||||
|
mock_onboarding_done,
|
||||||
|
mock_create_map_dashboard,
|
||||||
|
) -> None:
|
||||||
|
"""Test we create map dashboard once when onboarded."""
|
||||||
|
hass_storage[DOMAIN] = {
|
||||||
|
"version": 1,
|
||||||
|
"minor_version": 1,
|
||||||
|
"key": "map",
|
||||||
|
"data": {"migrated": True},
|
||||||
|
}
|
||||||
|
|
||||||
|
# Mock the lovelace integration to prevent it from creating a map dashboard
|
||||||
|
mock_integration(hass, MockModule("lovelace"))
|
||||||
|
|
||||||
|
assert await async_setup_component(hass, DOMAIN, {})
|
||||||
|
|
||||||
|
mock_create_map_dashboard.assert_not_called()
|
||||||
|
assert hass_storage[DOMAIN]["data"] == {"migrated": True}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_create_dashboards_when_not_onboarded(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
hass_storage: dict[str, Any],
|
||||||
|
mock_onboarding_not_done,
|
||||||
|
mock_create_map_dashboard,
|
||||||
|
) -> None:
|
||||||
|
"""Test we do not create map dashboard when not onboarded."""
|
||||||
|
# Mock the lovelace integration to prevent it from creating a map dashboard
|
||||||
|
mock_integration(hass, MockModule("lovelace"))
|
||||||
|
|
||||||
|
assert await async_setup_component(hass, DOMAIN, {})
|
||||||
|
|
||||||
|
mock_create_map_dashboard.assert_not_called()
|
||||||
|
assert hass_storage[DOMAIN]["data"] == {"migrated": True}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_create_issue_when_not_manually_configured(hass: HomeAssistant) -> None:
|
||||||
|
"""Test creating issue registry issues."""
|
||||||
|
assert await async_setup_component(hass, DOMAIN, {})
|
||||||
|
|
||||||
|
issue_registry = ir.async_get(hass)
|
||||||
|
assert not issue_registry.async_get_issue(
|
||||||
|
HOMEASSISTANT_DOMAIN, "deprecated_yaml_map"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_create_issue_when_manually_configured(hass: HomeAssistant) -> None:
|
||||||
|
"""Test creating issue registry issues."""
|
||||||
|
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
|
||||||
|
|
||||||
|
issue_registry = ir.async_get(hass)
|
||||||
|
assert issue_registry.async_get_issue(HOMEASSISTANT_DOMAIN, "deprecated_yaml_map")
|
@ -48,10 +48,10 @@ async def test_is_onboarded() -> None:
|
|||||||
|
|
||||||
assert onboarding.async_is_onboarded(hass)
|
assert onboarding.async_is_onboarded(hass)
|
||||||
|
|
||||||
hass.data[onboarding.DOMAIN] = True
|
hass.data[onboarding.DOMAIN] = onboarding.OnboardingData([], True, {"done": []})
|
||||||
assert onboarding.async_is_onboarded(hass)
|
assert onboarding.async_is_onboarded(hass)
|
||||||
|
|
||||||
hass.data[onboarding.DOMAIN] = {"done": []}
|
hass.data[onboarding.DOMAIN] = onboarding.OnboardingData([], False, {"done": []})
|
||||||
assert not onboarding.async_is_onboarded(hass)
|
assert not onboarding.async_is_onboarded(hass)
|
||||||
|
|
||||||
|
|
||||||
@ -62,10 +62,15 @@ async def test_is_user_onboarded() -> None:
|
|||||||
|
|
||||||
assert onboarding.async_is_user_onboarded(hass)
|
assert onboarding.async_is_user_onboarded(hass)
|
||||||
|
|
||||||
hass.data[onboarding.DOMAIN] = True
|
hass.data[onboarding.DOMAIN] = onboarding.OnboardingData([], True, {"done": []})
|
||||||
assert onboarding.async_is_user_onboarded(hass)
|
assert onboarding.async_is_user_onboarded(hass)
|
||||||
|
|
||||||
hass.data[onboarding.DOMAIN] = {"done": []}
|
hass.data[onboarding.DOMAIN] = onboarding.OnboardingData(
|
||||||
|
[], False, {"done": ["user"]}
|
||||||
|
)
|
||||||
|
assert onboarding.async_is_user_onboarded(hass)
|
||||||
|
|
||||||
|
hass.data[onboarding.DOMAIN] = onboarding.OnboardingData([], False, {"done": []})
|
||||||
assert not onboarding.async_is_user_onboarded(hass)
|
assert not onboarding.async_is_user_onboarded(hass)
|
||||||
|
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import asyncio
|
|||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
import os
|
import os
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from unittest.mock import patch
|
from unittest.mock import Mock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@ -655,3 +655,64 @@ async def test_onboarding_installation_type_after_done(
|
|||||||
resp = await client.get("/api/onboarding/installation_type")
|
resp = await client.get("/api/onboarding/installation_type")
|
||||||
|
|
||||||
assert resp.status == 401
|
assert resp.status == 401
|
||||||
|
|
||||||
|
|
||||||
|
async def test_complete_onboarding(
|
||||||
|
hass: HomeAssistant, hass_client: ClientSessionGenerator
|
||||||
|
) -> None:
|
||||||
|
"""Test completing onboarding calls listeners."""
|
||||||
|
listener_1 = Mock()
|
||||||
|
onboarding.async_add_listener(hass, listener_1)
|
||||||
|
listener_1.assert_not_called()
|
||||||
|
|
||||||
|
assert await async_setup_component(hass, "onboarding", {})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
listener_2 = Mock()
|
||||||
|
onboarding.async_add_listener(hass, listener_2)
|
||||||
|
listener_2.assert_not_called()
|
||||||
|
|
||||||
|
client = await hass_client()
|
||||||
|
|
||||||
|
assert not onboarding.async_is_onboarded(hass)
|
||||||
|
|
||||||
|
# Complete the user step
|
||||||
|
resp = await client.post(
|
||||||
|
"/api/onboarding/users",
|
||||||
|
json={
|
||||||
|
"client_id": CLIENT_ID,
|
||||||
|
"name": "Test Name",
|
||||||
|
"username": "test-user",
|
||||||
|
"password": "test-pass",
|
||||||
|
"language": "en",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert resp.status == 200
|
||||||
|
assert not onboarding.async_is_onboarded(hass)
|
||||||
|
listener_2.assert_not_called()
|
||||||
|
|
||||||
|
# Complete the core config step
|
||||||
|
resp = await client.post("/api/onboarding/core_config")
|
||||||
|
assert resp.status == 200
|
||||||
|
assert not onboarding.async_is_onboarded(hass)
|
||||||
|
listener_2.assert_not_called()
|
||||||
|
|
||||||
|
# Complete the integration step
|
||||||
|
resp = await client.post(
|
||||||
|
"/api/onboarding/integration",
|
||||||
|
json={"client_id": CLIENT_ID, "redirect_uri": CLIENT_REDIRECT_URI},
|
||||||
|
)
|
||||||
|
assert resp.status == 200
|
||||||
|
assert not onboarding.async_is_onboarded(hass)
|
||||||
|
listener_2.assert_not_called()
|
||||||
|
|
||||||
|
# Complete the analytics step
|
||||||
|
resp = await client.post("/api/onboarding/analytics")
|
||||||
|
assert resp.status == 200
|
||||||
|
assert onboarding.async_is_onboarded(hass)
|
||||||
|
listener_1.assert_not_called() # Registered before the integration was setup
|
||||||
|
listener_2.assert_called_once_with()
|
||||||
|
|
||||||
|
listener_3 = Mock()
|
||||||
|
onboarding.async_add_listener(hass, listener_3)
|
||||||
|
listener_3.assert_called_once_with()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user