Skip to content

Commit 22d8528

Browse files
committed
Add form validation
1 parent 93918f9 commit 22d8528

File tree

7 files changed

+469
-6
lines changed

7 files changed

+469
-6
lines changed

packages/client/src/pages/CollectionForm/EditForm.tsx

+5-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React, { useMemo, useState } from 'react';
22
import {
33
PluginBox,
44
useCollectionPlugins,
5+
validatePluginsFieldsData,
56
WidgetRenderer
67
} from '@stac-manager/data-core';
78
import { Box, Button, ButtonGroup, Flex, Heading } from '@chakra-ui/react';
@@ -42,14 +43,17 @@ export function EditForm(props: {
4243
<Flex direction='column' gap={4}>
4344
<Formik
4445
validateOnChange={false}
45-
validateOnBlur={false}
4646
enableReinitialize
4747
initialValues={editorData}
4848
onSubmit={(values, actions) => {
4949
const exitData =
5050
view === 'json' ? values.jsonData : toOutData(values);
5151
return onSubmit(exitData, actions);
5252
}}
53+
validate={(values) => {
54+
const [, error] = validatePluginsFieldsData(plugins, values);
55+
if (error) return error;
56+
}}
5357
>
5458
{({ handleSubmit, values, isSubmitting }) => (
5559
<Flex

packages/client/src/pages/CollectionForm/index.tsx

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { useEffect, useState } from 'react';
22
import { Box, useToast } from '@chakra-ui/react';
33
import { FormikHelpers } from 'formik';
4-
import { useParams } from 'react-router-dom';
4+
import { useNavigate, useParams } from 'react-router-dom';
55
import { useCollection } from '@developmentseed/stac-react';
66
import { StacCollection } from 'stac-ts';
77

@@ -23,6 +23,7 @@ export function CollectionFormNew() {
2323
usePageTitle('New collection');
2424

2525
const toast = useToast();
26+
const navigate = useNavigate();
2627

2728
const onSubmit = async (data: any, formikHelpers: FormikHelpers<any>) => {
2829
try {
@@ -42,6 +43,8 @@ export function CollectionFormNew() {
4243
duration: 5000,
4344
isClosable: true
4445
});
46+
47+
navigate(`/collections/${data.id}`);
4548
} catch (error: any) {
4649
toast.update('collection-submit', {
4750
title: 'Collection creation failed',
@@ -64,6 +67,8 @@ export function CollectionFormEdit(props: { id: string }) {
6467

6568
usePageTitle(collection ? `Edit collection ${id}` : 'Edit collection');
6669

70+
const navigate = useNavigate();
71+
6772
const toast = useToast();
6873

6974
useEffect(() => {
@@ -98,6 +103,8 @@ export function CollectionFormEdit(props: { id: string }) {
98103
duration: 5000,
99104
isClosable: true
100105
});
106+
107+
navigate(`/collections/${data.id}`);
101108
} catch (error: any) {
102109
toast.update('collection-submit', {
103110
title: 'Collection update failed',

packages/data-core/lib/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from './plugin-utils/plugin';
2+
export * from './plugin-utils/validate';
23
export * from './plugin-utils/use-plugins-hook';
34
export * from './context/plugin-config';
45
export * from './config';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
import { Plugin, PluginEditSchema } from './plugin';
2+
import { validatePluginsFieldsData } from './validate';
3+
4+
describe('validatePluginsFieldsData', () => {
5+
test('No data', () => {
6+
const plugins = [
7+
{
8+
name: 'Render Extension',
9+
editSchema(): PluginEditSchema {
10+
return {
11+
type: 'root',
12+
required: ['description', 'license'],
13+
properties: {
14+
description: {
15+
label: 'Description',
16+
type: 'string'
17+
},
18+
license: {
19+
label: 'License',
20+
type: 'string',
21+
enum: [
22+
['one', 'License One'],
23+
['two', 'License Two'],
24+
['three', 'License Three']
25+
]
26+
}
27+
}
28+
};
29+
}
30+
}
31+
] as Plugin[];
32+
const [, errors] = validatePluginsFieldsData(plugins, {});
33+
expect(errors).toEqual({
34+
description: 'Description is a required field',
35+
license: 'License is a required field'
36+
});
37+
});
38+
39+
test('Invalid enum', () => {
40+
const plugins = [
41+
{
42+
editSchema(): PluginEditSchema {
43+
return {
44+
type: 'root',
45+
required: ['license'],
46+
properties: {
47+
license: {
48+
label: 'License',
49+
type: 'string',
50+
enum: [
51+
['one', 'License One'],
52+
['two', 'License Two'],
53+
['three', 'License Three']
54+
]
55+
}
56+
}
57+
};
58+
}
59+
}
60+
] as Plugin[];
61+
const data = {
62+
license: 'invalid'
63+
};
64+
const [, errors] = validatePluginsFieldsData(plugins, data);
65+
expect(errors).toEqual({
66+
license: 'License value is invalid'
67+
});
68+
});
69+
70+
test('Invalid enum empty', () => {
71+
const plugins = [
72+
{
73+
name: 'Render Extension',
74+
editSchema(): PluginEditSchema {
75+
return {
76+
type: 'root',
77+
required: ['license'],
78+
properties: {
79+
license: {
80+
label: 'License',
81+
type: 'string',
82+
enum: [
83+
['one', 'License One'],
84+
['two', 'License Two'],
85+
['three', 'License Three']
86+
]
87+
}
88+
}
89+
};
90+
}
91+
}
92+
] as Plugin[];
93+
const data = {
94+
license: ''
95+
};
96+
const [, errors] = validatePluginsFieldsData(plugins, data);
97+
expect(errors).toEqual({
98+
license: 'License value is invalid'
99+
});
100+
});
101+
102+
test('Allowed empty enum', () => {
103+
const plugins = [
104+
{
105+
name: 'Render Extension',
106+
editSchema(): PluginEditSchema {
107+
return {
108+
type: 'root',
109+
properties: {
110+
type: {
111+
label: 'Type',
112+
type: 'string',
113+
enum: [
114+
['one', 'Type One'],
115+
['two', 'Type Two'],
116+
['three', 'Type Three']
117+
]
118+
}
119+
}
120+
};
121+
}
122+
}
123+
] as Plugin[];
124+
const data = {
125+
type: ''
126+
};
127+
const [, errors] = validatePluginsFieldsData(plugins, data);
128+
expect(errors).toEqual(null);
129+
});
130+
131+
test('Allowed other enum', () => {
132+
const plugins = [
133+
{
134+
name: 'Render Extension',
135+
editSchema(): PluginEditSchema {
136+
return {
137+
type: 'root',
138+
properties: {
139+
type: {
140+
label: 'Type',
141+
type: 'string',
142+
allowOther: {
143+
type: 'string'
144+
},
145+
enum: [
146+
['one', 'Type One'],
147+
['two', 'Type Two'],
148+
['three', 'Type Three']
149+
]
150+
}
151+
}
152+
};
153+
}
154+
}
155+
] as Plugin[];
156+
const data = {
157+
type: 'special'
158+
};
159+
const [, errors] = validatePluginsFieldsData(plugins, data);
160+
expect(errors).toEqual(null);
161+
});
162+
163+
test('Missing nested', () => {
164+
const plugins = [
165+
{
166+
name: 'Render Extension',
167+
editSchema(): PluginEditSchema {
168+
return {
169+
type: 'root',
170+
properties: {
171+
providers: {
172+
type: 'array',
173+
label: 'Providers',
174+
items: {
175+
type: 'object',
176+
required: ['name'],
177+
properties: {
178+
name: {
179+
label: 'Name',
180+
type: 'string'
181+
}
182+
}
183+
}
184+
}
185+
}
186+
};
187+
}
188+
}
189+
] as Plugin[];
190+
const data = {
191+
providers: [
192+
{
193+
url: 'http://example.com'
194+
}
195+
]
196+
};
197+
const [, errors] = validatePluginsFieldsData(plugins, data);
198+
expect(errors).toEqual({
199+
providers: [{ name: 'Name is a required field' }]
200+
});
201+
});
202+
203+
test('Wrong array count', () => {
204+
const plugins = [
205+
{
206+
name: 'Render Extension',
207+
editSchema(): PluginEditSchema {
208+
return {
209+
type: 'root',
210+
properties: {
211+
spatial: {
212+
label: 'Spatial Extent',
213+
type: 'array',
214+
minItems: 2,
215+
items: {
216+
type: 'number'
217+
}
218+
}
219+
}
220+
};
221+
}
222+
}
223+
] as Plugin[];
224+
const data = {
225+
spatial: [1]
226+
};
227+
const [, errors] = validatePluginsFieldsData(plugins, data);
228+
expect(errors).toEqual({
229+
spatial: 'Spatial Extent field must have at least 2 items'
230+
});
231+
});
232+
233+
test('Wrong field type', () => {
234+
const plugins = [
235+
{
236+
name: 'Render Extension',
237+
editSchema(): PluginEditSchema {
238+
return {
239+
type: 'root',
240+
required: ['description', 'license'],
241+
properties: {
242+
spatial: {
243+
label: 'Spatial Extent',
244+
type: 'array',
245+
minItems: 2,
246+
items: {
247+
type: 'number'
248+
}
249+
}
250+
}
251+
};
252+
}
253+
}
254+
] as Plugin[];
255+
const data = {
256+
spatial: ['one', 'two']
257+
};
258+
const [, errors] = validatePluginsFieldsData(plugins, data);
259+
expect(errors).toEqual({
260+
spatial: ['Value must be a number', 'Value must be a number']
261+
});
262+
});
263+
});

0 commit comments

Comments
 (0)