|
| 1 | +"""Integration test for ``VarData.module_code``. |
| 2 | +
|
| 3 | +A Var can declare module-level JS that the page compiler emits at the top of |
| 4 | +the page module — alongside ``custom_code`` from Components. When the Var's |
| 5 | +``_js_expr`` references that helper, it must be defined for the rendered |
| 6 | +output to be correct. |
| 7 | +
|
| 8 | +This exercises three facets in one app: |
| 9 | +
|
| 10 | +- A Var carrying ``module_code`` directly, used twice on the same page |
| 11 | + (deduplication doesn't break correctness). |
| 12 | +- Two distinct Vars with different helpers coexisting on a single page |
| 13 | + (merge preserves both snippets). |
| 14 | +- A Var whose ``module_code`` rides on a *hook's* VarData (the ``__init__`` |
| 15 | + hook-merge path on ``VarData`` propagates ``module_code`` up). |
| 16 | +""" |
| 17 | + |
| 18 | +from collections.abc import Generator |
| 19 | + |
| 20 | +import pytest |
| 21 | +from playwright.sync_api import Page, expect |
| 22 | + |
| 23 | +from reflex.testing import AppHarness |
| 24 | + |
| 25 | + |
| 26 | +def VarModuleCodeApp(): |
| 27 | + """App where Vars contribute module-level JS helpers.""" |
| 28 | + import reflex as rx |
| 29 | + from reflex.vars.base import Var, VarData |
| 30 | + |
| 31 | + greet_helper = "const greet = (name) => `Hello, ${name}!`;" |
| 32 | + pi_helper = "const PI_APPROX = 3.14;" |
| 33 | + counter_helper = "const fmtCount = (n) => `count=${n}`;" |
| 34 | + |
| 35 | + greeting = Var( |
| 36 | + _js_expr="greet('World')", |
| 37 | + _var_type=str, |
| 38 | + _var_data=VarData(module_code=(greet_helper,)), |
| 39 | + ) |
| 40 | + pi = Var( |
| 41 | + _js_expr="PI_APPROX", |
| 42 | + _var_type=str, |
| 43 | + _var_data=VarData(module_code=(pi_helper,)), |
| 44 | + ) |
| 45 | + counter = Var( |
| 46 | + _js_expr="fmtCount(0)", |
| 47 | + _var_type=str, |
| 48 | + _var_data=VarData( |
| 49 | + hooks={ |
| 50 | + "const _unused_counter = 0": VarData(module_code=(counter_helper,)), |
| 51 | + }, |
| 52 | + ), |
| 53 | + ) |
| 54 | + |
| 55 | + def basic(): |
| 56 | + return rx.box( |
| 57 | + rx.text(greeting, id="greeting"), |
| 58 | + rx.text(greeting, id="greeting-2"), |
| 59 | + ) |
| 60 | + |
| 61 | + def multi(): |
| 62 | + return rx.box( |
| 63 | + rx.text(greeting, id="greeting"), |
| 64 | + rx.text(pi, id="pi"), |
| 65 | + ) |
| 66 | + |
| 67 | + def hook(): |
| 68 | + return rx.box(rx.text(counter, id="counter")) |
| 69 | + |
| 70 | + app = rx.App() |
| 71 | + app.add_page(basic, route="/") |
| 72 | + app.add_page(multi, route="/multi") |
| 73 | + app.add_page(hook, route="/hook") |
| 74 | + |
| 75 | + |
| 76 | +@pytest.fixture(scope="module") |
| 77 | +def var_module_code_app( |
| 78 | + tmp_path_factory: pytest.TempPathFactory, |
| 79 | +) -> Generator[AppHarness, None, None]: |
| 80 | + """Run the var-module-code app under an AppHarness. |
| 81 | +
|
| 82 | + Args: |
| 83 | + tmp_path_factory: Pytest fixture for creating temporary directories. |
| 84 | +
|
| 85 | + Yields: |
| 86 | + The running harness. |
| 87 | + """ |
| 88 | + with AppHarness.create( |
| 89 | + root=tmp_path_factory.mktemp("var_module_code"), |
| 90 | + app_source=VarModuleCodeApp, |
| 91 | + ) as harness: |
| 92 | + yield harness |
| 93 | + |
| 94 | + |
| 95 | +def test_var_module_code_renders_helper_output( |
| 96 | + var_module_code_app: AppHarness, page: Page |
| 97 | +) -> None: |
| 98 | + """A Var whose ``_js_expr`` calls a ``module_code`` helper renders correctly. |
| 99 | +
|
| 100 | + Two usages of the same Var on one page must both resolve — proving the |
| 101 | + helper is emitted at module level and that deduplication does not drop it. |
| 102 | +
|
| 103 | + Args: |
| 104 | + var_module_code_app: Running app harness. |
| 105 | + page: Playwright page. |
| 106 | + """ |
| 107 | + assert var_module_code_app.frontend_url is not None |
| 108 | + page.goto(var_module_code_app.frontend_url) |
| 109 | + |
| 110 | + expect(page.locator("#greeting")).to_have_text("Hello, World!") |
| 111 | + expect(page.locator("#greeting-2")).to_have_text("Hello, World!") |
| 112 | + |
| 113 | + |
| 114 | +def test_var_module_code_multiple_distinct_helpers( |
| 115 | + var_module_code_app: AppHarness, page: Page |
| 116 | +) -> None: |
| 117 | + """Two distinct ``module_code`` Vars on one page each resolve their helper. |
| 118 | +
|
| 119 | + Args: |
| 120 | + var_module_code_app: Running app harness. |
| 121 | + page: Playwright page. |
| 122 | + """ |
| 123 | + assert var_module_code_app.frontend_url is not None |
| 124 | + page.goto(var_module_code_app.frontend_url + "multi") |
| 125 | + |
| 126 | + expect(page.locator("#greeting")).to_have_text("Hello, World!") |
| 127 | + expect(page.locator("#pi")).to_have_text("3.14") |
| 128 | + |
| 129 | + |
| 130 | +def test_var_module_code_via_hook_var_data( |
| 131 | + var_module_code_app: AppHarness, page: Page |
| 132 | +) -> None: |
| 133 | + """``module_code`` carried on a hook's VarData propagates to the page. |
| 134 | +
|
| 135 | + Constructing the outer ``VarData`` triggers the hook-merge fast-forward in |
| 136 | + ``VarData.__init__``, which must surface the inner ``module_code`` so the |
| 137 | + helper is emitted alongside the hook itself. |
| 138 | +
|
| 139 | + Args: |
| 140 | + var_module_code_app: Running app harness. |
| 141 | + page: Playwright page. |
| 142 | + """ |
| 143 | + assert var_module_code_app.frontend_url is not None |
| 144 | + page.goto(var_module_code_app.frontend_url + "hook") |
| 145 | + |
| 146 | + expect(page.locator("#counter")).to_have_text("count=0") |
0 commit comments