diff --git a/.coveragerc b/.coveragerc
index a60f216af1b0fcbd328ea01d5a67a4f05554ab67..54d543dfbf3937181380a58ab0f309b976b09474 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -747,7 +747,6 @@ omit =
     homeassistant/components/lyric/climate.py
     homeassistant/components/lyric/sensor.py
     homeassistant/components/mailgun/notify.py
-    homeassistant/components/map/*
     homeassistant/components/mastodon/notify.py
     homeassistant/components/matrix/__init__.py
     homeassistant/components/matrix/notify.py
diff --git a/homeassistant/components/lovelace/__init__.py b/homeassistant/components/lovelace/__init__.py
index 73be119880f35ccb10cf695d9a3887f3e5f2a5ac..60d03717be0cc218daa0ce11d7170596e94a4654 100644
--- a/homeassistant/components/lovelace/__init__.py
+++ b/homeassistant/components/lovelace/__init__.py
@@ -4,7 +4,7 @@ import logging
 
 import voluptuous as vol
 
-from homeassistant.components import frontend, websocket_api
+from homeassistant.components import frontend, onboarding, websocket_api
 from homeassistant.config import (
     async_hass_config_yaml,
     async_process_component_and_handle_errors,
@@ -14,11 +14,13 @@ from homeassistant.core import HomeAssistant, ServiceCall, callback
 from homeassistant.exceptions import HomeAssistantError
 from homeassistant.helpers import collection, config_validation as cv
 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.loader import async_get_integration
 
 from . import dashboard, resources, websocket
 from .const import (  # noqa: F401
+    CONF_ALLOW_SINGLE_WORD,
     CONF_ICON,
     CONF_REQUIRE_ADMIN,
     CONF_SHOW_IN_SIDEBAR,
@@ -201,6 +203,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
     # Process storage dashboards
     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)
     await dashboards_collection.async_load()
 
@@ -212,6 +217,12 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
         STORAGE_DASHBOARD_UPDATE_FIELDS,
     ).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
 
 
@@ -249,3 +260,25 @@ def _register_panel(hass, url_path, mode, config, update):
         kwargs["sidebar_icon"] = config.get(CONF_ICON, DEFAULT_ICON)
 
     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"}})
diff --git a/homeassistant/components/lovelace/const.py b/homeassistant/components/lovelace/const.py
index 0f1e818e8bd8576935fc5a321902c7e2b89d700b..538bd49d72cd8e07800f92bc2f833b332e37405a 100644
--- a/homeassistant/components/lovelace/const.py
+++ b/homeassistant/components/lovelace/const.py
@@ -24,6 +24,7 @@ MODE_STORAGE = "storage"
 MODE_AUTO = "auto-gen"
 
 LOVELACE_CONFIG_FILE = "ui-lovelace.yaml"
+CONF_ALLOW_SINGLE_WORD = "allow_single_word"
 CONF_URL_PATH = "url_path"
 CONF_RESOURCE_TYPE_WS = "res_type"
 
@@ -75,6 +76,8 @@ STORAGE_DASHBOARD_CREATE_FIELDS = {
     # For now we write "storage" as all modes.
     # In future we can adjust this to be other modes.
     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
diff --git a/homeassistant/components/lovelace/dashboard.py b/homeassistant/components/lovelace/dashboard.py
index 98c03e20d874376e9ce354375d78ae45cfad1430..17116a011a4fd3cfa596102bf379fca05fc8fe12 100644
--- a/homeassistant/components/lovelace/dashboard.py
+++ b/homeassistant/components/lovelace/dashboard.py
@@ -18,6 +18,7 @@ from homeassistant.helpers import collection, storage
 from homeassistant.util.yaml import Secrets, load_yaml_dict
 
 from .const import (
+    CONF_ALLOW_SINGLE_WORD,
     CONF_ICON,
     CONF_URL_PATH,
     DOMAIN,
@@ -234,10 +235,14 @@ class DashboardsCollection(collection.DictStorageCollection):
 
     async def _process_create_data(self, data: dict) -> dict:
         """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 (-)")
 
-        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")
 
         return self.CREATE_SCHEMA(data)
diff --git a/homeassistant/components/lovelace/manifest.json b/homeassistant/components/lovelace/manifest.json
index 9dcffdb3b6cf31dd47058c066a067473492499a7..ed55142ee773f5f8c4830143af3d3baee807caba 100644
--- a/homeassistant/components/lovelace/manifest.json
+++ b/homeassistant/components/lovelace/manifest.json
@@ -2,6 +2,7 @@
   "domain": "lovelace",
   "name": "Dashboards",
   "codeowners": ["@home-assistant/frontend"],
+  "dependencies": ["onboarding"],
   "documentation": "https://www.home-assistant.io/integrations/lovelace",
   "integration_type": "system",
   "quality_scale": "internal"
diff --git a/homeassistant/components/map/__init__.py b/homeassistant/components/map/__init__.py
index a78199ad6607c25490dba0da42dd37875597ab2e..beddde5df1d441869462e4c1977714225964b8d6 100644
--- a/homeassistant/components/map/__init__.py
+++ b/homeassistant/components/map/__init__.py
@@ -1,16 +1,52 @@
 """Support for showing device locations."""
-
-from homeassistant.components import frontend
-from homeassistant.core import HomeAssistant
+from homeassistant.components import onboarding
+from homeassistant.components.lovelace import _create_map_dashboard
+from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
 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
 
 DOMAIN = "map"
 
 CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)
 
+STORAGE_KEY = DOMAIN
+STORAGE_VERSION_MAJOR = 1
+
 
 async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
-    """Register the built-in map panel."""
-    frontend.async_register_built_in_panel(hass, "map", "map", "hass:tooltip-account")
+    """Create a map panel."""
+
+    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
diff --git a/homeassistant/components/map/manifest.json b/homeassistant/components/map/manifest.json
index b617aa3e5faf57e17541738673e7e166797d81c9..6a0333c862a0534f723cc9a2aa9522fe8b1434f2 100644
--- a/homeassistant/components/map/manifest.json
+++ b/homeassistant/components/map/manifest.json
@@ -2,7 +2,7 @@
   "domain": "map",
   "name": "Map",
   "codeowners": [],
-  "dependencies": ["frontend"],
+  "dependencies": ["frontend", "lovelace"],
   "documentation": "https://www.home-assistant.io/integrations/map",
   "integration_type": "system",
   "quality_scale": "internal"
diff --git a/homeassistant/components/onboarding/__init__.py b/homeassistant/components/onboarding/__init__.py
index 61576c831bf4b56256fb5a12c54a5987f640e7b2..c11bd79c377faa90304163d05a1b39f7dbabe0a1 100644
--- a/homeassistant/components/onboarding/__init__.py
+++ b/homeassistant/components/onboarding/__init__.py
@@ -2,7 +2,9 @@
 
 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.helpers import config_validation as cv
@@ -26,15 +28,30 @@ STORAGE_VERSION = 4
 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."""
 
     async def _async_migrate_func(
         self,
         old_major_version: int,
         old_minor_version: int,
-        old_data: dict[str, list[str]],
-    ) -> dict[str, list[str]]:
+        old_data: OnboardingStoreData,
+    ) -> OnboardingStoreData:
         """Migrate to the new version."""
         # From version 1 -> 2, we automatically mark the integration step done
         if old_major_version < 2:
@@ -50,21 +67,37 @@ class OnboadingStorage(Store[dict[str, list[str]]]):
 @callback
 def async_is_onboarded(hass: HomeAssistant) -> bool:
     """Return if Home Assistant has been onboarded."""
-    data = hass.data.get(DOMAIN)
-    return data is None or data is True
+    data: OnboardingData | None = hass.data.get(DOMAIN)
+    return data is None or data.onboarded is True
 
 
 @bind_hass
 @callback
 def async_is_user_onboarded(hass: HomeAssistant) -> bool:
     """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:
     """Set up the onboarding component."""
-    store = OnboadingStorage(hass, STORAGE_VERSION, STORAGE_KEY, private=True)
-    data: dict[str, list[str]] | None
+    store = OnboardingStorage(hass, STORAGE_VERSION, STORAGE_KEY, private=True)
+    data: OnboardingStoreData | None
     if (data := await store.async_load()) is None:
         data = {"done": []}
 
@@ -88,7 +121,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
     if set(data["done"]) == set(STEPS):
         return True
 
-    hass.data[DOMAIN] = data
+    hass.data[DOMAIN] = OnboardingData([], False, data)
 
     await views.async_setup(hass, data, store)
 
diff --git a/homeassistant/components/onboarding/strings.json b/homeassistant/components/onboarding/strings.json
index 9e3806927d28f04e66b54ad11aca368d750b071d..bc8bb6e54ff4688cb058711d1d8223ec8eb1b370 100644
--- a/homeassistant/components/onboarding/strings.json
+++ b/homeassistant/components/onboarding/strings.json
@@ -3,5 +3,8 @@
     "living_room": "Living Room",
     "bedroom": "Bedroom",
     "kitchen": "Kitchen"
+  },
+  "dashboard": {
+    "map": { "title": "Map" }
   }
 }
diff --git a/homeassistant/components/onboarding/views.py b/homeassistant/components/onboarding/views.py
index bcc4bd26a588beef23c01aa1177a6eeee7b50d03..bb4d84d1b504912c8b91916032d8bb47fbf9cdd3 100644
--- a/homeassistant/components/onboarding/views.py
+++ b/homeassistant/components/onboarding/views.py
@@ -26,7 +26,7 @@ from homeassistant.setup import async_setup_component
 from homeassistant.util.async_ import create_eager_task
 
 if TYPE_CHECKING:
-    from . import OnboadingStorage
+    from . import OnboardingData, OnboardingStorage, OnboardingStoreData
 
 from .const import (
     DEFAULT_AREAS,
@@ -40,7 +40,7 @@ from .const import (
 
 
 async def async_setup(
-    hass: HomeAssistant, data: dict[str, list[str]], store: OnboadingStorage
+    hass: HomeAssistant, data: OnboardingStoreData, store: OnboardingStorage
 ) -> None:
     """Set up the onboarding view."""
     hass.http.register_view(OnboardingView(data, store))
@@ -58,7 +58,7 @@ class OnboardingView(HomeAssistantView):
     url = "/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."""
         self._store = store
         self._data = data
@@ -77,7 +77,7 @@ class InstallationTypeOnboardingView(HomeAssistantView):
     url = "/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."""
         self._data = data
 
@@ -96,7 +96,7 @@ class _BaseOnboardingView(HomeAssistantView):
 
     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."""
         self._store = store
         self._data = data
@@ -113,7 +113,10 @@ class _BaseOnboardingView(HomeAssistantView):
         await self._store.async_save(self._data)
 
         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):
diff --git a/script/hassfest/translations.py b/script/hassfest/translations.py
index f595776febea7e9be7ca1d03a0ee37875dbbdf55..724f65eafb62e2e7edda989c843e7933fcfbf452 100644
--- a/script/hassfest/translations.py
+++ b/script/hassfest/translations.py
@@ -441,7 +441,10 @@ def gen_platform_strings_schema(config: Config, integration: Integration) -> vol
 
 
 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}},
+    }
 )
 
 
diff --git a/tests/components/lovelace/test_cast.py b/tests/components/lovelace/test_cast.py
index 12cf4a84095d55666829958c8d2fa2d8d3056479..5fdce538d4ca0a03a374aa086b8f751fe30d15b3 100644
--- a/tests/components/lovelace/test_cast.py
+++ b/tests/components/lovelace/test_cast.py
@@ -1,7 +1,8 @@
 """Test the Lovelace Cast platform."""
 
+from collections.abc import Generator
 from time import time
-from unittest.mock import patch
+from unittest.mock import MagicMock, patch
 
 import pytest
 
@@ -15,6 +16,19 @@ from homeassistant.setup import async_setup_component
 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
 async def mock_https_url(hass):
     """Mock valid URL."""
diff --git a/tests/components/lovelace/test_dashboard.py b/tests/components/lovelace/test_dashboard.py
index a85770d8ae42d402e607b3e65af307e5dbe8cccd..d0ac6e988359bd1c6269785a6b1ee48274f26306 100644
--- a/tests/components/lovelace/test_dashboard.py
+++ b/tests/components/lovelace/test_dashboard.py
@@ -1,7 +1,8 @@
 """Test the Lovelace initialization."""
 
+from collections.abc import Generator
 from typing import Any
-from unittest.mock import patch
+from unittest.mock import MagicMock, patch
 
 import pytest
 
@@ -14,6 +15,19 @@ from tests.common import assert_setup_component, async_capture_events
 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(
     hass: HomeAssistant, hass_ws_client, hass_storage: dict[str, Any]
 ) -> None:
@@ -277,7 +291,7 @@ async def test_dashboard_from_yaml(
 
 async def test_wrong_key_dashboard_from_yaml(hass: HomeAssistant) -> None:
     """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(
             hass,
             "lovelace",
diff --git a/tests/components/lovelace/test_init.py b/tests/components/lovelace/test_init.py
new file mode 100644
index 0000000000000000000000000000000000000000..a88745e450019bae3ad22e23d178fde4b156853f
--- /dev/null
+++ b/tests/components/lovelace/test_init.py
@@ -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"}}
diff --git a/tests/components/lovelace/test_system_health.py b/tests/components/lovelace/test_system_health.py
index 11cb8776e6262fcaef325c3ccc70f24a6a06a103..9bd8543004cce4b97092330eb57e294121b4c1a9 100644
--- a/tests/components/lovelace/test_system_health.py
+++ b/tests/components/lovelace/test_system_health.py
@@ -1,7 +1,10 @@
 """Tests for Lovelace system health."""
 
+from collections.abc import Generator
 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.core import HomeAssistant
@@ -10,6 +13,19 @@ from homeassistant.setup import async_setup_component
 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:
     """Test system health info endpoint."""
     assert await async_setup_component(hass, "lovelace", {})
diff --git a/tests/components/map/__init__.py b/tests/components/map/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..142afc0d5c985e4848d11f067c046a8b9bfdca2c
--- /dev/null
+++ b/tests/components/map/__init__.py
@@ -0,0 +1 @@
+"""Tests for Map."""
diff --git a/tests/components/map/test_init.py b/tests/components/map/test_init.py
new file mode 100644
index 0000000000000000000000000000000000000000..6d79afefab3743dfacbf31ddfd2cd37b7e405bc6
--- /dev/null
+++ b/tests/components/map/test_init.py
@@ -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")
diff --git a/tests/components/onboarding/test_init.py b/tests/components/onboarding/test_init.py
index 690630573877c007cad66d48e65bb16499dac311..f6941098b18292625ca8fe7f4adc449b760a1ebd 100644
--- a/tests/components/onboarding/test_init.py
+++ b/tests/components/onboarding/test_init.py
@@ -48,10 +48,10 @@ async def test_is_onboarded() -> None:
 
     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)
 
-    hass.data[onboarding.DOMAIN] = {"done": []}
+    hass.data[onboarding.DOMAIN] = onboarding.OnboardingData([], False, {"done": []})
     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)
 
-    hass.data[onboarding.DOMAIN] = True
+    hass.data[onboarding.DOMAIN] = onboarding.OnboardingData([], True, {"done": []})
     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)
 
 
diff --git a/tests/components/onboarding/test_views.py b/tests/components/onboarding/test_views.py
index 406faff0dac6bb909d591c370f82ff2f9481d56b..272d0e997733fbefc71f0022588c7d1d370d305a 100644
--- a/tests/components/onboarding/test_views.py
+++ b/tests/components/onboarding/test_views.py
@@ -4,7 +4,7 @@ import asyncio
 from http import HTTPStatus
 import os
 from typing import Any
-from unittest.mock import patch
+from unittest.mock import Mock, patch
 
 import pytest
 
@@ -655,3 +655,64 @@ async def test_onboarding_installation_type_after_done(
     resp = await client.get("/api/onboarding/installation_type")
 
     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()