Client test benches

Oda's source ships two benches for testing its interface: a slot bench that renders one part of the interface in isolation, and a whole-client bench that starts the web interface's client plugin set with every call to the host side answered by a mock you script. Both are developer tooling. To choose between them and the other test tiers, see Testing tiers.

The slot bench

The slot bench assembles the real rendering machinery around test-owned doubles: the app's slot renderer, test-owned session and workspace services and a file-upload stub. It exists to prove one feature's interface in isolation.

The whole-client bench

The whole-client bench starts the interface the way the app starts it. It loads the client plugin set's modules in the test process and never fetches bundles, replaces the row that owns the connection with a mock transport so every remote call is answered by your mock, and boots through the same boot the app uses.

Start takes these options, and repeated start calls share one promise, so asking twice boots one client:

OptionDefaultWhat it changes
mountfalsetrue mounts the interface into a fresh element in the page body; pass your own element to place it yourself.
awaitConnectedtruefalse returns as soon as the client is up, without waiting for the connection.
connectTimeoutMs5000The readiness budget: how long the start waits for the connection before failing.

Which client plugins it starts

The whole-client bench reads the web interface's plugin set from the bundles the app composes, in order: the base layer first, then the layer that adds the web application. The set is composed the way the app composes it, so a bundle change is visible at the next import; nothing is copied.

The loading model behind the set is on How client plugins load.

What the mock answers at startup

The mock is loaded with answers for the calls the interface makes while it starts, so a fresh client boots and renders with no sessions, no workspaces and default settings. A spec layers its own answers on top of this table.

While starting, the client asks forThe mock answers
Its chat listAn empty list.
The default workspace setupSuccess, with first-use initialisation ineligible.
Its settings descriptionWritable, with no document and no namespaces.
The model catalogueA default model entry.
Its preset listEmpty.
The account stateSigned out.
The follow streamDeclared, so a spec that forgets to script it sees a stream miss rather than a unary one.

One stream is built into the mock itself: the events stream, which answers every opening with one ready frame that names the client and a host, then stays open. That frame is what lets the assembled client reach connected. Endpoints the startup never touches stay absent, so a new call fails loudly instead of answering nothing.

Scripting the mock

Failures you will meet

Limits

Questions, answered

Why does mounting a feature report a missing service?

The bench checks the services a feature declares before it mounts, so a missing one is named instead of leaving the mount suspended. Provide the service on the bench first, or stub it. To test what a delayed service does, mount directly on the context.

Why does rendering a slot key refuse until I declare it?

Rendering goes through the bench's frame, which must declare, and thereby authorize, the keys it will render. Declare the key first, or declare a custom frame for your own structure. A bench has one declaration.

Why does the store lookup refuse before I render the root?

The host face a store belongs to exists only inside the installed renderer, so resolve the store after rendering a root. It also refuses for a key with no registration and for an entry that declares no store.

Why is file upload refused in my test?

The bench provides a stub whose upload function rejects every call until a suite replaces it. Replace the upload function in tests that exercise uploads.

Why did the whole-client start fail with a connection-state message?

The start waited for the connection and the wait ran out, 5 seconds by default. The message names the connection's state, the budget, the calls that found no answer and the streams still open, so the usual fix is a rule for a call the startup makes; otherwise give the wait a longer budget.

Why does teardown report unmatched requests?

A call found no rule while the client ran. Teardown checks for unanswered calls last, on purpose, so cleanup still happened; add the missing rule or expect the call. The report names each request's mode and the registered endpoints.

How do I keep a stream under the test's control?

Declare the endpoint as a stream and register a script for its openings. The stream stays open after the script returns until the test ends or fails it; pushing, ending and failing report how many open streams changed; and the wait helpers let you wait for an opening or for a consumer to catch up.

Which client plugins does the whole-client bench start?

The web interface's set, composed from its bundles the same way the app composes it: enabled rows whose package targets the web platform, with a group switched off disabling everything beneath it. The set differs from the desktop one; How client plugins load covers the loading model.

Can two whole-client clients run in the same test process?

Yes. Each start loads its own modules and binds its own mock, so two clients keep separate mocks; the page globals the bench adds are reference counted and go away when the last client disposes.

What is the difference between clearing the mock and resetting it?

Clearing keeps the registered answers and queued replies, so a later call still gets them. Resetting drops overrides and restores the initial implementation, which reads the latest registered answer.