# Ui.HoldServer

> 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.

<!-- id: testing-ui-hold-server · area: testing · stability: stable · html: https://osysharp.com/reference/testing/ui-hold-server/ -->

## Summary        {#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      {#signature}
```osy syntax
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    {#description}

### Why a test needs it   {#why}
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   {#what-is-held}
| Held until released | Never held |
|---|---|
| a page's ROWS — the second half of the answer that opens a page, after its layout | the 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 function | a 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   {#verbs-while-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](https://osysharp.com/reference/ui/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   {#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       {#examples}

```osy title="a report that shows its shape while it is read, then its numbers" test app=testing-ui-hold-server
[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 = "kim@observatory.test" };
  new DeskGrant { Grantee = k, Level = DeskRole.Member };
  new Comet { Name = "Halley" };
  new Comet { Name = "Encke" };
}

principal Kim => Keeper.Single(k => k.Email == "kim@observatory.test");

[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       {#see-also}
- [skeleton](https://osysharp.com/reference/ui/skeleton/) — the stand-in a page draws while its first read is out, and the kit's parts for one
- [Pending](https://osysharp.com/reference/ui/pending/) — an action's busy state, which a held server keeps on
- [Ui — drive the app's UI from a test](https://osysharp.com/reference/testing/ui/) — the other UI verbs, and how an assertion waits for a settled screen
- [Ui.Tick](https://osysharp.com/reference/testing/ui-tick/) — the same idea for a poll: make the moment happen when the test says so
