@kasperpeulen/vitest-plugin-nextjs 0.0.0-alpha.0 → 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (250) hide show
  1. package/README.md +249 -2
  2. package/dist/_virtual/_rolldown/runtime.js +14 -0
  3. package/dist/action-ids.d.ts +18 -0
  4. package/dist/action-ids.js +44 -0
  5. package/dist/adapter.d.ts +8 -0
  6. package/dist/adapter.js +12 -0
  7. package/dist/app-requests.d.ts +63 -0
  8. package/dist/app-requests.js +253 -0
  9. package/dist/browser-context.d.ts +24 -0
  10. package/dist/browser-context.js +157 -0
  11. package/dist/build-output.d.ts +68 -0
  12. package/dist/build-output.js +216 -0
  13. package/dist/build.d.ts +26 -0
  14. package/dist/build.js +39 -0
  15. package/dist/built-files.d.ts +9 -0
  16. package/dist/built-files.js +22 -0
  17. package/dist/cache.d.ts +44 -0
  18. package/dist/cache.js +99 -0
  19. package/dist/client-components.d.ts +3 -0
  20. package/dist/client-components.js +4 -0
  21. package/dist/client-file-key.d.ts +5 -0
  22. package/dist/client-file-key.js +5 -0
  23. package/dist/client-files.d.ts +83 -0
  24. package/dist/client-files.js +186 -0
  25. package/dist/client-graph.d.ts +94 -0
  26. package/dist/client-graph.js +266 -0
  27. package/dist/client-modules.d.ts +10 -0
  28. package/dist/client-modules.js +43 -0
  29. package/dist/client.d.ts +42 -0
  30. package/dist/client.js +263 -0
  31. package/dist/commonjs.d.ts +44 -0
  32. package/dist/commonjs.js +111 -0
  33. package/dist/compat/browser/client.d.ts +19 -0
  34. package/dist/compat/browser/client.js +13 -0
  35. package/dist/compat/build.d.ts +145 -0
  36. package/dist/compat/build.js +149 -0
  37. package/dist/compat/checks.d.ts +16 -0
  38. package/dist/compat/checks.js +148 -0
  39. package/dist/compat/patches.d.ts +13 -0
  40. package/dist/compat/patches.js +30 -0
  41. package/dist/compat/paths.d.ts +74 -0
  42. package/dist/compat/paths.js +160 -0
  43. package/dist/compat/rsc/handlers.d.ts +2 -0
  44. package/dist/compat/rsc/handlers.js +2 -0
  45. package/dist/compat/rsc/prerender.d.ts +10 -0
  46. package/dist/compat/rsc/prerender.js +9 -0
  47. package/dist/compat/server/headers.d.ts +3 -0
  48. package/dist/compat/server/headers.js +3 -0
  49. package/dist/compat/ssr/build-output.d.ts +4 -0
  50. package/dist/compat/ssr/build-output.js +3 -0
  51. package/dist/compat/ssr/cache.d.ts +6 -0
  52. package/dist/compat/ssr/cache.js +6 -0
  53. package/dist/compat/ssr/handler.d.ts +8 -0
  54. package/dist/compat/ssr/handler.js +8 -0
  55. package/dist/compat/ssr/node-server.d.ts +5 -0
  56. package/dist/compat/ssr/node-server.js +4 -0
  57. package/dist/compat/ssr/render-errors.d.ts +4 -0
  58. package/dist/compat/ssr/render-errors.js +4 -0
  59. package/dist/compat/ssr/senders.d.ts +6 -0
  60. package/dist/compat/ssr/senders.js +6 -0
  61. package/dist/compat/types.d.ts +8 -0
  62. package/dist/compat/types.js +1 -0
  63. package/dist/compile.d.ts +31 -0
  64. package/dist/compile.js +140 -0
  65. package/dist/disk.d.ts +16 -0
  66. package/dist/disk.js +13 -0
  67. package/dist/document-loads.d.ts +15 -0
  68. package/dist/document-loads.js +53 -0
  69. package/dist/document.d.ts +79 -0
  70. package/dist/document.js +328 -0
  71. package/dist/downloads.d.ts +15 -0
  72. package/dist/downloads.js +71 -0
  73. package/dist/esm-require.d.ts +11 -0
  74. package/dist/esm-require.js +32 -0
  75. package/dist/fast-crypto/index.js +1093 -0
  76. package/dist/fast-crypto.d.ts +20 -0
  77. package/dist/fast-crypto.js +151 -0
  78. package/dist/flight.d.ts +19 -0
  79. package/dist/flight.js +42 -0
  80. package/dist/form-submission.d.ts +15 -0
  81. package/dist/form-submission.js +27 -0
  82. package/dist/from-page.d.ts +5 -0
  83. package/dist/from-page.js +44 -0
  84. package/dist/globals.d.ts +1 -0
  85. package/dist/globals.js +197 -0
  86. package/dist/host-document.d.ts +26 -0
  87. package/dist/host-document.js +34 -0
  88. package/dist/index.d.ts +280 -0
  89. package/dist/index.js +281 -0
  90. package/dist/internal.d.ts +2 -0
  91. package/dist/internal.js +3 -0
  92. package/dist/leftovers.d.ts +11 -0
  93. package/dist/leftovers.js +55 -0
  94. package/dist/mode.d.ts +169 -0
  95. package/dist/mode.js +31 -0
  96. package/dist/node/LICENSES.md +3540 -0
  97. package/dist/node/assert/strict.js +29 -0
  98. package/dist/node/assert.js +3044 -0
  99. package/dist/node/buffer.js +1499 -0
  100. package/dist/node/child_process.js +45 -0
  101. package/dist/node/cluster.js +85 -0
  102. package/dist/node/console.js +96 -0
  103. package/dist/node/constants.js +495 -0
  104. package/dist/node/crypto.js +1388 -0
  105. package/dist/node/dgram.js +78 -0
  106. package/dist/node/diagnostics_channel.js +122 -0
  107. package/dist/node/dns/promises.js +129 -0
  108. package/dist/node/dns.js +137 -0
  109. package/dist/node/domain.js +43 -0
  110. package/dist/node/events.js +1155 -0
  111. package/dist/node/fs/promises.js +40 -0
  112. package/dist/node/fs.js +10375 -0
  113. package/dist/node/http.js +326 -0
  114. package/dist/node/http2.js +305 -0
  115. package/dist/node/https.js +57 -0
  116. package/dist/node/inspector/promises.js +66 -0
  117. package/dist/node/inspector.js +76 -0
  118. package/dist/node/module.js +205 -0
  119. package/dist/node/net.js +392 -0
  120. package/dist/node/os.js +259 -0
  121. package/dist/node/path/posix.js +25 -0
  122. package/dist/node/path/win32.js +25 -0
  123. package/dist/node/path.js +384 -0
  124. package/dist/node/perf_hooks.js +313 -0
  125. package/dist/node/process.js +452 -0
  126. package/dist/node/punycode.js +178 -0
  127. package/dist/node/querystring.js +934 -0
  128. package/dist/node/readline/promises.js +71 -0
  129. package/dist/node/readline.js +56 -0
  130. package/dist/node/repl.js +44 -0
  131. package/dist/node/sqlite.js +22 -0
  132. package/dist/node/stream/consumers.js +47 -0
  133. package/dist/node/stream/promises.js +6 -0
  134. package/dist/node/stream/web.js +52 -0
  135. package/dist/node/stream.js +4323 -0
  136. package/dist/node/string_decoder.js +276 -0
  137. package/dist/node/sys.js +6 -0
  138. package/dist/node/timers/promises.js +71 -0
  139. package/dist/node/timers.js +133 -0
  140. package/dist/node/tls.js +122 -0
  141. package/dist/node/trace_events.js +23 -0
  142. package/dist/node/tty.js +69 -0
  143. package/dist/node/url.js +1426 -0
  144. package/dist/node/util/types.js +1456 -0
  145. package/dist/node/util.js +4807 -0
  146. package/dist/node/v8.js +160 -0
  147. package/dist/node/vm.js +88 -0
  148. package/dist/node/wasi.js +16 -0
  149. package/dist/node/worker_threads.js +123 -0
  150. package/dist/node/zlib.js +1396 -0
  151. package/dist/node-builtins.d.ts +40 -0
  152. package/dist/node-builtins.js +74 -0
  153. package/dist/node-on-page.d.ts +46 -0
  154. package/dist/node-on-page.js +101 -0
  155. package/dist/node-platform.d.ts +36 -0
  156. package/dist/node-platform.js +152 -0
  157. package/dist/node-rendered.d.ts +21 -0
  158. package/dist/node-rendered.js +26 -0
  159. package/dist/node-server.d.ts +46 -0
  160. package/dist/node-server.js +193 -0
  161. package/dist/optimizer-cache-key.d.ts +26 -0
  162. package/dist/optimizer-cache-key.js +73 -0
  163. package/dist/own-package.d.ts +1 -0
  164. package/dist/own-package.js +6 -0
  165. package/dist/page-store.d.ts +33 -0
  166. package/dist/page-store.js +80 -0
  167. package/dist/page-timers.d.ts +22 -0
  168. package/dist/page-timers.js +47 -0
  169. package/dist/page.d.ts +112 -0
  170. package/dist/page.js +549 -0
  171. package/dist/paths.d.ts +13 -0
  172. package/dist/paths.js +47 -0
  173. package/dist/plugin.d.ts +22 -0
  174. package/dist/plugin.js +995 -0
  175. package/dist/prerender-verdict.d.ts +8 -0
  176. package/dist/prerender-verdict.js +67 -0
  177. package/dist/project/context.d.ts +546 -0
  178. package/dist/project/context.js +111 -0
  179. package/dist/project/entries.d.ts +42 -0
  180. package/dist/project/entries.js +189 -0
  181. package/dist/project/env-worker.d.ts +1 -0
  182. package/dist/project/env-worker.js +9 -0
  183. package/dist/project/env.d.ts +36 -0
  184. package/dist/project/env.js +82 -0
  185. package/dist/project/layers.d.ts +31 -0
  186. package/dist/project/layers.js +150 -0
  187. package/dist/project/loaders.d.ts +71 -0
  188. package/dist/project/loaders.js +300 -0
  189. package/dist/project/metadata.d.ts +8 -0
  190. package/dist/project/metadata.js +34 -0
  191. package/dist/project/module-exports.d.ts +29 -0
  192. package/dist/project/module-exports.js +77 -0
  193. package/dist/project/routes.d.ts +19 -0
  194. package/dist/project/routes.js +92 -0
  195. package/dist/project/routing.d.ts +46 -0
  196. package/dist/project/routing.js +280 -0
  197. package/dist/project/transform.d.ts +8 -0
  198. package/dist/project/transform.js +110 -0
  199. package/dist/project/webpack.d.ts +46 -0
  200. package/dist/project/webpack.js +160 -0
  201. package/dist/project.d.ts +264 -0
  202. package/dist/project.js +58 -0
  203. package/dist/registry-global.d.ts +5 -0
  204. package/dist/registry-global.js +5 -0
  205. package/dist/registry.d.ts +241 -0
  206. package/dist/registry.js +30 -0
  207. package/dist/route-chunks.d.ts +13 -0
  208. package/dist/route-chunks.js +32 -0
  209. package/dist/route-lookup.d.ts +34 -0
  210. package/dist/route-lookup.js +207 -0
  211. package/dist/routes-ahead-shared.d.ts +17 -0
  212. package/dist/routes-ahead-shared.js +9 -0
  213. package/dist/routes-ahead.d.ts +10 -0
  214. package/dist/routes-ahead.js +54 -0
  215. package/dist/rsc.d.ts +19 -0
  216. package/dist/rsc.js +171 -0
  217. package/dist/server-action.d.ts +9 -0
  218. package/dist/server-action.js +15 -0
  219. package/dist/server-code.d.ts +176 -0
  220. package/dist/server-code.js +357 -0
  221. package/dist/server-in-front.d.ts +37 -0
  222. package/dist/server-in-front.js +90 -0
  223. package/dist/server-layers.d.ts +22 -0
  224. package/dist/server-layers.js +33 -0
  225. package/dist/setup-browser.d.ts +1 -0
  226. package/dist/setup-browser.js +8 -0
  227. package/dist/setup-node.d.ts +1 -0
  228. package/dist/setup-node.js +6 -0
  229. package/dist/setup.d.ts +8 -0
  230. package/dist/setup.js +62 -0
  231. package/dist/speed-features.d.ts +13 -0
  232. package/dist/speed-features.js +95 -0
  233. package/dist/ssr.d.ts +70 -0
  234. package/dist/ssr.js +480 -0
  235. package/dist/static-build.d.ts +36 -0
  236. package/dist/static-build.js +92 -0
  237. package/dist/storage.d.ts +31 -0
  238. package/dist/storage.js +94 -0
  239. package/dist/styles-shared.d.ts +137 -0
  240. package/dist/styles-shared.js +124 -0
  241. package/dist/styles.d.ts +102 -0
  242. package/dist/styles.js +730 -0
  243. package/dist/stylesheet-versions.d.ts +17 -0
  244. package/dist/stylesheet-versions.js +43 -0
  245. package/dist/tester.html +10 -0
  246. package/dist/use-cache.d.ts +15 -0
  247. package/dist/use-cache.js +93 -0
  248. package/dist/vitest-version.d.ts +6 -0
  249. package/dist/vitest-version.js +12 -0
  250. package/package.json +125 -4
package/README.md CHANGED
@@ -1,3 +1,250 @@
1
- # @kasperpeulen/vitest-plugin-nextjs
1
+ # Storybook for Pages
2
2
 
3
- Placeholder. The first real release is `0.1.0-alpha.0`.
3
+ > End-to-end stories and tests of the pages of a framework app, with as little mocking as possible. Next.js first.
4
+
5
+ [![CI](https://github.com/storybookjs/storybook-for-pages/actions/workflows/ci.yml/badge.svg)](https://github.com/storybookjs/storybook-for-pages/actions/workflows/ci.yml)
6
+ [![License](https://img.shields.io/badge/license-MIT-blue)](https://github.com/storybookjs/storybook-for-pages/blob/main/LICENSE)
7
+ [![Node](https://img.shields.io/badge/node-%3E%3D24-339933?logo=node.js&logoColor=white)](https://nodejs.org/)
8
+ [![pnpm](https://img.shields.io/badge/pnpm-11-F69220?logo=pnpm&logoColor=white)](https://pnpm.io/)
9
+
10
+ A page of your app runs with the framework's own runtime: in a Vitest test, in `vite dev` and in a Storybook story. Next.js comes first. React Router, TanStack Start, SvelteKit and Nuxt come later, each as a Vite plugin with a Storybook framework on top.
11
+
12
+ | Package | What it is |
13
+ | ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
14
+ | [`@kasperpeulen/vitest-plugin-nextjs`](https://github.com/storybookjs/storybook-for-pages/blob/main/packages/vitest-plugin-nextjs) | The Next.js plugin for Vite: a page of a Next.js app in Vitest Browser Mode, `vite dev` and Storybook. |
15
+ | [`@kasperpeulen/storybook-nextjs-vite-rsc`](https://github.com/storybookjs/storybook-for-pages/blob/main/packages/storybook-nextjs-vite-rsc) | The Storybook framework on top of it: stories of whole pages and of Server Components. |
16
+ | [`@kasperpeulen/vitest-plugin-rsc`](https://github.com/storybookjs/storybook-for-pages/blob/main/packages/vitest-plugin-rsc) | The core, without a framework: React Server Components in one browser tab, server, Flight and hydration. |
17
+ | [`@kasperpeulen/dom-locators`](https://github.com/storybookjs/storybook-for-pages/blob/main/packages/dom-locators) | Vitest Browser Mode's page locators answered in the document, also in a DOM of Node.js. |
18
+
19
+ All of them are alphas: `npm install` without a tag gets the last one.
20
+
21
+ Write a test, render a page of your Next.js app, click around, check the database. No dev server, no deployment, no mocks of Next.js.
22
+
23
+ ```tsx
24
+ test("archive a note", async () => {
25
+ await signedInAs(testUser);
26
+ await db.insert(notes).values({ ownerId: testUser.id, title: "Inbox triage" });
27
+
28
+ await renderServer({ url: "/notes" });
29
+ await expect.element(page.getByText("Inbox triage")).toBeVisible();
30
+
31
+ await page.getByRole("button", { name: "Archive Inbox triage" }).click();
32
+ await expect.element(page.getByText("Inbox triage")).not.toBeInTheDocument();
33
+ });
34
+ ```
35
+
36
+ That test seeds a database, renders the `/notes` route on the server, hydrates it in a real browser, runs a Server Action and checks the result. All in one browser tab.
37
+
38
+ The trick: the plugin runs your server code _in the test's browser tab_. Next's own request handler renders the route, Next's own client hydrates it, and the `db` your test seeds is the very same module your Server Components read. In the notes demo, that database is Postgres compiled to WebAssembly.
39
+
40
+ ## Why
41
+
42
+ Server Components have been hard to test. Rendering one in a unit test has been [an open problem since 2023](https://github.com/testing-library/react-testing-library/issues/1209), so they've mostly been left to E2E tests. And E2E tests are slow, share a server and a database, and get flaky. That's why validation errors, roles, empty states, feature flags and time-dependent code usually go untested.
43
+
44
+ <p align="center">
45
+ <img src="https://raw.githubusercontent.com/storybookjs/storybook-for-pages/main/docs/assets/test-pyramid.svg" alt="Test pyramid: a small Playwright E2E layer above a wider Vitest unit, component, and integration layer" width="760" />
46
+ </p>
47
+
48
+ This plugin moves that coverage down to the fast part of the pyramid:
49
+
50
+ - **Everything lives in the tab.** The file system is in memory, the database is a [real engine in WebAssembly](https://github.com/storybookjs/storybook-for-pages/blob/main/docs/databases.md), and outgoing requests go to [MSW](https://mswjs.io/docs/recipes/vitest-browser-mode).
51
+ - **Test files run in parallel.** Every Vitest worker gets its own tab, with its own server, database, disk, cookies and storage.
52
+ - **Every test starts clean.** Cookies, storage, the disk and Next's cache are reset before each test.
53
+
54
+ That also makes it a nice loop for coding agents: edit, run, fix, repeat.
55
+
56
+ ### Is it really Next.js?
57
+
58
+ Yes. The plugin doesn't reimplement Next. It swaps the bundler for Vite and runs Next's runtime unchanged. To keep that honest, the repo runs Next's own e2e tests against the plugin: **402 of the 474 that apply pass (85%)** on `next@16.4.0`. Every failure is listed with a reason in the [conformance report](https://github.com/storybookjs/storybook-for-pages/blob/main/docs/internals/next-conformance.md). CI also tests against `next@latest` and `next@canary`.
59
+
60
+ ## Quick start
61
+
62
+ You need:
63
+
64
+ - Vite 8+ and Vitest 5.0.4+ in [Browser Mode](https://vitest.dev/guide/browser/) (the examples use Playwright).
65
+ - A Next.js App Router app on `next@16.4` or later.
66
+
67
+ ### 1. Install
68
+
69
+ ```bash
70
+ npm install -D @kasperpeulen/vitest-plugin-rsc @kasperpeulen/vitest-plugin-nextjs vitest @vitest/browser-playwright playwright @types/node@24
71
+ # Next's route resolution, pinned to your `next` version
72
+ npm install -D -E @next/routing@$(node -p "require('next/package.json').version")
73
+ npx playwright install chromium
74
+ ```
75
+
76
+ A few notes:
77
+
78
+ - The plugins are in **alpha**. The API can still change from one release to the next.
79
+ - `@kasperpeulen/vitest-plugin-nextjs` is built on `@kasperpeulen/vitest-plugin-rsc`, which it has as a peer dependency: install both, at the same release.
80
+ - `@next/routing` must match your `next` version exactly. Upgrade them together.
81
+ - Vitest 5 needs `@types/node` 22+, and create-next-app installs 20, hence the bump.
82
+
83
+ ### 2. Configure Vitest
84
+
85
+ ```ts
86
+ // vitest.config.mts
87
+ import { playwright } from "@vitest/browser-playwright";
88
+ import { defineConfig } from "vitest/config";
89
+ import { vitestPluginRSC } from "@kasperpeulen/vitest-plugin-rsc";
90
+ import { vitestPluginNext } from "@kasperpeulen/vitest-plugin-nextjs";
91
+
92
+ export default defineConfig({
93
+ plugins: [vitestPluginRSC(), vitestPluginNext()],
94
+ test: {
95
+ browser: {
96
+ enabled: true,
97
+ headless: true,
98
+ provider: playwright(),
99
+ instances: [{ browser: "chromium" }],
100
+ },
101
+ include: ["**/*.test.{ts,tsx}"],
102
+ isolate: false,
103
+ },
104
+ });
105
+ ```
106
+
107
+ - No setup file needed: the plugin cleans up around every test.
108
+ - `isolate: false` loads your app's server once per worker instead of once per file. It does mean the test files in a worker share modules, so put mocks in a [setup file](https://github.com/storybookjs/storybook-for-pages/blob/main/docs/nextjs-guide.md#mocks).
109
+ - `.mts` makes the config load as ESM, which matters if your `package.json` has no `"type": "module"`.
110
+ - `include` keeps Playwright's `*.spec.ts` files out.
111
+ - Add `.vitest/` to `.gitignore`. That's where screenshots of failed tests go.
112
+
113
+ ### 3. Write a test
114
+
115
+ A Server Component that reads a cookie and bumps it from a Server Action:
116
+
117
+ ```tsx
118
+ // app/likes.tsx
119
+ import { cookies } from "next/headers";
120
+
121
+ export async function Likes() {
122
+ const likes = Number((await cookies()).get("likes")?.value ?? 0);
123
+
124
+ return (
125
+ <form
126
+ action={async () => {
127
+ "use server";
128
+ (await cookies()).set("likes", String(likes + 1));
129
+ }}
130
+ >
131
+ <button>Likes: {likes}</button>
132
+ </form>
133
+ );
134
+ }
135
+ ```
136
+
137
+ And its test:
138
+
139
+ ```tsx
140
+ // app/likes.test.tsx
141
+ import { expect, test } from "vitest";
142
+ import { page } from "vitest/browser";
143
+ import { renderServer } from "@kasperpeulen/vitest-plugin-nextjs/testing-library";
144
+ import { Likes } from "./likes";
145
+
146
+ test("likes from a Server Action", async () => {
147
+ await renderServer(<Likes />, { headers: { cookie: "likes=41" } });
148
+
149
+ await page.getByRole("button", { name: "Likes: 41" }).click();
150
+ await expect.element(page.getByRole("button", { name: "Likes: 42" })).toBeVisible();
151
+ });
152
+
153
+ test("opens the home page", async () => {
154
+ const { response } = await renderServer({ url: "/" });
155
+
156
+ expect(response.status).toBe(200);
157
+ });
158
+ ```
159
+
160
+ The first test renders a single component, Testing Library style, but on Next's server. The click runs the Server Action, and Next renders again with the new cookie. The second test opens your home page like a browser would.
161
+
162
+ ### 4. Run it
163
+
164
+ ```bash
165
+ npx vitest
166
+ ```
167
+
168
+ Vitest starts Chromium and watches your files. Use `npx vitest run` in CI. The first test in each worker is a bit slower, because it loads Next's runtime.
169
+
170
+ ## What you can test
171
+
172
+ Pretty much everything Next does. Your files go through Next's own compiler, so `next/font`, `next/image`, `next/dynamic` and styled-jsx just work, and a mistake that would break `next build` breaks your test too. Routes, layouts, `loading.tsx`, error boundaries, redirects, cookies, Server Actions, route handlers, caching and `proxy.ts` all behave as they do in Next.
173
+
174
+ Head to the **[Next.js guide](https://github.com/storybookjs/storybook-for-pages/blob/main/docs/nextjs-guide.md)** for an example of each.
175
+
176
+ For a quicker feedback loop, `VITEST_PLUGIN_RSC_MODE=fast npx vitest` runs in [fast mode](https://github.com/storybookjs/storybook-for-pages/blob/main/docs/nextjs-guide.md#fast-mode): the pages of a test file render in one app, without HTML from the server, prefetching or page loads. Keep CI in fidelity mode, the default. [Fast testing patterns](https://github.com/storybookjs/storybook-for-pages/blob/main/docs/fast-testing-patterns.md) has what each test costs, and what else makes it cheaper.
177
+
178
+ ### Databases
179
+
180
+ Browsers have no TCP sockets, so the database runs in the tab: a real engine in WebAssembly. Your app keeps its ORM, queries and migrations. Tests mock just one module, `db.ts`.
181
+
182
+ | | Drizzle | Prisma | Kysely | SQL |
183
+ | -------------------------------- | ------- | ------ | ------ | --- |
184
+ | Postgres, on PGlite | ✅ | ✅ | ✅ | ✅ |
185
+ | SQLite, on `@libsql/client-wasm` | ✅ | ✅ | ✅ | ✅ |
186
+ | MySQL, on MariaDB in WebAssembly | ✅ | ✅ | ✅ | ✅ |
187
+
188
+ There's a Redis recipe for `ioredis` too. See [Databases](https://github.com/storybookjs/storybook-for-pages/blob/main/docs/databases.md).
189
+
190
+ ### Storybook
191
+
192
+ [`@kasperpeulen/storybook-nextjs-vite-rsc`](https://github.com/storybookjs/storybook-for-pages/blob/main/packages/storybook-nextjs-vite-rsc) brings the same thing to Storybook. A story can render a whole page of your app, with the same mocks as your tests. Even a static, deployed Storybook carries its database along, so forms really submit.
193
+
194
+ ### Without Next.js
195
+
196
+ `vitestPluginRSC()` on its own renders Server Components for any React app. See [RSC without Next.js](https://github.com/storybookjs/storybook-for-pages/blob/main/docs/rsc-without-nextjs.md).
197
+
198
+ ## Good to know
199
+
200
+ It's Next's real runtime, but in a browser tab. A few things follow from that:
201
+
202
+ - **No `test.concurrent`.** Tests in one file run one after another. Files still run in parallel.
203
+ - **No native addons or TCP sockets.** Use a [database recipe](https://github.com/storybookjs/storybook-for-pages/blob/main/docs/databases.md), a WebAssembly alternative, or MSW.
204
+ - **Development React by default.** A Server Component's error message reaches the client, where a deployment would send a digest. Use [`build: "production"`](https://github.com/storybookjs/storybook-for-pages/blob/main/docs/nextjs-guide.md#development-or-production) to match a deployment.
205
+ - **Some Next features aren't supported yet**, like `opengraph-image` image rendering, the React Compiler and most custom `webpack` config.
206
+
207
+ The full list is in [Limitations](https://github.com/storybookjs/storybook-for-pages/blob/main/docs/limitations.md).
208
+
209
+ ## Documentation
210
+
211
+ **Guides**
212
+
213
+ - [Testing a Next.js app](https://github.com/storybookjs/storybook-for-pages/blob/main/docs/nextjs-guide.md): the main guide.
214
+ - [Testing Client Components](https://github.com/storybookjs/storybook-for-pages/blob/main/docs/use-client-test-files.md) in a `"use client"` test file.
215
+ - [Databases](https://github.com/storybookjs/storybook-for-pages/blob/main/docs/databases.md): Postgres, SQLite, MySQL and Redis in the tab.
216
+ - [Fast testing patterns](https://github.com/storybookjs/storybook-for-pages/blob/main/docs/fast-testing-patterns.md): how to make a suite fast, and what each pattern costs and gives up.
217
+ - [Fast tests](https://github.com/storybookjs/storybook-for-pages/blob/main/docs/fast-tests.md): the measurements behind them, and the quickest feedback loop.
218
+ - [Node.js in the browser](https://github.com/storybookjs/storybook-for-pages/blob/main/docs/nodejs-in-the-browser.md): which Node APIs work, and the in-memory disk.
219
+ - [Static builds](https://github.com/storybookjs/storybook-for-pages/blob/main/docs/static-build.md): `vite build`, and how to serve the result.
220
+ - [RSC without Next.js](https://github.com/storybookjs/storybook-for-pages/blob/main/docs/rsc-without-nextjs.md).
221
+
222
+ **Reference**
223
+
224
+ - [API reference](https://github.com/storybookjs/storybook-for-pages/blob/main/docs/api.md)
225
+ - [Limitations](https://github.com/storybookjs/storybook-for-pages/blob/main/docs/limitations.md)
226
+
227
+ **Under the hood**
228
+
229
+ - [Internals](https://github.com/storybookjs/storybook-for-pages/blob/main/docs/internals/README.md): how it all works, the Next.js conformance suite, benchmarks and releasing. Detailed, and mostly for contributors.
230
+
231
+ ## Publishing
232
+
233
+ Release Please and `.github/workflows/release.yml` release the four packages, each with a version of its own: see [Releasing](https://github.com/storybookjs/storybook-for-pages/blob/main/docs/internals/releasing.md). A release goes to npm with npm's trusted publishing, an OIDC token of the workflow, without an npm token.
234
+
235
+ That is set up for all four packages:
236
+
237
+ - Each package exists on npm, from a first publish by hand.
238
+ - Each has a trusted publisher on npmjs.com: GitHub Actions, organization `storybookjs`, repository `storybook-for-pages`, workflow `release.yml`.
239
+
240
+ To check it for a package:
241
+
242
+ ```sh
243
+ npm trust list @kasperpeulen/vitest-plugin-nextjs
244
+ ```
245
+
246
+ npm makes no provenance for a package of a private repository, so the workflow publishes without `--provenance`.
247
+
248
+ ## Contributing
249
+
250
+ See [CONTRIBUTING.md](https://github.com/storybookjs/storybook-for-pages/blob/main/CONTRIBUTING.md).
@@ -0,0 +1,14 @@
1
+ import "node:module";
2
+ //#region \0rolldown/runtime.js
3
+ var __defProp = Object.defineProperty;
4
+ var __exportAll = (all, no_symbols) => {
5
+ let target = {};
6
+ for (var name in all) __defProp(target, name, {
7
+ get: all[name],
8
+ enumerable: true
9
+ });
10
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
11
+ return target;
12
+ };
13
+ //#endregion
14
+ export { __exportAll };
@@ -0,0 +1,18 @@
1
+ import { ServerRequest } from "./registry.js";
2
+
3
+ //#region src/action-ids.d.ts
4
+ /**
5
+ * The ids of the Server Actions that `request` names. Next's router names the
6
+ * one it calls in the `next-action` header. A form that the browser posts
7
+ * itself, as React renders one for a Server Action, to work before the app
8
+ * has hydrated or without JavaScript, names them in its fields, which Next's
9
+ * `decodeAction()` and `areAllActionIdsValid()` read: a field
10
+ * `$ACTION_ID_<id>`, or `$ACTION_REF_<n>` with the id in the field
11
+ * `$ACTION_<n>:0`, for an action with bound arguments.
12
+ *
13
+ * Next reads the whole body of such a request first, and so does this: from
14
+ * a copy, which leaves `request` a body of its own.
15
+ */
16
+ declare function actionIdsOf(request: ServerRequest): Promise<string[]>;
17
+ //#endregion
18
+ export { actionIdsOf };
@@ -0,0 +1,44 @@
1
+ import { registry } from "./registry.js";
2
+ //#region src/action-ids.ts
3
+ const actionIdField = "$ACTION_ID_";
4
+ const actionReferenceField = "$ACTION_REF_";
5
+ /**
6
+ * The ids of the Server Actions that `request` names. Next's router names the
7
+ * one it calls in the `next-action` header. A form that the browser posts
8
+ * itself, as React renders one for a Server Action, to work before the app
9
+ * has hydrated or without JavaScript, names them in its fields, which Next's
10
+ * `decodeAction()` and `areAllActionIdsValid()` read: a field
11
+ * `$ACTION_ID_<id>`, or `$ACTION_REF_<n>` with the id in the field
12
+ * `$ACTION_<n>:0`, for an action with bound arguments.
13
+ *
14
+ * Next reads the whole body of such a request first, and so does this: from
15
+ * a copy, which leaves `request` a body of its own.
16
+ */
17
+ async function actionIdsOf(request) {
18
+ if (request.method !== "POST") return [];
19
+ const header = request.headers.get("next-action");
20
+ if (header !== null) return [header];
21
+ const contentType = request.headers.get("content-type") ?? "";
22
+ if (!/^multipart\/form-data\b/i.test(contentType)) return [];
23
+ let { body } = request;
24
+ if (body instanceof ReadableStream) [body, request.body] = body.tee();
25
+ let form;
26
+ try {
27
+ form = await new registry.Response(body, { headers: { "content-type": contentType } }).formData();
28
+ } catch {
29
+ return [];
30
+ }
31
+ const ids = [];
32
+ for (const name of form.keys()) {
33
+ if (name.startsWith(actionIdField)) ids.push(name.slice(11));
34
+ if (!name.startsWith(actionReferenceField)) continue;
35
+ const reference = form.get(`$ACTION_${name.slice(12)}:0`);
36
+ try {
37
+ const { id } = JSON.parse(typeof reference === "string" ? reference : "");
38
+ if (typeof id === "string") ids.push(id);
39
+ } catch {}
40
+ }
41
+ return ids;
42
+ }
43
+ //#endregion
44
+ export { actionIdsOf };
@@ -0,0 +1,8 @@
1
+ import { NextAdapter } from "next";
2
+
3
+ //#region src/adapter.d.ts
4
+ /** Who waits for the outcome of a build, by the build directory it made up for it. */
5
+ declare const receivers: Map<any, any>;
6
+ declare const adapter: NextAdapter;
7
+ //#endregion
8
+ export { adapter as default, receivers };
@@ -0,0 +1,12 @@
1
+ //#region src/adapter.ts
2
+ const scope = globalThis;
3
+ /** Who waits for the outcome of a build, by the build directory it made up for it. */
4
+ const receivers = scope.__vitest_plugin_rsc_next_adapter__ ??= /* @__PURE__ */ new Map();
5
+ const adapter = {
6
+ name: "vitest-plugin-rsc",
7
+ onBuildComplete(context) {
8
+ receivers.get(context.distDir)?.(context);
9
+ }
10
+ };
11
+ //#endregion
12
+ export { adapter as default, receivers };
@@ -0,0 +1,63 @@
1
+ import { Opened } from "./registry.js";
2
+ import { PageStore } from "./page-store.js";
3
+
4
+ //#region src/app-requests.d.ts
5
+ /** Says which page of the app is open, by its signal, which aborts as it is left. */
6
+ declare function setOpenPage(signal: AbortSignal | undefined): void;
7
+ /** Says whether the router of the pages outlives them, from now on. */
8
+ declare function setRouterOutlivesPage(outlives: boolean): void;
9
+ declare function leftTheApp(url: URL): Error;
10
+ declare function unlessAborted<T>(answer: Promise<T>, signal: AbortSignal, abandon: (answer: Promise<T>) => void): Promise<T>;
11
+ type Sending<Passed = never> = {
12
+ /**
13
+ * The store of the page, as it was when the request started: what it sends
14
+ * besides its own headers, and where the cookies of its responses go. None
15
+ * for a request the server makes to itself while it handles another. That
16
+ * one carries the headers it was given, cookies included, and does not wait
17
+ * for the request it is part of.
18
+ */
19
+ from: PageStore | undefined;
20
+ /**
21
+ * Whether it sends the cookies of the store and keeps the ones it gets: not
22
+ * with `credentials: "omit"`. A `cookie` header of its own it sends either way.
23
+ */
24
+ credentials?: boolean;
25
+ /**
26
+ * A page load, which leaves the app when it is redirected to another
27
+ * origin: one that the test opens, or one of the app's own, sent while the
28
+ * page before it is still open (see `navigate()` in page.ts). That one
29
+ * opens nothing of what the test opened, also after a redirect, and
30
+ * starts no files of its route, which leaving that page would take back.
31
+ * What it opens is its `opened`, after a redirect too: from a page of fast
32
+ * mode's kept app, a page without HTML.
33
+ */
34
+ navigation?: "test" | "app"; /** Who answers a request that the server has nothing for: see `HandleOptions.unrouted`. */
35
+ network?: () => Promise<Response | NoInfer<Passed>>; /** What the request opens, not after a redirect: see `HandleOptions.opened`. */
36
+ opened?: Opened;
37
+ };
38
+ declare function sendRequest<Passed extends undefined = never>(request: Request, {
39
+ from,
40
+ credentials,
41
+ navigation,
42
+ network,
43
+ opened
44
+ }: Sending<Passed>): Promise<Response | NoInfer<Passed>>;
45
+ type Network<Passed> = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response | Passed>;
46
+ /**
47
+ * The browser's `fetch` while a browser context of the app is open: what
48
+ * Next's client router and Client Components call. `store()` is the store of
49
+ * the context, and `network()` the `fetch` the document had before it, which
50
+ * gets every request that is not the app's, or, where the DOM hands over the
51
+ * page's requests, one that answers none, for the DOM to send. Without a
52
+ * context, `network()` gets every request: code that took this `fetch` in a
53
+ * context has the document's own after it.
54
+ *
55
+ * Next marks the requests it sends for a page, with an `rsc` or a
56
+ * `next-action` header. They belong to the page that is open as they are
57
+ * sent. Its router is there only once its page is, so without a page that
58
+ * page was left, and it sends nothing more. A test asks with
59
+ * `handleRequest()`.
60
+ */
61
+ declare function pageFetch<Passed extends undefined = never>(store: () => PageStore | undefined, network: () => Network<NoInfer<Passed>>): Network<Passed>;
62
+ //#endregion
63
+ export { Sending, leftTheApp, pageFetch, sendRequest, setOpenPage, setRouterOutlivesPage, unlessAborted };