You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
128 lines
3.7 KiB
128 lines
3.7 KiB
import contextlib
|
|
import importlib.util
|
|
import io
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
|
|
MODULE_PATH = (
|
|
Path(__file__).resolve().parents[1] / "scripts" / "validate_lovelace_view.py"
|
|
)
|
|
MODULE_NAME = "validate_lovelace_view"
|
|
SPEC = importlib.util.spec_from_file_location(MODULE_NAME, MODULE_PATH)
|
|
if SPEC is None or SPEC.loader is None:
|
|
raise RuntimeError(f"Unable to load module spec from {MODULE_PATH}")
|
|
MODULE = importlib.util.module_from_spec(SPEC)
|
|
sys.modules[MODULE_NAME] = MODULE
|
|
SPEC.loader.exec_module(MODULE)
|
|
|
|
|
|
class ValidateLovelaceViewTests(unittest.TestCase):
|
|
def _run_validator(self, yaml_text: str, *extra_args: str) -> tuple[int, str, str]:
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
view_path = Path(tmpdir) / "view.yaml"
|
|
view_path.write_text(yaml_text, encoding="utf-8")
|
|
|
|
stdout = io.StringIO()
|
|
stderr = io.StringIO()
|
|
with contextlib.redirect_stdout(stdout), contextlib.redirect_stderr(stderr):
|
|
rc = MODULE.main([str(view_path), *extra_args])
|
|
return rc, stdout.getvalue(), stderr.getvalue()
|
|
|
|
def test_valid_classic_view_passes(self) -> None:
|
|
yaml_text = """
|
|
title: Main
|
|
path: main
|
|
cards:
|
|
- type: custom:button-card
|
|
template:
|
|
- status_chip
|
|
"""
|
|
rc, out, err = self._run_validator(yaml_text)
|
|
self.assertEqual(rc, 0, out + err)
|
|
self.assertEqual(out.strip(), "")
|
|
self.assertEqual(err.strip(), "")
|
|
|
|
def test_horizontal_stack_fails(self) -> None:
|
|
yaml_text = """
|
|
title: Main
|
|
path: main
|
|
cards:
|
|
- type: horizontal-stack
|
|
cards:
|
|
- type: custom:button-card
|
|
template: status_chip
|
|
"""
|
|
rc, out, _ = self._run_validator(yaml_text)
|
|
self.assertEqual(rc, 1)
|
|
self.assertIn("Disallowed card type: horizontal-stack", out)
|
|
|
|
def test_button_card_missing_template_fails(self) -> None:
|
|
yaml_text = """
|
|
title: Main
|
|
path: main
|
|
cards:
|
|
- type: custom:button-card
|
|
entity: light.kitchen
|
|
"""
|
|
rc, out, _ = self._run_validator(yaml_text)
|
|
self.assertEqual(rc, 1)
|
|
self.assertIn("custom:button-card must set template", out)
|
|
|
|
def test_sections_wrapper_rule_fails_when_full_width_missing(self) -> None:
|
|
yaml_text = """
|
|
title: Sections
|
|
path: sections
|
|
type: sections
|
|
sections:
|
|
- type: grid
|
|
column_span: 4
|
|
cards:
|
|
- type: custom:vertical-stack-in-card
|
|
cards:
|
|
- type: custom:button-card
|
|
template: status_chip
|
|
"""
|
|
rc, out, _ = self._run_validator(yaml_text)
|
|
self.assertEqual(rc, 1)
|
|
self.assertIn('Wrapper card must set grid_options.columns: "full".', out)
|
|
|
|
def test_sections_wrapper_rule_passes_when_full_width_wrapper_present(self) -> None:
|
|
yaml_text = """
|
|
title: Sections
|
|
path: sections
|
|
type: sections
|
|
sections:
|
|
- type: grid
|
|
column_span: 4
|
|
cards:
|
|
- type: custom:vertical-stack-in-card
|
|
grid_options:
|
|
columns: "full"
|
|
cards:
|
|
- type: custom:button-card
|
|
template: status_chip
|
|
"""
|
|
rc, out, err = self._run_validator(yaml_text)
|
|
self.assertEqual(rc, 0, out + err)
|
|
self.assertEqual(out.strip(), "")
|
|
self.assertEqual(err.strip(), "")
|
|
|
|
def test_unknown_include_tag_is_parse_safe(self) -> None:
|
|
yaml_text = """
|
|
title: Included Sections
|
|
path: included_sections
|
|
type: sections
|
|
sections: !include ../sections/core.yaml
|
|
"""
|
|
rc, out, err = self._run_validator(yaml_text)
|
|
self.assertEqual(rc, 0, out + err)
|
|
self.assertIn("validator cannot inspect included sections content", out)
|
|
self.assertEqual(err.strip(), "")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|