Skip to content

Commit

Permalink
Change set_state comparison method (#1256)
Browse files Browse the repository at this point in the history
  • Loading branch information
Archmonger authored Jan 26, 2025
1 parent 754dc11 commit a54ce4e
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 34 deletions.
4 changes: 4 additions & 0 deletions docs/source/about/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 ``<data-table>`` element by calling ``html.data_table()``.
- :pull:`1256` - Change ``set_state`` comparison method to check equality with ``==`` more consistently.

**Removed**

Expand All @@ -34,6 +35,7 @@ Unreleased

v1.1.0
------
:octicon:`milestone` *released on 2024-11-24*

**Fixed**

Expand Down Expand Up @@ -69,6 +71,7 @@ v1.1.0

v1.0.2
------
:octicon:`milestone` *released on 2023-07-03*

**Fixed**

Expand All @@ -77,6 +80,7 @@ v1.0.2

v1.0.1
------
:octicon:`milestone` *released on 2023-06-16*

**Changed**

Expand Down
43 changes: 28 additions & 15 deletions src/reactpy/core/hooks.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -517,18 +518,30 @@ 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,
}
# Return early if the objects are not the same type
if type(x) is not type(y):
return False

# Compare the source code of lambda and local functions
if (
hasattr(x, "__qualname__")
and ("<lambda>" in x.__qualname__ or "<locals>" 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
28 changes: 25 additions & 3 deletions tests/test_core/test_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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",
Expand Down
37 changes: 21 additions & 16 deletions tests/test_testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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")
Expand Down

0 comments on commit a54ce4e

Please sign in to comment.