Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/thin-frogs-create.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

fix: expose `issue.path` in `.allIssues()`
8 changes: 6 additions & 2 deletions packages/kit/src/exports/public.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1911,12 +1911,12 @@ export type RemoteFormField<Value extends RemoteFormFieldValue> = RemoteFormFiel

type RemoteFormFieldContainer<Value> = RemoteFormFieldMethods<Value> & {
/** Validation issues belonging to this or any of the fields that belong to it, if any */
allIssues(): RemoteFormIssue[] | undefined;
allIssues(): RemoteFormAllIssue[] | undefined;
};

type UnknownField<Value> = RemoteFormFieldMethods<Value> & {
/** 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.
Expand Down Expand Up @@ -1965,6 +1965,10 @@ export interface RemoteFormIssue {
message: string;
}

export interface RemoteFormAllIssue extends RemoteFormIssue {
path: Array<string | number>;
}

// If the schema specifies `id` as a string or number, ensure that `for(...)`
// only accepts that type. Otherwise, accept `string | number`
type ExtractId<Input> = Input extends { id: infer Id }
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/src/runtime/app/server/remote/form.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions packages/kit/src/runtime/form-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script>
import { my_form } from './form.remote.js';
import { issue_path_form, my_form } from './form.remote.js';
import * as v from 'valibot';

const schema = v.object({
Expand All @@ -11,7 +11,7 @@
$inspect(my_form.fields.allIssues());
</script>

<form {...my_form.preflight(schema)} oninput={() => my_form.validate()}>
<form id="my-form" {...my_form.preflight(schema)} oninput={() => my_form.validate()}>
{#each my_form.fields.foo.issues() as issue}
<p>{issue.message}</p>
{/each}
Expand Down Expand Up @@ -39,3 +39,15 @@
>
trigger validation
</button>

<form id="issue-path-form" {...issue_path_form}>
<input {...issue_path_form.fields.nested.value.as('text')} />
<button
type="button"
id="validate"
onclick={() => issue_path_form.validate({ includeUntouched: true })}
>
Validate
</button>
<pre id="allIssues">{JSON.stringify(issue_path_form.fields.allIssues())}</pre>
</form>
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
);
21 changes: 14 additions & 7 deletions packages/kit/test/apps/basics/test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1871,31 +1871,38 @@ 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"'
);

// Test imperative validation
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 }) => {
Expand Down
8 changes: 6 additions & 2 deletions packages/kit/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1887,12 +1887,12 @@ declare module '@sveltejs/kit' {

type RemoteFormFieldContainer<Value> = RemoteFormFieldMethods<Value> & {
/** Validation issues belonging to this or any of the fields that belong to it, if any */
allIssues(): RemoteFormIssue[] | undefined;
allIssues(): RemoteFormAllIssue[] | undefined;
};

type UnknownField<Value> = RemoteFormFieldMethods<Value> & {
/** 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.
Expand Down Expand Up @@ -1941,6 +1941,10 @@ declare module '@sveltejs/kit' {
message: string;
}

export interface RemoteFormAllIssue extends RemoteFormIssue {
path: Array<string | number>;
}

// If the schema specifies `id` as a string or number, ensure that `for(...)`
// only accepts that type. Otherwise, accept `string | number`
type ExtractId<Input> = Input extends { id: infer Id }
Expand Down
Loading