async def test_duplicate_sibling_keys_causes_error(caplog): hook = HookCatcher() should_error = True @idom.component @hook.capture def ComponentReturnsDuplicateKeys(): if should_error: return idom.html.div( idom.html.div(key="duplicate"), idom.html.div(key="duplicate"), ) else: return idom.html.div() async with idom.Layout(ComponentReturnsDuplicateKeys()) as layout: with assert_idom_did_log( error_type=ValueError, match_error=r"Duplicate keys \['duplicate'\] at '/children/0'", ): await layout.render() hook.latest.schedule_render() should_error = False await layout.render() should_error = True hook.latest.schedule_render() with assert_idom_did_log( error_type=ValueError, match_error=r"Duplicate keys \['duplicate'\] at '/children/0'", ): await layout.render()
async def test_use_debug_mode_with_factory(): set_message = idom.Ref() component_hook = HookCatcher() @idom.component @component_hook.capture def SomeComponent(): message, set_message.current = idom.use_state("hello") idom.use_debug_value(lambda: f"message is {message!r}") return idom.html.div() async with idom.Layout(SomeComponent()) as layout: with assert_idom_did_log(r"SomeComponent\(.*?\) message is 'hello'"): await layout.render() set_message.current("bye") with assert_idom_did_log(r"SomeComponent\(.*?\) message is 'bye'"): await layout.render() component_hook.latest.schedule_render() with assert_idom_did_not_log(r"SomeComponent\(.*?\) message is 'bye'"): await layout.render()
def test_assert_idom_logged_error(): with testing.assert_idom_did_log( match_message="log message", error_type=ValueError, match_error="my value error", ): try: raise ValueError("my value error") except ValueError: ROOT_LOGGER.exception("log message") with pytest.raises( AssertionError, match=r"Could not find a log record matching the given", ): with testing.assert_idom_did_log( match_message="log message", error_type=ValueError, match_error="my value error", ): try: # change error type raise RuntimeError("my value error") except RuntimeError: ROOT_LOGGER.exception("log message") with pytest.raises( AssertionError, match=r"Could not find a log record matching the given", ): with testing.assert_idom_did_log( match_message="log message", error_type=ValueError, match_error="my value error", ): try: # change error message raise ValueError("something else") except ValueError: ROOT_LOGGER.exception("log message") with pytest.raises( AssertionError, match=r"Could not find a log record matching the given", ): with testing.assert_idom_did_log( match_message="log message", error_type=ValueError, match_error="my value error", ): try: # change error message raise ValueError("my error message") except ValueError: ROOT_LOGGER.exception("something else")
async def test_layout_render_error_has_partial_update_with_error_message(): @idom.component def Main(): return idom.html.div([OkChild(), BadChild(), OkChild()]) @idom.component def OkChild(): return idom.html.div(["hello"]) @idom.component def BadChild(): raise ValueError("error from bad child") with assert_idom_did_log(match_error="error from bad child"): async with idom.Layout(Main()) as layout: patch = await render_json_patch(layout) assert_same_items( patch.changes, [ { "op": "add", "path": "/children", "value": [{ "tagName": "div", "children": [ { "tagName": "", "children": [{ "tagName": "div", "children": ["hello"] }], }, { "tagName": "", "error": "ValueError: error from bad child", }, { "tagName": "", "children": [{ "tagName": "div", "children": ["hello"] }], }, ], }], }, { "op": "add", "path": "/tagName", "value": "" }, ], )
def test_bad_schedule_render_callback(): def bad_callback(): raise ValueError("something went wrong") with assert_idom_did_log( match_message=f"Failed to schedule render via {bad_callback}"): LifeCycleHook(bad_callback).schedule_render()
async def test_error_in_effect_pre_unmount_cleanup_is_gracefully_handled(): set_key = idom.Ref() @idom.component def OuterComponent(): key, set_key.current = idom.use_state("first") return ComponentWithEffect(key=key) @idom.component def ComponentWithEffect(): @idom.hooks.use_effect def ok_effect(): def bad_cleanup(): raise ValueError("Something went wong :(") return bad_cleanup return idom.html.div() with assert_idom_did_log( match_message=r"Pre-unmount effect .*? failed", error_type=ValueError, ): async with idom.Layout(OuterComponent()) as layout: await layout.render() set_key.current("second") await layout.render() # no error
def test_assert_idom_logged_ignores_level(): original_level = ROOT_LOGGER.level ROOT_LOGGER.setLevel(logging.INFO) try: with testing.assert_idom_did_log(match_message=r".*"): # this log would normally be ignored ROOT_LOGGER.debug("my message") finally: ROOT_LOGGER.setLevel(original_level)
def test_web_module_from_file_replace_existing(tmp_path): file1 = tmp_path / "temp1.js" file1.touch() idom.web.module_from_file("temp", file1) file2 = tmp_path / "temp2.js" file2.write_text("something") with assert_idom_did_log(r"Existing web module .* will be replaced with"): idom.web.module_from_file("temp", file2)
def test_assert_idom_logged_assertion_error_message(): with pytest.raises( AssertionError, match=r"Could not find a log record matching the given", ): with testing.assert_idom_did_log( # put in all possible params full assertion error message match_message=r".*", error_type=Exception, match_error=r".*", ): pass
async def test_error_in_effect_is_gracefully_handled(caplog): @idom.component def ComponentWithEffect(): @idom.hooks.use_effect def bad_effect(): raise ValueError("Something went wong :(") return idom.html.div() with assert_idom_did_log( match_message=r"Layout post-render effect .* failed"): async with idom.Layout(ComponentWithEffect()) as layout: await layout.render() # no error
def test_web_module_from_file_symlink_twice(tmp_path): file_1 = tmp_path / "temp_1.js" file_1.touch() idom.web.module_from_file("temp", file_1, symlink=True) with assert_idom_did_not_log( r"Existing web module .* will be replaced with"): idom.web.module_from_file("temp", file_1, symlink=True) file_2 = tmp_path / "temp_2.js" file_2.write_text("something") with assert_idom_did_log(r"Existing web module .* will be replaced with"): idom.web.module_from_file("temp", file_2, symlink=True)
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)
def test_module_from_file_source_conflict(tmp_path): first_file = tmp_path / "first.js" with pytest.raises(FileNotFoundError, match="does not exist"): idom.web.module_from_file("temp", first_file) first_file.touch() idom.web.module_from_file("temp", first_file) second_file = tmp_path / "second.js" second_file.touch() # ok, same content idom.web.module_from_file("temp", second_file) third_file = tmp_path / "third.js" third_file.write_text("something-different") with assert_idom_did_log(r"Existing web module .* will be replaced with"): idom.web.module_from_file("temp", third_file)
async def test_component_error_in_should_render_is_handled_gracefully(): root_hook = HookCatcher() @idom.component @root_hook.capture def Root(): def bad_should_render(new): raise ValueError("The error message") return ComponentShouldRender(html.div(), should_render=bad_should_render) with assert_idom_did_log( match_message= r".* component failed to check if .* should be rendered", error_type=ValueError, match_error="The error message", ): async with idom.Layout(Root()) as layout: await layout.render() root_hook.latest.schedule_render() await layout.render()
async def test_error_in_effect_cleanup_is_gracefully_handled(caplog): caplog.clear() component_hook = HookCatcher() @idom.component @component_hook.capture def ComponentWithEffect(): @idom.hooks.use_effect(dependencies=None ) # force this to run every time def ok_effect(): def bad_cleanup(): raise ValueError("Something went wong :(") return bad_cleanup return idom.html.div() with assert_idom_did_log( match_error=r"Layout post-render effect .* failed"): async with idom.Layout(ComponentWithEffect()) as layout: await layout.render() component_hook.latest.schedule_render() await layout.render() # no error
async def test_error_in_effect_cleanup_is_gracefully_handled(): component_hook = HookCatcher() @idom.component @component_hook.capture def ComponentWithEffect(): hook = current_hook() def bad_effect(): raise ValueError("The error message") hook.add_effect(COMPONENT_DID_RENDER_EFFECT, bad_effect) return idom.html.div() with assert_idom_did_log( match_message="Component post-render effect .*? failed", error_type=ValueError, match_error="The error message", ): async with idom.Layout(ComponentWithEffect()) as layout: await layout.render() component_hook.latest.schedule_render() await layout.render() # no error
async def test_schedule_render_from_unmounted_hook(): parent_set_state = idom.Ref() @idom.component def Parent(): state, parent_set_state.current = idom.hooks.use_state(1) return Child(key=state, state=state) child_hook = HookCatcher() @idom.component @child_hook.capture def Child(state): idom.hooks.use_effect(lambda: lambda: print("unmount", state)) return idom.html.div(state) with assert_idom_did_log( r"Did not render component with model state ID .*? - component already unmounted", ): async with idom.Layout(Parent()) as layout: await layout.render() old_hook = child_hook.latest # cause initial child to be unmounted parent_set_state.current(2) await layout.render() # trigger render for hook that's been unmounted old_hook.schedule_render() # schedule one more render just to make it so `layout.render()` doesn't hang # when the scheduled render above gets skipped parent_set_state.current(3) await layout.render()
def test_module_from_string(): idom.web.module_from_string("temp", "old") with assert_idom_did_log(r"Existing web module .* will be replaced with"): idom.web.module_from_string("temp", "new")
def test_assert_idom_logged_does_not_supress_errors(): with pytest.raises(RuntimeError, match="expected error"): with testing.assert_idom_did_log(): raise RuntimeError("expected error")
def test_log_on_unknown_export_type(): with assert_idom_did_log(match_message="Unknown export type "): assert resolve_module_exports_from_source( "export something unknown;", exclude_default=False) == (set(), set())
def test_assert_idom_logged_message(): with testing.assert_idom_did_log(match_message="my message"): ROOT_LOGGER.info("my message") with testing.assert_idom_did_log(match_message=r".*"): ROOT_LOGGER.info("my message")