1919
2020import pytest
2121import voluptuous as vol
22+ import yaml # via homeassistant -> pyyaml
2223from homeassistant .config_entries import SOURCE_USER
2324from homeassistant .data_entry_flow import FlowResultType
2425from homeassistant .helpers import entity_registry as er
@@ -1897,6 +1898,73 @@ async def test_copy_dashboard_yaml_shows_all_entries(
18971898 assert "aaa111" in yaml
18981899 assert "bbb222" in yaml
18991900 assert "eink-dashboard-card" in yaml
1901+ assert "type: sections" in yaml
1902+
1903+ async def test_copy_dashboard_yaml_groups_similar_sizes_together (
1904+ self , hass : HomeAssistant
1905+ ) -> None :
1906+ # Entries with similar card sizes share one grid section and
1907+ # get no explicit grid_options, since the default sizing
1908+ # already fits them side by side. _make_options_flow's own
1909+ # backing entry (default 758x1024) is also included in the
1910+ # entries list and contributes to the baseline calculation.
1911+ flow = await _make_options_flow (hass , {"webhook_urls" : []})
1912+ entry_a = MockConfigEntry (
1913+ domain = DOMAIN ,
1914+ entry_id = "kindle_pw" ,
1915+ options = {"width" : 758 , "height" : 1024 },
1916+ )
1917+ entry_a .add_to_hass (hass )
1918+ entry_b = MockConfigEntry (
1919+ domain = DOMAIN ,
1920+ entry_id = "kindle_pw4" ,
1921+ options = {"width" : 1072 , "height" : 1448 },
1922+ )
1923+ entry_b .add_to_hass (hass )
1924+ result = await flow .async_step_copy_dashboard_yaml (None )
1925+
1926+ yaml = result ["description_placeholders" ]["yaml" ]
1927+ assert yaml .count ("type: grid" ) == 1
1928+ assert "grid_options" not in yaml
1929+
1930+ async def test_copy_dashboard_yaml_isolates_oversized_entry (
1931+ self , hass : HomeAssistant
1932+ ) -> None :
1933+ # An entry much larger than the others (e.g. a landscape
1934+ # reTerminal E1003 next to portrait Kindles) gets its own
1935+ # full-width grid section instead of sharing a row.
1936+ # _make_options_flow's own backing entry (default 758x1024)
1937+ # is also included in the entries list and contributes to
1938+ # the median baseline, making this a 4-entry median.
1939+ flow = await _make_options_flow (hass , {"webhook_urls" : []})
1940+ entry_a = MockConfigEntry (
1941+ domain = DOMAIN ,
1942+ entry_id = "kindle_pw" ,
1943+ options = {"width" : 758 , "height" : 1024 },
1944+ )
1945+ entry_a .add_to_hass (hass )
1946+ entry_b = MockConfigEntry (
1947+ domain = DOMAIN ,
1948+ entry_id = "kindle_oasis" ,
1949+ options = {"width" : 1264 , "height" : 1680 },
1950+ )
1951+ entry_b .add_to_hass (hass )
1952+ entry_huge = MockConfigEntry (
1953+ domain = DOMAIN ,
1954+ entry_id = "reterminal_e1003" ,
1955+ options = {"width" : 1872 , "height" : 1404 },
1956+ )
1957+ entry_huge .add_to_hass (hass )
1958+ result = await flow .async_step_copy_dashboard_yaml (None )
1959+
1960+ yaml = result ["description_placeholders" ]["yaml" ]
1961+ assert yaml .count ("type: grid" ) == 2
1962+ assert yaml .count ("grid_options" ) == 1
1963+ assert yaml .count ("columns: full" ) == 1
1964+ sections = yaml .split ("type: grid" )[1 :]
1965+ huge_section = next (s for s in sections if "reterminal_e1003" in s )
1966+ assert "kindle_pw" not in huge_section
1967+ assert "kindle_oasis" not in huge_section
19001968
19011969 async def test_copy_dashboard_yaml_submit_returns_to_init (
19021970 self , hass : HomeAssistant
@@ -1908,6 +1976,116 @@ async def test_copy_dashboard_yaml_submit_returns_to_init(
19081976 assert result ["type" ] is FlowResultType .MENU
19091977 assert result ["step_id" ] == "init"
19101978
1979+ async def test_copy_dashboard_yaml_parses_to_valid_structure (
1980+ self , hass : HomeAssistant
1981+ ) -> None :
1982+ # Generated YAML is syntactically valid and matches the
1983+ # expected Lovelace sections-view structure: a shared grid
1984+ # for normal-sized entries and a full-width grid per
1985+ # oversized entry, each holding the right card config.
1986+ # _make_options_flow's own backing entry (default
1987+ # 758x1024) is also included in the entries list.
1988+ flow = await _make_options_flow (hass , {"webhook_urls" : []})
1989+ entry_a = MockConfigEntry (
1990+ domain = DOMAIN ,
1991+ entry_id = "kindle_pw" ,
1992+ options = {"width" : 758 , "height" : 1024 },
1993+ )
1994+ entry_a .add_to_hass (hass )
1995+ entry_huge = MockConfigEntry (
1996+ domain = DOMAIN ,
1997+ entry_id = "reterminal_e1003" ,
1998+ options = {"width" : 1872 , "height" : 1404 },
1999+ )
2000+ entry_huge .add_to_hass (hass )
2001+ result = await flow .async_step_copy_dashboard_yaml (None )
2002+
2003+ raw = result ["description_placeholders" ]["yaml" ]
2004+ doc = yaml .safe_load (raw )
2005+ view = doc ["views" ][0 ]
2006+ assert view ["type" ] == "sections"
2007+ assert view ["max_columns" ] == 10
2008+ assert view ["title" ] == "E-Ink Dashboards"
2009+ assert "path" not in view
2010+ sections = view ["sections" ]
2011+ assert len (sections ) == 2
2012+
2013+ normal_section = sections [0 ]
2014+ assert normal_section ["type" ] == "grid"
2015+ assert normal_section ["column_span" ] == 10
2016+ assert len (normal_section ["cards" ]) == 2
2017+ assert all (
2018+ c ["type" ] == "custom:eink-dashboard-card"
2019+ for c in normal_section ["cards" ]
2020+ )
2021+
2022+ large_section = sections [1 ]
2023+ assert large_section ["type" ] == "grid"
2024+ assert len (large_section ["cards" ]) == 1
2025+ large_card = large_section ["cards" ][0 ]
2026+ assert large_card ["config_entry" ] == "reterminal_e1003"
2027+ assert large_card ["grid_options" ] == {"columns" : "full" }
2028+
2029+ async def test_copy_dashboard_yaml_isolates_multiple_oversized (
2030+ self , hass : HomeAssistant
2031+ ) -> None :
2032+ # Two entries much larger than the normals each get their
2033+ # own full-width grid section rather than sharing one.
2034+ # _make_options_flow's own backing entry (default
2035+ # 758x1024) is also included in the entries list.
2036+ flow = await _make_options_flow (hass , {"webhook_urls" : []})
2037+ for entry_id in ("normal_a" , "normal_b" ):
2038+ entry = MockConfigEntry (
2039+ domain = DOMAIN ,
2040+ entry_id = entry_id ,
2041+ options = {"width" : 758 , "height" : 1024 },
2042+ )
2043+ entry .add_to_hass (hass )
2044+ for entry_id , width , height in (
2045+ ("large_a" , 1872 , 1404 ),
2046+ ("large_b" , 2560 , 1600 ),
2047+ ):
2048+ entry = MockConfigEntry (
2049+ domain = DOMAIN ,
2050+ entry_id = entry_id ,
2051+ options = {"width" : width , "height" : height },
2052+ )
2053+ entry .add_to_hass (hass )
2054+ result = await flow .async_step_copy_dashboard_yaml (None )
2055+
2056+ raw = result ["description_placeholders" ]["yaml" ]
2057+ assert raw .count ("type: grid" ) == 3
2058+ assert raw .count ("grid_options" ) == 2
2059+ assert raw .count ("columns: full" ) == 2
2060+
2061+ async def test_copy_dashboard_yaml_exact_threshold_is_normal (
2062+ self , hass : HomeAssistant
2063+ ) -> None :
2064+ # An entry at exactly 1.5x the baseline stays in the normal
2065+ # group, since _split_large_entries uses a strict > compare.
2066+ # _make_options_flow's own backing entry (default
2067+ # 758x1024) is also included in the entries list.
2068+ flow = await _make_options_flow (hass , {"webhook_urls" : []})
2069+ entry_anchor = MockConfigEntry (
2070+ domain = DOMAIN ,
2071+ entry_id = "anchor" ,
2072+ options = {"width" : 758 , "height" : 1024 },
2073+ )
2074+ entry_anchor .add_to_hass (hass )
2075+ # Exactly 1.5x the 758x1024 baseline: 758*1.5=1137,
2076+ # 1024*1.5=1536.
2077+ entry_boundary = MockConfigEntry (
2078+ domain = DOMAIN ,
2079+ entry_id = "boundary" ,
2080+ options = {"width" : 1137 , "height" : 1536 },
2081+ )
2082+ entry_boundary .add_to_hass (hass )
2083+ result = await flow .async_step_copy_dashboard_yaml (None )
2084+
2085+ raw = result ["description_placeholders" ]["yaml" ]
2086+ assert raw .count ("type: grid" ) == 1
2087+ assert "grid_options" not in raw
2088+
19112089 async def test_display_settings_persists_entry_options (
19122090 self , hass : HomeAssistant
19132091 ) -> None :
0 commit comments