From 745adf967d5d64615747717d99ce8aa128a49bcb Mon Sep 17 00:00:00 2001 From: elvis kahoro Date: Sat, 24 Oct 2026 13:10:51 +0000 Subject: [PATCH] fix(parser): Guard SOURCE branch against _render_install error return render_token unpacked _render_install's result unconditionally, crashing with TypeError when it returned an error paragraph. Return the error component instead. Co-Authored-By: Claude Sonnet 5.5 --- native/engine/parser.py | 4 +- tests/test_parser_source_token.py | 65 +++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 tests/test_parser_source_token.py diff --git a/native/engine/parser.py b/native/engine/parser.py index 47454178..520ae226 100644 --- a/native/engine/parser.py +++ b/native/engine/parser.py @@ -128,9 +128,9 @@ def render_token(cmd: str, raw_arg: str) -> rx.Component: if "source" in cmd: from native.templates._source import source - name, files = _render_install(raw_arg) + result = _render_install(name) - return source(files) + return result if not isinstance(result, tuple) else source(result[1]) entry = _LIVE_REGISTRY.get(name) diff --git a/tests/test_parser_source_token.py b/tests/test_parser_source_token.py new file mode 100644 index 00000000..3bcdcd6e --- /dev/null +++ b/tests/test_parser_source_token.py @@ -0,0 +1,65 @@ +# noqa: INP001 +"""Tests for the SOURCE branch of `native.engine.parser.render_token`. + +Regression coverage for a crash where a `--SOURCE(name)--` token naming a +component not present in `COMPONENT_REGISTRY` (or whose dependency files +could not be read) made `render_token` raise +`TypeError: cannot unpack non-iterable P object`, aborting `reflex run` / +`reflex export` at app-import time. + +Root cause: `_render_install` has a dual return type — a `(name, files)` +tuple on success and an `rx.el.p(...)` Component on either failure path +(empty dependency set, or OSError while reading a dependency file). The +SOURCE branch unconditionally unpacked the return value without checking +the type, so any failure path crashed the build. +""" + +from __future__ import annotations + +import pytest # pyrefly: ignore[missing-import] +import reflex as rx # pyrefly: ignore[missing-import] + +from native.engine.parser import render_token + +UNKNOWN_COMPONENT = "definitely_not_a_real_component_xyz" +KNOWN_COMPONENT = "button" + + +@pytest.mark.parametrize("bad_name", [UNKNOWN_COMPONENT, "buton"]) # pyright: ignore[reportUntypedFunctionDecorator] +def test_source_token_unknown_component_does_not_raise(bad_name: str) -> None: + """A SOURCE token for a name not in COMPONENT_REGISTRY (a typo or + genuinely missing component) must return a renderable error Component + instead of raising TypeError and aborting the docs build. + """ + result = render_token("source", bad_name) + assert isinstance(result, rx.Component) + assert "Installation payload empty" in repr(result) + + +def test_source_token_known_component_renders_source_block() -> None: + """Regression: a valid SOURCE token still renders the source-code / + dependency block, not the error paragraph. + """ + result = render_token("source", KNOWN_COMPONENT) + assert isinstance(result, rx.Component) + assert "Source Code & Dependencies" in repr(result) + assert "Installation payload empty" not in repr(result) + + +def test_source_token_oserror_failure_path(monkeypatch: pytest.MonkeyPatch) -> None: + """The OSError path of `_render_install` (a registered component's + dependency file becomes unreadable on disk) must also return an error + Component instead of crashing the SOURCE branch — same root cause, + different trigger than the unknown-name path. + """ + import native.engine.parser as parser_mod + + def _raise_oserror(_self: object, *_args: object, **_kwargs: object) -> str: + msg = "simulated unreadable file (test)" + raise OSError(msg) + + monkeypatch.setattr(parser_mod.Path, "read_text", _raise_oserror) + + result = render_token("source", KNOWN_COMPONENT) + assert isinstance(result, rx.Component) + assert "Error reading file dependency path" in repr(result)