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/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) { 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/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 }) => { 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 }