diff --git a/src/idom/client/app/package-lock.json b/src/idom/client/app/package-lock.json index 38c5e6119..7779cb4c8 100644 --- a/src/idom/client/app/package-lock.json +++ b/src/idom/client/app/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "license": "MIT", "workspaces": [ - "packages/idom-client-react" + "./packages/*" ], "dependencies": { "idom-client-react": "file:packages/idom-client-react", diff --git a/src/idom/client/app/package.json b/src/idom/client/app/package.json index 6652705fe..6a9f9522c 100644 --- a/src/idom/client/app/package.json +++ b/src/idom/client/app/package.json @@ -4,7 +4,9 @@ "version": "0.1.0", "author": "Ryan Morshead", "main": "index.js", - "workspaces": [ "./packages/*" ], + "workspaces": [ + "./packages/*" + ], "license": "MIT", "repository": { "type": "git", diff --git a/src/idom/core/vdom.py b/src/idom/core/vdom.py index 9901d20c7..6e54f343e 100644 --- a/src/idom/core/vdom.py +++ b/src/idom/core/vdom.py @@ -5,15 +5,21 @@ from __future__ import annotations +import logging from typing import Any, Iterable, List, Mapping, Optional, Sequence, Tuple, Union from fastjsonschema import compile as compile_json_schema from mypy_extensions import TypedDict from typing_extensions import Protocol +from idom.config import IDOM_DEBUG_MODE + from .events import EventHandler +logger = logging.getLogger() + + VDOM_JSON_SCHEMA = { "$schema": "http://json-schema.org/draft-07/schema", "$ref": "#/definitions/element", @@ -209,15 +215,59 @@ def coalesce_attributes_and_children( children_or_iterables: Sequence[Any] attributes, *children_or_iterables = values - if not isinstance(attributes, Mapping) or "tagName" in attributes: + if not _is_attributes(attributes): attributes = {} children_or_iterables = values children: List[Any] = [] for child in children_or_iterables: - if isinstance(child, (str, Mapping)) or not hasattr(child, "__iter__"): + if _is_single_child(child): children.append(child) else: children.extend(child) return attributes, children + + +def _is_attributes(value: Any) -> bool: + return isinstance(value, Mapping) and "tagName" not in value + + +if IDOM_DEBUG_MODE.current: + + _debug_is_attributes = _is_attributes + + def _is_attributes(value: Any) -> bool: + result = _debug_is_attributes(value) + if result and "children" in value: + logger.error(f"Reserved key 'children' found in attributes {value}") + return result + + +def _is_single_child(value: Any) -> bool: + return isinstance(value, (str, Mapping)) or not hasattr(value, "__iter__") + + +if IDOM_DEBUG_MODE.current: + + _debug_is_single_child = _is_single_child + + def _is_single_child(value: Any) -> bool: + if _debug_is_single_child(value): + return True + + from .component import AbstractComponent + + if hasattr(value, "__iter__") and not hasattr(value, "__len__"): + logger.error( + f"Did not verify key-path integrity of children in generator {value} " + "- pass a sequence (i.e. list of finite length) in order to verify" + ) + else: + for child in value: + if (isinstance(child, AbstractComponent) and child.key is None) or ( + isinstance(child, Mapping) and "key" not in child + ): + logger.error(f"Key not specified for dynamic child {child}") + + return False diff --git a/tests/test_core/test_vdom.py b/tests/test_core/test_vdom.py index cdb1f5ac7..d647a0fa5 100644 --- a/tests/test_core/test_vdom.py +++ b/tests/test_core/test_vdom.py @@ -2,6 +2,7 @@ from fastjsonschema import JsonSchemaException import idom +from idom.config import IDOM_DEBUG_MODE from idom.core.vdom import make_vdom_constructor, validate_vdom @@ -256,3 +257,40 @@ def test_valid_vdom(value): def test_invalid_vdom(value, error_message_pattern): with pytest.raises(JsonSchemaException, match=error_message_pattern): validate_vdom(value) + + +@pytest.mark.skipif(not IDOM_DEBUG_MODE.current, reason="Only logs in debug mode") +def test_debug_log_if_children_in_attributes(caplog): + idom.vdom("div", {"children": ["hello"]}) + assert len(caplog.records) == 1 + assert caplog.records[0].message.startswith( + "Reserved key 'children' found in attributes" + ) + caplog.records.clear() + + +@pytest.mark.skipif(not IDOM_DEBUG_MODE.current, reason="Only logs in debug mode") +def test_debug_log_cannot_verify_keypath_for_genereators(caplog): + idom.vdom("div", (1 for i in range(10))) + assert len(caplog.records) == 1 + assert caplog.records[0].message.startswith( + "Did not verify key-path integrity of children in generator" + ) + caplog.records.clear() + + +@pytest.mark.skipif(not IDOM_DEBUG_MODE.current, reason="Only logs in debug mode") +def test_debug_log_dynamic_children_must_have_keys(caplog): + idom.vdom("div", [idom.vdom("div")]) + assert len(caplog.records) == 1 + assert caplog.records[0].message.startswith("Key not specified for dynamic child") + + caplog.records.clear() + + @idom.component + def MyComponent(): + return idom.vdom("div") + + idom.vdom("div", [MyComponent()]) + assert len(caplog.records) == 1 + assert caplog.records[0].message.startswith("Key not specified for dynamic child")