@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
@@ -0,0 +1,280 @@
1
+ import { JSXElementConstructor, ReactNode } from "react";
2
+
3
+ //#region src/index.d.ts
4
+ /**
5
+ * Sends a request to the Next.js server of the app, like `fetch` from a page
6
+ * of the app would. Use it to assert on a response itself: its status, its
7
+ * headers, its HTML or Flight body.
8
+ *
9
+ * The request carries the app's cookies and the `headers` of what
10
+ * `renderServer()` opened, under its own. A `cookie` header is sent instead
11
+ * of the app's cookies, with this request and the redirects it follows. The
12
+ * cookies the server sets are the app's from then on:
13
+ * `response.headers.getSetCookie()` has them, where the `fetch` of the page
14
+ * does not show them.
15
+ * One to the pathname of what `renderServer()` opened is that page's, as a
16
+ * `fetch` of the page is: it gets the route of the node, and skips the proxy
17
+ * if the page does.
18
+ *
19
+ * Ask the server yourself with this, not with `fetch`: a `fetch` with Next's
20
+ * `rsc` or `next-action` header is Next's. It is the page's that is open as
21
+ * it is sent, and it never settles once that page is left, or without one.
22
+ */
23
+ declare function handleRequest(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
24
+ type RunInServerActionOptions = {
25
+ /**
26
+ * The URL of the page the action is of. Defaults to `/`. For this request
27
+ * its pathname gets a route of its own that renders nothing, with the params
28
+ * the app's route for it has.
29
+ */
30
+ url?: string;
31
+ /**
32
+ * Whether the server in front of the app takes the request: `proxy.ts`, and
33
+ * the `redirects`, `rewrites` and `headers` of `next.config`. Defaults to
34
+ * `false`, as for a node. A redirect there rejects: the action did not run.
35
+ */
36
+ proxy?: boolean;
37
+ /**
38
+ * Headers for the request of the action, next to the ones a browser sends.
39
+ * The ones that make it a Server Action are the plugin's: `accept`,
40
+ * `content-type` and `next-action`. A `cookie` header is sent instead of
41
+ * the app's cookies.
42
+ */
43
+ headers?: HeadersInit;
44
+ };
45
+ /**
46
+ * Runs `action` as a Server Action of the page at `url`, the way Next runs
47
+ * one: in the request of the action, where `cookies()` can be set and
48
+ * `redirect()`, `refresh()` and `after()` work. Resolves with what it
49
+ * returns, and rejects with what it throws, also the error of a `redirect()`.
50
+ * Its arguments and its result are the test's own, not sent through Flight.
51
+ *
52
+ * It sends a Server Action request like the one of Next's router, to a route
53
+ * at `url` that renders nothing, and opens no page: there is no app to start.
54
+ * After a `redirect()` no page renders: open the page the action redirects
55
+ * to in the test. A page that is open stays open. The request carries the app's cookies, the `headers` of
56
+ * what `renderServer()` opened and the ones passed here, and the cookies the
57
+ * action sets are the app's from then on. For code that a Server Action
58
+ * calls; test a form of a page through the page.
59
+ *
60
+ * The function runs in the request, so it cannot send a request to the app
61
+ * itself, which would wait for this one: call the app's code instead.
62
+ */
63
+ declare function runInServerAction<T>(action: () => T, options?: RunInServerActionOptions): Promise<Awaited<T>>;
64
+ type RequestOptions = {
65
+ /** The URL to open. Defaults to `/`. */url?: string;
66
+ /**
67
+ * Headers for the requests of the browser to the app, next to the ones a
68
+ * browser sends: the request of the document, and every one after it, like
69
+ * a Server Action, a `router.refresh()`, a navigation or a `fetch`. Until
70
+ * the test opens something else, or ends. A request keeps the headers it
71
+ * sets itself. An `accept` header is for the document alone. A `cookie`
72
+ * header sets those cookies for the app, as a `Set-Cookie` would: they go
73
+ * with the document and the requests after it, until the server or a
74
+ * script of the page changes them, or the test ends.
75
+ */
76
+ headers?: HeadersInit;
77
+ /**
78
+ * Stops the render when it aborts, as it stops a `fetch`: the page load
79
+ * ends, the page is left, and `renderServer()` rejects at once with the
80
+ * signal's reason, an `AbortError` unless it has another. Leaving goes on
81
+ * after that, and the next `renderServer()` or `cleanup()` waits for it.
82
+ * Once `renderServer()` has resolved, the page stays: leave it with
83
+ * `unmount()` or `cleanup()`. An aborted signal opens nothing, and leaves
84
+ * nothing either.
85
+ */
86
+ signal?: AbortSignal;
87
+ };
88
+ type RenderServerOptions = RequestOptions & {
89
+ /**
90
+ * Whether the server in front of the app takes the request: `proxy.ts`, and
91
+ * the `redirects`, `rewrites` and `headers` of `next.config`. Defaults to
92
+ * `true`. With `false` the URL goes straight to the app's route for its
93
+ * pathname, and so do the requests the page sends there while it is open:
94
+ * Server Actions and `router.refresh()`. A navigation to another route goes
95
+ * through it, as in the app.
96
+ */
97
+ proxy?: boolean;
98
+ /**
99
+ * Whether the layouts of the app render around the page. Defaults to
100
+ * `true`, and `false` is not supported yet: to render a page on its own,
101
+ * pass it as a node, `renderServer(<Page />, { url })`.
102
+ */
103
+ layouts?: boolean;
104
+ /**
105
+ * Whether the server renders the HTML of the page, which the browser then
106
+ * hydrates. With `false` the browser renders the app from the Flight
107
+ * payload alone, with `createRoot()`, as Next does for an error page: a
108
+ * test no longer sees the HTML of the server, or a mismatch between it and
109
+ * the browser.
110
+ *
111
+ * Defaults to `true`, and to `false` with the speed feature `keepApp` of
112
+ * `vitestPluginNext()`, on in fast mode, where a page without HTML of the
113
+ * server renders in the app of the page before it in the test file: see
114
+ * `SpeedOptions.keepApp`. A test that asserts on the HTML of the server
115
+ * passes `ssr: true`, and gets a page load of its own.
116
+ */
117
+ ssr?: boolean;
118
+ };
119
+ type RenderServerResult = {
120
+ /**
121
+ * The server's response to the request of the document. With the speed
122
+ * feature `flightLoads`, for a page of the kept app, the response to the
123
+ * request of its Flight payload, `text/x-component`, which loads it
124
+ * without a document: for a page whose render calls `redirect()`, the one
125
+ * of the page it redirects to, with `redirected` and its URL.
126
+ */
127
+ response: Response;
128
+ /**
129
+ * The errors that React reported on the page, in order: what threw as the
130
+ * server or the browser rendered, what an error boundary caught, what a
131
+ * Server Action threw into a form or a transition. Not Next's own, like a
132
+ * `redirect()` or a `notFound()`, which Next does not report either. Empty
133
+ * when nothing failed. A render that fails still opens its page, with
134
+ * Next's error page or the app's `error.tsx`, so `renderServer()` resolves
135
+ * with the error here; it rejects when there is no page to open. The array
136
+ * grows while the page is open, also with the pages the app loads from it,
137
+ * until `unmount()`, `cleanup()` or another `renderServer()` leaves it.
138
+ */
139
+ errors: unknown[]; /** Leaves the page. The cookies and the `headers` stay until the test ends. */
140
+ unmount(): Promise<void>;
141
+ };
142
+ type RenderComponentOptions = RequestOptions & {
143
+ /**
144
+ * Where the node renders. Defaults to a `<div>` appended to `baseElement`,
145
+ * which `cleanup()` removes. A container of the test's is only emptied.
146
+ */
147
+ container?: HTMLElement; /** Defaults to the `container` of the test, or else to `document.body`. */
148
+ baseElement?: HTMLElement; /** Wraps the node on the server. It can be a Server Component. */
149
+ wrapper?: JSXElementConstructor<{
150
+ children: ReactNode;
151
+ }>;
152
+ /**
153
+ * For another host than Vitest: the files of the host that render the
154
+ * node, for their CSS. A Vitest test never passes it: the plugin's setup
155
+ * file fills in the setup files and the test file. Another host passes
156
+ * the files of its own that render the node: absolute, or from the project
157
+ * root without a leading slash, like `host/button.demo.tsx`. The node has
158
+ * the stylesheets that these files import, as a page has those of its
159
+ * route. What is passed replaces the default, it adds nothing to it. A
160
+ * file that is not the host's, like one of the app, is left out, with a
161
+ * warning. `host.files` of `vitestPluginNext()` says which files are the
162
+ * host's at all, and in a static build only those have stylesheets.
163
+ */
164
+ hostFiles?: string[];
165
+ /**
166
+ * Whether the server in front of the app takes the request: `proxy.ts`, and
167
+ * the `redirects`, `rewrites` and `headers` of `next.config`. Defaults to
168
+ * `false`: the node renders at `url` as it is given, and so do its Server
169
+ * Actions and `router.refresh()`. A navigation to another route goes
170
+ * through it, as in the app.
171
+ */
172
+ proxy?: boolean;
173
+ /**
174
+ * Renders the node in place of the page at `url`, inside the layouts of the
175
+ * app, with the `loading`, `error` and `not-found` of that route. The root
176
+ * layout renders the document, so `container` and `baseElement` cannot be
177
+ * passed, and the `container` in the result is the `<body>`. Defaults to
178
+ * `false`.
179
+ */
180
+ layouts?: boolean;
181
+ };
182
+ type RenderComponentResult = RenderServerResult & {
183
+ container: HTMLElement;
184
+ baseElement: HTMLElement;
185
+ /**
186
+ * What the container holds now, as a fragment of its own, for a snapshot.
187
+ * Without the scripts that run, which are Next's and React's.
188
+ */
189
+ asFragment(): DocumentFragment;
190
+ /**
191
+ * Renders `ui` in place of the node, like Testing Library's `rerender`:
192
+ * on the server, in one request of Next's router, as `router.refresh()`
193
+ * sends, and without a page load, so the state of the Client Components in
194
+ * the node stays. `ui` gets the same `wrapper`, and the page keeps its
195
+ * `url`, `headers` and the other options: for others, call
196
+ * `renderServer()`. Resolves once React has committed the new node, or a
197
+ * later one, and run its effects. A Suspense boundary that is new in it
198
+ * may still show its fallback.
199
+ *
200
+ * Every call is a request of its own, and React commits once the last has
201
+ * answered: a host that renders often, as a user types, waits for one call
202
+ * before it makes the next. When the app changes the history without a
203
+ * request while it waits, like a hash link or `history.back()`, Next drops
204
+ * the refresh, and the call waits for the next one.
205
+ *
206
+ * Rejects with a `NodeReplacedError` when something else is in the node's
207
+ * place: an error page, a not-found page, a page the node redirected to,
208
+ * or a route the app navigated to. Its `cause` is the error React reported
209
+ * since the call, if any, which for `notFound()` and `redirect()` is
210
+ * Next's own. In the layouts of a route, an error of a layout or of
211
+ * another slot before the node has hydrated counts as one too. Rejects
212
+ * when the page was left, and with an `AbortError` when it is left while
213
+ * the node renders again.
214
+ */
215
+ rerender(ui: ReactNode): Promise<void>;
216
+ };
217
+ /**
218
+ * Opens a route of the Next.js app, as a browser does: it requests
219
+ * the document from the server, shows the HTML it gets back, and starts the
220
+ * app's client code, which hydrates it. From there Next's own router is in
221
+ * charge, so links, forms and Server Actions work as they do in the app.
222
+ *
223
+ * With a node, it renders that node in a container, like Testing Library
224
+ * does, on a route of its own: one without the app's layouts, at the URL of
225
+ * `url` with the params the app's route for it has. The request, the cookies,
226
+ * the Server Actions and the router are Next's, as for a page. A navigation
227
+ * to a route of the app loads that page.
228
+ *
229
+ * Two options say how much of the app is around it. `proxy` runs the server
230
+ * in front of the app, `proxy.ts` and the routing of `next.config`, and
231
+ * `layouts` renders the app's layouts. A route has both by default, and a
232
+ * node neither.
233
+ *
234
+ * Resolves once the page has hydrated.
235
+ */
236
+ declare function renderServer(options: RenderServerOptions): Promise<RenderServerResult>;
237
+ declare function renderServer(ui: ReactNode, options?: RenderComponentOptions): Promise<RenderComponentResult>;
238
+ type CleanupOptions = {
239
+ /**
240
+ * The URL of the document once the page is left: the host's own, like one
241
+ * that says what it shows next. Leaving puts back the URL that the
242
+ * document had as the page opened; this one replaces it, in the same entry
243
+ * of the history, as leaving does. Relative to that URL, and of its
244
+ * origin. Defaults to the URL that leaving puts back.
245
+ */
246
+ url?: string;
247
+ };
248
+ /**
249
+ * Leaves the page that `renderServer()` opened, removes the containers it made
250
+ * and closes the browser context of the app: its `headers`, the app's
251
+ * cookies and what the app put in its storage go, and the document has the
252
+ * browser's own APIs again. What still runs of the page, like a timer, uses
253
+ * those. The server forgets what it has cached, and what was written to its
254
+ * disk, the `fs` of the server code and the tests.
255
+ * Unlinks the stylesheets of the files of the host: the host links a file's
256
+ * again as it uses it, with `linkStylesheetsOf()`. Runs before and after
257
+ * every test, after which the plugin's setup file links those of the setup
258
+ * files and the test file again.
259
+ *
260
+ * What was set on the document, `<html>`, `<head>` and the container of a
261
+ * node while the page was open goes with the page; what the host set there
262
+ * while no page was open stays. So a library that marks the document once,
263
+ * on its first use, is prepared before the first page.
264
+ */
265
+ declare function cleanup({
266
+ url
267
+ }?: CleanupOptions): Promise<void>;
268
+ /**
269
+ * Leaves the page, as `cleanup()` does, and opens a new browser context of
270
+ * the app: until `cleanup()`, the document has the app's cookies, storage,
271
+ * requests and navigations, empty, as a page of the app has them. Outside
272
+ * one it has the browser's own. `renderServer()`, `handleRequest()` and
273
+ * `runInServerAction()` open one when none is open, so call this before
274
+ * what seeds the page before it opens, like an item of `localStorage`. The
275
+ * plugin's setup file calls it before every test of a file without
276
+ * `"use client"`.
277
+ */
278
+ declare function newBrowserContext(): Promise<void>;
279
+ //#endregion
280
+ export { CleanupOptions, RenderComponentOptions, RenderComponentResult, RenderServerOptions, RenderServerResult, RunInServerActionOptions, cleanup, handleRequest, newBrowserContext, renderServer, runInServerAction };
package/dist/index.js ADDED
@@ -0,0 +1,281 @@
1
+ import { registry } from "./registry.js";
2
+ import "./globals.js";
3
+ import { speedFeature } from "./speed-features.js";
4
+ import { loadedSsrLayer, rscLayer, ssrLayer } from "./server-layers.js";
5
+ import { sendRequest, unlessAborted } from "./app-requests.js";
6
+ import { unlinkStylesheets } from "./client-graph.js";
7
+ import { leavePage, loadPage } from "./page.js";
8
+ import "./document-loads.js";
9
+ import { closeContext, openContext } from "./browser-context.js";
10
+ import { resetDisk } from "./disk.js";
11
+ import { NodeOnPage } from "./node-on-page.js";
12
+ import { createElement } from "react";
13
+ //#region src/index.ts
14
+ ssrLayer();
15
+ await rscLayer();
16
+ /**
17
+ * Sends a request to the Next.js server of the app, like `fetch` from a page
18
+ * of the app would. Use it to assert on a response itself: its status, its
19
+ * headers, its HTML or Flight body.
20
+ *
21
+ * The request carries the app's cookies and the `headers` of what
22
+ * `renderServer()` opened, under its own. A `cookie` header is sent instead
23
+ * of the app's cookies, with this request and the redirects it follows. The
24
+ * cookies the server sets are the app's from then on:
25
+ * `response.headers.getSetCookie()` has them, where the `fetch` of the page
26
+ * does not show them.
27
+ * One to the pathname of what `renderServer()` opened is that page's, as a
28
+ * `fetch` of the page is: it gets the route of the node, and skips the proxy
29
+ * if the page does.
30
+ *
31
+ * Ask the server yourself with this, not with `fetch`: a `fetch` with Next's
32
+ * `rsc` or `next-action` header is Next's. It is the page's that is open as
33
+ * it is sent, and it never settles once that page is left, or without one.
34
+ */
35
+ function handleRequest(input, init) {
36
+ const request = new registry.Request(typeof input === "string" ? new URL(input, window.location.href) : input, init);
37
+ return sendRequest(request, {
38
+ from: openContext().store,
39
+ credentials: request.credentials !== "omit"
40
+ });
41
+ }
42
+ let serverActionKeys = 0;
43
+ /**
44
+ * Runs `action` as a Server Action of the page at `url`, the way Next runs
45
+ * one: in the request of the action, where `cookies()` can be set and
46
+ * `redirect()`, `refresh()` and `after()` work. Resolves with what it
47
+ * returns, and rejects with what it throws, also the error of a `redirect()`.
48
+ * Its arguments and its result are the test's own, not sent through Flight.
49
+ *
50
+ * It sends a Server Action request like the one of Next's router, to a route
51
+ * at `url` that renders nothing, and opens no page: there is no app to start.
52
+ * After a `redirect()` no page renders: open the page the action redirects
53
+ * to in the test. A page that is open stays open. The request carries the app's cookies, the `headers` of
54
+ * what `renderServer()` opened and the ones passed here, and the cookies the
55
+ * action sets are the app's from then on. For code that a Server Action
56
+ * calls; test a form of a page through the page.
57
+ *
58
+ * The function runs in the request, so it cannot send a request to the app
59
+ * itself, which would wait for this one: call the app's code instead.
60
+ */
61
+ async function runInServerAction(action, options = {}) {
62
+ const from = openContext().store;
63
+ const url = new URL(options.url ?? "/", window.location.origin);
64
+ if (url.origin !== window.location.origin) throw new Error(`vitest-plugin-rsc: a Server Action is of a page of the app, not of ${url.href}`);
65
+ const { runInServerActionOfTest } = await import("./server-action.js");
66
+ const key = serverActionKeys++;
67
+ const ran = {};
68
+ registry.serverActions.set(key, async () => {
69
+ try {
70
+ ran.outcome = { value: await action() };
71
+ } catch (error) {
72
+ ran.outcome = { error };
73
+ throw error;
74
+ }
75
+ });
76
+ try {
77
+ const headers = new Headers(options.headers);
78
+ headers.set("accept", "text/x-component");
79
+ headers.set("content-type", "text/plain;charset=UTF-8");
80
+ headers.set("next-action", serverActionId(runInServerActionOfTest));
81
+ const request = new registry.Request(url, {
82
+ method: "POST",
83
+ headers,
84
+ body: JSON.stringify([key]),
85
+ redirect: "manual"
86
+ });
87
+ const response = await sendRequest(request, {
88
+ from,
89
+ opened: {
90
+ pathname: url.pathname,
91
+ proxy: options.proxy ?? false,
92
+ node: {
93
+ ui: null,
94
+ layouts: false,
95
+ files: []
96
+ },
97
+ rendersRedirect: false
98
+ }
99
+ });
100
+ if (ran.outcome) await response.body?.pipeTo(new WritableStream()).catch(() => {});
101
+ else {
102
+ await response.body?.cancel();
103
+ const location = response.headers.get("location");
104
+ throw new Error(`vitest-plugin-rsc: the Server Action did not run: ${response.url} ` + (location ? `redirected to ${location}.` : `responded with ${response.status}.`));
105
+ }
106
+ } finally {
107
+ registry.serverActions.delete(key);
108
+ }
109
+ if ("error" in ran.outcome) throw ran.outcome.error;
110
+ return ran.outcome.value;
111
+ }
112
+ function serverActionId(action) {
113
+ const id = Reflect.get(Object(action), "$$id");
114
+ if (typeof id !== "string") throw new Error("vitest-plugin-rsc: Vite RSC did not compile the plugin's own Server Action. Are `@kasperpeulen/vitest-plugin-rsc` and `@kasperpeulen/vitest-plugin-nextjs` excluded from `optimizeDeps`?");
115
+ return id;
116
+ }
117
+ const unmount = () => leavePage("unmount()");
118
+ async function renderServer(...args) {
119
+ const [first, second] = args;
120
+ const options = (isOptions(first) ? first : second) ?? {};
121
+ const { signal } = options;
122
+ signal?.throwIfAborted();
123
+ const { store } = openContext();
124
+ const errors = [];
125
+ const url = new URL(options.url ?? "/", window.location.origin);
126
+ const headers = new Headers(options.headers);
127
+ const cookie = headers.get("cookie");
128
+ headers.delete("cookie");
129
+ const sticky = new Headers(headers);
130
+ sticky.delete("accept");
131
+ if (!headers.has("accept")) headers.set("accept", "text/html");
132
+ const { pathname } = url;
133
+ if (isOptions(first)) {
134
+ if (first.layouts === false) throw new Error("vitest-plugin-rsc: `renderServer({ url, layouts: false })` is not supported yet. To render a page without the layouts of the app, render it as a node: `renderServer(<Page />, { url })`.");
135
+ const ssr = first.ssr ?? !speedFeature("keepApp");
136
+ const opened = {
137
+ pathname,
138
+ proxy: first.proxy ?? true,
139
+ ssr
140
+ };
141
+ return {
142
+ response: await loadPage(url, { headers }, store, {
143
+ opened,
144
+ headers: sticky,
145
+ cookie,
146
+ signal,
147
+ errors,
148
+ inKeptApp: speedFeature("keepApp") && !ssr
149
+ }),
150
+ errors,
151
+ unmount
152
+ };
153
+ }
154
+ const { wrapper, proxy = false } = options;
155
+ const wrap = (node) => wrapper ? createElement(wrapper, null, node) : node;
156
+ const files = options.hostFiles ?? registry.nodeFiles?.() ?? [];
157
+ const layouts = options.layouts ?? false;
158
+ const served = {
159
+ ui: wrap(first),
160
+ layouts,
161
+ files,
162
+ version: 0
163
+ };
164
+ const opened = {
165
+ pathname,
166
+ proxy,
167
+ node: served
168
+ };
169
+ const nodeOnPage = new NodeOnPage(pathname);
170
+ const rerender = (next) => {
171
+ nodeOnPage.at(window.location.pathname);
172
+ return nodeOnPage.rerender((version) => Object.assign(served, {
173
+ ui: wrap(next),
174
+ version
175
+ }));
176
+ };
177
+ if (layouts) {
178
+ if (options.container || options.baseElement) throw new Error("vitest-plugin-rsc: with `layouts` a node renders in the document of its route, whose root layout has the <html> and the <body>. There is no `container` or `baseElement` to pass.");
179
+ return {
180
+ response: await loadPage(url, { headers }, store, {
181
+ opened,
182
+ headers: sticky,
183
+ cookie,
184
+ nodeOnPage,
185
+ signal,
186
+ errors
187
+ }),
188
+ errors,
189
+ get container() {
190
+ return document.body;
191
+ },
192
+ get baseElement() {
193
+ return document.body;
194
+ },
195
+ asFragment: () => fragmentOf(document.body),
196
+ unmount,
197
+ rerender
198
+ };
199
+ }
200
+ if (options.container === document.body || options.container === document.documentElement) throw new Error("vitest-plugin-rsc: the container of a node cannot be the <body> or the <html> of the document, which hold the test's own elements. Pass an element in it, or no container.");
201
+ const left = leavePage("another renderServer()");
202
+ await (signal ? unlessAborted(left, signal, () => {}) : left);
203
+ const base = options.baseElement instanceof HTMLBodyElement ? void 0 : options.baseElement;
204
+ const defaultsToContainer = !options.baseElement && options.container;
205
+ if (options.container && !options.container.isConnected) throw new Error("vitest-plugin-rsc: the container of a node has to be in the document. What is added to the document while a page is open is removed when that page is left, so add the container after it.");
206
+ const container = options.container ?? (base ?? document.body).appendChild(document.createElement("div"));
207
+ if (!options.container) containers.add(container);
208
+ return {
209
+ response: await loadPage(url, { headers }, store, {
210
+ container,
211
+ opened,
212
+ headers: sticky,
213
+ cookie,
214
+ nodeOnPage,
215
+ signal,
216
+ errors
217
+ }),
218
+ errors,
219
+ container,
220
+ get baseElement() {
221
+ return base ?? (defaultsToContainer || document.body);
222
+ },
223
+ asFragment: () => fragmentOf(container),
224
+ unmount,
225
+ rerender
226
+ };
227
+ }
228
+ function fragmentOf(container) {
229
+ const fragment = document.createRange().createContextualFragment(container.innerHTML);
230
+ for (const script of fragment.querySelectorAll("script")) if (!script.type || /^(text\/javascript|module)$/i.test(script.type)) script.remove();
231
+ return fragment;
232
+ }
233
+ const containers = /* @__PURE__ */ new Set();
234
+ function isOptions(value) {
235
+ if (typeof value !== "object" || value === null || "$$typeof" in value) return false;
236
+ const prototype = Object.getPrototypeOf(value);
237
+ return prototype === Object.prototype || prototype === null;
238
+ }
239
+ /**
240
+ * Leaves the page that `renderServer()` opened, removes the containers it made
241
+ * and closes the browser context of the app: its `headers`, the app's
242
+ * cookies and what the app put in its storage go, and the document has the
243
+ * browser's own APIs again. What still runs of the page, like a timer, uses
244
+ * those. The server forgets what it has cached, and what was written to its
245
+ * disk, the `fs` of the server code and the tests.
246
+ * Unlinks the stylesheets of the files of the host: the host links a file's
247
+ * again as it uses it, with `linkStylesheetsOf()`. Runs before and after
248
+ * every test, after which the plugin's setup file links those of the setup
249
+ * files and the test file again.
250
+ *
251
+ * What was set on the document, `<html>`, `<head>` and the container of a
252
+ * node while the page was open goes with the page; what the host set there
253
+ * while no page was open stays. So a library that marks the document once,
254
+ * on its first use, is prepared before the first page.
255
+ */
256
+ async function cleanup({ url } = {}) {
257
+ await leavePage("cleanup()");
258
+ for (const container of containers) container.remove();
259
+ containers.clear();
260
+ unlinkStylesheets();
261
+ closeContext();
262
+ await loadedSsrLayer()?.resetCaches();
263
+ resetDisk();
264
+ if (url !== void 0) window.history.replaceState(window.history.state, "", url);
265
+ }
266
+ /**
267
+ * Leaves the page, as `cleanup()` does, and opens a new browser context of
268
+ * the app: until `cleanup()`, the document has the app's cookies, storage,
269
+ * requests and navigations, empty, as a page of the app has them. Outside
270
+ * one it has the browser's own. `renderServer()`, `handleRequest()` and
271
+ * `runInServerAction()` open one when none is open, so call this before
272
+ * what seeds the page before it opens, like an item of `localStorage`. The
273
+ * plugin's setup file calls it before every test of a file without
274
+ * `"use client"`.
275
+ */
276
+ async function newBrowserContext() {
277
+ await cleanup();
278
+ openContext();
279
+ }
280
+ //#endregion
281
+ export { cleanup, handleRequest, newBrowserContext, renderServer, runInServerAction };
@@ -0,0 +1,2 @@
1
+ import { loadClientFile } from "./client-graph.js";
2
+ export { loadClientFile };
@@ -0,0 +1,3 @@
1
+ import "./globals.js";
2
+ import { loadClientFile } from "./client-graph.js";
3
+ export { loadClientFile };
@@ -0,0 +1,11 @@
1
+ //#region src/leftovers.d.ts
2
+ type Leftovers = {
3
+ /** Stops recording. */stop(): void; /** Stops recording, and removes what was recorded. */
4
+ remove(): void;
5
+ };
6
+ /** Records the listeners that are added to `target`, until `stop()`. */
7
+ declare function recordListeners(target: EventTarget): Leftovers;
8
+ /** Records the message channels that the page opens, until `stop()`. */
9
+ declare function recordMessageChannels(): Leftovers;
10
+ //#endregion
11
+ export { Leftovers, recordListeners, recordMessageChannels };
@@ -0,0 +1,55 @@
1
+ //#region src/leftovers.ts
2
+ /** Records the listeners that are added to `target`, until `stop()`. */
3
+ function recordListeners(target) {
4
+ const own = Object.getOwnPropertyDescriptor(target, "addEventListener");
5
+ const add = target.addEventListener;
6
+ const added = [];
7
+ let recording = true;
8
+ const record = function(...args) {
9
+ if (recording) added.push(args);
10
+ add.apply(this ?? target, args);
11
+ };
12
+ target.addEventListener = record;
13
+ const stop = () => {
14
+ recording = false;
15
+ if (target.addEventListener !== record) return;
16
+ if (own) target.addEventListener = add;
17
+ else delete target.addEventListener;
18
+ };
19
+ return {
20
+ stop,
21
+ remove() {
22
+ stop();
23
+ for (const args of added.splice(0)) target.removeEventListener(...args);
24
+ }
25
+ };
26
+ }
27
+ /** Records the message channels that the page opens, until `stop()`. */
28
+ function recordMessageChannels() {
29
+ const Native = window.MessageChannel;
30
+ const opened = [];
31
+ let recording = true;
32
+ const Recorded = class MessageChannel extends Native {
33
+ constructor() {
34
+ super();
35
+ if (recording) opened.push(this);
36
+ }
37
+ };
38
+ window.MessageChannel = Recorded;
39
+ const stop = () => {
40
+ recording = false;
41
+ if (window.MessageChannel === Recorded) window.MessageChannel = Native;
42
+ };
43
+ return {
44
+ stop,
45
+ remove() {
46
+ stop();
47
+ for (const channel of opened.splice(0)) {
48
+ channel.port1.close();
49
+ channel.port2.close();
50
+ }
51
+ }
52
+ };
53
+ }
54
+ //#endregion
55
+ export { recordListeners, recordMessageChannels };