Skip to main content
Alpha

Testing Fresh pages

The @netscript/fresh testing export provides fixtures for exercising routes, loaders, and layouts without standing up an HTTP server. Reach for it when you want to unit-test a page's loader logic, assert on the context a route receives, or drive defer-region helpers with a controlled policy. The helpers build the same shape your route code reads at runtime, so a test can call your loader directly and inspect the result.

What the testing surface provides

The export centers on two factory functions:

  • createMockRouteContext() builds a minimal page context fixture for route, loader, and layout tests. It returns a MockRouteContext that mirrors the request URL, route params, parsed path and search values, layer data, named resources, and an abort signal — everything a loader or layout reads from its context.
  • createMockDeferPolicy() builds a defer policy fixture accepted by defer-region helpers, so tests that cover deferred or streaming partials can pin freshness and prewarm behavior.

Both factories take a plain options object, so a test composes only the fields it cares about and leaves the rest at their fixture defaults.

Testing a route loader

createMockRouteContext() accepts MockRouteContextOptions. Every field is optional: supply a url, params, state, parsed path and search values, a routePattern, named resources, an explicit req, or a signal. The returned MockRouteContext exposes those values as readonly properties, plus a resource(key) method that returns a single named resource and a nav fixture with makeHref().

import { assertEquals } from "@std/assert";
import { createMockRouteContext } from "@netscript/fresh/testing";

// The loader under test reads params and a named resource from its context.
const loadDashboard = (ctx: {
  path: { id: string };
  resource: (key: "metrics") => { read: () => number };
}) => ({
  id: ctx.path.id,
  total: ctx.resource("metrics").read(),
});

Deno.test("dashboard loader reads the path id and metrics resource", () => {
  const ctx = createMockRouteContext<
    Record<string, never>,
    { metrics: { read: () => number } },
    { id: string }
  >({
    url: "https://example.com/dashboards/42",
    routePattern: "/dashboards/:id",
    path: { id: "42" },
    resources: { metrics: { read: () => 128 } },
  });

  const result = loadDashboard(ctx);

  assertEquals(result.id, "42");
  assertEquals(result.total, 128);
  assertEquals(ctx.resource("metrics").read(), 128);
});

The fixture's url is a URL, req is a Request, params is a Record<string, string | undefined>, and signal is an AbortSignal. Tests that need to assert on cancellation can pass their own signal through the options and observe it on the returned context.

Testing layouts

Layout tests read layerData from the context, a Record<string, unknown> carrying the data a layout receives. Construct a fixture with the layer data your layout expects and pass the context to the layout under test.

import { createMockRouteContext } from "@netscript/fresh/testing";

const ctx = createMockRouteContext({
  url: "https://example.com/reports",
  routePattern: "/reports",
});

// `ctx.layerData` is the Record<string, unknown> a layout reads.
const layerData = ctx.layerData;

Defer policy fixtures

createMockDeferPolicy() accepts either a MockDeferPolicyProfile name or a MockDeferPolicyInput override object, and returns the resolved policy for use with defer-region helpers. The profile names are "balanced", "aggressive-first-paint", "background-refresh", and "low-bandwidth". The MockDeferPolicyInput form lets a test start from a named profile and then override individual fields: staleTimeMs for the freshness window, prewarmOnMiss and prewarmOnStale for cache-prewarm behavior, and clientRefreshOnFreshCache and skipClientWhenServerPrewarm for client refresh behavior.

import { createMockDeferPolicy } from "@netscript/fresh/testing";

// Start from a named profile.
const balanced = createMockDeferPolicy("balanced");

// Or override individual fields on top of a profile.
const tuned = createMockDeferPolicy({
  profile: "background-refresh",
  staleTimeMs: 5_000,
  prewarmOnStale: true,
  skipClientWhenServerPrewarm: true,
});

API summary

Symbol Kind Description
createMockRouteContext() function Create a minimal page context fixture for route, loader, and layout tests.
createMockDeferPolicy() function Create a defer policy fixture accepted by defer-region helpers.
MockRouteContext interface Mock route context returned by createMockRouteContext().
MockRouteContextOptions interface Options used to construct a route context fixture.
MockDeferPolicyInput interface Mock defer policy override accepted by defer test fixtures.
MockDeferPolicyProfile type Named policy profile: "balanced", "aggressive-first-paint", "background-refresh", "low-bandwidth".

MockRouteContext properties

Property Type Description
url URL Request URL for the fixture.
req Request Raw request object for the fixture.
params Record<string, string | undefined> Fresh route params for the fixture.
state TState Fresh state object for the fixture.
signal AbortSignal Request abort signal.
path TPath Parsed route path params.
search TSearch Parsed route search values.
layerData Record<string, unknown> Layer data available to layout tests.
routePattern string Route pattern represented by the fixture.
nav { makeHref(): string } Minimal route navigation fixture.
route unknown Optional route reference for tests that need routed context.
resources TResources Named resources available to route loaders.
resource(key) method Return a named resource from the fixture.

MockDeferPolicyInput properties

Property Type Description
profile MockDeferPolicyProfile Named policy profile to start from.
staleTimeMs number Override for the freshness window in milliseconds.
prewarmOnMiss boolean Prewarm the partial when the cache is missing.
prewarmOnStale boolean Prewarm the partial when the cache is stale.
clientRefreshOnFreshCache boolean Allow client refresh even when server cache is fresh.
skipClientWhenServerPrewarm boolean Skip client refresh when the server is already prewarming.