diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f6eac70ca..d5fa75b7a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,11 +11,11 @@ on: - cron: "0 0 * * *" jobs: - python-coverage: + python-exhaustive: uses: ./.github/workflows/.nox-session.yml with: job-name: "python-{0}" - session-name: test_python_suite + session-name: test_python session-arguments: --maxfail=3 python-environments: uses: ./.github/workflows/.nox-session.yml diff --git a/noxfile.py b/noxfile.py index ac9fcf08f..a50ba1912 100644 --- a/noxfile.py +++ b/noxfile.py @@ -200,7 +200,7 @@ def test_python_types(session: Session) -> None: install_requirements_file(session, "check-types") install_requirements_file(session, "pkg-deps") install_requirements_file(session, "pkg-extras") - session.run("mypy", "--strict", "src/idom") + session.run("mypy", "--show-error-codes", "--strict", "src/idom") @nox.session diff --git a/src/idom/_option.py b/src/idom/_option.py index e0a303ac9..1367e6bd9 100644 --- a/src/idom/_option.py +++ b/src/idom/_option.py @@ -103,7 +103,7 @@ def __init__(self, new_opt: Option[_O], name: str) -> None: self._new_opt = new_opt @property # type: ignore - def _current(self) -> _O: # type: ignore + def _current(self) -> _O: warnings.warn( f"{self.name!r} has been renamed to {self._new_opt.name!r}", DeprecationWarning, diff --git a/src/idom/core/component.py b/src/idom/core/component.py index 5ef0f1844..0bf62c9a7 100644 --- a/src/idom/core/component.py +++ b/src/idom/core/component.py @@ -2,13 +2,13 @@ import inspect from functools import wraps -from typing import Any, Callable, Dict, Optional, Tuple, Union +from typing import Any, Callable, Dict, Optional, Tuple from .types import ComponentType, VdomDict def component( - function: Callable[..., Union[ComponentType, VdomDict | None]] + function: Callable[..., ComponentType | VdomDict | str | None] ) -> Callable[..., Component]: """A decorator for defining a new component. @@ -39,7 +39,7 @@ class Component: def __init__( self, - function: Callable[..., ComponentType | VdomDict | None], + function: Callable[..., ComponentType | VdomDict | str | None], key: Optional[Any], args: Tuple[Any, ...], kwargs: Dict[str, Any], @@ -51,7 +51,7 @@ def __init__( self._kwargs = kwargs self._sig = sig - def render(self) -> VdomDict | ComponentType | None: + def render(self) -> ComponentType | VdomDict | str | None: return self.type(*self._args, **self._kwargs) def should_render(self, new: Component) -> bool: diff --git a/src/idom/core/hooks.py b/src/idom/core/hooks.py index 45e7c7dd9..428f9a963 100644 --- a/src/idom/core/hooks.py +++ b/src/idom/core/hooks.py @@ -23,7 +23,6 @@ from typing_extensions import Protocol -from idom.config import IDOM_DEBUG_MODE from idom.utils import Ref from ._thread_local import ThreadLocal @@ -226,9 +225,6 @@ def use_debug_value( :func:`id` is different). By default these are inferred based on local variables that are referenced by the given function. """ - if not IDOM_DEBUG_MODE.current: - return # pragma: no cover - old: Ref[Any] = _use_const(lambda: Ref(object())) memo_func = message if callable(message) else lambda: message new = use_memo(memo_func, dependencies) @@ -541,7 +537,7 @@ def _try_to_infer_closure_values( else: return None else: - return cast("Sequence[Any] | None", values) + return values def current_hook() -> LifeCycleHook: diff --git a/src/idom/core/layout.py b/src/idom/core/layout.py index a7d21b87c..db861142d 100644 --- a/src/idom/core/layout.py +++ b/src/idom/core/layout.py @@ -234,7 +234,7 @@ def _render_component( old_parent_model = parent.model.current old_parent_children = old_parent_model["children"] parent.model.current = { - **old_parent_model, + **old_parent_model, # type: ignore[misc] "children": [ *old_parent_children[:index], new_state.model.current, diff --git a/src/idom/core/types.py b/src/idom/core/types.py index 3943a154b..a2b7cc902 100644 --- a/src/idom/core/types.py +++ b/src/idom/core/types.py @@ -44,7 +44,7 @@ class ComponentType(Protocol): This is used to see if two component instances share the same definition. """ - def render(self) -> VdomDict | ComponentType | None: + def render(self) -> VdomDict | ComponentType | str | None: """Render the component's view model.""" def should_render(self: _OwnType, new: _OwnType) -> bool: diff --git a/src/idom/testing/display.py b/src/idom/testing/display.py index 4a7608d32..5e315111e 100644 --- a/src/idom/testing/display.py +++ b/src/idom/testing/display.py @@ -12,7 +12,6 @@ async_playwright, ) -from idom import html from idom.config import IDOM_TESTING_DEFAULT_TIMEOUT from idom.types import RootComponentConstructor @@ -49,7 +48,7 @@ async def goto(self, path: str, query: Any | None = None) -> None: await self.page.goto(self.backend.url(path, query)) async def root_element(self) -> ElementHandle: - element = await self.page.wait_for_selector(f"#app", state="attached") + element = await self.page.wait_for_selector("#app", state="attached") if element is None: raise RuntimeError("Root element not attached") # pragma: no cover return element diff --git a/src/idom/utils.py b/src/idom/utils.py index ec114b2c3..e176da660 100644 --- a/src/idom/utils.py +++ b/src/idom/utils.py @@ -56,7 +56,9 @@ def __repr__(self) -> str: return f"{type(self).__name__}({current})" -def html_to_vdom(html: str, *transforms: _ModelTransform, strict: bool = True) -> VdomDict: +def html_to_vdom( + html: str, *transforms: _ModelTransform, strict: bool = True +) -> VdomDict: """Transform HTML into a DOM model. Unique keys can be provided to HTML elements using a ``key=...`` attribute within your HTML tag. @@ -82,7 +84,9 @@ def html_to_vdom(html: str, *transforms: _ModelTransform, strict: bool = True) - recover=not strict, ) try: - nodes: List = fragments_fromstring(html, no_leading_text=True, parser=parser) + nodes: list[etree._Element] = fragments_fromstring( + html, no_leading_text=True, parser=parser + ) except etree.XMLSyntaxError as e: if not strict: raise e # pragma: no cover @@ -139,10 +143,11 @@ def _etree_to_vdom( attributes = dict(node.items()) key = attributes.pop("key", None) + vdom: VdomDict if hasattr(idom.html, node.tag): vdom = getattr(idom.html, node.tag)(attributes, *children, key=key) else: - vdom: VdomDict = {"tagName": node.tag} + vdom = {"tagName": node.tag} if children: vdom["children"] = children if attributes: @@ -160,7 +165,7 @@ def _etree_to_vdom( return vdom -def _mutate_vdom(vdom: VdomDict): +def _mutate_vdom(vdom: VdomDict) -> None: """Performs any necessary mutations on the VDOM attributes to meet VDOM spec. Currently, this function only transforms the ``style`` attribute into a dictionary whose keys are @@ -216,5 +221,5 @@ def _hypen_to_camel_case(string: str) -> str: return first.lower() + remainder.title().replace("-", "") -class HTMLParseError(etree.LxmlSyntaxError): +class HTMLParseError(etree.LxmlSyntaxError): # type: ignore[misc] """Raised when an HTML document cannot be parsed using strict parsing.""" diff --git a/tests/test_core/test_layout.py b/tests/test_core/test_layout.py index 60d39e65e..42c9d00c3 100644 --- a/tests/test_core/test_layout.py +++ b/tests/test_core/test_layout.py @@ -20,7 +20,7 @@ capture_idom_logs, ) from idom.utils import Ref -from tests.tooling.asserts import assert_same_items +from tests.tooling.hooks import use_toggle @pytest.fixture(autouse=True) @@ -495,11 +495,6 @@ def SomeComponent(): ) -def use_toggle(init=False): - state, set_state = idom.hooks.use_state(init) - return state, lambda: set_state(lambda old: not old) - - async def test_model_key_preserves_callback_identity_for_common_elements(caplog): called_good_trigger = idom.Ref(False) good_handler = StaticEventHandler() @@ -818,13 +813,9 @@ async def test_elements_and_components_with_the_same_key_can_be_interchanged(): set_toggle = idom.Ref() effects = [] - def use_toggle(): - state, set_state = idom.hooks.use_state(True) - return state, lambda: set_state(not state) - @idom.component def Root(): - toggle, set_toggle.current = use_toggle() + toggle, set_toggle.current = use_toggle(True) if toggle: return SomeComponent("x") else: diff --git a/tests/test_html.py b/tests/test_html.py index d4fe685d8..269e9e5a7 100644 --- a/tests/test_html.py +++ b/tests/test_html.py @@ -1,18 +1,9 @@ import pytest -from idom import component, config, html, use_state +from idom import component, config, html from idom.testing import DisplayFixture, poll from idom.utils import Ref - - -def use_toggle(initial=True): - state, set_state = use_state(initial) - return state, lambda: set_state(not state) - - -def use_counter(initial_value): - state, set_state = use_state(initial_value) - return state, lambda: set_state(state + 1) +from tests.tooling.hooks import use_counter, use_toggle async def test_script_mount_unmount(display: DisplayFixture): @@ -20,7 +11,7 @@ async def test_script_mount_unmount(display: DisplayFixture): @component def Root(): - is_mounted, toggle_is_mounted.current = use_toggle() + is_mounted, toggle_is_mounted.current = use_toggle(True) return html.div( html.div({"id": "mount-state", "data-value": False}), HasScript() if is_mounted else html.div(), diff --git a/tests/tooling/hooks.py b/tests/tooling/hooks.py new file mode 100644 index 000000000..f6488af8a --- /dev/null +++ b/tests/tooling/hooks.py @@ -0,0 +1,11 @@ +from idom import use_state + + +def use_toggle(init=False): + state, set_state = use_state(init) + return state, lambda: set_state(lambda old: not old) + + +def use_counter(initial_value): + state, set_state = use_state(initial_value) + return state, lambda: set_state(state + 1)