"""Test the namespaced icon-key module (task 707994b7). :func:`validate_icon` is the strict write-side check (create/edit); :func:`normalize_icon_for_read` the lenient read-side normalizer that lets legacy bare Lucide names render without a data migration. """ from __future__ import annotations import json from pathlib import Path import pytest from custom_components.nestquest.core.icons import ( EMOJI_MAX_CODE_POINTS, ICON_NAMESPACES, normalize_icon_for_read, validate_icon, ) def test_namespace_and_cap_constants() -> None: assert ICON_NAMESPACES == {"lucide", "fa", "emoji"} assert EMOJI_MAX_CODE_POINTS == 8 @pytest.mark.parametrize( ("value", "expected"), [ ("lucide:dog", "lucide:dog"), ("fa:dog", "fa:dog"), ("fa:paw-print", "fa:paw-print"), ("lucide:toy-box-2", "lucide:toy-box-2"), ("emoji:πŸ•", "emoji:πŸ•"), # A ZWJ family sequence (7 code points) is within the cap. ("emoji:πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦", "emoji:πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦"), ("emoji:" + "⭐" * EMOJI_MAX_CODE_POINTS, "emoji:" + "⭐" * 8), # Legacy bare Lucide names normalize to the lucide namespace. ("dog", "lucide:dog"), ], ) def test_validate_icon_accepts(value: str, expected: str) -> None: assert validate_icon(value) == expected @pytest.mark.parametrize("value", [None, "", " ", "\t\n"]) def test_validate_icon_clears(value: str | None) -> None: assert validate_icon(value) is None @pytest.mark.parametrize( "value", [ "foo:dog", # unknown namespace "javascript:alert(1)", # unknown namespace (and markup-ish) "mdi:dog", # not one of the supported namespaces "lucide:", # empty name "fa:", # empty name "emoji:", # empty payload "lucide:Dog", # uppercase "lucide:has space", # whitespace "lucide:-dog", # leading hyphen "lucide:dog-", # trailing hyphen "lucide:dog_bone", # not kebab "fa:dΓΆg", # non-ASCII "lucide:a:b", # colon inside the name "Dog", # bare legacy name must still be kebab "has space", "