From 618f89c5179f3e57599cb58580b2f0b5f6b9b7f9 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Sat, 25 Jan 2025 22:25:30 -0800 Subject: [PATCH 1/6] Change `set_state` comparison method --- src/reactpy/core/hooks.py | 24 +++++++++--------------- tests/test_core/test_hooks.py | 6 +++--- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/src/reactpy/core/hooks.py b/src/reactpy/core/hooks.py index 0ece8cccf..381abe71e 100644 --- a/src/reactpy/core/hooks.py +++ b/src/reactpy/core/hooks.py @@ -1,6 +1,7 @@ from __future__ import annotations import asyncio +import contextlib from collections.abc import Coroutine, MutableMapping, Sequence from logging import getLogger from types import FunctionType @@ -517,18 +518,11 @@ def strictly_equal(x: Any, y: Any) -> bool: - ``bytearray`` - ``memoryview`` """ - return x is y or (type(x) in _NUMERIC_TEXT_BINARY_TYPES and x == y) - - -_NUMERIC_TEXT_BINARY_TYPES = { - # numeric - int, - float, - complex, - # text - str, - # binary types - bytes, - bytearray, - memoryview, -} + if type(x) is not type(y): + return False + + with contextlib.suppress(Exception): + if hasattr(x, "__eq__"): + return x == y + + return x is y diff --git a/tests/test_core/test_hooks.py b/tests/test_core/test_hooks.py index 550d35cbc..6d7162bed 100644 --- a/tests/test_core/test_hooks.py +++ b/tests/test_core/test_hooks.py @@ -159,7 +159,7 @@ def Counter(): await layout.render() -async def test_set_state_checks_identity_not_equality(display: DisplayFixture): +async def test_set_state_checks_equality_not_identity(display: DisplayFixture): r_1 = reactpy.Ref("value") r_2 = reactpy.Ref("value") @@ -219,12 +219,12 @@ def TestComponent(): await client_r_2_button.click() await poll_event_count.until_equals(2) - await poll_render_count.until_equals(2) + await poll_render_count.until_equals(1) await client_r_2_button.click() await poll_event_count.until_equals(3) - await poll_render_count.until_equals(2) + await poll_render_count.until_equals(1) async def test_simple_input_with_use_state(display: DisplayFixture): From 95d93bd8218252b1da2fb9f5663b90eef6ba8f7e Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Sat, 25 Jan 2025 22:31:42 -0800 Subject: [PATCH 2/6] mypy sucks big time --- src/reactpy/core/hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reactpy/core/hooks.py b/src/reactpy/core/hooks.py index 381abe71e..7169b9405 100644 --- a/src/reactpy/core/hooks.py +++ b/src/reactpy/core/hooks.py @@ -523,6 +523,6 @@ def strictly_equal(x: Any, y: Any) -> bool: with contextlib.suppress(Exception): if hasattr(x, "__eq__"): - return x == y + return x == y # type: ignore return x is y From 9812d9e360a7705ead0285d24a82bdc2f4fac0bf Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Sat, 25 Jan 2025 23:20:11 -0800 Subject: [PATCH 3/6] Compare the source code of lambda and local functions --- src/reactpy/core/hooks.py | 23 +++++++++++++++++++++-- tests/test_core/test_hooks.py | 22 ++++++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/reactpy/core/hooks.py b/src/reactpy/core/hooks.py index 7169b9405..5a3c9fd13 100644 --- a/src/reactpy/core/hooks.py +++ b/src/reactpy/core/hooks.py @@ -518,11 +518,30 @@ def strictly_equal(x: Any, y: Any) -> bool: - ``bytearray`` - ``memoryview`` """ + # Return early if the objects are not the same type if type(x) is not type(y): return False - with contextlib.suppress(Exception): - if hasattr(x, "__eq__"): + # Compare the source code of lambda and local functions + if ( + hasattr(x, "__qualname__") + and ("" in x.__qualname__ or "" in x.__qualname__) + and hasattr(x, "__code__") + ): + if x.__qualname__ != y.__qualname__: + return False + + return all( + getattr(x.__code__, attr) == getattr(y.__code__, attr) + for attr in dir(x.__code__) + if attr.startswith("co_") + and attr not in {"co_positions", "co_linetable", "co_lines"} + ) + + # Check via the `==` operator if possible + if hasattr(x, "__eq__"): + with contextlib.suppress(Exception): return x == y # type: ignore + # Fallback to identity check return x is y diff --git a/tests/test_core/test_hooks.py b/tests/test_core/test_hooks.py index 6d7162bed..1f444cb68 100644 --- a/tests/test_core/test_hooks.py +++ b/tests/test_core/test_hooks.py @@ -1172,6 +1172,28 @@ def test_strictly_equal(x, y, result): assert strictly_equal(x, y) is result +def test_strictly_equal_named_closures(): + assert strictly_equal(lambda: "text", lambda: "text") is True + assert strictly_equal(lambda: "text", lambda: "not-text") is False + + def x(): + return "text" + + def y(): + return "not-text" + + def generator(): + def z(): + return "text" + + return z + + assert strictly_equal(x, x) is True + assert strictly_equal(x, y) is False + assert strictly_equal(x, generator()) is False + assert strictly_equal(generator(), generator()) is True + + STRICT_EQUALITY_VALUE_CONSTRUCTORS = [ lambda: "string-text", lambda: b"byte-text", From 02f1a1a67b118e4840a6f33066eb3b37ee9b7aeb Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Sat, 25 Jan 2025 23:23:48 -0800 Subject: [PATCH 4/6] Add changelog --- docs/source/about/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/source/about/changelog.rst b/docs/source/about/changelog.rst index e7a0b6ccf..b37f3d979 100644 --- a/docs/source/about/changelog.rst +++ b/docs/source/about/changelog.rst @@ -20,6 +20,7 @@ Unreleased - :pull:`1251` - Substitute client-side usage of ``react`` with ``preact``. - :pull:`1239` - Script elements no longer support behaving like effects. They now strictly behave like plain HTML script elements. - :pull:`1255` - The ``reactpy.html`` module has been modified to allow for auto-creation of any HTML nodes. For example, you can create a ```` element by calling ``html.data_table()``. +- :pull:`1256` - Change ``set_state`` comparison method to check equality with ``==`` more consistently. **Removed** From 43bc16cde7d8585de3135a46095ed2c97aacbb3e Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Sat, 25 Jan 2025 23:28:29 -0800 Subject: [PATCH 5/6] Fix missing dates on the changelog --- docs/source/about/changelog.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/source/about/changelog.rst b/docs/source/about/changelog.rst index b37f3d979..a9ddbe854 100644 --- a/docs/source/about/changelog.rst +++ b/docs/source/about/changelog.rst @@ -35,6 +35,7 @@ Unreleased v1.1.0 ------ +:octicon:`milestone` *released on 2024-11-24* **Fixed** @@ -70,6 +71,7 @@ v1.1.0 v1.0.2 ------ +:octicon:`milestone` *released on 2023-07-03* **Fixed** @@ -78,6 +80,7 @@ v1.0.2 v1.0.1 ------ +:octicon:`milestone` *released on 2023-06-16* **Changed** From dab0ec38a0567ff5e05f0bef33510f073b1422c4 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Sat, 25 Jan 2025 23:43:03 -0800 Subject: [PATCH 6/6] Fix hotswap test --- tests/test_testing.py | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/tests/test_testing.py b/tests/test_testing.py index 63439c194..a6517abc0 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -173,7 +173,7 @@ def test_list_logged_excptions(): assert logged_errors == [the_error] -async def test_hostwap_update_on_change(display: DisplayFixture): +async def test_hotswap_update_on_change(display: DisplayFixture): """Ensure shared hotswapping works This basically means that previously rendered views of a hotswap component get updated @@ -183,34 +183,39 @@ async def test_hostwap_update_on_change(display: DisplayFixture): hotswap component to be updated """ - def make_next_count_constructor(count): - """We need to construct a new function so they're different when we set_state""" + def hotswap_1(): + return html.div({"id": "hotswap-1"}, 1) - def constructor(): - count.current += 1 - return html.div({"id": f"hotswap-{count.current}"}, count.current) + def hotswap_2(): + return html.div({"id": "hotswap-2"}, 2) - return constructor + def hotswap_3(): + return html.div({"id": "hotswap-3"}, 3) @component def ButtonSwapsDivs(): count = Ref(0) + mount, hostswap = _hotswap(update_on_change=True) async def on_click(event): - mount(make_next_count_constructor(count)) - - incr = html.button({"on_click": on_click, "id": "incr-button"}, "incr") - - mount, make_hostswap = _hotswap(update_on_change=True) - mount(make_next_count_constructor(count)) - hotswap_view = make_hostswap() - - return html.div(incr, hotswap_view) + count.set_current(count.current + 1) + if count.current == 1: + mount(hotswap_1) + if count.current == 2: + mount(hotswap_2) + if count.current == 3: + mount(hotswap_3) + + return html.div( + html.button({"on_click": on_click, "id": "incr-button"}, "incr"), + hostswap(), + ) await display.show(ButtonSwapsDivs) client_incr_button = await display.page.wait_for_selector("#incr-button") + await client_incr_button.click() await display.page.wait_for_selector("#hotswap-1") await client_incr_button.click() await display.page.wait_for_selector("#hotswap-2")