Exemple #1
0
async def test_model_key_preserves_callback_identity_for_common_elements(
        caplog):
    called_good_trigger = idom.Ref(False)
    good_handler = StaticEventHandler()
    bad_handler = StaticEventHandler()

    @idom.component
    def MyComponent():
        reverse_children, set_reverse_children = use_toggle()

        @good_handler.use
        def good_trigger():
            called_good_trigger.current = True
            set_reverse_children()

        @bad_handler.use
        def bad_trigger():
            raise ValueError("Called bad trigger")

        children = [
            idom.html.button({
                "onClick": good_trigger,
                "id": "good"
            },
                             "good",
                             key="good"),
            idom.html.button({
                "onClick": bad_trigger,
                "id": "bad"
            },
                             "bad",
                             key="bad"),
        ]

        if reverse_children:
            children.reverse()

        return idom.html.div(children)

    async with idom.Layout(MyComponent()) as layout:
        await layout.render()
        for i in range(3):
            event = LayoutEvent(good_handler.target, [])
            await layout.deliver(event)

            assert called_good_trigger.current
            # reset after checking
            called_good_trigger.current = False

            await layout.render()

    assert not caplog.records
Exemple #2
0
async def test_model_key_preserves_callback_identity_for_components():
    called_good_trigger = idom.Ref(False)
    good_handler = StaticEventHandler()
    bad_handler = StaticEventHandler()

    @idom.component
    def RootComponent():
        reverse_children, set_reverse_children = use_toggle()

        children = [
            Trigger(set_reverse_children, name=name, key=name)
            for name in ["good", "bad"]
        ]

        if reverse_children:
            children.reverse()

        return idom.html.div(children)

    @idom.component
    def Trigger(set_reverse_children, name):
        if name == "good":

            @good_handler.use
            def callback():
                called_good_trigger.current = True
                set_reverse_children()

        else:

            @bad_handler.use
            def callback():
                raise ValueError("Called bad trigger")

        return idom.html.button({"onClick": callback, "id": "good"}, "good")

    async with idom.Layout(RootComponent()) as layout:
        await layout.render()
        for _ in range(3):
            event = LayoutEvent(good_handler.target, [])
            await layout.deliver(event)

            assert called_good_trigger.current
            # reset after checking
            called_good_trigger.current = False

            await layout.render()
Exemple #3
0
async def test_element_keys_inside_components_do_not_reset_state_of_component(
):
    """This is a regression test for a bug.

    You would not expect that calling `set_child_key_num` would trigger state to be
    reset in any `Child()` components but there was a bug where that happened.
    """

    effect_calls_without_state = []
    set_child_key_num = StaticEventHandler()
    did_call_effect = asyncio.Event()

    @component
    def Parent():
        state, set_state = use_state(0)
        return html.div(
            html.button(
                {
                    "onClick":
                    set_child_key_num.use(lambda: set_state(state + 1))
                },
                "click me",
            ),
            Child("some-key"),
            Child(f"key-{state}"),
        )

    @component
    def Child(child_key):
        state, set_state = use_state(0)

        @use_effect
        async def record_if_state_is_reset():
            if state:
                return
            effect_calls_without_state.append(child_key)
            set_state(1)
            did_call_effect.set()

        return html.div(
            child_key,
            key=child_key,
        )

    async with idom.Layout(Parent()) as layout:
        await layout.render()
        await did_call_effect.wait()
        assert effect_calls_without_state == ["some-key", "key-0"]
        did_call_effect.clear()

        for i in range(1, 5):
            await layout.deliver(LayoutEvent(set_child_key_num.target, []))
            await layout.render()
            assert effect_calls_without_state == ["some-key", "key-0"]
            did_call_effect.clear()
Exemple #4
0
async def test_dispatcher_handles_more_than_one_event_at_a_time():
    block_and_never_set = asyncio.Event()
    will_block = asyncio.Event()
    second_event_did_execute = asyncio.Event()

    blocked_handler = StaticEventHandler()
    non_blocked_handler = StaticEventHandler()

    @idom.component
    def ComponentWithTwoEventHandlers():
        @blocked_handler.use
        async def block_forever():
            will_block.set()
            await block_and_never_set.wait()

        @non_blocked_handler.use
        async def handle_event():
            second_event_did_execute.set()

        return idom.html.div(
            idom.html.button({"onClick": block_forever}),
            idom.html.button({"onClick": handle_event}),
        )

    send_queue = asyncio.Queue()
    recv_queue = asyncio.Queue()

    asyncio.ensure_future(
        serve_json_patch(
            idom.Layout(ComponentWithTwoEventHandlers()),
            send_queue.put,
            recv_queue.get,
        ))

    await recv_queue.put(LayoutEvent(blocked_handler.target, []))
    await will_block.wait()

    await recv_queue.put(LayoutEvent(non_blocked_handler.target, []))
    await second_event_did_execute.wait()
Exemple #5
0
async def test_switching_node_type_with_event_handlers():
    toggle_type = idom.Ref()
    element_static_handler = StaticEventHandler()
    component_static_handler = StaticEventHandler()

    @idom.component
    def Root():
        toggle, toggle_type.current = use_toggle(True)
        handler = element_static_handler.use(lambda: None)
        if toggle:
            return html.div(html.button({"onEvent": handler}))
        else:
            return html.div(SomeComponent())

    @idom.component
    def SomeComponent():
        handler = component_static_handler.use(lambda: None)
        return html.button({"onAnotherEvent": handler})

    async with idom.Layout(Root()) as layout:
        await layout.render()

        assert element_static_handler.target in layout._event_handlers
        assert component_static_handler.target not in layout._event_handlers

        toggle_type.current()
        await layout.render()

        assert element_static_handler.target not in layout._event_handlers
        assert component_static_handler.target in layout._event_handlers

        toggle_type.current()
        await layout.render()

        assert element_static_handler.target in layout._event_handlers
        assert component_static_handler.target not in layout._event_handlers
Exemple #6
0
async def test_log_error_on_bad_event_handler():
    bad_handler = StaticEventHandler()

    @idom.component
    def ComponentWithBadEventHandler():
        @bad_handler.use
        def raise_error():
            raise Exception("bad event handler")

        return idom.html.button({"onClick": raise_error})

    with assert_idom_did_log(match_error="bad event handler"):

        async with idom.Layout(ComponentWithBadEventHandler()) as layout:
            await layout.render()
            event = LayoutEvent(bad_handler.target, [])
            await layout.deliver(event)
Exemple #7
0
async def test_changing_event_handlers_in_the_next_render():
    set_event_name = Ref()
    event_handler = StaticEventHandler()
    did_trigger = Ref(False)

    @component
    def Root():
        event_name, set_event_name.current = use_state("first")
        return html.button({
            event_name:
            event_handler.use(lambda: did_trigger.set_current(True))
        })

    async with Layout(Root()) as layout:
        await layout.render()
        await layout.deliver(LayoutEvent(event_handler.target, []))
        assert did_trigger.current
        did_trigger.current = False

        set_event_name.current("second")
        await layout.render()
        await layout.deliver(LayoutEvent(event_handler.target, []))
        assert did_trigger.current
        did_trigger.current = False
Exemple #8
0
import asyncio
from typing import Any, Sequence

import idom
from idom.core.layout import Layout, LayoutEvent, LayoutUpdate
from idom.core.serve import VdomJsonPatch, serve_json_patch
from idom.testing import StaticEventHandler

EVENT_NAME = "onEvent"
STATIC_EVENT_HANDLER = StaticEventHandler()


def test_vdom_json_patch_create_from_apply_to():
    update = LayoutUpdate("", {"a": 1, "b": [1]}, {"a": 2, "b": [1, 2]})
    patch = VdomJsonPatch.create_from(update)
    result = patch.apply_to({"a": 1, "b": [1]})
    assert result == {"a": 2, "b": [1, 2]}


def make_send_recv_callbacks(events_to_inject):
    changes = []

    # We need a semaphor here to simulate recieving an event after each update is sent.
    # The effect is that the send() and recv() callbacks trade off control. If we did
    # not do this, it would easy to determine when to halt because, while we might have
    # received all the events, they might not have been sent since the two callbacks are
    # executed in separate loops.
    sem = asyncio.Semaphore(0)

    async def send(patch):
        changes.append(patch)