chore: Test remote functions with full-async setup (#15033)
authorElliott Johnson <elliott.johnson@vercel.com>
Sat, 13 Dec 2025 00:09:39 +0000 (17:09 -0700)
committerGitHub <noreply@github.com>
Sat, 13 Dec 2025 00:09:39 +0000 (17:09 -0700)
* checkpoint

* donesies

* fix: lockfile

* update svelte-check

* add .env for test app

* fix flaky test

* polyfill withResolvers

87 files changed:
packages/kit/test/apps/async/.env [new file with mode: 0644]
packages/kit/test/apps/async/.gitignore [new file with mode: 0644]
packages/kit/test/apps/async/README.md [new file with mode: 0644]
packages/kit/test/apps/async/package.json [new file with mode: 0644]
packages/kit/test/apps/async/playwright.config.js [new file with mode: 0644]
packages/kit/test/apps/async/src/app.html [new file with mode: 0644]
packages/kit/test/apps/async/src/hooks.js [new file with mode: 0644]
packages/kit/test/apps/async/src/hooks.server.js [new file with mode: 0644]
packages/kit/test/apps/async/src/lib/assets/favicon.svg [new file with mode: 0644]
packages/kit/test/apps/async/src/lib/index.js [new file with mode: 0644]
packages/kit/test/apps/async/src/routes/+error.svelte [new file with mode: 0644]
packages/kit/test/apps/async/src/routes/+layout.svelte [new file with mode: 0644]
packages/kit/test/apps/async/src/routes/remote/+page.js [moved from packages/kit/test/apps/basics/src/routes/remote/+page.js with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/+page.svelte [moved from packages/kit/test/apps/basics/src/routes/remote/+page.svelte with 89% similarity]
packages/kit/test/apps/async/src/routes/remote/accessing-env.remote.js [moved from packages/kit/test/apps/basics/src/routes/remote/accessing-env.remote.js with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/batch/+page.js [moved from packages/kit/test/apps/basics/src/routes/remote/batch/+page.js with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/batch/+page.svelte [moved from packages/kit/test/apps/basics/src/routes/remote/batch/+page.svelte with 73% similarity]
packages/kit/test/apps/async/src/routes/remote/batch/batch.remote.js [moved from packages/kit/test/apps/basics/src/routes/remote/batch/batch.remote.js with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/dev/+page.svelte [moved from packages/kit/test/apps/basics/src/routes/remote/dev/+page.svelte with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/dev/preload/+page.server.js [moved from packages/kit/test/apps/basics/src/routes/remote/dev/preload/+page.server.js with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/dev/preload/+page.svelte [moved from packages/kit/test/apps/basics/src/routes/remote/dev/preload/+page.svelte with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/dev/preload/example.remote.js [moved from packages/kit/test/apps/basics/src/routes/remote/dev/preload/example.remote.js with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/dev/preload/schema.js [moved from packages/kit/test/apps/basics/src/routes/remote/dev/preload/schema.js with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/event/+page.svelte [new file with mode: 0644]
packages/kit/test/apps/async/src/routes/remote/event/data.remote.ts [moved from packages/kit/test/apps/basics/src/routes/remote/event/data.remote.ts with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/form/[test_name]/+page.svelte [moved from packages/kit/test/apps/basics/src/routes/remote/form/[test_name]/+page.svelte with 95% similarity]
packages/kit/test/apps/async/src/routes/remote/form/[test_name]/form.remote.ts [moved from packages/kit/test/apps/basics/src/routes/remote/form/[test_name]/form.remote.ts with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/form/file-upload/+page.svelte [moved from packages/kit/test/apps/basics/src/routes/remote/form/file-upload/+page.svelte with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/form/file-upload/form.remote.ts [moved from packages/kit/test/apps/basics/src/routes/remote/form/file-upload/form.remote.ts with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/form/imperative/+page.svelte [moved from packages/kit/test/apps/basics/src/routes/remote/form/imperative/+page.svelte with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/form/preflight-only/+page.svelte [moved from packages/kit/test/apps/basics/src/routes/remote/form/preflight-only/+page.svelte with 82% similarity]
packages/kit/test/apps/async/src/routes/remote/form/preflight-only/form.remote.ts [moved from packages/kit/test/apps/basics/src/routes/remote/form/preflight-only/form.remote.ts with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/form/preflight/+page.svelte [moved from packages/kit/test/apps/basics/src/routes/remote/form/preflight/+page.svelte with 91% similarity]
packages/kit/test/apps/async/src/routes/remote/form/preflight/form.remote.ts [moved from packages/kit/test/apps/basics/src/routes/remote/form/preflight/form.remote.ts with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/form/select-untouched/+page.svelte [moved from packages/kit/test/apps/basics/src/routes/remote/form/select-untouched/+page.svelte with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/form/select-untouched/form.remote.ts [moved from packages/kit/test/apps/basics/src/routes/remote/form/select-untouched/form.remote.ts with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/form/submitter/+page.svelte [moved from packages/kit/test/apps/basics/src/routes/remote/form/submitter/+page.svelte with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/form/submitter/form.remote.ts [moved from packages/kit/test/apps/basics/src/routes/remote/form/submitter/form.remote.ts with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/form/underscore/+page.svelte [moved from packages/kit/test/apps/basics/src/routes/remote/form/underscore/+page.svelte with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/form/underscore/form.remote.ts [moved from packages/kit/test/apps/basics/src/routes/remote/form/underscore/form.remote.ts with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/form/validate/+page.svelte [moved from packages/kit/test/apps/basics/src/routes/remote/form/validate/+page.svelte with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/form/validate/form.remote.ts [moved from packages/kit/test/apps/basics/src/routes/remote/form/validate/form.remote.ts with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/form/value/+page.svelte [moved from packages/kit/test/apps/basics/src/routes/remote/form/value/+page.svelte with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/form/value/value.remote.ts [moved from packages/kit/test/apps/basics/src/routes/remote/form/value/value.remote.ts with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/prerender/+page.svelte [moved from packages/kit/test/apps/basics/src/routes/remote/prerender/+page.svelte with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/prerender/functions-only/+page.js [moved from packages/kit/test/apps/basics/src/routes/remote/prerender/functions-only/+page.js with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/prerender/functions-only/+page.svelte [moved from packages/kit/test/apps/basics/src/routes/remote/prerender/functions-only/+page.svelte with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/prerender/prerender.remote.js [moved from packages/kit/test/apps/basics/src/routes/remote/prerender/prerender.remote.js with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/prerender/test.txt [moved from packages/kit/test/apps/basics/src/routes/remote/prerender/test.txt with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/prerender/whole-page/+page.js [moved from packages/kit/test/apps/basics/src/routes/remote/prerender/whole-page/+page.js with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/prerender/whole-page/+page.svelte [moved from packages/kit/test/apps/basics/src/routes/remote/prerender/whole-page/+page.svelte with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/query-command.remote.js [moved from packages/kit/test/apps/basics/src/routes/remote/query-command.remote.js with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/query-non-exported/+page.svelte [new file with mode: 0644]
packages/kit/test/apps/async/src/routes/remote/query-non-exported/data.remote.ts [moved from packages/kit/test/apps/basics/src/routes/remote/query-non-exported/data.remote.ts with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/query-redirect/+page.svelte [new file with mode: 0644]
packages/kit/test/apps/async/src/routes/remote/query-redirect/from-common-layout/+layout.svelte [new file with mode: 0644]
packages/kit/test/apps/async/src/routes/remote/query-redirect/from-common-layout/+page.svelte [moved from packages/kit/test/apps/basics/src/routes/remote/query-redirect/from-common-layout/+page.svelte with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/query-redirect/from-common-layout/redirected/+page.svelte [moved from packages/kit/test/apps/basics/src/routes/remote/query-redirect/from-common-layout/redirected/+page.svelte with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/query-redirect/from-page/+page.svelte [new file with mode: 0644]
packages/kit/test/apps/async/src/routes/remote/query-redirect/redirect.remote.js [moved from packages/kit/test/apps/basics/src/routes/remote/query-redirect/redirect.remote.js with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/query-redirect/redirected/+page.svelte [moved from packages/kit/test/apps/basics/src/routes/remote/query-redirect/redirected/+page.svelte with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/server-endpoint/+page.svelte [moved from packages/kit/test/apps/basics/src/routes/remote/server-endpoint/+page.svelte with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/server-endpoint/api/+server.ts [moved from packages/kit/test/apps/basics/src/routes/remote/server-endpoint/api/+server.ts with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/server-endpoint/internal.remote.ts [moved from packages/kit/test/apps/basics/src/routes/remote/server-endpoint/internal.remote.ts with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/transport/+page.svelte [new file with mode: 0644]
packages/kit/test/apps/async/src/routes/remote/transport/data.remote.ts [moved from packages/kit/test/apps/basics/src/routes/remote/transport/data.remote.ts with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/validation/+page.svelte [moved from packages/kit/test/apps/basics/src/routes/remote/validation/+page.svelte with 100% similarity]
packages/kit/test/apps/async/src/routes/remote/validation/validation.remote.js [moved from packages/kit/test/apps/basics/src/routes/remote/validation/validation.remote.js with 100% similarity]
packages/kit/test/apps/async/static/robots.txt [new file with mode: 0644]
packages/kit/test/apps/async/svelte.config.js [new file with mode: 0644]
packages/kit/test/apps/async/test/client.test.js [new file with mode: 0644]
packages/kit/test/apps/async/test/server.test.js [new file with mode: 0644]
packages/kit/test/apps/async/test/test.js [new file with mode: 0644]
packages/kit/test/apps/async/tsconfig.json [new file with mode: 0644]
packages/kit/test/apps/async/vite.config.js [new file with mode: 0644]
packages/kit/test/apps/basics/src/hooks.server.js
packages/kit/test/apps/basics/src/routes/remote/event/+page.svelte [deleted file]
packages/kit/test/apps/basics/src/routes/remote/query-non-exported/+page.svelte [deleted file]
packages/kit/test/apps/basics/src/routes/remote/query-redirect/+page.svelte [deleted file]
packages/kit/test/apps/basics/src/routes/remote/query-redirect/from-common-layout/+layout.svelte [deleted file]
packages/kit/test/apps/basics/src/routes/remote/query-redirect/from-page/+page.svelte [deleted file]
packages/kit/test/apps/basics/src/routes/remote/transport/+page.svelte [deleted file]
packages/kit/test/apps/basics/test/client.test.js
packages/kit/test/apps/basics/test/server.test.js
packages/kit/test/apps/basics/test/test.js
pnpm-lock.yaml
pnpm-workspace.yaml

diff --git a/packages/kit/test/apps/async/.env b/packages/kit/test/apps/async/.env
new file mode 100644 (file)
index 0000000..b127734
--- /dev/null
@@ -0,0 +1,5 @@
+PRIVATE_STATIC="accessible to server-side code/replaced at build time"
+PRIVATE_DYNAMIC="accessible to server-side code/evaluated at run time"
+
+PUBLIC_STATIC="accessible anywhere/replaced at build time"
+PUBLIC_DYNAMIC="accessible anywhere/evaluated at run time"
\ No newline at end of file
diff --git a/packages/kit/test/apps/async/.gitignore b/packages/kit/test/apps/async/.gitignore
new file mode 100644 (file)
index 0000000..d5868cb
--- /dev/null
@@ -0,0 +1 @@
+!/.env
\ No newline at end of file
diff --git a/packages/kit/test/apps/async/README.md b/packages/kit/test/apps/async/README.md
new file mode 100644 (file)
index 0000000..75842c4
--- /dev/null
@@ -0,0 +1,38 @@
+# sv
+
+Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).
+
+## Creating a project
+
+If you're seeing this, you've probably already done this step. Congrats!
+
+```sh
+# create a new project in the current directory
+npx sv create
+
+# create a new project in my-app
+npx sv create my-app
+```
+
+## Developing
+
+Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
+
+```sh
+npm run dev
+
+# or start the server and open the app in a new browser tab
+npm run dev -- --open
+```
+
+## Building
+
+To create a production version of your app:
+
+```sh
+npm run build
+```
+
+You can preview the production build with `npm run preview`.
+
+> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
diff --git a/packages/kit/test/apps/async/package.json b/packages/kit/test/apps/async/package.json
new file mode 100644 (file)
index 0000000..7afccae
--- /dev/null
@@ -0,0 +1,25 @@
+{
+       "name": "test-async",
+       "private": true,
+       "version": "0.0.1",
+       "type": "module",
+       "scripts": {
+               "dev": "vite dev",
+               "build": "vite build",
+               "preview": "vite preview",
+               "prepare": "svelte-kit sync || echo ''",
+               "check": "svelte-kit sync && tsc && svelte-check",
+               "test": "pnpm test:dev && pnpm test:build",
+               "test:dev": "DEV=true playwright test",
+               "test:build": "playwright test"
+       },
+       "devDependencies": {
+               "@sveltejs/kit": "workspace:^",
+               "@sveltejs/vite-plugin-svelte": "catalog:",
+               "svelte": "catalog:",
+               "svelte-check": "catalog:",
+               "typescript": "^5.5.4",
+               "valibot": "catalog:",
+               "vite": "catalog:"
+       }
+}
diff --git a/packages/kit/test/apps/async/playwright.config.js b/packages/kit/test/apps/async/playwright.config.js
new file mode 100644 (file)
index 0000000..f3ef088
--- /dev/null
@@ -0,0 +1,11 @@
+import process from 'node:process';
+import { config } from '../../utils.js';
+import { defineConfig } from '@playwright/test';
+
+export default defineConfig({
+       ...config,
+       webServer: {
+               command: process.env.DEV ? `pnpm dev` : `pnpm build && pnpm preview`,
+               port: process.env.DEV ? 5173 : 4173
+       }
+});
diff --git a/packages/kit/test/apps/async/src/app.html b/packages/kit/test/apps/async/src/app.html
new file mode 100644 (file)
index 0000000..f273cc5
--- /dev/null
@@ -0,0 +1,11 @@
+<!doctype html>
+<html lang="en">
+       <head>
+               <meta charset="utf-8" />
+               <meta name="viewport" content="width=device-width, initial-scale=1" />
+               %sveltekit.head%
+       </head>
+       <body data-sveltekit-preload-data="hover">
+               <div style="display: contents">%sveltekit.body%</div>
+       </body>
+</html>
diff --git a/packages/kit/test/apps/async/src/hooks.js b/packages/kit/test/apps/async/src/hooks.js
new file mode 100644 (file)
index 0000000..238cc77
--- /dev/null
@@ -0,0 +1,9 @@
+import { Foo } from '$lib';
+
+/** @type {import("@sveltejs/kit").Transport} */
+export const transport = {
+       Foo: {
+               encode: (value) => value instanceof Foo && [value.message],
+               decode: ([message]) => new Foo(message)
+       }
+};
diff --git a/packages/kit/test/apps/async/src/hooks.server.js b/packages/kit/test/apps/async/src/hooks.server.js
new file mode 100644 (file)
index 0000000..9851e98
--- /dev/null
@@ -0,0 +1,21 @@
+/** @type {import('@sveltejs/kit').HandleValidationError} */
+export const handleValidationError = ({ issues }) => {
+       return { message: issues[0].message };
+};
+
+/** @type {import('@sveltejs/kit').HandleServerError} */
+export const handleError = ({ error: e, status, message }) => {
+       const error = /** @type {Error} */ (e);
+
+       return { message: `${error.message} (${status} ${message})` };
+};
+
+// @ts-ignore this doesn't exist in old Node TODO remove SvelteKit 3 (same in test-basics)
+Promise.withResolvers ??= () => {
+       const d = {};
+       d.promise = new Promise((resolve, reject) => {
+               d.resolve = resolve;
+               d.reject = reject;
+       });
+       return d;
+};
diff --git a/packages/kit/test/apps/async/src/lib/assets/favicon.svg b/packages/kit/test/apps/async/src/lib/assets/favicon.svg
new file mode 100644 (file)
index 0000000..cc5dc66
--- /dev/null
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116" style="fill:#ff3e00"/><path d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328" style="fill:#fff"/></svg>
\ No newline at end of file
diff --git a/packages/kit/test/apps/async/src/lib/index.js b/packages/kit/test/apps/async/src/lib/index.js
new file mode 100644 (file)
index 0000000..f9917fd
--- /dev/null
@@ -0,0 +1,9 @@
+export class Foo {
+       constructor(message) {
+               this.message = message;
+       }
+
+       bar() {
+               return this.message + '!';
+       }
+}
diff --git a/packages/kit/test/apps/async/src/routes/+error.svelte b/packages/kit/test/apps/async/src/routes/+error.svelte
new file mode 100644 (file)
index 0000000..61ad908
--- /dev/null
@@ -0,0 +1,35 @@
+<script>
+       import { page } from '$app/state';
+</script>
+
+<svelte:head>
+       <title>Custom error page: {page.error.message}</title>
+</svelte:head>
+
+<h1>{page.status}</h1>
+
+<p id="message">This is your custom error page saying: "<b>{page.error.message}</b>"</p>
+
+<style>
+       h1,
+       p {
+               margin: 0 auto;
+       }
+
+       h1 {
+               font-size: 2.8em;
+               font-weight: 700;
+               margin: 0 0 0.5em 0;
+               color: red;
+       }
+
+       p {
+               margin: 1em auto;
+       }
+
+       @media (min-width: 480px) {
+               h1 {
+                       font-size: 4em;
+               }
+       }
+</style>
diff --git a/packages/kit/test/apps/async/src/routes/+layout.svelte b/packages/kit/test/apps/async/src/routes/+layout.svelte
new file mode 100644 (file)
index 0000000..9694913
--- /dev/null
@@ -0,0 +1,9 @@
+<script lang="ts">
+       import { setup } from '../../../../setup.js';
+
+       const { children } = $props();
+
+       setup();
+</script>
+
+{@render children?.()}
similarity index 89%
rename from packages/kit/test/apps/basics/src/routes/remote/+page.svelte
rename to packages/kit/test/apps/async/src/routes/remote/+page.svelte
index a7dbab7bf6abe4e2aa00f3533eb98e6a6f3deb97..c7efb3edc63b7356931db3d7159f25180ef6e4b3 100644 (file)
 </script>
 
 <p id="echo-result">{data.echo_result}</p>
-<!-- TODO use await here once async lands -->
 {#if browser}
        <p id="count-result">
-               {#await count then result}{result}{/await} / {count.current} ({count.loading})
+               {await count} / {count.current} ({count.loading})
        </p>
        <!-- this is just here to check that it is re-requested after the command -->
-       {#await add({ a: 2, b: 2 }) then result}{result}{/await}
+       {await add({ a: 2, b: 2 })}
 {/if}
 <p id="command-result">{command_result}</p>
 
 </button>
 <button id="resolve-deferreds" onclick={() => resolve_deferreds()}>Resolve Deferreds</button>
 
-<a href="/remote/event">/remote/event</a>
+<!-- 
+       TODO: preloading currently breaks this, because the fork is run on the current route
+       or something like that. Remove `data-sveltekit-preload-data="off"` when fixed.
+-->
+<a href="/remote/event" data-sveltekit-preload-data="off">/remote/event</a>
similarity index 73%
rename from packages/kit/test/apps/basics/src/routes/remote/batch/+page.svelte
rename to packages/kit/test/apps/async/src/routes/remote/batch/+page.svelte
index 0d801a2dde218c5165be5a6c1f91952315a9b474..9c4ab7aa73cca5da9e3ec10dcd3fca6b8a0ba343 100644 (file)
@@ -1,4 +1,4 @@
-<script>
+<script lang="ts">
        import {
                get_todo,
                set_todo_title_server_refresh,
 <ul>
        {#each todos as { id, promise }, idx (idx)}
                <li>
-                       {#await promise}
-                               <span id="batch-result-{idx + 1}">Loading todo {id}...</span>
-                       {:then todo}
-                               <span id="batch-result-{idx + 1}">{todo.title}</span>
-                       {:catch error}
-                               <span id="batch-result-{idx + 1}">Error loading todo {id}: {error.body.message}</span>
-                       {/await}
+                       <svelte:boundary>
+                               <span id="batch-result-{idx + 1}">{(await promise).title}</span>
+
+                               {#snippet pending()}
+                                       <span id="batch-result-{idx + 1}">Loading todo {id}...</span>
+                               {/snippet}
+
+                               {#snippet failed(error)}
+                                       <span id="batch-result-{idx + 1}"
+                                               >Error loading todo {id}: {(error as any).body.message}</span
+                                       >
+                               {/snippet}
+                       </svelte:boundary>
                </li>
        {/each}
 </ul>
diff --git a/packages/kit/test/apps/async/src/routes/remote/event/+page.svelte b/packages/kit/test/apps/async/src/routes/remote/event/+page.svelte
new file mode 100644 (file)
index 0000000..e472de0
--- /dev/null
@@ -0,0 +1,8 @@
+<script>
+       import { get_event } from './data.remote.js';
+
+       const event = await get_event();
+</script>
+
+<p data-id="route">route: {event.route.id}</p>
+<p data-id="pathname">pathname: {event.url.pathname}</p>
similarity index 95%
rename from packages/kit/test/apps/basics/src/routes/remote/form/[test_name]/+page.svelte
rename to packages/kit/test/apps/async/src/routes/remote/form/[test_name]/+page.svelte
index 9607ab161ce567f2bab4e107d42b33091925adf3..485cfab34a113fae5ae3ffa73f41f6753f95fac4 100644 (file)
 
 <p>message.current: {message.current}</p>
 
-<!-- TODO use await here once async lands -->
-{#await message then m}
-       <p>await get_message(): {m}</p>
-{/await}
+<p>await get_message(): {await message}</p>
 
 <hr />
 
similarity index 82%
rename from packages/kit/test/apps/basics/src/routes/remote/form/preflight-only/+page.svelte
rename to packages/kit/test/apps/async/src/routes/remote/form/preflight-only/+page.svelte
index 6de838b61e638f2e9f4c6994788dc57a723d8764..0b2cb866102e47a2eedf9385b65b7c6dd7071f86 100644 (file)
        });
 </script>
 
-<!-- TODO use await here once async lands -->
-{#await data then { a, b, c }}
-       <p>a: {a}</p>
-       <p>b: {b}</p>
-       <p>c: {c}</p>
-{/await}
+<p>a: {(await data).a}</p>
+<p>b: {(await data).b}</p>
+<p>c: {(await data).c}</p>
 
 <hr />
 
similarity index 91%
rename from packages/kit/test/apps/basics/src/routes/remote/form/preflight/+page.svelte
rename to packages/kit/test/apps/async/src/routes/remote/form/preflight/+page.svelte
index 38d1248d45155ac0e0a93037405d919decfeb242..85269d87cff437dd7d3ee3a135a408e773ff87bc 100644 (file)
 
 <p>number.current: {number.current}</p>
 
-<!-- TODO use await here once async lands -->
-{#await number then n}
-       <p>await get_number(): {n}</p>
-{/await}
+<p>await get_number(): {await number}</p>
 
 <hr />
 
diff --git a/packages/kit/test/apps/async/src/routes/remote/query-non-exported/+page.svelte b/packages/kit/test/apps/async/src/routes/remote/query-non-exported/+page.svelte
new file mode 100644 (file)
index 0000000..d69be7f
--- /dev/null
@@ -0,0 +1,5 @@
+<script>
+       import { total } from './data.remote.js';
+</script>
+
+<h1>{await total()}</h1>
diff --git a/packages/kit/test/apps/async/src/routes/remote/query-redirect/+page.svelte b/packages/kit/test/apps/async/src/routes/remote/query-redirect/+page.svelte
new file mode 100644 (file)
index 0000000..efc180a
--- /dev/null
@@ -0,0 +1,4 @@
+<!-- TODO can remove this when preloading doesn't eagerly redirect -->
+<a data-sveltekit-preload-data="off" href="/remote/query-redirect/from-page">from page</a>
+<a data-sveltekit-preload-data="off" href="/remote/query-redirect/from-common-layout">from layout</a
+>
diff --git a/packages/kit/test/apps/async/src/routes/remote/query-redirect/from-common-layout/+layout.svelte b/packages/kit/test/apps/async/src/routes/remote/query-redirect/from-common-layout/+layout.svelte
new file mode 100644 (file)
index 0000000..1eebf5f
--- /dev/null
@@ -0,0 +1,12 @@
+<script>
+       import { page } from '$app/state';
+       import { layoutRedirect } from '../redirect.remote';
+
+       let { children } = $props();
+</script>
+
+<p id="layout-query">
+       on page {await layoutRedirect(page.url.pathname)} (== {page.url.pathname})
+</p>
+
+{@render children()}
diff --git a/packages/kit/test/apps/async/src/routes/remote/query-redirect/from-page/+page.svelte b/packages/kit/test/apps/async/src/routes/remote/query-redirect/from-page/+page.svelte
new file mode 100644 (file)
index 0000000..8cad914
--- /dev/null
@@ -0,0 +1,8 @@
+<script>
+       import { pageRedirect } from '../redirect.remote';
+</script>
+
+<svelte:boundary>
+       {await pageRedirect()}
+       <p>should never see this</p>
+</svelte:boundary>
diff --git a/packages/kit/test/apps/async/src/routes/remote/transport/+page.svelte b/packages/kit/test/apps/async/src/routes/remote/transport/+page.svelte
new file mode 100644 (file)
index 0000000..43a5205
--- /dev/null
@@ -0,0 +1,5 @@
+<script>
+       import { greeting } from './data.remote.js';
+</script>
+
+<h1>{(await greeting()).bar()}</h1>
diff --git a/packages/kit/test/apps/async/static/robots.txt b/packages/kit/test/apps/async/static/robots.txt
new file mode 100644 (file)
index 0000000..b6dd667
--- /dev/null
@@ -0,0 +1,3 @@
+# allow crawling everything by default
+User-agent: *
+Disallow:
diff --git a/packages/kit/test/apps/async/svelte.config.js b/packages/kit/test/apps/async/svelte.config.js
new file mode 100644 (file)
index 0000000..d5098a9
--- /dev/null
@@ -0,0 +1,16 @@
+/** @type {import('@sveltejs/kit').Config} */
+const config = {
+       compilerOptions: {
+               experimental: {
+                       async: true
+               }
+       },
+
+       kit: {
+               experimental: {
+                       remoteFunctions: true
+               }
+       }
+};
+
+export default config;
diff --git a/packages/kit/test/apps/async/test/client.test.js b/packages/kit/test/apps/async/test/client.test.js
new file mode 100644 (file)
index 0000000..8266b5a
--- /dev/null
@@ -0,0 +1,310 @@
+import process from 'node:process';
+import { expect } from '@playwright/test';
+import { test } from '../../../utils.js';
+
+test.skip(({ javaScriptEnabled }) => !javaScriptEnabled);
+
+test.describe('remote functions', () => {
+       test('preloading data works when the page component and server load both import a remote function', async ({
+               page
+       }) => {
+               test.skip(!process.env.DEV, 'remote functions are only analysed in dev mode');
+               await page.goto('/remote/dev');
+               await page.locator('a[href="/remote/dev/preload"]').hover();
+               await Promise.all([
+                       page.waitForTimeout(100), // wait for preloading to start
+                       page.waitForLoadState('networkidle') // wait for preloading to finish
+               ]);
+               await page.click('a[href="/remote/dev/preload"]');
+               await expect(page.locator('p')).toHaveText('foobar');
+       });
+});
+
+// have to run in serial because commands mutate in-memory data on the server (should fix this at some point)
+test.describe('remote function mutations', () => {
+       test.afterEach(async ({ page }) => {
+               if (page.url().endsWith('/remote')) {
+                       await page.click('#reset-btn');
+               }
+       });
+
+       test('query.set works', async ({ page }) => {
+               await page.goto('/remote');
+               let request_count = 0;
+               page.on('request', (r) => (request_count += r.url().includes('/_app/remote') ? 1 : 0));
+
+               await page.click('#set-btn');
+               await expect(page.locator('#count-result')).toHaveText('999 / 999 (false)');
+               await page.waitForTimeout(100); // allow all requests to finish (in case there are query refreshes which shouldn't happen)
+               expect(request_count).toBe(0);
+       });
+
+       test('hydrated data is reused', async ({ page }) => {
+               let request_count = 0;
+               page.on('request', (r) => (request_count += r.url().includes('/_app/remote') ? 1 : 0));
+
+               await page.goto('/remote');
+               await expect(page.locator('#count-result')).toHaveText('0 / 0 (false)');
+               // only the calls in the template are done, not the one in the load function
+               expect(request_count).toBe(2);
+       });
+
+       test('command returns correct sum but does not refresh data by default', async ({ page }) => {
+               await page.goto('/remote');
+               await expect(page.locator('#count-result')).toHaveText('0 / 0 (false)');
+
+               let request_count = 0;
+               page.on('request', (r) => (request_count += r.url().includes('/_app/remote') ? 1 : 0));
+
+               await page.click('#multiply-btn');
+               await expect(page.locator('#command-result')).toHaveText('2');
+               await expect(page.locator('#count-result')).toHaveText('0 / 0 (false)');
+               await page.waitForTimeout(100); // allow all requests to finish
+               expect(request_count).toBe(1); // 1 for the command, no refreshes
+       });
+
+       test('command returns correct sum and does client-initiated single flight mutation', async ({
+               page
+       }) => {
+               await page.goto('/remote');
+               await expect(page.locator('#count-result')).toHaveText('0 / 0 (false)');
+
+               let request_count = 0;
+               page.on('request', (r) => (request_count += r.url().includes('/_app/remote') ? 1 : 0));
+
+               await page.click('#multiply-refresh-btn');
+               await expect(page.locator('#command-result')).toHaveText('3');
+               await expect(page.locator('#count-result')).toHaveText('3 / 3 (false)');
+               await page.waitForTimeout(100); // allow all requests to finish
+               expect(request_count).toBe(1); // no query refreshes, since that happens as part of the command response
+       });
+
+       test('command does server-initiated single flight mutation (refresh)', async ({ page }) => {
+               await page.goto('/remote');
+               await expect(page.locator('#count-result')).toHaveText('0 / 0 (false)');
+
+               let request_count = 0;
+               page.on('request', (r) => (request_count += r.url().includes('/_app/remote') ? 1 : 0));
+
+               await page.click('#multiply-server-refresh-btn');
+               await expect(page.locator('#command-result')).toHaveText('4');
+               await expect(page.locator('#count-result')).toHaveText('4 / 4 (false)');
+               await page.waitForTimeout(100); // allow all requests to finish (in case there are query refreshes which shouldn't happen)
+               expect(request_count).toBe(1); // no query refreshes, since that happens as part of the command response
+       });
+
+       test('command refresh after reading query reruns the query', async ({ page }) => {
+               await page.goto('/remote');
+               await expect(page.locator('#count-result')).toHaveText('0 / 0 (false)');
+
+               let request_count = 0;
+               page.on('request', (r) => (request_count += r.url().includes('/_app/remote') ? 1 : 0));
+
+               await page.click('#multiply-server-refresh-after-read-btn');
+               await expect(page.locator('#command-result')).toHaveText('6');
+               await expect(page.locator('#count-result')).toHaveText('6 / 6 (false)');
+               await page.waitForTimeout(100); // allow all requests to finish (in case there are query refreshes which shouldn't happen)
+               expect(request_count).toBe(1);
+       });
+
+       test('command does server-initiated single flight mutation (set)', async ({ page }) => {
+               await page.goto('/remote');
+               await expect(page.locator('#count-result')).toHaveText('0 / 0 (false)');
+
+               let request_count = 0;
+               page.on('request', (r) => (request_count += r.url().includes('/_app/remote') ? 1 : 0));
+
+               await page.click('#multiply-server-set-btn');
+               await expect(page.locator('#command-result')).toHaveText('8');
+               await expect(page.locator('#count-result')).toHaveText('8 / 8 (false)');
+               await page.waitForTimeout(100); // allow all requests to finish (in case there are query refreshes which shouldn't happen)
+               expect(request_count).toBe(1); // no query refreshes, since that happens as part of the command response
+       });
+
+       test('command does client-initiated single flight mutation with override', async ({ page }) => {
+               await page.goto('/remote');
+               await expect(page.locator('#count-result')).toHaveText('0 / 0 (false)');
+
+               let request_count = 0;
+               page.on('request', (r) => (request_count += r.url().includes('/_app/remote') ? 1 : 0));
+
+               page.click('#multiply-override-refresh-btn');
+               await expect(page.locator('#count-result')).toHaveText('6 / 6 (false)');
+               await expect(page.locator('#command-result')).toHaveText('5');
+               await expect(page.locator('#count-result')).toHaveText('5 / 5 (false)');
+               await page.waitForTimeout(100); // allow all requests to finish (in case there are query refreshes which shouldn't happen)
+               expect(request_count).toBe(1); // no query refreshes, since that happens as part of the command response
+       });
+
+       test('query/command inside endpoint works', async ({ page }) => {
+               await page.goto('/remote/server-endpoint');
+
+               await page.getByRole('button', { name: 'get' }).click();
+               await expect(page.locator('p')).toHaveText('get');
+
+               await page.getByRole('button', { name: 'post' }).click();
+               await expect(page.locator('p')).toHaveText('post');
+       });
+
+       test('prerendered entries not called in prod', async ({ page }) => {
+               let request_count = 0;
+               page.on('request', (r) => (request_count += r.url().includes('/_app/remote') ? 1 : 0));
+               await page.goto('/remote/prerender');
+
+               await page.click('#fetch-prerendered');
+               await expect(page.locator('#fetch-prerendered')).toHaveText('yes');
+
+               await page.click('#fetch-not-prerendered');
+               await expect(page.locator('#fetch-not-prerendered')).toHaveText('d');
+       });
+
+       test('refreshAll reloads remote functions and load functions', async ({ page }) => {
+               await page.goto('/remote');
+               await expect(page.locator('#count-result')).toHaveText('0 / 0 (false)');
+
+               let request_count = 0;
+               page.on('request', (r) => (request_count += r.url().includes('/_app/remote') ? 1 : 0));
+
+               await page.click('#refresh-all');
+               await page.waitForTimeout(100); // allow things to rerun
+               expect(request_count).toBe(3);
+       });
+
+       test('refreshAll({ includeLoadFunctions: false }) reloads remote functions only', async ({
+               page
+       }) => {
+               await page.goto('/remote');
+               await expect(page.locator('#count-result')).toHaveText('0 / 0 (false)');
+
+               let request_count = 0;
+               page.on('request', (r) => (request_count += r.url().includes('/_app/remote') ? 1 : 0));
+
+               await page.click('#refresh-remote-only');
+               await page.waitForTimeout(100); // allow things to rerun
+               expect(request_count).toBe(2);
+       });
+
+       test('command tracks pending state', async ({ page }) => {
+               await page.goto('/remote');
+
+               // Initial pending should be 0
+               await expect(page.locator('#command-pending')).toHaveText('Command pending: 0');
+
+               // Start a slow command - this will hang until we resolve it
+               await page.click('#command-deferred-btn');
+
+               // Check that pending has incremented to 1
+               await expect(page.locator('#command-pending')).toHaveText('Command pending: 1');
+
+               // Resolve the deferred command
+               await page.click('#resolve-deferreds');
+
+               // Wait for the command to complete and pending to go back to 0
+               await expect(page.locator('#command-pending')).toHaveText('Command pending: 0');
+       });
+
+       test('validation works', async ({ page }) => {
+               await page.goto('/remote/validation');
+               await expect(page.locator('p')).toHaveText('pending');
+
+               await page.click('button:nth-of-type(1)');
+               await expect(page.locator('p')).toHaveText('success');
+
+               await page.click('button:nth-of-type(2)');
+               await expect(page.locator('p')).toHaveText('success');
+
+               await page.click('button:nth-of-type(3)');
+               await expect(page.locator('p')).toHaveText('success');
+
+               await page.click('button:nth-of-type(4)');
+               await expect(page.locator('p')).toHaveText('success');
+       });
+
+       test('fields.set updates DOM before validate', async ({ page }) => {
+               await page.goto('/remote/form/imperative');
+
+               const input = page.locator('input[name="message"]');
+               await input.fill('123');
+
+               await page.locator('#set-and-validate').click();
+
+               await expect(input).toHaveValue('hello');
+               await expect(page.locator('#issue')).toHaveText('ok');
+       });
+
+       test('command pending state is tracked correctly', async ({ page }) => {
+               await page.goto('/remote');
+
+               // Initially no pending commands
+               await expect(page.locator('#command-pending')).toHaveText('Command pending: 0');
+
+               // Start a slow command - this will hang until we resolve it
+               await page.click('#command-deferred-btn');
+
+               // Check that pending has incremented to 1
+               await expect(page.locator('#command-pending')).toHaveText('Command pending: 1');
+
+               // Resolve the deferred command
+               await page.click('#resolve-deferreds');
+
+               // Wait for the command to complete and verify results
+               await expect(page.locator('#command-result')).toHaveText('7');
+
+               // Verify pending count returns to 0
+               await expect(page.locator('#command-pending')).toHaveText('Command pending: 0');
+       });
+
+       // TODO once we have async SSR adjust the test and move this into test.js
+       test('query.batch works', async ({ page }) => {
+               await page.goto('/remote/batch');
+
+               await expect(page.locator('#batch-result-1')).toHaveText('Buy groceries');
+               await expect(page.locator('#batch-result-2')).toHaveText('Walk the dog');
+               await expect(page.locator('#batch-result-3')).toHaveText('Buy groceries');
+               await expect(page.locator('#batch-result-4')).toHaveText('Error loading todo error: Not found');
+
+               let request_count = 0;
+               page.on('request', (r) => (request_count += r.url().includes('/_app/remote') ? 1 : 0));
+
+               await page.click('button');
+               await page.waitForTimeout(100); // allow all requests to finish
+               expect(request_count).toBe(1);
+       });
+
+       test('query.batch set updates cache without extra request', async ({ page }) => {
+               await page.goto('/remote/batch');
+               await page.click('#batch-reset-btn');
+               await expect(page.locator('#batch-result-1')).toHaveText('Buy groceries');
+
+               let request_count = 0;
+               const handler = (r) => (request_count += r.url().includes('/_app/remote') ? 1 : 0);
+               page.on('request', handler);
+
+               await page.click('#batch-set-btn');
+               await expect(page.locator('#batch-result-1')).toHaveText('Buy cat food');
+               await page.waitForTimeout(100); // allow all requests to finish
+               expect(request_count).toBe(1); // only the command request
+       });
+
+       test('query.batch refresh in command reuses single flight', async ({ page }) => {
+               await page.goto('/remote/batch');
+               await page.click('#batch-reset-btn');
+               await expect(page.locator('#batch-result-2')).toHaveText('Walk the dog');
+
+               let request_count = 0;
+               const handler = (r) => (request_count += r.url().includes('/_app/remote') ? 1 : 0);
+               page.on('request', handler);
+
+               await page.click('#batch-refresh-btn');
+               await expect(page.locator('#batch-result-2')).toHaveText('Walk the dog (refreshed)');
+               await page.waitForTimeout(100); // allow all requests to finish
+               expect(request_count).toBe(1); // only the command request
+       });
+
+       // TODO ditto
+       test('query works with transport', async ({ page }) => {
+               await page.goto('/remote/transport');
+
+               await expect(page.locator('h1')).toHaveText('hello from remote function!');
+       });
+});
diff --git a/packages/kit/test/apps/async/test/server.test.js b/packages/kit/test/apps/async/test/server.test.js
new file mode 100644 (file)
index 0000000..69412ea
--- /dev/null
@@ -0,0 +1,17 @@
+import process from 'node:process';
+import { expect } from '@playwright/test';
+import { test } from '../../../utils.js';
+import fs from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+test.skip(({ javaScriptEnabled }) => javaScriptEnabled);
+
+const root = path.resolve(fileURLToPath(import.meta.url), '..', '..');
+
+test.describe('remote functions', () => {
+       test("doesn't write bundle to disk when treeshaking prerendered remote functions", () => {
+               test.skip(!!process.env.DEV, 'skip when in dev mode');
+               expect(fs.existsSync(path.join(root, 'dist'))).toBe(false);
+       });
+});
diff --git a/packages/kit/test/apps/async/test/test.js b/packages/kit/test/apps/async/test/test.js
new file mode 100644 (file)
index 0000000..bf647f6
--- /dev/null
@@ -0,0 +1,486 @@
+import { expect } from '@playwright/test';
+import { test } from '../../../utils.js';
+
+test.describe('remote functions', () => {
+       test('query returns correct data', async ({ page, javaScriptEnabled }) => {
+               await page.goto('/remote');
+               await expect(page.locator('#echo-result')).toHaveText('Hello world');
+               if (javaScriptEnabled) {
+                       await expect(page.locator('#count-result')).toHaveText('0 / 0 (false)');
+               }
+       });
+
+       test('query redirects on page load (query in common layout)', async ({ page }) => {
+               await page.goto('/remote/query-redirect');
+               await page.click('a[href="/remote/query-redirect/from-common-layout"]');
+               await expect(page.locator('#redirected')).toHaveText('redirected');
+               await expect(page.locator('#layout-query')).toHaveText(
+                       'on page /remote/query-redirect/from-common-layout/redirected (== /remote/query-redirect/from-common-layout/redirected)'
+               );
+       });
+
+       test('query redirects on page load (query on page)', async ({ page }) => {
+               await page.goto('/remote/query-redirect');
+               await page.click('a[href="/remote/query-redirect/from-page"]');
+               await expect(page.locator('#redirected')).toHaveText('redirected');
+       });
+
+       test('non-exported queries do not clobber each other', async ({ page }) => {
+               await page.goto('/remote/query-non-exported');
+
+               await expect(page.locator('h1')).toHaveText('3');
+       });
+
+       test('queries can access the route/url of the page they were called from', async ({
+               page,
+               clicknav
+       }) => {
+               await page.goto('/remote');
+
+               await clicknav('[href="/remote/event"]');
+
+               await expect(page.locator('[data-id="route"]')).toHaveText('route: /remote/event');
+               await expect(page.locator('[data-id="pathname"]')).toHaveText('pathname: /remote/event');
+       });
+
+       test('form works', async ({ page, javaScriptEnabled }) => {
+               await page.goto(`/remote/form/basic-${javaScriptEnabled}`);
+
+               if (javaScriptEnabled) {
+                       await expect(page.getByText('message.current:')).toHaveText('message.current: initial');
+               }
+               await expect(page.getByText('await get_message():')).toHaveText('await get_message(): initial');
+
+               await page.fill('[data-unscoped] input', 'hello');
+               await page.getByText('set message').click();
+
+               if (javaScriptEnabled) {
+                       await expect(page.getByText('set_message.pending:')).toHaveText('set_message.pending: 1');
+                       await page.getByText('resolve deferreds').click();
+                       await expect(page.getByText('set_message.pending:')).toHaveText('set_message.pending: 0');
+                       await expect(page.getByText('message.current:')).toHaveText('message.current: hello');
+               }
+
+               await expect(page.getByText('await get_message():')).toHaveText('await get_message(): hello');
+
+               await expect(page.getByText('set_message.result')).toHaveText('set_message.result: hello');
+               await expect(page.locator('[data-unscoped] input[name="message"]')).toHaveValue('');
+       });
+
+       test('form submitters work', async ({ page }) => {
+               await page.goto('/remote/form/submitter');
+
+               await page.locator('button').click();
+
+               await expect(page.locator('#result')).toHaveText('hello');
+       });
+
+       test('form updates inputs live', async ({ page, javaScriptEnabled }) => {
+               await page.goto('/remote/form/live-update');
+
+               await page.fill('input', 'hello');
+
+               if (javaScriptEnabled) {
+                       await expect(page.getByText('set_message.input.message:')).toHaveText(
+                               'set_message.input.message: hello'
+                       );
+               }
+
+               await page.getByText('set message').click();
+
+               if (javaScriptEnabled) {
+                       await page.getByText('resolve deferreds').click();
+               }
+
+               await expect(page.getByText('set_message.input.message:')).toHaveText(
+                       'set_message.input.message:'
+               );
+       });
+
+       test('form reports validation issues', async ({ page }) => {
+               await page.goto('/remote/form/validation-issues');
+
+               await page.fill('input', 'invalid');
+               await page.getByText('set message').click();
+
+               await page.getByText('message is invalid').waitFor();
+       });
+
+       test('form handles unexpected error', async ({ page }) => {
+               await page.goto('/remote/form/unexpected-error');
+
+               await page.fill('input', 'unexpected error');
+               await page.getByText('set message').click();
+
+               await page
+                       .getByText('This is your custom error page saying: "oops (500 Internal Error)"')
+                       .waitFor();
+       });
+
+       test('form handles expected error', async ({ page }) => {
+               await page.goto('/remote/form/expected-error');
+
+               await page.fill('input', 'expected error');
+               await page.getByText('set message').click();
+
+               await page.getByText('This is your custom error page saying: "oops"').waitFor();
+       });
+
+       test('form redirects', async ({ page }) => {
+               await page.goto('/remote/form/redirect');
+
+               await page.fill('input', 'redirect');
+               await page.getByText('set message').click();
+
+               await page.waitForURL('/remote');
+       });
+
+       test('form.buttonProps works', async ({ page, javaScriptEnabled }) => {
+               await page.goto('/remote/form/button-props');
+
+               await page.fill('[data-unscoped] input', 'backwards');
+               await page.getByText('set reverse message').click();
+
+               if (javaScriptEnabled) {
+                       await page.getByText('message.current: sdrawkcab').waitFor();
+                       await expect(page.getByText('await get_message():')).toHaveText(
+                               'await get_message(): sdrawkcab'
+                       );
+               }
+
+               await expect(page.getByText('set_reverse_message.result')).toHaveText(
+                       'set_reverse_message.result: sdrawkcab'
+               );
+       });
+
+       test('form scoping with for(...) works', async ({ page, javaScriptEnabled }) => {
+               await page.goto('/remote/form/form-scoped');
+
+               await page.fill('[data-scoped] input', 'hello');
+               await page.getByText('set scoped message').click();
+
+               if (javaScriptEnabled) {
+                       await expect(page.getByText('scoped.pending:')).toHaveText('scoped.pending: 1');
+                       await page.getByText('resolve deferreds').click();
+                       await expect(page.getByText('scoped.pending:')).toHaveText('scoped.pending: 0');
+
+                       await page.getByText('message.current: hello').waitFor();
+                       await expect(page.getByText('await get_message():')).toHaveText('await get_message(): hello');
+               }
+
+               await expect(page.getByText('scoped.result')).toHaveText(
+                       'scoped.result: hello (from: scoped:form-scoped)'
+               );
+               await expect(page.locator('[data-scoped] input[name="message"]')).toHaveValue('');
+       });
+
+       test('form enhance(...) works', async ({ page, javaScriptEnabled }) => {
+               await page.goto('/remote/form/enhanced');
+
+               await page.fill('[data-enhanced] input', 'hello');
+
+               // Click on the span inside the button to test the event.target vs event.currentTarget issue (#14159)
+               await page.locator('[data-enhanced] span').click();
+
+               if (javaScriptEnabled) {
+                       await expect(page.getByText('enhanced.pending:')).toHaveText('enhanced.pending: 1');
+
+                       await page.getByText('message.current: hello (override)').waitFor();
+
+                       await page.getByText('resolve deferreds').click();
+                       await expect(page.getByText('enhanced.pending:')).toHaveText('enhanced.pending: 0');
+                       await expect(page.getByText('await get_message():')).toHaveText('await get_message(): hello');
+
+                       // enhanced submission should not clear the input; the developer must do that at the appropriate time
+                       await expect(page.locator('[data-enhanced] input[name="message"]')).toHaveValue('hello');
+               } else {
+                       await expect(page.locator('[data-enhanced] input[name="message"]')).toHaveValue('');
+               }
+
+               await expect(page.getByText('enhanced.result')).toHaveText(
+                       'enhanced.result: hello (from: enhanced:enhanced)'
+               );
+       });
+
+       test('form preflight works', async ({ page, javaScriptEnabled }) => {
+               if (!javaScriptEnabled) return;
+
+               await page.goto('/remote/form/preflight');
+
+               for (const enhanced of [true, false]) {
+                       const input = page.locator(enhanced ? '[data-enhanced] input' : '[data-default] input');
+                       const button = page.getByText(enhanced ? 'set enhanced number' : 'set number');
+
+                       await input.fill('21');
+                       await button.click();
+                       await page.getByText('too big').waitFor();
+
+                       await input.fill('9');
+                       await button.click();
+                       await page.getByText('too small').waitFor();
+
+                       await input.fill('15');
+                       await button.click();
+                       await expect(page.getByText('number.current')).toHaveText('number.current: 15');
+               }
+       });
+
+       test('form preflight-only validation works', async ({ page, javaScriptEnabled }) => {
+               if (!javaScriptEnabled) return;
+
+               await page.goto('/remote/form/preflight-only');
+
+               const a = page.locator('[name="a"]');
+               const button = page.locator('button');
+               const issues = page.locator('.issues');
+
+               await button.click();
+               await expect(issues).toContainText('a is too short');
+               await expect(issues).toContainText('b is too short');
+               await expect(issues).toContainText('c is too short');
+
+               await a.fill('aaaaaaaa');
+               await expect(issues).toContainText('a is too long');
+
+               // server issues should be preserved...
+               await expect(issues).toContainText('b is too short');
+               await expect(issues).toContainText('c is too short');
+
+               // ...unless overridden by client issues
+               await expect(issues).not.toContainText('a is too short');
+       });
+
+       test('form validate works', async ({ page, javaScriptEnabled }) => {
+               if (!javaScriptEnabled) return;
+
+               await page.goto('/remote/form/validate');
+
+               const myForm = page.locator('form#my-form');
+               const foo = page.locator('input[name="foo"]');
+               const bar = page.locator('input[name="bar"]');
+               const submit = page.locator('button:has-text("imperative validation")');
+
+               await foo.fill('a');
+               await expect(myForm).not.toContainText('Invalid type: Expected');
+
+               await bar.fill('g');
+               await expect(myForm).toContainText('Invalid type: Expected ("d" | "e") but received "g"');
+
+               await bar.fill('d');
+               await expect(myForm).not.toContainText('Invalid type: Expected');
+
+               await page.locator('#trigger-validate').click();
+               await expect(myForm).toContainText(
+                       'Invalid type: Expected "submitter" but received "incorrect_value"'
+               );
+
+               // Test imperative validation
+               await foo.fill('c');
+               await bar.fill('d');
+               await submit.click();
+               await expect(myForm).toContainText('Imperative: foo cannot be c');
+
+               const nestedValue = page.locator('input[name="nested.value"]');
+               const validate = page.locator('button#validate');
+               const allIssues = page.locator('#allIssues');
+
+               await nestedValue.fill('in');
+               await validate.click();
+               await expect(allIssues).toContainText('"path":["nested","value"]');
+       });
+
+       test('form validation issues cleared', async ({ page, javaScriptEnabled }) => {
+               if (!javaScriptEnabled) return;
+
+               await page.goto('/remote/form/validate');
+
+               const baz = page.locator('input[name="baz"]');
+               const submit = page.locator('#my-form-2 button');
+
+               await baz.fill('c');
+               await submit.click();
+               await expect(page.locator('#my-form-2')).toContainText('Invalid type: Expected');
+
+               await baz.fill('a');
+               await submit.click();
+               await expect(page.locator('#my-form-2')).not.toContainText('Invalid type: Expected');
+               await expect(page.locator('[data-error]')).toHaveText('An error occurred');
+
+               await baz.fill('c');
+               await submit.click();
+               await expect(page.locator('#my-form-2')).toContainText('Invalid type: Expected');
+
+               await baz.fill('b');
+               await submit.click();
+               await expect(page.locator('#my-form-2')).not.toContainText('Invalid type: Expected');
+               await expect(page.locator('[data-error]')).toHaveText('No error');
+       });
+
+       test('form inputs excludes underscore-prefixed fields', async ({ page, javaScriptEnabled }) => {
+               if (javaScriptEnabled) return;
+
+               await page.goto('/remote/form/underscore');
+
+               await page.fill('input[name="username"]', 'abcdefg');
+               await page.fill('input[name="_password"]', 'pqrstuv');
+               await page.locator('button').click();
+
+               await expect(page.locator('input[name="username"]')).toHaveValue('abcdefg');
+               await expect(page.locator('input[name="_password"]')).toHaveValue('');
+       });
+
+       test('prerendered entries not called in prod', async ({ page, clicknav }) => {
+               await page.goto('/remote/prerender');
+               await clicknav('[href="/remote/prerender/whole-page"]');
+               await expect(page.locator('#prerendered-data')).toHaveText('a c ä¸­æ–‡ yes');
+
+               await page.goto('/remote/prerender');
+               await clicknav('[href="/remote/prerender/functions-only"]');
+               await expect(page.locator('#prerendered-data')).toHaveText('a c ä¸­æ–‡ yes');
+       });
+
+       test('form.fields.value() returns correct nested object structure', async ({
+               page,
+               javaScriptEnabled
+       }) => {
+               if (!javaScriptEnabled) return;
+
+               await page.goto('/remote/form/value');
+
+               // Initially should be empty object or undefined values
+               const initialValue = await page.locator('#full-value').textContent();
+               expect(JSON.parse(initialValue)).toEqual({});
+
+               // Fill leaf field
+               await page.fill('input[name="leaf"]', 'leaf-value');
+               const afterLeaf = await page.locator('#full-value').textContent();
+               expect(JSON.parse(afterLeaf)).toEqual({
+                       leaf: 'leaf-value'
+               });
+
+               // Fill object.leaf field
+               await page.fill('input[name="object.leaf"]', 'object-leaf-value');
+               const afterObjectLeaf = await page.locator('#full-value').textContent();
+               expect(JSON.parse(afterObjectLeaf)).toEqual({
+                       leaf: 'leaf-value',
+                       object: {
+                               leaf: 'object-leaf-value'
+                       }
+               });
+
+               // Fill object.array fields
+               await page.fill('input[name="object.array[0]"]', 'array-item-1');
+               const afterArrayItem1 = await page.locator('#full-value').textContent();
+               expect(JSON.parse(afterArrayItem1)).toEqual({
+                       leaf: 'leaf-value',
+                       object: {
+                               leaf: 'object-leaf-value',
+                               array: ['array-item-1']
+                       }
+               });
+
+               await page.fill('input[name="object.array[1]"]', 'array-item-2');
+               const afterArrayItem2 = await page.locator('#full-value').textContent();
+               expect(JSON.parse(afterArrayItem2)).toEqual({
+                       leaf: 'leaf-value',
+                       object: {
+                               leaf: 'object-leaf-value',
+                               array: ['array-item-1', 'array-item-2']
+                       }
+               });
+
+               // Fill array[0].leaf field
+               await page.fill('input[name="array[0].leaf"]', 'array-0-leaf');
+               const afterArray0 = await page.locator('#full-value').textContent();
+               expect(JSON.parse(afterArray0)).toEqual({
+                       leaf: 'leaf-value',
+                       object: {
+                               leaf: 'object-leaf-value',
+                               array: ['array-item-1', 'array-item-2']
+                       },
+                       array: [{ leaf: 'array-0-leaf' }]
+               });
+
+               // Fill array[1].leaf field
+               await page.fill('input[name="array[1].leaf"]', 'array-1-leaf');
+               const afterArray1 = await page.locator('#full-value').textContent();
+               expect(JSON.parse(afterArray1)).toEqual({
+                       leaf: 'leaf-value',
+                       object: {
+                               leaf: 'object-leaf-value',
+                               array: ['array-item-1', 'array-item-2']
+                       },
+                       array: [{ leaf: 'array-0-leaf' }, { leaf: 'array-1-leaf' }]
+               });
+
+               // Test nested object value access
+               const objectValue = await page.locator('#object-value').textContent();
+               expect(JSON.parse(objectValue)).toEqual({
+                       leaf: 'object-leaf-value',
+                       array: ['array-item-1', 'array-item-2']
+               });
+
+               // Test array value access
+               const arrayValue = await page.locator('#array-value').textContent();
+               expect(JSON.parse(arrayValue)).toEqual([{ leaf: 'array-0-leaf' }, { leaf: 'array-1-leaf' }]);
+       });
+
+       test('selects are not nuked when unrelated controls change', async ({
+               page,
+               javaScriptEnabled
+       }) => {
+               if (!javaScriptEnabled) return;
+
+               await page.goto('/remote/form/select-untouched');
+
+               await page.fill('input', 'hello');
+               await expect(page.locator('select')).toHaveValue('one');
+       });
+       test('file uploads work', async ({ page }) => {
+               await page.goto('/remote/form/file-upload');
+
+               await page.locator('input[name="file1"]').setInputFiles({
+                       name: 'a.txt',
+                       mimeType: 'text/plain',
+                       buffer: Buffer.from('a')
+               });
+               await page.locator('input[name="file2"]').setInputFiles({
+                       name: 'b.txt',
+                       mimeType: 'text/plain',
+                       buffer: Buffer.from('b')
+               });
+               await page.locator('input[type="checkbox"]').check();
+               await page.locator('button').click();
+
+               await expect(page.locator('pre')).toHaveText(
+                       JSON.stringify({
+                               text: 'Hello world',
+                               file1: 'a',
+                               file2: 'b'
+                       })
+               );
+       });
+       test('large file uploads work', async ({ page }) => {
+               await page.goto('/remote/form/file-upload');
+
+               await page.locator('input[name="file1"]').setInputFiles({
+                       name: 'a.txt',
+                       mimeType: 'text/plain',
+                       buffer: Buffer.alloc(1024 * 1024 * 10)
+               });
+               await page.locator('input[name="file2"]').setInputFiles({
+                       name: 'b.txt',
+                       mimeType: 'text/plain',
+                       buffer: Buffer.from('b')
+               });
+               await page.locator('button').click();
+
+               await expect(page.locator('pre')).toHaveText(
+                       JSON.stringify({
+                               text: 'Hello world',
+                               file1: 1024 * 1024 * 10,
+                               file2: 1
+                       })
+               );
+       });
+});
diff --git a/packages/kit/test/apps/async/tsconfig.json b/packages/kit/test/apps/async/tsconfig.json
new file mode 100644 (file)
index 0000000..1d66588
--- /dev/null
@@ -0,0 +1,10 @@
+{
+       "compilerOptions": {
+               "allowJs": true,
+               "checkJs": true,
+               "esModuleInterop": true,
+               "noEmit": true,
+               "resolveJsonModule": true
+       },
+       "extends": "./.svelte-kit/tsconfig.json"
+}
diff --git a/packages/kit/test/apps/async/vite.config.js b/packages/kit/test/apps/async/vite.config.js
new file mode 100644 (file)
index 0000000..69200cd
--- /dev/null
@@ -0,0 +1,18 @@
+import * as path from 'node:path';
+import { sveltekit } from '@sveltejs/kit/vite';
+
+/** @type {import('vite').UserConfig} */
+const config = {
+       build: {
+               minify: false
+       },
+       clearScreen: false,
+       plugins: [sveltekit()],
+       server: {
+               fs: {
+                       allow: [path.resolve('../../../src')]
+               }
+       }
+};
+
+export default config;
index 34d0e9cba874b885b132d206729d2be4992fbf8f..c988355d8eceffae3f6f1a0dc1bbab92aeb20bfe 100644 (file)
@@ -62,11 +62,6 @@ export const handleError = ({ event, error: e, status, message }) => {
                : { message: `${error.message} (${status} ${message})` };
 };
 
-/** @type {import('@sveltejs/kit').HandleValidationError} */
-export const handleValidationError = ({ issues }) => {
-       return { message: issues[0].message };
-};
-
 export const handle = sequence(
        // eslint-disable-next-line prefer-arrow-callback -- this needs a name for tests
        function set_tracing_test_id({ event, resolve }) {
diff --git a/packages/kit/test/apps/basics/src/routes/remote/event/+page.svelte b/packages/kit/test/apps/basics/src/routes/remote/event/+page.svelte
deleted file mode 100644 (file)
index 3aa05f3..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<script>
-       import { get_event } from './data.remote.js';
-</script>
-
-<!-- TODO use inline await when we can -->
-{#await get_event() then event}
-       <p data-id="route">route: {event.route.id}</p>
-       <p data-id="pathname">pathname: {event.url.pathname}</p>
-{/await}
diff --git a/packages/kit/test/apps/basics/src/routes/remote/query-non-exported/+page.svelte b/packages/kit/test/apps/basics/src/routes/remote/query-non-exported/+page.svelte
deleted file mode 100644 (file)
index 5fc45d9..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-<script>
-       import { total } from './data.remote.js';
-</script>
-
-<!-- TODO replace with inline await -->
-{#await total() then t}
-       <h1>{t}</h1>
-{/await}
diff --git a/packages/kit/test/apps/basics/src/routes/remote/query-redirect/+page.svelte b/packages/kit/test/apps/basics/src/routes/remote/query-redirect/+page.svelte
deleted file mode 100644 (file)
index 2cf96c3..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-<a href="/remote/query-redirect/from-page">from page</a>
-<a href="/remote/query-redirect/from-common-layout">from layout</a>
diff --git a/packages/kit/test/apps/basics/src/routes/remote/query-redirect/from-common-layout/+layout.svelte b/packages/kit/test/apps/basics/src/routes/remote/query-redirect/from-common-layout/+layout.svelte
deleted file mode 100644 (file)
index dbfd19f..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-<script>
-       import { page } from '$app/state';
-       import { layoutRedirect } from '../redirect.remote';
-
-       let { children } = $props();
-</script>
-
-<!-- TODO convert to await expression once experimental async turned on -->
-{#await layoutRedirect(page.url.pathname) then path}
-       <p id="layout-query">on page {path} (== {page.url.pathname})</p>
-{/await}
-
-{@render children()}
diff --git a/packages/kit/test/apps/basics/src/routes/remote/query-redirect/from-page/+page.svelte b/packages/kit/test/apps/basics/src/routes/remote/query-redirect/from-page/+page.svelte
deleted file mode 100644 (file)
index fa4783f..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-<script>
-       import { pageRedirect } from '../redirect.remote';
-</script>
-
-<!-- TODO convert to await expression once experimental async turned on -->
-{#await pageRedirect() then _}
-       <p>should never see this</p>
-{/await}
diff --git a/packages/kit/test/apps/basics/src/routes/remote/transport/+page.svelte b/packages/kit/test/apps/basics/src/routes/remote/transport/+page.svelte
deleted file mode 100644 (file)
index 023c95b..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-<script>
-       import { greeting } from './data.remote.js';
-</script>
-
-<!-- TODO use `await` expression once async SSR lands -->
-{#await greeting() then x}
-       <h1>{x.bar()}</h1>
-{/await}
index 350d91d0fbb319e6fc5ecadd2079776dcfe12be4..a6730dd5c1c4194af9b9ab43e6f235716753bebf 100644 (file)
@@ -1800,312 +1800,3 @@ test.describe('routing', () => {
                await expect(page).toHaveURL((url) => url.pathname === '/routing');
        });
 });
-
-test.describe('remote functions', () => {
-       test('preloading data works when the page component and server load both import a remote function', async ({
-               page
-       }) => {
-               test.skip(!process.env.DEV, 'remote functions are only analysed in dev mode');
-               await page.goto('/remote/dev');
-               await page.locator('a[href="/remote/dev/preload"]').hover();
-               await Promise.all([
-                       page.waitForTimeout(100), // wait for preloading to start
-                       page.waitForLoadState('networkidle') // wait for preloading to finish
-               ]);
-               await page.click('a[href="/remote/dev/preload"]');
-               await expect(page.locator('p')).toHaveText('foobar');
-       });
-});
-
-// have to run in serial because commands mutate in-memory data on the server
-test.describe('remote function mutations', () => {
-       test.describe.configure({ mode: 'default' });
-       test.afterEach(async ({ page }) => {
-               if (page.url().endsWith('/remote')) {
-                       await page.click('#reset-btn');
-               }
-       });
-
-       test('query.set works', async ({ page }) => {
-               await page.goto('/remote');
-               let request_count = 0;
-               page.on('request', (r) => (request_count += r.url().includes('/_app/remote') ? 1 : 0));
-
-               await page.click('#set-btn');
-               await expect(page.locator('#count-result')).toHaveText('999 / 999 (false)');
-               await page.waitForTimeout(100); // allow all requests to finish (in case there are query refreshes which shouldn't happen)
-               expect(request_count).toBe(0);
-       });
-
-       test('hydrated data is reused', async ({ page }) => {
-               let request_count = 0;
-               page.on('request', (r) => (request_count += r.url().includes('/_app/remote') ? 1 : 0));
-
-               await page.goto('/remote');
-               await expect(page.locator('#count-result')).toHaveText('0 / 0 (false)');
-               // only the calls in the template are done, not the one in the load function
-               expect(request_count).toBe(2);
-       });
-
-       test('command returns correct sum but does not refresh data by default', async ({ page }) => {
-               await page.goto('/remote');
-               await expect(page.locator('#count-result')).toHaveText('0 / 0 (false)');
-
-               let request_count = 0;
-               page.on('request', (r) => (request_count += r.url().includes('/_app/remote') ? 1 : 0));
-
-               await page.click('#multiply-btn');
-               await expect(page.locator('#command-result')).toHaveText('2');
-               await expect(page.locator('#count-result')).toHaveText('0 / 0 (false)');
-               await page.waitForTimeout(100); // allow all requests to finish
-               expect(request_count).toBe(1); // 1 for the command, no refreshes
-       });
-
-       test('command returns correct sum and does client-initiated single flight mutation', async ({
-               page
-       }) => {
-               await page.goto('/remote');
-               await expect(page.locator('#count-result')).toHaveText('0 / 0 (false)');
-
-               let request_count = 0;
-               page.on('request', (r) => (request_count += r.url().includes('/_app/remote') ? 1 : 0));
-
-               await page.click('#multiply-refresh-btn');
-               await expect(page.locator('#command-result')).toHaveText('3');
-               await expect(page.locator('#count-result')).toHaveText('3 / 3 (false)');
-               await page.waitForTimeout(100); // allow all requests to finish
-               expect(request_count).toBe(1); // no query refreshes, since that happens as part of the command response
-       });
-
-       test('command does server-initiated single flight mutation (refresh)', async ({ page }) => {
-               await page.goto('/remote');
-               await expect(page.locator('#count-result')).toHaveText('0 / 0 (false)');
-
-               let request_count = 0;
-               page.on('request', (r) => (request_count += r.url().includes('/_app/remote') ? 1 : 0));
-
-               await page.click('#multiply-server-refresh-btn');
-               await expect(page.locator('#command-result')).toHaveText('4');
-               await expect(page.locator('#count-result')).toHaveText('4 / 4 (false)');
-               await page.waitForTimeout(100); // allow all requests to finish (in case there are query refreshes which shouldn't happen)
-               expect(request_count).toBe(1); // no query refreshes, since that happens as part of the command response
-       });
-
-       test('command refresh after reading query reruns the query', async ({ page }) => {
-               await page.goto('/remote');
-               await expect(page.locator('#count-result')).toHaveText('0 / 0 (false)');
-
-               let request_count = 0;
-               page.on('request', (r) => (request_count += r.url().includes('/_app/remote') ? 1 : 0));
-
-               await page.click('#multiply-server-refresh-after-read-btn');
-               await expect(page.locator('#command-result')).toHaveText('6');
-               await expect(page.locator('#count-result')).toHaveText('6 / 6 (false)');
-               await page.waitForTimeout(100); // allow all requests to finish (in case there are query refreshes which shouldn't happen)
-               expect(request_count).toBe(1);
-       });
-
-       test('command does server-initiated single flight mutation (set)', async ({ page }) => {
-               await page.goto('/remote');
-               await expect(page.locator('#count-result')).toHaveText('0 / 0 (false)');
-
-               let request_count = 0;
-               page.on('request', (r) => (request_count += r.url().includes('/_app/remote') ? 1 : 0));
-
-               await page.click('#multiply-server-set-btn');
-               await expect(page.locator('#command-result')).toHaveText('8');
-               await expect(page.locator('#count-result')).toHaveText('8 / 8 (false)');
-               await page.waitForTimeout(100); // allow all requests to finish (in case there are query refreshes which shouldn't happen)
-               expect(request_count).toBe(1); // no query refreshes, since that happens as part of the command response
-       });
-
-       test('command does client-initiated single flight mutation with override', async ({ page }) => {
-               await page.goto('/remote');
-               await expect(page.locator('#count-result')).toHaveText('0 / 0 (false)');
-
-               let request_count = 0;
-               page.on('request', (r) => (request_count += r.url().includes('/_app/remote') ? 1 : 0));
-
-               page.click('#multiply-override-refresh-btn');
-               await expect(page.locator('#count-result')).toHaveText('6 / 6 (false)');
-               await expect(page.locator('#command-result')).toHaveText('5');
-               await expect(page.locator('#count-result')).toHaveText('5 / 5 (false)');
-               await page.waitForTimeout(100); // allow all requests to finish (in case there are query refreshes which shouldn't happen)
-               expect(request_count).toBe(1); // no query refreshes, since that happens as part of the command response
-       });
-
-       test('query/command inside endpoint works', async ({ page }) => {
-               await page.goto('/remote/server-endpoint');
-
-               await page.getByRole('button', { name: 'get' }).click();
-               await expect(page.locator('p')).toHaveText('get');
-
-               await page.getByRole('button', { name: 'post' }).click();
-               await expect(page.locator('p')).toHaveText('post');
-       });
-
-       test('prerendered entries not called in prod', async ({ page }) => {
-               let request_count = 0;
-               page.on('request', (r) => (request_count += r.url().includes('/_app/remote') ? 1 : 0));
-               await page.goto('/remote/prerender');
-
-               await page.click('#fetch-prerendered');
-               await expect(page.locator('#fetch-prerendered')).toHaveText('yes');
-
-               await page.click('#fetch-not-prerendered');
-               await expect(page.locator('#fetch-not-prerendered')).toHaveText('d');
-       });
-
-       test('refreshAll reloads remote functions and load functions', async ({ page }) => {
-               await page.goto('/remote');
-               await expect(page.locator('#count-result')).toHaveText('0 / 0 (false)');
-
-               let request_count = 0;
-               page.on('request', (r) => (request_count += r.url().includes('/_app/remote') ? 1 : 0));
-
-               await page.click('#refresh-all');
-               await page.waitForTimeout(100); // allow things to rerun
-               expect(request_count).toBe(3);
-       });
-
-       test('refreshAll({ includeLoadFunctions: false }) reloads remote functions only', async ({
-               page
-       }) => {
-               await page.goto('/remote');
-               await expect(page.locator('#count-result')).toHaveText('0 / 0 (false)');
-
-               let request_count = 0;
-               page.on('request', (r) => (request_count += r.url().includes('/_app/remote') ? 1 : 0));
-
-               await page.click('#refresh-remote-only');
-               await page.waitForTimeout(100); // allow things to rerun
-               expect(request_count).toBe(2);
-       });
-
-       test('command tracks pending state', async ({ page }) => {
-               await page.goto('/remote');
-
-               // Initial pending should be 0
-               await expect(page.locator('#command-pending')).toHaveText('Command pending: 0');
-
-               // Start a slow command - this will hang until we resolve it
-               await page.click('#command-deferred-btn');
-
-               // Check that pending has incremented to 1
-               await expect(page.locator('#command-pending')).toHaveText('Command pending: 1');
-
-               // Resolve the deferred command
-               await page.click('#resolve-deferreds');
-
-               // Wait for the command to complete and pending to go back to 0
-               await expect(page.locator('#command-pending')).toHaveText('Command pending: 0');
-       });
-
-       test('validation works', async ({ page }) => {
-               await page.goto('/remote/validation');
-               await expect(page.locator('p')).toHaveText('pending');
-
-               let request_count = 0;
-               page.on('request', (r) => (request_count += r.url().includes('/_app/remote') ? 1 : 0));
-
-               await page.click('button:nth-of-type(1)');
-               await expect(page.locator('p')).toHaveText('success');
-
-               await page.click('button:nth-of-type(2)');
-               await expect(page.locator('p')).toHaveText('success');
-
-               await page.click('button:nth-of-type(3)');
-               await expect(page.locator('p')).toHaveText('success');
-
-               await page.click('button:nth-of-type(4)');
-               await expect(page.locator('p')).toHaveText('success');
-       });
-
-       test('fields.set updates DOM before validate', async ({ page }) => {
-               await page.goto('/remote/form/imperative');
-
-               const input = page.locator('input[name="message"]');
-               await input.fill('123');
-
-               await page.locator('#set-and-validate').click();
-
-               await expect(input).toHaveValue('hello');
-               await expect(page.locator('#issue')).toHaveText('ok');
-       });
-
-       test('command pending state is tracked correctly', async ({ page }) => {
-               await page.goto('/remote');
-
-               // Initially no pending commands
-               await expect(page.locator('#command-pending')).toHaveText('Command pending: 0');
-
-               // Start a slow command - this will hang until we resolve it
-               await page.click('#command-deferred-btn');
-
-               // Check that pending has incremented to 1
-               await expect(page.locator('#command-pending')).toHaveText('Command pending: 1');
-
-               // Resolve the deferred command
-               await page.click('#resolve-deferreds');
-
-               // Wait for the command to complete and verify results
-               await expect(page.locator('#command-result')).toHaveText('7');
-
-               // Verify pending count returns to 0
-               await expect(page.locator('#command-pending')).toHaveText('Command pending: 0');
-       });
-
-       // TODO once we have async SSR adjust the test and move this into test.js
-       test('query.batch works', async ({ page }) => {
-               await page.goto('/remote/batch');
-
-               await expect(page.locator('#batch-result-1')).toHaveText('Buy groceries');
-               await expect(page.locator('#batch-result-2')).toHaveText('Walk the dog');
-               await expect(page.locator('#batch-result-3')).toHaveText('Buy groceries');
-               await expect(page.locator('#batch-result-4')).toHaveText('Error loading todo error: Not found');
-
-               let request_count = 0;
-               page.on('request', (r) => (request_count += r.url().includes('/_app/remote') ? 1 : 0));
-
-               await page.click('button');
-               await page.waitForTimeout(100); // allow all requests to finish
-               expect(request_count).toBe(1);
-       });
-
-       test('query.batch set updates cache without extra request', async ({ page }) => {
-               await page.goto('/remote/batch');
-               await page.click('#batch-reset-btn');
-               await expect(page.locator('#batch-result-1')).toHaveText('Buy groceries');
-
-               let request_count = 0;
-               const handler = (r) => (request_count += r.url().includes('/_app/remote') ? 1 : 0);
-               page.on('request', handler);
-
-               await page.click('#batch-set-btn');
-               await expect(page.locator('#batch-result-1')).toHaveText('Buy cat food');
-               await page.waitForTimeout(100); // allow all requests to finish
-               expect(request_count).toBe(1); // only the command request
-       });
-
-       test('query.batch refresh in command reuses single flight', async ({ page }) => {
-               await page.goto('/remote/batch');
-               await page.click('#batch-reset-btn');
-               await expect(page.locator('#batch-result-2')).toHaveText('Walk the dog');
-
-               let request_count = 0;
-               const handler = (r) => (request_count += r.url().includes('/_app/remote') ? 1 : 0);
-               page.on('request', handler);
-
-               await page.click('#batch-refresh-btn');
-               await expect(page.locator('#batch-result-2')).toHaveText('Walk the dog (refreshed)');
-               await page.waitForTimeout(100); // allow all requests to finish
-               expect(request_count).toBe(1); // only the command request
-       });
-
-       // TODO ditto
-       test('query works with transport', async ({ page }) => {
-               await page.goto('/remote/transport');
-
-               await expect(page.locator('h1')).toHaveText('hello from remote function!');
-       });
-});
index eea785051107e5092fac01d6c6b68e9341cee1a7..b8a502d92d454ac790fb418ffdd42910eb6d6334 100644 (file)
@@ -1364,13 +1364,6 @@ test.describe('tracing', () => {
        });
 });
 
-test.describe('remote functions', () => {
-       test("doesn't write bundle to disk when treeshaking prerendered remote functions", () => {
-               test.skip(!!process.env.DEV, 'skip when in dev mode');
-               expect(fs.existsSync(path.join(root, 'dist'))).toBe(false);
-       });
-});
-
 test.describe('asset preload', () => {
        if (!process.env.DEV) {
                test('injects Link headers', async ({ request }) => {
index 554a4d8ba59dfed6339dbc29aafe5a671281e848..b29ab7d3447cd342e834fec3ae9cd9469ad37846 100644 (file)
@@ -1600,509 +1600,6 @@ test.describe('getRequestEvent', () => {
        });
 });
 
-test.describe('remote functions', () => {
-       test('query returns correct data', async ({ page, javaScriptEnabled }) => {
-               await page.goto('/remote');
-               await expect(page.locator('#echo-result')).toHaveText('Hello world');
-               if (javaScriptEnabled) {
-                       await expect(page.locator('#count-result')).toHaveText('0 / 0 (false)');
-               }
-       });
-
-       test('query redirects on page load (query in common layout)', async ({
-               page,
-               javaScriptEnabled
-       }) => {
-               // TODO remove once async SSR exists
-               if (!javaScriptEnabled) return;
-
-               await page.goto('/remote/query-redirect');
-               await page.click('a[href="/remote/query-redirect/from-common-layout"]');
-               await expect(page.locator('#redirected')).toHaveText('redirected');
-               await expect(page.locator('#layout-query')).toHaveText(
-                       'on page /remote/query-redirect/from-common-layout/redirected (== /remote/query-redirect/from-common-layout/redirected)'
-               );
-       });
-
-       test('query redirects on page load (query on page)', async ({ page, javaScriptEnabled }) => {
-               // TODO remove once async SSR exists
-               if (!javaScriptEnabled) return;
-
-               await page.goto('/remote/query-redirect');
-               await page.click('a[href="/remote/query-redirect/from-page"]');
-               await expect(page.locator('#redirected')).toHaveText('redirected');
-       });
-
-       test('non-exported queries do not clobber each other', async ({ page, javaScriptEnabled }) => {
-               // TODO remove once async SSR exists
-               if (!javaScriptEnabled) return;
-
-               await page.goto('/remote/query-non-exported');
-
-               await expect(page.locator('h1')).toHaveText('3');
-       });
-
-       test('queries can access the route/url of the page they were called from', async ({
-               page,
-               javaScriptEnabled,
-               clicknav
-       }) => {
-               // TODO remove once async SSR exists
-               if (!javaScriptEnabled) return;
-
-               await page.goto('/remote');
-
-               await clicknav('[href="/remote/event"]');
-
-               await expect(page.locator('[data-id="route"]')).toHaveText('route: /remote/event');
-               await expect(page.locator('[data-id="pathname"]')).toHaveText('pathname: /remote/event');
-       });
-
-       test('form works', async ({ page, javaScriptEnabled }) => {
-               await page.goto('/remote/form/basic');
-
-               if (javaScriptEnabled) {
-                       // TODO remove the `if` â€” once async SSR lands these assertions should always succeed
-                       await expect(page.getByText('message.current:')).toHaveText('message.current: initial');
-                       await expect(page.getByText('await get_message():')).toHaveText(
-                               'await get_message(): initial'
-                       );
-               }
-
-               await page.fill('[data-unscoped] input', 'hello');
-               await page.getByText('set message').click();
-
-               if (javaScriptEnabled) {
-                       await expect(page.getByText('set_message.pending:')).toHaveText('set_message.pending: 1');
-                       await page.getByText('resolve deferreds').click();
-                       await expect(page.getByText('set_message.pending:')).toHaveText('set_message.pending: 0');
-
-                       await expect(page.getByText('message.current:')).toHaveText('message.current: hello');
-                       await expect(page.getByText('await get_message():')).toHaveText('await get_message(): hello');
-               }
-
-               await expect(page.getByText('set_message.result')).toHaveText('set_message.result: hello');
-               await expect(page.locator('[data-unscoped] input[name="message"]')).toHaveValue('');
-       });
-
-       test('form submitters work', async ({ page }) => {
-               await page.goto('/remote/form/submitter');
-
-               await page.locator('button').click();
-
-               await expect(page.locator('#result')).toHaveText('hello');
-       });
-
-       test('form updates inputs live', async ({ page, javaScriptEnabled }) => {
-               await page.goto('/remote/form/live-update');
-
-               await page.fill('input', 'hello');
-
-               if (javaScriptEnabled) {
-                       await expect(page.getByText('set_message.input.message:')).toHaveText(
-                               'set_message.input.message: hello'
-                       );
-               }
-
-               await page.getByText('set message').click();
-
-               if (javaScriptEnabled) {
-                       await page.getByText('resolve deferreds').click();
-               }
-
-               await expect(page.getByText('set_message.input.message:')).toHaveText(
-                       'set_message.input.message:'
-               );
-       });
-
-       test('form reports validation issues', async ({ page }) => {
-               await page.goto('/remote/form/validation-issues');
-
-               await page.fill('input', 'invalid');
-               await page.getByText('set message').click();
-
-               await page.getByText('message is invalid').waitFor();
-       });
-
-       test('form handles unexpected error', async ({ page }) => {
-               await page.goto('/remote/form/unexpected-error');
-
-               await page.fill('input', 'unexpected error');
-               await page.getByText('set message').click();
-
-               await page
-                       .getByText('This is your custom error page saying: "oops (500 Internal Error)"')
-                       .waitFor();
-       });
-
-       test('form handles expected error', async ({ page }) => {
-               await page.goto('/remote/form/expected-error');
-
-               await page.fill('input', 'expected error');
-               await page.getByText('set message').click();
-
-               await page.getByText('This is your custom error page saying: "oops"').waitFor();
-       });
-
-       test('form redirects', async ({ page }) => {
-               await page.goto('/remote/form/redirect');
-
-               await page.fill('input', 'redirect');
-               await page.getByText('set message').click();
-
-               await page.waitForURL('/remote');
-       });
-
-       test('form.buttonProps works', async ({ page, javaScriptEnabled }) => {
-               await page.goto('/remote/form/button-props');
-
-               await page.fill('[data-unscoped] input', 'backwards');
-               await page.getByText('set reverse message').click();
-
-               if (javaScriptEnabled) {
-                       await page.getByText('message.current: sdrawkcab').waitFor();
-                       await expect(page.getByText('await get_message():')).toHaveText(
-                               'await get_message(): sdrawkcab'
-                       );
-               }
-
-               await expect(page.getByText('set_reverse_message.result')).toHaveText(
-                       'set_reverse_message.result: sdrawkcab'
-               );
-       });
-
-       test('form scoping with for(...) works', async ({ page, javaScriptEnabled }) => {
-               await page.goto('/remote/form/form-scoped');
-
-               await page.fill('[data-scoped] input', 'hello');
-               await page.getByText('set scoped message').click();
-
-               if (javaScriptEnabled) {
-                       await expect(page.getByText('scoped.pending:')).toHaveText('scoped.pending: 1');
-                       await page.getByText('resolve deferreds').click();
-                       await expect(page.getByText('scoped.pending:')).toHaveText('scoped.pending: 0');
-
-                       await page.getByText('message.current: hello').waitFor();
-                       await expect(page.getByText('await get_message():')).toHaveText('await get_message(): hello');
-               }
-
-               await expect(page.getByText('scoped.result')).toHaveText(
-                       'scoped.result: hello (from: scoped:form-scoped)'
-               );
-               await expect(page.locator('[data-scoped] input[name="message"]')).toHaveValue('');
-       });
-
-       test('form enhance(...) works', async ({ page, javaScriptEnabled }) => {
-               await page.goto('/remote/form/enhanced');
-
-               await page.fill('[data-enhanced] input', 'hello');
-
-               // Click on the span inside the button to test the event.target vs event.currentTarget issue (#14159)
-               await page.locator('[data-enhanced] span').click();
-
-               if (javaScriptEnabled) {
-                       await expect(page.getByText('enhanced.pending:')).toHaveText('enhanced.pending: 1');
-
-                       await page.getByText('message.current: hello (override)').waitFor();
-
-                       await page.getByText('resolve deferreds').click();
-                       await expect(page.getByText('enhanced.pending:')).toHaveText('enhanced.pending: 0');
-                       await expect(page.getByText('await get_message():')).toHaveText('await get_message(): hello');
-
-                       // enhanced submission should not clear the input; the developer must do that at the appropriate time
-                       await expect(page.locator('[data-enhanced] input[name="message"]')).toHaveValue('hello');
-               } else {
-                       await expect(page.locator('[data-enhanced] input[name="message"]')).toHaveValue('');
-               }
-
-               await expect(page.getByText('enhanced.result')).toHaveText(
-                       'enhanced.result: hello (from: enhanced:enhanced)'
-               );
-       });
-
-       test('form preflight works', async ({ page, javaScriptEnabled }) => {
-               if (!javaScriptEnabled) return;
-
-               await page.goto('/remote/form/preflight');
-
-               for (const enhanced of [true, false]) {
-                       const input = page.locator(enhanced ? '[data-enhanced] input' : '[data-default] input');
-                       const button = page.getByText(enhanced ? 'set enhanced number' : 'set number');
-
-                       await input.fill('21');
-                       await button.click();
-                       await page.getByText('too big').waitFor();
-
-                       await input.fill('9');
-                       await button.click();
-                       await page.getByText('too small').waitFor();
-
-                       await input.fill('15');
-                       await button.click();
-                       await expect(page.getByText('number.current')).toHaveText('number.current: 15');
-               }
-       });
-
-       test('form preflight-only validation works', async ({ page, javaScriptEnabled }) => {
-               if (!javaScriptEnabled) return;
-
-               await page.goto('/remote/form/preflight-only');
-
-               const a = page.locator('[name="a"]');
-               const button = page.locator('button');
-               const issues = page.locator('.issues');
-
-               await button.click();
-               await expect(issues).toContainText('a is too short');
-               await expect(issues).toContainText('b is too short');
-               await expect(issues).toContainText('c is too short');
-
-               await a.fill('aaaaaaaa');
-               await expect(issues).toContainText('a is too long');
-
-               // server issues should be preserved...
-               await expect(issues).toContainText('b is too short');
-               await expect(issues).toContainText('c is too short');
-
-               // ...unless overridden by client issues
-               await expect(issues).not.toContainText('a is too short');
-       });
-
-       test('form validate works', async ({ page, javaScriptEnabled }) => {
-               if (!javaScriptEnabled) return;
-
-               await page.goto('/remote/form/validate');
-
-               const myForm = page.locator('form#my-form');
-               const foo = page.locator('input[name="foo"]');
-               const bar = page.locator('input[name="bar"]');
-               const submit = page.locator('button:has-text("imperative validation")');
-
-               await foo.fill('a');
-               await expect(myForm).not.toContainText('Invalid type: Expected');
-
-               await bar.fill('g');
-               await expect(myForm).toContainText('Invalid type: Expected ("d" | "e") but received "g"');
-
-               await bar.fill('d');
-               await expect(myForm).not.toContainText('Invalid type: Expected');
-
-               await page.locator('#trigger-validate').click();
-               await expect(myForm).toContainText(
-                       'Invalid type: Expected "submitter" but received "incorrect_value"'
-               );
-
-               // Test imperative validation
-               await foo.fill('c');
-               await bar.fill('d');
-               await submit.click();
-               await expect(myForm).toContainText('Imperative: foo cannot be c');
-
-               const nestedValue = page.locator('input[name="nested.value"]');
-               const validate = page.locator('button#validate');
-               const allIssues = page.locator('#allIssues');
-
-               await nestedValue.fill('in');
-               await validate.click();
-               await expect(allIssues).toContainText('"path":["nested","value"]');
-       });
-
-       test('form validation issues cleared', async ({ page, javaScriptEnabled }) => {
-               if (!javaScriptEnabled) return;
-
-               await page.goto('/remote/form/validate');
-
-               const baz = page.locator('input[name="baz"]');
-               const submit = page.locator('#my-form-2 button');
-
-               await baz.fill('c');
-               await submit.click();
-               await expect(page.locator('#my-form-2')).toContainText('Invalid type: Expected');
-
-               await baz.fill('a');
-               await submit.click();
-               await expect(page.locator('#my-form-2')).not.toContainText('Invalid type: Expected');
-               await expect(page.locator('[data-error]')).toHaveText('An error occurred');
-
-               await baz.fill('c');
-               await submit.click();
-               await expect(page.locator('#my-form-2')).toContainText('Invalid type: Expected');
-
-               await baz.fill('b');
-               await submit.click();
-               await expect(page.locator('#my-form-2')).not.toContainText('Invalid type: Expected');
-               await expect(page.locator('[data-error]')).toHaveText('No error');
-       });
-
-       test('form inputs excludes underscore-prefixed fields', async ({ page, javaScriptEnabled }) => {
-               if (javaScriptEnabled) return;
-
-               await page.goto('/remote/form/underscore');
-
-               await page.fill('input[name="username"]', 'abcdefg');
-               await page.fill('input[name="_password"]', 'pqrstuv');
-               await page.locator('button').click();
-
-               await expect(page.locator('input[name="username"]')).toHaveValue('abcdefg');
-               await expect(page.locator('input[name="_password"]')).toHaveValue('');
-       });
-
-       test('prerendered entries not called in prod', async ({ page, clicknav }) => {
-               await page.goto('/remote/prerender');
-               await clicknav('[href="/remote/prerender/whole-page"]');
-               await expect(page.locator('#prerendered-data')).toHaveText('a c ä¸­æ–‡ yes');
-
-               await page.goto('/remote/prerender');
-               await clicknav('[href="/remote/prerender/functions-only"]');
-               await expect(page.locator('#prerendered-data')).toHaveText('a c ä¸­æ–‡ yes');
-       });
-
-       test('form.fields.value() returns correct nested object structure', async ({
-               page,
-               javaScriptEnabled
-       }) => {
-               if (!javaScriptEnabled) return;
-
-               await page.goto('/remote/form/value');
-
-               // Initially should be empty object or undefined values
-               const initialValue = await page.locator('#full-value').textContent();
-               expect(JSON.parse(initialValue)).toEqual({});
-
-               // Fill leaf field
-               await page.fill('input[name="leaf"]', 'leaf-value');
-               const afterLeaf = await page.locator('#full-value').textContent();
-               expect(JSON.parse(afterLeaf)).toEqual({
-                       leaf: 'leaf-value'
-               });
-
-               // Fill object.leaf field
-               await page.fill('input[name="object.leaf"]', 'object-leaf-value');
-               const afterObjectLeaf = await page.locator('#full-value').textContent();
-               expect(JSON.parse(afterObjectLeaf)).toEqual({
-                       leaf: 'leaf-value',
-                       object: {
-                               leaf: 'object-leaf-value'
-                       }
-               });
-
-               // Fill object.array fields
-               await page.fill('input[name="object.array[0]"]', 'array-item-1');
-               const afterArrayItem1 = await page.locator('#full-value').textContent();
-               expect(JSON.parse(afterArrayItem1)).toEqual({
-                       leaf: 'leaf-value',
-                       object: {
-                               leaf: 'object-leaf-value',
-                               array: ['array-item-1']
-                       }
-               });
-
-               await page.fill('input[name="object.array[1]"]', 'array-item-2');
-               const afterArrayItem2 = await page.locator('#full-value').textContent();
-               expect(JSON.parse(afterArrayItem2)).toEqual({
-                       leaf: 'leaf-value',
-                       object: {
-                               leaf: 'object-leaf-value',
-                               array: ['array-item-1', 'array-item-2']
-                       }
-               });
-
-               // Fill array[0].leaf field
-               await page.fill('input[name="array[0].leaf"]', 'array-0-leaf');
-               const afterArray0 = await page.locator('#full-value').textContent();
-               expect(JSON.parse(afterArray0)).toEqual({
-                       leaf: 'leaf-value',
-                       object: {
-                               leaf: 'object-leaf-value',
-                               array: ['array-item-1', 'array-item-2']
-                       },
-                       array: [{ leaf: 'array-0-leaf' }]
-               });
-
-               // Fill array[1].leaf field
-               await page.fill('input[name="array[1].leaf"]', 'array-1-leaf');
-               const afterArray1 = await page.locator('#full-value').textContent();
-               expect(JSON.parse(afterArray1)).toEqual({
-                       leaf: 'leaf-value',
-                       object: {
-                               leaf: 'object-leaf-value',
-                               array: ['array-item-1', 'array-item-2']
-                       },
-                       array: [{ leaf: 'array-0-leaf' }, { leaf: 'array-1-leaf' }]
-               });
-
-               // Test nested object value access
-               const objectValue = await page.locator('#object-value').textContent();
-               expect(JSON.parse(objectValue)).toEqual({
-                       leaf: 'object-leaf-value',
-                       array: ['array-item-1', 'array-item-2']
-               });
-
-               // Test array value access
-               const arrayValue = await page.locator('#array-value').textContent();
-               expect(JSON.parse(arrayValue)).toEqual([{ leaf: 'array-0-leaf' }, { leaf: 'array-1-leaf' }]);
-       });
-
-       test('selects are not nuked when unrelated controls change', async ({
-               page,
-               javaScriptEnabled
-       }) => {
-               if (!javaScriptEnabled) return;
-
-               await page.goto('/remote/form/select-untouched');
-
-               await page.fill('input', 'hello');
-               await expect(page.locator('select')).toHaveValue('one');
-       });
-       test('file uploads work', async ({ page }) => {
-               await page.goto('/remote/form/file-upload');
-
-               await page.locator('input[name="file1"]').setInputFiles({
-                       name: 'a.txt',
-                       mimeType: 'text/plain',
-                       buffer: Buffer.from('a')
-               });
-               await page.locator('input[name="file2"]').setInputFiles({
-                       name: 'b.txt',
-                       mimeType: 'text/plain',
-                       buffer: Buffer.from('b')
-               });
-               await page.locator('input[type="checkbox"]').check();
-               await page.locator('button').click();
-
-               await expect(page.locator('pre')).toHaveText(
-                       JSON.stringify({
-                               text: 'Hello world',
-                               file1: 'a',
-                               file2: 'b'
-                       })
-               );
-       });
-       test('large file uploads work', async ({ page }) => {
-               await page.goto('/remote/form/file-upload');
-
-               await page.locator('input[name="file1"]').setInputFiles({
-                       name: 'a.txt',
-                       mimeType: 'text/plain',
-                       buffer: Buffer.alloc(1024 * 1024 * 10)
-               });
-               await page.locator('input[name="file2"]').setInputFiles({
-                       name: 'b.txt',
-                       mimeType: 'text/plain',
-                       buffer: Buffer.from('b')
-               });
-               await page.locator('button').click();
-
-               await expect(page.locator('pre')).toHaveText(
-                       JSON.stringify({
-                               text: 'Hello world',
-                               file1: 1024 * 1024 * 10,
-                               file2: 1
-                       })
-               );
-       });
-});
-
 test.describe('params prop', () => {
        test('params prop is passed to the page', async ({ page, clicknav }) => {
                await page.goto('/params-prop');
index 911efa0a043c99307d59d94010b4784fb3e39d52..53780fb1c00630c6e702d4a98273ec4852f17e1e 100644 (file)
@@ -94,8 +94,8 @@ catalogs:
       specifier: ^5.42.1
       version: 5.42.2
     svelte-check:
-      specifier: ^4.1.1
-      version: 4.1.1
+      specifier: ^4.3.4
+      version: 4.3.4
     svelte-preprocess:
       specifier: ^6.0.0
       version: 6.0.0
@@ -625,7 +625,7 @@ importers:
         version: 5.42.2
       svelte-check:
         specifier: 'catalog:'
-        version: 4.1.1(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
+        version: 4.3.4(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
       typescript:
         specifier: ^5.5.4
         version: 5.8.3
@@ -633,6 +633,30 @@ importers:
         specifier: 'catalog:'
         version: 6.3.6(@types/node@18.19.119)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0)
 
+  packages/kit/test/apps/async:
+    devDependencies:
+      '@sveltejs/kit':
+        specifier: workspace:^
+        version: link:../../..
+      '@sveltejs/vite-plugin-svelte':
+        specifier: 'catalog:'
+        version: 6.0.0-next.3(svelte@5.42.2)(vite@6.3.6(@types/node@18.19.119)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0))
+      svelte:
+        specifier: 'catalog:'
+        version: 5.42.2
+      svelte-check:
+        specifier: 'catalog:'
+        version: 4.3.4(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
+      typescript:
+        specifier: ^5.5.4
+        version: 5.8.3
+      valibot:
+        specifier: 'catalog:'
+        version: 1.2.0(typescript@5.8.3)
+      vite:
+        specifier: 'catalog:'
+        version: 6.3.6(@types/node@18.19.119)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0)
+
   packages/kit/test/apps/basics:
     devDependencies:
       '@opentelemetry/api':
@@ -658,7 +682,7 @@ importers:
         version: 5.42.2
       svelte-check:
         specifier: 'catalog:'
-        version: 4.1.1(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
+        version: 4.3.4(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
       test-redirect-importer:
         specifier: workspace:*
         version: link:../../../../test-redirect-importer
@@ -718,7 +742,7 @@ importers:
         version: 5.42.2
       svelte-check:
         specifier: 'catalog:'
-        version: 4.1.1(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
+        version: 4.3.4(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
       typescript:
         specifier: ^5.5.4
         version: 5.8.3
@@ -739,7 +763,7 @@ importers:
         version: 5.42.2
       svelte-check:
         specifier: 'catalog:'
-        version: 4.1.1(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
+        version: 4.3.4(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
       typescript:
         specifier: ^5.5.4
         version: 5.8.3
@@ -760,7 +784,7 @@ importers:
         version: 5.42.2
       svelte-check:
         specifier: 'catalog:'
-        version: 4.1.1(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
+        version: 4.3.4(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
       typescript:
         specifier: ^5.5.4
         version: 5.8.3
@@ -781,7 +805,7 @@ importers:
         version: 5.42.2
       svelte-check:
         specifier: 'catalog:'
-        version: 4.1.1(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
+        version: 4.3.4(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
       typescript:
         specifier: ^5.5.4
         version: 5.8.3
@@ -805,7 +829,7 @@ importers:
         version: 5.42.2
       svelte-check:
         specifier: 'catalog:'
-        version: 4.1.1(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
+        version: 4.3.4(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
       typescript:
         specifier: ^5.5.4
         version: 5.8.3
@@ -829,7 +853,7 @@ importers:
         version: 5.42.2
       svelte-check:
         specifier: 'catalog:'
-        version: 4.1.1(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
+        version: 4.3.4(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
       typescript:
         specifier: ^5.5.4
         version: 5.8.3
@@ -856,7 +880,7 @@ importers:
         version: 5.42.2
       svelte-check:
         specifier: 'catalog:'
-        version: 4.1.1(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
+        version: 4.3.4(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
       typescript:
         specifier: ^5.5.4
         version: 5.8.3
@@ -877,7 +901,7 @@ importers:
         version: 5.42.2
       svelte-check:
         specifier: 'catalog:'
-        version: 4.1.1(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
+        version: 4.3.4(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
       typescript:
         specifier: ^5.5.4
         version: 5.8.3
@@ -898,7 +922,7 @@ importers:
         version: 5.42.2
       svelte-check:
         specifier: 'catalog:'
-        version: 4.1.1(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
+        version: 4.3.4(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
       typescript:
         specifier: ^5.5.4
         version: 5.8.3
@@ -928,7 +952,7 @@ importers:
         version: 5.42.2
       svelte-check:
         specifier: 'catalog:'
-        version: 4.1.1(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
+        version: 4.3.4(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
       typescript:
         specifier: ^5.5.4
         version: 5.8.3
@@ -952,7 +976,7 @@ importers:
         version: 5.42.2
       svelte-check:
         specifier: 'catalog:'
-        version: 4.1.1(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
+        version: 4.3.4(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
       typescript:
         specifier: ^5.5.4
         version: 5.8.3
@@ -976,7 +1000,7 @@ importers:
         version: 5.42.2
       svelte-check:
         specifier: 'catalog:'
-        version: 4.1.1(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
+        version: 4.3.4(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
       typescript:
         specifier: ^5.5.4
         version: 5.8.3
@@ -1000,7 +1024,7 @@ importers:
         version: 5.42.2
       svelte-check:
         specifier: 'catalog:'
-        version: 4.1.1(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
+        version: 4.3.4(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
       typescript:
         specifier: ^5.5.4
         version: 5.8.3
@@ -1021,7 +1045,7 @@ importers:
         version: 5.42.2
       svelte-check:
         specifier: 'catalog:'
-        version: 4.1.1(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
+        version: 4.3.4(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
       typescript:
         specifier: ^5.5.4
         version: 5.8.3
@@ -1042,7 +1066,7 @@ importers:
         version: 5.42.2
       svelte-check:
         specifier: 'catalog:'
-        version: 4.1.1(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
+        version: 4.3.4(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
       typescript:
         specifier: ^5.5.4
         version: 5.8.3
@@ -1063,7 +1087,7 @@ importers:
         version: 5.42.2
       svelte-check:
         specifier: 'catalog:'
-        version: 4.1.1(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
+        version: 4.3.4(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
       typescript:
         specifier: ^5.5.4
         version: 5.8.3
@@ -1084,7 +1108,7 @@ importers:
         version: 5.42.2
       svelte-check:
         specifier: 'catalog:'
-        version: 4.1.1(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
+        version: 4.3.4(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
       typescript:
         specifier: ^5.5.4
         version: 5.8.3
@@ -1105,7 +1129,7 @@ importers:
         version: 5.42.2
       svelte-check:
         specifier: 'catalog:'
-        version: 4.1.1(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
+        version: 4.3.4(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
       typescript:
         specifier: ^5.5.4
         version: 5.8.3
@@ -1126,7 +1150,7 @@ importers:
         version: 5.42.2
       svelte-check:
         specifier: 'catalog:'
-        version: 4.1.1(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
+        version: 4.3.4(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
       typescript:
         specifier: ^5.5.4
         version: 5.8.3
@@ -1147,7 +1171,7 @@ importers:
         version: 5.42.2
       svelte-check:
         specifier: 'catalog:'
-        version: 4.1.1(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
+        version: 4.3.4(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
       typescript:
         specifier: ^5.5.4
         version: 5.8.3
@@ -1168,7 +1192,7 @@ importers:
         version: 5.42.2
       svelte-check:
         specifier: 'catalog:'
-        version: 4.1.1(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
+        version: 4.3.4(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
       typescript:
         specifier: ^5.5.4
         version: 5.8.3
@@ -1189,7 +1213,7 @@ importers:
         version: 5.42.2
       svelte-check:
         specifier: 'catalog:'
-        version: 4.1.1(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
+        version: 4.3.4(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
       typescript:
         specifier: ^5.5.4
         version: 5.8.3
@@ -1210,7 +1234,7 @@ importers:
         version: 5.42.2
       svelte-check:
         specifier: 'catalog:'
-        version: 4.1.1(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
+        version: 4.3.4(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
       typescript:
         specifier: ^5.5.4
         version: 5.8.3
@@ -1231,7 +1255,7 @@ importers:
         version: 5.42.2
       svelte-check:
         specifier: 'catalog:'
-        version: 4.1.1(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
+        version: 4.3.4(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
       typescript:
         specifier: ^5.5.4
         version: 5.8.3
@@ -1252,7 +1276,7 @@ importers:
         version: 5.42.2
       svelte-check:
         specifier: 'catalog:'
-        version: 4.1.1(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
+        version: 4.3.4(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
       typescript:
         specifier: ^5.5.4
         version: 5.8.3
@@ -1276,7 +1300,7 @@ importers:
         version: 5.42.2
       svelte-check:
         specifier: 'catalog:'
-        version: 4.1.1(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
+        version: 4.3.4(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
       typescript:
         specifier: ^5.5.4
         version: 5.8.3
@@ -1300,7 +1324,7 @@ importers:
         version: 5.42.2
       svelte-check:
         specifier: 'catalog:'
-        version: 4.1.1(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
+        version: 4.3.4(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
       typescript:
         specifier: ^5.5.4
         version: 5.8.3
@@ -1409,7 +1433,7 @@ importers:
         version: 5.42.2
       svelte-check:
         specifier: 'catalog:'
-        version: 4.1.1(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
+        version: 4.3.4(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3)
       typescript:
         specifier: ^5.5.0
         version: 5.8.3
@@ -6616,8 +6640,8 @@ packages:
     resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
     engines: {node: '>= 0.4'}
 
-  svelte-check@4.1.1:
-    resolution: {integrity: sha512-NfaX+6Qtc8W/CyVGS/F7/XdiSSyXz+WGYA9ZWV3z8tso14V2vzjfXviKaTFEzB7g8TqfgO2FOzP6XT4ApSTUTw==}
+  svelte-check@4.3.4:
+    resolution: {integrity: sha512-DVWvxhBrDsd+0hHWKfjP99lsSXASeOhHJYyuKOFYJcP7ThfSCKgjVarE8XfuMWpS5JV3AlDf+iK1YGGo2TACdw==}
     engines: {node: '>= 18.0.0'}
     hasBin: true
     peerDependencies:
@@ -12892,7 +12916,7 @@ snapshots:
 
   supports-preserve-symlinks-flag@1.0.0: {}
 
-  svelte-check@4.1.1(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3):
+  svelte-check@4.3.4(picomatch@4.0.3)(svelte@5.42.2)(typescript@5.8.3):
     dependencies:
       '@jridgewell/trace-mapping': 0.3.25
       chokidar: 4.0.3
index 6228fbf4878514f14fd9b30c7795de3501247307..52556b76f569d174da95a77271519c93ab118533 100644 (file)
@@ -41,7 +41,7 @@ catalog:
   semver: ^7.5.4
   sirv-cli: ^3.0.0
   svelte: ^5.42.1
-  svelte-check: ^4.1.1
+  svelte-check: ^4.3.4
   svelte-preprocess: ^6.0.0
   typescript-eslint: ^8.43.0
   valibot: ^1.1.0