renderPage
Render a page in your test process through the worker's own pipeline, and inspect its status, HTML, props, cookies, redirect and cacheability.
ts
import { renderPage } from '@gio.js/core/testing';
const page = await renderPage('/posts/1');Reference
renderPage(path, options?). path is an absolute path, query string allowed (/posts?page=2).
| Option | Type | Default | Description |
|---|---|---|---|
appDir | string | GIO_APP_DIR, else ./app | The app/ directory, resolved from the working directory. |
method | 'GET' | 'HEAD' | 'GET' | Other methods throw - use callRoute to post to a page action. |
headers | Record<string, string> | - | Request headers; names are case-insensitive. host defaults to localhost. |
cookies | Record<string, string> | - | Sent as the Cookie header, after any cookie in headers. Values are sent as given. |
query | Record<string, string> | - | Merged over the query string in path. |
locale | string | '' | The locale [i18n] detection would have picked. No detection runs: for /fr/about, pass renderPage('/about', { locale: 'fr' }). |
Returns
A Promise<RenderPageResult>:
| Field | Type | Default | Description |
|---|---|---|---|
status | number | - | The status the worker answered with. |
headers | Record<string, string> | - | The worker's response headers, lowercase. The Rust server adds its own (security headers, Cache-Control, X-Gio-Cache) on top - those are not here. |
setCookies | string[] | - | Every Set-Cookie value, in order. |
html | string | - | The rendered document, with its hydration envelope and stylesheet links; '' for HEAD. |
props | Record<string, unknown> | null | - | The hydration props as serialized into the page. null for a redirect, a 404, an error, or props that are not JSON-serializable. |
cacheable | boolean | - | Whether the server would store the response in its shared page cache: revalidate set, no cookies sent, no credentials read. |
cacheMaxAge | number | - | Seconds it would be kept; 0 when not cacheable. |
cacheTags | string[] | - | The tags it would be stored under for revalidateTag(): export const tags plus the ones getServerSideProps returned, validated and de-duplicated. Empty when not cacheable. |
redirect | { destination: string; permanent: boolean } | undefined | - | Set for a 3xx answer; permanent for 301 and 308. |
error | { message: string; digest?: string; stack?: string } | undefined | - | Set when the render failed and no error.tsx answered (status 500). message is generic in production; stack is set in development only. |
Behavior
- On the first call per app directory it discovers your routes, layouts,
route.tshandlers,not-foundanderrorfiles, metadata routes andgio.config.tsplugins (running theironStartup), and loads the project's.envfiles - never over a variable already set. A route conflict fails the call like it fails the worker's boot. getServerSideProps, layouts,notFound(),redirect()anderror.tsxfiles answer exactly as on the server. Nothing is cached between calls.- Not applied, because the Rust server does them:
gio.tomlandmiddleware.tsrules and guards, CSRF checks, rate limits, security headers, the page cache, compression and locale detection. Test those with createTestServer. - The test runs in production mode unless
NODE_ENV=development(vitest setstest): error pages show only a digest, and the message is on thessr render failedlog line. - With no
GIO_SESSION_SECRETin the environment or a.envfile, discovery sets a random one for the test process, so session modules load.
Errors
- A
methodother thanGETorHEAD:TypeError. - A page that answers with an event stream:
TypeError- read it withcallRoute. - A path that does not start with
/, or one the server would refuse with400before any page sees it (a.or..segment, a stray%):TypeError. - An invalid cookie name, or a cookie value with
;, CR or LF:TypeError.
Examples
Props, tags and cacheability
For the cached, tagged /posts/[id] page from revalidateTag:
tests/posts.test.ts
import { expect, it } from 'vitest';
import { renderPage } from '@gio.js/core/testing';
it('renders a cached, tagged post', async () => {
const page = await renderPage('/posts/1');
expect(page.status).toBe(200);
expect(page.props).toEqual({ post: { id: '1', title: 'Hello', slug: 'hello' } });
expect(page.cacheable).toBe(true);
expect(page.cacheMaxAge).toBe(60);
expect(page.cacheTags).toEqual(['posts', 'post:1']);
});
it('404s an unknown post', async () => {
const page = await renderPage('/posts/999');
expect(page.status).toBe(404);
expect(page.props).toBeNull();
});A redirect and a cookie
For the /dashboard guard from redirect and the /welcome page from Cookie helpers:
ts
it('sends visitors without a session to /login', async () => {
const page = await renderPage('/dashboard');
expect(page.status).toBe(302);
expect(page.redirect).toEqual({ destination: '/login?next=%2Fdashboard', permanent: false });
});
it('greets a returning visitor', async () => {
const page = await renderPage('/welcome', { cookies: { seen: '1' } });
expect(page.props).toEqual({ firstVisit: false });
expect(page.cacheable).toBe(false); // it read and set cookies
});Good to know
- Modules are imported once per process. resetTestApp re-runs discovery for added or removed files, but an edit to an imported module shows up only in a new test process.
- Assert on
propsrather than on HTML text: React separates adjacent text with<!-- -->in server HTML. - CSS Modules render the server's class names under node:test; under vitest add gioVitest().
- No client bundles are built. The page's bootstrap script points at
TEST_ENTRY_SCRIPT(/_gio/testing/entry.js, also exported from@gio.js/core/testing) instead of a real chunk; the hydration envelope thatpropsis read from is the real one. - Server-only.
@gio.js/core/testingimports the server-only marker: a page or component that imports it has its client bundle refused.
Related
Version history
| Version | Changes |
|---|---|
v0.1.0-beta.8 | Introduced. |