Skip to content

Commit 1de3f93

Browse files
author
Larry Botha
committed
feat(form): allow context to be passed into component
closes #101
1 parent 3f8c6f5 commit 1de3f93

File tree

2 files changed

+134
-113
lines changed

2 files changed

+134
-113
lines changed

lib/components/Form.svelte

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,17 @@
66
export let initialValues = {};
77
export let validate = null;
88
export let validationSchema = null;
9-
export let onSubmit;
9+
export let onSubmit = () => {
10+
throw new Error(
11+
'onSubmit is a required property in <Form /> when using the fallback context',
12+
);
13+
};
14+
export let context = createForm({
15+
initialValues,
16+
onSubmit,
17+
validate,
18+
validationSchema,
19+
});
1020
1121
const {
1222
form,
@@ -20,12 +30,7 @@
2030
updateTouched,
2131
updateValidateField,
2232
validateField,
23-
} = createForm({
24-
initialValues,
25-
validationSchema,
26-
validate,
27-
onSubmit,
28-
});
33+
} = context;
2934
3035
setContext(key, {
3136
form,

test/specs/components/form.spec.js

Lines changed: 122 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -4,138 +4,154 @@ import Input from './fixtures/input.svelte';
44
import StoreContextInspector from './fixtures/store-context-inspector.svelte';
55

66
import Form from 'lib/components/Form.svelte';
7+
import {createForm} from 'lib/index';
78

89
const defaultProps = {
910
initialValues: {foo: ''},
1011
onSubmit() {},
1112
};
1213

14+
const contextType = {
15+
userDefined: 'user',
16+
fallback: 'fallback',
17+
};
18+
1319
describe('Form', () => {
14-
test('-> exposes `form` store to children via context', async () => {
15-
const inputId = 'foo';
16-
const props = {
17-
...defaultProps,
18-
initialValues: {[inputId]: ''},
20+
describe.each`
21+
contextName | type
22+
${'fallback'} | ${contextType.fallback}
23+
${'user-defined'} | ${contextType.userDefined}
24+
`('-> with $contextName context', ({type}) => {
25+
const getForm = (props = defaultProps) => {
26+
const form =
27+
type === contextType.userDefined ? createForm(props) : undefined;
28+
const resultProps = type === contextType.userDefined ? {} : props;
29+
30+
return [resultProps, form];
1931
};
20-
const {getByLabelText, getByTestId} = render(
21-
<Form {...props}>
22-
<Input field={inputId} />
2332

24-
<StoreContextInspector data-testid="values" storeProp="form" />
25-
</Form>,
26-
);
27-
const input = getByLabelText(inputId);
28-
const valuesEl = getByTestId('values');
29-
let values = JSON.parse(valuesEl.textContent);
33+
test('-> exposes `form` store to children via context', async () => {
34+
const inputId = 'foo';
35+
const baseProps = {...defaultProps, initialValues: {[inputId]: ''}};
36+
const [props, form] = getForm(baseProps);
37+
const {getByLabelText, getByTestId} = render(
38+
<Form {...props} context={form}>
39+
<Input field={inputId} />
3040

31-
expect(values[inputId]).toBe(props.initialValues[inputId]);
32-
expect(input.value).toBe(values[inputId]);
41+
<StoreContextInspector data-testid="values" storeProp="form" />
42+
</Form>,
43+
);
44+
const input = getByLabelText(inputId);
45+
const valuesEl = getByTestId('values');
46+
let values = JSON.parse(valuesEl.textContent);
3347

34-
const value = 'bar';
48+
expect(values[inputId]).toBe(baseProps.initialValues[inputId]);
49+
expect(input.value).toBe(values[inputId]);
3550

36-
await fireEvent.input(input, {target: {value}});
51+
const value = 'bar';
3752

38-
values = JSON.parse(valuesEl.textContent);
53+
await fireEvent.input(input, {target: {value}});
3954

40-
expect(values[inputId]).toBe(value);
41-
expect(input.value).toBe(value);
42-
});
55+
values = JSON.parse(valuesEl.textContent);
4356

44-
test('-> exposes `state` store to children via context', async () => {
45-
const inputId = 'foo';
46-
const props = {
47-
...defaultProps,
48-
initialValues: {[inputId]: ''},
49-
};
50-
const {getByLabelText, getByTestId} = render(
51-
<Form {...props}>
52-
<Input field={inputId} />
57+
expect(values[inputId]).toBe(value);
58+
expect(input.value).toBe(value);
59+
});
5360

54-
<StoreContextInspector data-testid="state" storeProp="state" />
55-
</Form>,
56-
);
57-
const input = getByLabelText(inputId);
58-
const stateEl = getByTestId('state');
59-
let state = JSON.parse(stateEl.textContent);
61+
test('-> exposes `state` store to children via context', async () => {
62+
const inputId = 'foo';
63+
const baseProps = {...defaultProps, initialValues: {[inputId]: ''}};
64+
const [props, form] = getForm(baseProps);
65+
const {getByLabelText, getByTestId} = render(
66+
<Form {...props} context={form}>
67+
<Input field={inputId} />
6068

61-
expect(state).toHaveProperty('isModified', false);
69+
<StoreContextInspector data-testid="state" storeProp="state" />
70+
</Form>,
71+
);
72+
const input = getByLabelText(inputId);
73+
const stateEl = getByTestId('state');
74+
let state = JSON.parse(stateEl.textContent);
6275

63-
const value = 'bar';
76+
expect(state).toHaveProperty('isModified', false);
6477

65-
await fireEvent.input(input, {target: {value}});
78+
const value = 'bar';
6679

67-
state = JSON.parse(stateEl.textContent);
68-
expect(state).toHaveProperty('isModified', true);
69-
});
80+
await fireEvent.input(input, {target: {value}});
7081

71-
test('-> exposes `errors` store to children via context', async () => {
72-
const inputId = 'foo';
73-
const validationError = `${inputId} is required`;
74-
const props = {
75-
initialValues: {[inputId]: ''},
76-
validate: (values) => {
77-
if (!values[inputId]) {
78-
return {[inputId]: validationError};
79-
}
80-
},
81-
};
82-
const {getByTestId, getByLabelText, getByRole} = render(
83-
<Form {...props}>
84-
<Input field={inputId} />
85-
<StoreContextInspector data-testid="error" storeProp="errors" />
86-
87-
<button type="submit">submit</button>
88-
</Form>,
89-
);
90-
const input = getByLabelText(inputId);
91-
const button = getByRole('button');
92-
const errorEl = getByTestId('error');
93-
let errors = JSON.parse(errorEl.textContent);
94-
95-
expect(errors).toHaveProperty(inputId, '');
96-
97-
await fireEvent.click(button);
98-
99-
await waitFor(() => {
100-
errors = JSON.parse(errorEl.textContent);
101-
expect(errors).toHaveProperty(inputId, validationError);
82+
state = JSON.parse(stateEl.textContent);
83+
expect(state).toHaveProperty('isModified', true);
10284
});
10385

104-
const value = 'bar';
105-
106-
await fireEvent.input(input, {target: {value}});
107-
await fireEvent.click(button);
86+
test('-> exposes `errors` store to children via context', async () => {
87+
const inputId = 'foo';
88+
const validationError = `${inputId} is required`;
89+
const baseProps = {
90+
...defaultProps,
91+
initialValues: {[inputId]: ''},
92+
validate: (values) => {
93+
if (!values[inputId]) {
94+
return {[inputId]: validationError};
95+
}
96+
},
97+
};
98+
const [props, form] = getForm(baseProps);
99+
const {getByTestId, getByLabelText, getByRole} = render(
100+
<Form {...props} context={form}>
101+
<Input field={inputId} />
102+
<StoreContextInspector data-testid="error" storeProp="errors" />
103+
104+
<button type="submit">submit</button>
105+
</Form>,
106+
);
107+
const input = getByLabelText(inputId);
108+
const button = getByRole('button');
109+
const errorEl = getByTestId('error');
110+
let errors = JSON.parse(errorEl.textContent);
108111

109-
await waitFor(() => {
110-
errors = JSON.parse(errorEl.textContent);
111112
expect(errors).toHaveProperty(inputId, '');
113+
114+
await fireEvent.click(button);
115+
116+
await waitFor(() => {
117+
errors = JSON.parse(errorEl.textContent);
118+
expect(errors).toHaveProperty(inputId, validationError);
119+
});
120+
121+
const value = 'bar';
122+
123+
await fireEvent.input(input, {target: {value}});
124+
await fireEvent.click(button);
125+
126+
await waitFor(() => {
127+
errors = JSON.parse(errorEl.textContent);
128+
expect(errors).toHaveProperty(inputId, '');
129+
});
112130
});
113-
});
114131

115-
test.each`
116-
methodName
117-
${'handleChange'}
118-
${'handleSubmit'}
119-
${'updateField'}
120-
${'updateInitialValues'}
121-
${'updateTouched'}
122-
${'updateValidateField'}
123-
${'validateField'}
124-
`('-> exposes $methodName via slot properties', ({methodName}) => {
125-
const inputId = 'foo';
126-
const methodSetter = jest.fn();
127-
const props = {
128-
...defaultProps,
129-
initialValues: {[inputId]: ''},
130-
[`let_${methodName}`]: methodSetter,
131-
};
132-
render(
133-
<Form {...props}>
134-
<Input field={inputId} />
135-
</Form>,
136-
);
137-
138-
expect(methodSetter.mock.calls[0][0]).toBeInstanceOf(Function);
139-
expect(methodSetter.mock.calls[0][0]).toHaveProperty('name', methodName);
132+
test.each`
133+
methodName
134+
${'handleChange'}
135+
${'handleSubmit'}
136+
${'updateField'}
137+
${'updateInitialValues'}
138+
${'updateTouched'}
139+
${'updateValidateField'}
140+
${'validateField'}
141+
`('-> exposes $methodName via slot properties', ({methodName}) => {
142+
const inputId = 'foo';
143+
const methodSetter = jest.fn();
144+
const baseProps = {...defaultProps, initialValues: {[inputId]: ''}};
145+
const methodProps = {[`let_${methodName}`]: methodSetter};
146+
const [props, form] = getForm(baseProps);
147+
render(
148+
<Form {...props} {...methodProps} context={form}>
149+
<Input field={inputId} />
150+
</Form>,
151+
);
152+
153+
expect(methodSetter.mock.calls[0][0]).toBeInstanceOf(Function);
154+
expect(methodSetter.mock.calls[0][0]).toHaveProperty('name', methodName);
155+
});
140156
});
141157
});

0 commit comments

Comments
 (0)