Guide · Panelith 0.1.0 · plain HTML edition; the interactive edition has search and live examples
Testing an app
On this page 3 sections
Two layers, both cheap: Python tests against the App through FastAPI's test client, and a headless browser against the real shell for the things only a browser can tell you.
Python: the app through its API#
An App is an ordinary object. Build it, register a pane, send events, and assert on the actions that come back; the server mirrors state, so a second event sees what the first one did.
from fastapi.testclient import TestClient
from panelith.server import make_server
from myapp import MyApp
def test_bump():
c = TestClient(make_server(MyApp()))
c.get("/api/manifest") # the session cookie
out = c.post("/api/panes", json={"cid": "c1", "kind": "counter"}).json()
assert out["state"]["items"][0]["value"] == 0
out = c.post("/api/panes/c1/event", json={"name": "bump", "payload": {"by": 2}}).json()
assert out["actions"][0]["state"]["items"][0]["value"] == 2
A blocks kind is even easier: view(state) is a pure function returning dicts, so assert on the dict. panelith manifest module:App is the quick check that a kind, command or list is declared the way you meant.
The browser: Playwright against the real shell#
The framework's own suite (tests/e2e/ in the repository) serves an app on a spare port, drives headless Chromium through the command bar, and asserts no console errors on desktop and phone viewports in both colour schemes. The pattern is small enough to copy:
def open_command(page, title):
page.keyboard.press("Alt+Shift+Period")
page.wait_for_selector(".cmdbar-input")
page.fill(".cmdbar-input", "> " + title) # keep the sigil, or the list is empty
page.wait_for_selector(f".cmdbar-row:has-text('{title}')")
page.keyboard.press("Enter")
def test_about_opens(server, new_page):
page = new_page(viewport={"width": 1400, "height": 900})
page.goto(server.url + "/")
open_command(page, "About")
page.wait_for_selector(".pane:has-text('Hello')")
assert page.errors == []
Useful selectors: .pane[data-kind="<kind>"] for a pane by kind, [data-pl="topbar.list:<id>"] and the other spotlight hooks for shell controls, .widget-<name> for a widget body, .blk-* for blocks, .toast for a notice. A new_page fixture that records console errors and pageerror events onto page.errors turns "no red in the console" into an assertion.
Keeping docs honest#
These docs test themselves: every demo= id must exist, every relative link must resolve to a page and heading, every src= fence is filled from inspect.getsource so it cannot drift, and every public symbol must have a reference entry and a docstring. The same idea applies to any app with documentation of its own.