Using the engine from plain Python¶
The whole pipeline also runs headlessly — that's how you unit-test components without a browser:
from pyweft import load_component, Renderer, diff
App = load_component("myapp/app.weft")
app = App()
renderer = Renderer(app)
tree = renderer.render() # virtual DOM (Element/Text nodes)
# find a handler id on a rendered element and fire it, as the browser would
button = tree[0].children[1]
renderer.dispatch(button.events["click"], {})
new_tree = renderer.render()
patches = diff(tree, new_tree) # JSON-serialisable patch ops
assert app.cheers == 1
Effects and on_after_render run when the host reports that a render is on
the page, so a test that exercises them calls renderer.after_render() after
each render. It returns True when an effect changed state; render again and
repeat until it returns False.
Components with async handlers or an async on_init need an event loop, so
test them inside asyncio.run. Call renderer.init_root() to run the root's
on_init (child components get theirs automatically), and
await renderer.idle() to wait for every pending handler and task:
import asyncio
async def main():
app = App()
renderer = Renderer(app)
renderer.init_root()
tree = renderer.render() # "Loading…"
renderer.dispatch(tree[0].events["click"], {})
await renderer.idle() # the handler has finished
assert not app.loading
new_tree = renderer.render() # the finished state
asyncio.run(main())
run_in_background calls run on a thread in tests, through the same
lookup-by-name and pickling as the browser's worker, so a function or
argument the worker could not handle fails in the test too.
Errors in async handlers have no caller to raise to; set
renderer.on_error to a function to receive them, as the browser does to
show them in the overlay.
To see changes made outside a handler, set renderer.on_invalidate to a
function; it is called whenever state_has_changed() runs and no render is
already on its way. The browser uses it to schedule a render; a test can use
it to assert that one was requested, and renderer.needs_render() reports
whether any component is waiting to be redrawn.
examples/demo.py walks the full render → event → diff → apply cycle in the
terminal, tests/test_compose.py shows the patterns for testing composed
components, tests/test_effects.py the patterns for effects, and
tests/test_async.py the patterns for async handlers and tasks.