From e4f0a2f6f1df23161f49ac24e83704ce4da9af0c Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Tue, 21 Oct 2025 15:35:29 -0500 Subject: [PATCH 1/3] fix: expose `issue.path` in `.allIssues()` for field containers --- .changeset/thin-frogs-create.md | 5 +++++ packages/kit/src/exports/public.d.ts | 8 ++++++-- packages/kit/src/runtime/form-utils.js | 1 + packages/kit/types/index.d.ts | 8 ++++++-- 4 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 .changeset/thin-frogs-create.md diff --git a/.changeset/thin-frogs-create.md b/.changeset/thin-frogs-create.md new file mode 100644 index 000000000000..eba399fc2d1d --- /dev/null +++ b/.changeset/thin-frogs-create.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +fix: expose `issue.path` in `.allIssues()` diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index cb42dc6cc1c0..1ef333310326 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -1911,12 +1911,12 @@ export type RemoteFormField = RemoteFormFiel type RemoteFormFieldContainer = RemoteFormFieldMethods & { /** Validation issues belonging to this or any of the fields that belong to it, if any */ - allIssues(): RemoteFormIssue[] | undefined; + allIssues(): RemoteFormAllIssue[] | undefined; }; type UnknownField = RemoteFormFieldMethods & { /** Validation issues belonging to this or any of the fields that belong to it, if any */ - allIssues(): RemoteFormIssue[] | undefined; + allIssues(): RemoteFormAllIssue[] | undefined; /** * Returns an object that can be spread onto an input element with the correct type attribute, * aria-invalid attribute if the field is invalid, and appropriate value/checked property getters/setters. @@ -1965,6 +1965,10 @@ export interface RemoteFormIssue { message: string; } +export interface RemoteFormAllIssue extends RemoteFormIssue { + path: Array; +} + // If the schema specifies `id` as a string or number, ensure that `for(...)` // only accepts that type. Otherwise, accept `string | number` type ExtractId = Input extends { id: infer Id } diff --git a/packages/kit/src/runtime/form-utils.js b/packages/kit/src/runtime/form-utils.js index e268ff80b2b8..26468a4dd005 100644 --- a/packages/kit/src/runtime/form-utils.js +++ b/packages/kit/src/runtime/form-utils.js @@ -247,6 +247,7 @@ export function create_field_proxy(target, get_input, set_input, get_issues, pat if (prop === 'allIssues') { return all_issues?.map((issue) => ({ + path: issue.path, message: issue.message })); } diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 9fde95c8dd4c..9705e4a0b9bd 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -1887,12 +1887,12 @@ declare module '@sveltejs/kit' { type RemoteFormFieldContainer = RemoteFormFieldMethods & { /** Validation issues belonging to this or any of the fields that belong to it, if any */ - allIssues(): RemoteFormIssue[] | undefined; + allIssues(): RemoteFormAllIssue[] | undefined; }; type UnknownField = RemoteFormFieldMethods & { /** Validation issues belonging to this or any of the fields that belong to it, if any */ - allIssues(): RemoteFormIssue[] | undefined; + allIssues(): RemoteFormAllIssue[] | undefined; /** * Returns an object that can be spread onto an input element with the correct type attribute, * aria-invalid attribute if the field is invalid, and appropriate value/checked property getters/setters. @@ -1941,6 +1941,10 @@ declare module '@sveltejs/kit' { message: string; } + export interface RemoteFormAllIssue extends RemoteFormIssue { + path: Array; + } + // If the schema specifies `id` as a string or number, ensure that `for(...)` // only accepts that type. Otherwise, accept `string | number` type ExtractId = Input extends { id: infer Id } From 53ea941bef542d72432410ffcfd1a9751155b043 Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Wed, 22 Oct 2025 23:32:24 -0500 Subject: [PATCH 2/3] send normalized issues on validate request --- packages/kit/src/runtime/app/server/remote/form.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/runtime/app/server/remote/form.js b/packages/kit/src/runtime/app/server/remote/form.js index 7bc7259185e9..c1fe5a1d2c10 100644 --- a/packages/kit/src/runtime/app/server/remote/form.js +++ b/packages/kit/src/runtime/app/server/remote/form.js @@ -153,7 +153,7 @@ export function form(validate_or_fn, maybe_fn) { const validated = await schema?.['~standard'].validate(data); if (validate_only) { - return validated?.issues ?? []; + return validated?.issues?.map((issue) => normalize_issue(issue, true)) ?? []; } if (validated?.issues !== undefined) { From b8b001fd396a947a6b7a5ef43fb0635c6ad310c1 Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Wed, 22 Oct 2025 23:50:10 -0500 Subject: [PATCH 3/3] add test --- .../routes/remote/form/validate/+page.svelte | 16 ++++++++++++-- .../remote/form/validate/form.remote.ts | 11 ++++++++++ packages/kit/test/apps/basics/test/test.js | 21 ++++++++++++------- 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/packages/kit/test/apps/basics/src/routes/remote/form/validate/+page.svelte b/packages/kit/test/apps/basics/src/routes/remote/form/validate/+page.svelte index ac9604b266fa..6d53630be4ac 100644 --- a/packages/kit/test/apps/basics/src/routes/remote/form/validate/+page.svelte +++ b/packages/kit/test/apps/basics/src/routes/remote/form/validate/+page.svelte @@ -1,5 +1,5 @@ -
my_form.validate()}> + my_form.validate()}> {#each my_form.fields.foo.issues() as issue}

{issue.message}

{/each} @@ -39,3 +39,15 @@ > trigger validation + + + + +
{JSON.stringify(issue_path_form.fields.allIssues())}
+
diff --git a/packages/kit/test/apps/basics/src/routes/remote/form/validate/form.remote.ts b/packages/kit/test/apps/basics/src/routes/remote/form/validate/form.remote.ts index 10df815353a7..c3ae78ab9854 100644 --- a/packages/kit/test/apps/basics/src/routes/remote/form/validate/form.remote.ts +++ b/packages/kit/test/apps/basics/src/routes/remote/form/validate/form.remote.ts @@ -16,3 +16,14 @@ export const my_form = form( console.log(data); } ); + +export const issue_path_form = form( + v.object({ + nested: v.object({ + value: v.pipe(v.string(), v.minLength(3)) + }) + }), + async (data) => { + return data; + } +); diff --git a/packages/kit/test/apps/basics/test/test.js b/packages/kit/test/apps/basics/test/test.js index 9b57a59b79d7..2518529e27d5 100644 --- a/packages/kit/test/apps/basics/test/test.js +++ b/packages/kit/test/apps/basics/test/test.js @@ -1871,23 +1871,22 @@ test.describe('remote functions', () => { 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(page.locator('form')).not.toContainText('Invalid type: Expected'); + await expect(myForm).not.toContainText('Invalid type: Expected'); await bar.fill('g'); - await expect(page.locator('form')).toContainText( - 'Invalid type: Expected ("d" | "e") but received "g"' - ); + await expect(myForm).toContainText('Invalid type: Expected ("d" | "e") but received "g"'); await bar.fill('d'); - await expect(page.locator('form')).not.toContainText('Invalid type: Expected'); + await expect(myForm).not.toContainText('Invalid type: Expected'); await page.locator('#trigger-validate').click(); - await expect(page.locator('form')).toContainText( + await expect(myForm).toContainText( 'Invalid type: Expected "submitter" but received "incorrect_value"' ); @@ -1895,7 +1894,15 @@ test.describe('remote functions', () => { await foo.fill('c'); await bar.fill('d'); await submit.click(); - await expect(page.locator('form')).toContainText('Imperative: foo cannot be c'); + 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 inputs excludes underscore-prefixed fields', async ({ page, javaScriptEnabled }) => {