Osy#betaRun it on Osyrin: osyrin.com

Reference / Testing

Ui.HoldServer

Ui.HoldServer(); … Ui.ReleaseServer();

In a UI test, keeps the server's answers to the page's reads back until `Ui.ReleaseServer()`, so the test can stand on the screen a slow server leaves a person on — the page's `skeleton { }`, a "Loading…", a busy button — and then prove what fills in when the answers land.

stable1 example compiled by CItestinguiloading

Summary#

Ui.HoldServer() makes the server's answers to the page wait. The page still opens and paints — its frame, its skeleton { }, its static parts — but its rows and the results of its server calls do not arrive until the test says Ui.ReleaseServer(). In between, every assertion reads the page exactly as a person on a slow connection sees it.

Signature#

Ui.HoldServer();     // from now on, answers to the page's reads wait
Ui.ReleaseServer();  // let them through, and wait for the page to finish with them

Description#

Why a test needs it#

Every UI verb waits for the page to finish before it returns, which is what makes an assertion trustworthy — and is also why a loading state could never be tested: by the time Assert.Visible ran, the skeleton it meant to check had already given way to the data. Holding the server is how the moment in between becomes a screen a test can stand on.

What is held, and what is not#

Held until releasedNever held
a page's ROWS — the second half of the answer that opens a page, after its layoutthe page itself: its tree, its layout, the app's bundle and its controls
the answer to a server call — an on mount that reads a report, a live var that calls a function, an action's functiona live stream that stays open (a .Listen(), a subscription)
the answer to a query a page asks for later (a refetch, a search)

So a held page paints — that is the point — and stops where its data would have started.

How the other verbs behave while it is held#

  • Ui.Visit and a navigating Ui.Click return once the page has painted and is waiting, instead of waiting for data that cannot come. That includes a page inside a [Layout] that waits on its OWN reads — a live var server call with no skeleton { } holds its whole body, Outlet() included, until the answer lands — so the visit returns on the layout's stand-in and the page arrives in its outlet on release.
  • An action stays in flight. A button whose action is waiting on a held answer shows its busy state and save.Pending reads true (see Pending), so the in-flight state of an action is testable the same way.
  • An assertion does not wait out its bound for held work. Assert.Hidden("Walnut flat") answers at once: the row is not there, and nothing will put it there until the release.
  • It is sticky. It stays on across Ui.Visit until Ui.ReleaseServer(), as a slow network would.

Releasing#

Ui.ReleaseServer() lets every held answer through and then waits for the page as it does after a click: the reads land, the skeleton gives way, the actions that were waiting finish. Releasing when nothing is held is not an error.

⚠ Assert what the stand-in SAYS, not how it is built. A skeleton's grey blocks have no words; what a test can read is the frame the stand-in draws for real (a title, a card's heading) and a [[ui-skeleton#kit-parts|LoadingNote]]'s "Loading…". After the release, assert the content — and that the note is gone.

It runs the same in both renderers: osy test (in-process) and osy test --pixels (a real browser).

Examples#

[Role] enum DeskRole { Member }
[Principal] entity Keeper {
  [Required, Unique, MaxLength(200)] string Email;
  security { allow read when IsAuthenticated; }
}
[RoleGrant] entity DeskGrant { Keeper Grantee; [Required] DeskRole Level; }
policy IsMember => DeskGrant.Any(g => g.Grantee == user && g.Level == DeskRole.Member);

entity Comet {
  [Required, MaxLength(80)] string Name;
  security { allow read, create when IsMember; }
}

public sealed class SkyTally { public int Comets; }

SkyTally CountTheSky() { return new SkyTally { Comets = Comet.Count() }; }

[Route("/sky")] [Authorize(IsMember)] [Render(CSR)]
component SkyPage() {
  SkyTally tally = new SkyTally();
  on mount { tally = CountTheSky(); }

  skeleton { Stack(gap: 3) { Text("Tonight's sky"); Text("Loading…"); } }
  render   { Stack(gap: 3) { Text("Tonight's sky"); Text($"{tally.Comets} comets"); } }
}

[TestFixture]
void TwoComets() {
  var k = new Keeper { Email = "[email protected]" };
  new DeskGrant { Grantee = k, Level = DeskRole.Member };
  new Comet { Name = "Halley" };
  new Comet { Name = "Encke" };
}

principal Kim => Keeper.Single(k => k.Email == "[email protected]");

[Test(TwoComets)]
void the_sky_shows_its_shape_while_it_is_counted() {
  Ui.SignInAs(Kim);
  Ui.HoldServer();
  Ui.Visit("/sky");
  Assert.Visible("Loading…");
  Assert.Hidden("2 comets");

  Ui.ReleaseServer();
  Assert.Visible("2 comets");
  Assert.Hidden("Loading…");
}

See also#

  • skeleton — the stand-in a page draws while its first read is out, and the kit's parts for one
  • Pending — an action's busy state, which a held server keeps on
  • Ui — drive the app's UI from a test — the other UI verbs, and how an assertion waits for a settled screen
  • Ui.Tick — the same idea for a poll: make the moment happen when the test says so

Related

skeleton

A second render tree that stands in for a page or a component while its first read — its queries, its live server…

Pending

When a control's action waits on the server, the platform shows a busy spinner and disables the control — but only…

Ui — drive the app's UI from a test

Drive the real UI from a test: navigate to a route, click what a person would click, and assert on what the screen…

Ui.Tick

In a UI test, fires every `on every` clock armed on the screen once, now, and waits for their bodies — server calls…