Skip to content

Commit 15663c3

Browse files
committed
Enums could not be detected as an invalid value if the posted value was an empty string.
1 parent c8281a0 commit 15663c3

File tree

6 files changed

+161
-7
lines changed

6 files changed

+161
-7
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Fixed
1111

1212
- Added `focusOnError` option to `SuperForm.validateForm` type (it was only in the implementation).
13+
- Enums could not be detected as an invalid value if the posted value was an empty string, instead it defaulted to the enum first value.
1314

1415
## [2.2.0] - 2024-02-15
1516

src/lib/formData.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -249,15 +249,15 @@ function parseFormDataEntry(key: string, value: string, info: SchemaInfo): unkno
249249
return false;
250250
}
251251

252-
// Special case for numbers/integers with default value empty string
253-
// (for input field placeholders to show)
254-
/*
255-
if ((type == 'number' || type == 'integer') && info.schema.default === '') {
256-
return 0;
252+
const defaultValue = defaultValues<unknown>(info.schema, info.isOptional, [key]);
253+
254+
// Special case for empty posted enums, then the empty value should be returned,
255+
// otherwise even a required field will get a default value, resulting in that
256+
// posting missing enum values must use strict mode.
257+
if (info.schema.enum && defaultValue !== null && defaultValue !== undefined) {
258+
return value;
257259
}
258-
*/
259260

260-
const defaultValue = defaultValues<unknown>(info.schema, info.isOptional, [key]);
261261
if (defaultValue !== undefined) return defaultValue;
262262

263263
if (info.isNullable) return null;
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { zod } from '$lib/adapters/zod.js';
2+
import { message, superValidate } from '$lib/server/index.js';
3+
import { schema } from './schema.js';
4+
import { fail } from '@sveltejs/kit';
5+
6+
export const load = async () => {
7+
const adapter = zod(schema);
8+
adapter.defaults = { fish: '' as 'trout', moreFish: '' as 'trout' };
9+
10+
const form = await superValidate(adapter);
11+
const fish = adapter.jsonSchema?.properties?.['fish'];
12+
return { form, fish: typeof fish === 'object' && fish.enum ? fish.enum : [] };
13+
};
14+
15+
export const actions = {
16+
default: async ({ request }) => {
17+
const formData = await request.formData();
18+
console.log(formData);
19+
20+
const form = await superValidate(formData, zod(schema));
21+
console.log(form);
22+
23+
if (!form.valid) return fail(400, { form });
24+
25+
return message(form, 'Posted OK!');
26+
}
27+
};
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<script lang="ts">
2+
import { superForm } from '$lib/index.js';
3+
import SuperDebug from '$lib/client/SuperDebug.svelte';
4+
//import { zod } from '$lib/adapters/zod.js'
5+
//import { schema } from './schema.js';
6+
7+
export let data;
8+
9+
const { form, errors, tainted, message, enhance } = superForm(data.form, {
10+
taintedMessage: false
11+
//dataType: 'json',
12+
//validators: zod(schema)
13+
});
14+
</script>
15+
16+
<SuperDebug data={{ $form, $errors, $tainted }} />
17+
18+
{#if $message}<h4>{$message}</h4>{/if}
19+
20+
<form method="POST" use:enhance>
21+
<label>
22+
Fish:<br />
23+
{#each data.fish as fishName}
24+
<input name="fish" type="radio" value={fishName} bind:group={$form.fish} /> {fishName}<br />
25+
{/each}
26+
{#if $errors.fish}<span class="invalid">{$errors.fish}</span>{/if}
27+
</label>
28+
<label>
29+
More fish:<br />
30+
<select name="moreFish" bind:value={$form.moreFish}>
31+
<option value="">Pick one:</option>
32+
{#each data.fish as fishName}
33+
<option value={fishName}>{fishName}</option>
34+
{/each}
35+
</select>
36+
{#if $errors.moreFish}<span class="invalid">{$errors.moreFish}</span>{/if}
37+
</label>
38+
<div>
39+
<button>Submit</button>
40+
</div>
41+
</form>
42+
43+
<style lang="scss">
44+
form {
45+
margin: 2rem 0;
46+
47+
input {
48+
background-color: #dedede;
49+
50+
&[type='checkbox'] {
51+
margin-left: 1em;
52+
}
53+
}
54+
55+
.invalid {
56+
color: crimson;
57+
}
58+
}
59+
</style>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { z } from 'zod';
2+
3+
const fishes = ['trout', 'tuna', 'shark'] as const;
4+
5+
export const schema = z.object({
6+
fish: z.enum(fishes),
7+
moreFish: z.enum(fishes)
8+
});

src/tests/superValidate.test.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -820,3 +820,62 @@ describe('Array validation', () => {
820820
});
821821
});
822822
});
823+
824+
describe('Enum validation', () => {
825+
const fishes = ['trout', 'tuna', 'shark'] as const;
826+
827+
const schema = z.object({
828+
fish: z.enum(fishes),
829+
moreFish: z.enum(fishes)
830+
});
831+
832+
const schema2 = schema.extend({
833+
fish: schema.shape.fish.nullable(),
834+
moreFish: schema.shape.moreFish.optional()
835+
});
836+
837+
describe('With FormData', () => {
838+
it('should not return the default first enum value when an empty string is posted', async () => {
839+
const formData = new FormData();
840+
const adapter = zod(schema);
841+
const form = await superValidate(formData, adapter);
842+
assert(form.valid);
843+
expect(form.data).toEqual({ fish: 'trout', moreFish: 'trout' });
844+
845+
formData.set('fish', '');
846+
const form2 = await superValidate(formData, adapter);
847+
assert(!form2.valid);
848+
expect(form2.data).toEqual({ fish: '', moreFish: 'trout' });
849+
expect(form2.errors.fish?.[0]).toMatch(/^Invalid enum value\./);
850+
});
851+
852+
it('should still work with undefined or nullable', async () => {
853+
const formData = new FormData();
854+
const adapter = zod(schema2);
855+
const form = await superValidate(formData, adapter);
856+
assert(form.valid);
857+
expect(form.data).toEqual({ fish: null, moreFish: undefined });
858+
859+
formData.set('fish', '');
860+
const form2 = await superValidate(formData, adapter);
861+
assert(form2.valid);
862+
expect(form2.data).toEqual({ fish: null, moreFish: undefined });
863+
});
864+
});
865+
866+
describe('With data objects', () => {
867+
it('should add the default enum value if field does not exist', async () => {
868+
const adapter = zod(schema);
869+
const form = await superValidate({}, adapter);
870+
assert(form.valid);
871+
expect(form.data).toEqual({ fish: 'trout', moreFish: 'trout' });
872+
});
873+
874+
it('should add the default null/undefined if field does not exist', async () => {
875+
const adapter = zod(schema2);
876+
const form = await superValidate({}, adapter);
877+
assert(form.valid);
878+
expect(form.data).toEqual({ fish: null, moreFish: undefined });
879+
});
880+
});
881+
});

0 commit comments

Comments
 (0)