diff --git a/pynecone/components/component.py b/pynecone/components/component.py index ef20c9321..ccdce8e08 100644 --- a/pynecone/components/component.py +++ b/pynecone/components/component.py @@ -64,6 +64,9 @@ class Component(Base, ABC): # Whether the component should take the focus once the page is loaded autofocus: bool = False + # components that cannot be children + invalid_children: List[str] = [] + @classmethod def __init_subclass__(cls, **kwargs): """Set default properties. @@ -411,7 +414,8 @@ class Component(Base, ABC): The dictionary for template of component. """ tag = self._render() - return dict( + + rendered_dict = dict( tag.add_props( **self.event_triggers, key=self.key, @@ -425,6 +429,29 @@ class Component(Base, ABC): ), autofocus=self.autofocus, ) + self._validate_component_children( + rendered_dict["name"], rendered_dict["children"] + ) + return rendered_dict + + def _validate_component_children(self, comp_name: str, children: List[Dict]): + """Validate the children components. + + Args: + comp_name: name of the component. + children: list of children components. + + Raises: + ValueError: when an unsupported component is matched. + """ + if not self.invalid_children: + return + for child in children: + name = child["name"] + if name in self.invalid_children: + raise ValueError( + f"The component `{comp_name.lower()}` cannot have `{name.lower()}` as a child component" + ) def _get_custom_code(self) -> Optional[str]: """Get custom code for the component. diff --git a/pynecone/components/datadisplay/table.py b/pynecone/components/datadisplay/table.py index 8e3a097de..2f621e562 100644 --- a/pynecone/components/datadisplay/table.py +++ b/pynecone/components/datadisplay/table.py @@ -1,4 +1,5 @@ """Table components.""" +from typing import List from pynecone.components.component import Component from pynecone.components.layout.foreach import Foreach @@ -62,6 +63,9 @@ class Thead(ChakraComponent): tag = "Thead" + # invalid children components + invalid_children: List[str] = ["Tbody", "Thead", "Tfoot"] + @classmethod def create(cls, *children, headers=None, **props) -> Component: """Create a table header component. @@ -84,6 +88,9 @@ class Tbody(ChakraComponent): tag = "Tbody" + # invalid children components + invalid_children: List[str] = ["Tbody", "Thead", "Tfoot", "Td", "Th"] + @classmethod def create(cls, *children, rows=None, **props) -> Component: """Create a table body component. @@ -106,6 +113,9 @@ class Tfoot(ChakraComponent): tag = "Tfoot" + # invalid children components + invalid_children: List[str] = ["Tbody", "Thead", "Td", "Th", "Tfoot"] + @classmethod def create(cls, *children, footers=None, **props) -> Component: """Create a table footer component. @@ -128,6 +138,9 @@ class Tr(ChakraComponent): tag = "Tr" + # invalid children components + invalid_children: List[str] = ["Tbody", "Thead", "Tfoot", "Tr"] + @classmethod def create(cls, *children, cell_type: str = "", cells=None, **props) -> Component: """Create a table row component. @@ -156,6 +169,9 @@ class Th(ChakraComponent): tag = "Th" + # invalid children components + invalid_children: List[str] = ["Tbody", "Thead", "Tr", "Td", "Th"] + # Aligns the cell content to the right. is_numeric: Var[bool] @@ -165,6 +181,9 @@ class Td(ChakraComponent): tag = "Td" + # invalid children components + invalid_children: List[str] = ["Tbody", "Thead"] + # Aligns the cell content to the right. is_numeric: Var[bool] diff --git a/tests/components/test_component.py b/tests/components/test_component.py index b5340e0c7..e766cf7fe 100644 --- a/tests/components/test_component.py +++ b/tests/components/test_component.py @@ -112,6 +112,22 @@ def component4() -> Type[Component]: return TestComponent4 +@pytest.fixture +def component5() -> Type[Component]: + """A test component. + + Returns: + A test component. + """ + + class TestComponent5(Component): + tag = "Tag" + + invalid_children: List[str] = ["Text"] + + return TestComponent5 + + @pytest.fixture def on_click1() -> EventHandler: """A sample on click function. @@ -433,3 +449,18 @@ def test_get_hooks_nested2(component3, component4): ).get_hooks() == exp_hooks ) + + +def test_unsupported_child_components(component5): + """Test that a value error is raised when an unsupported component is provided as a child. + + Args: + component5: the test component + """ + with pytest.raises(ValueError) as err: + comp = component5.create(pc.text("testing component")) + comp.render() + assert ( + err.value.args[0] + == f"The component `tag` cannot have `text` as a child component" + )