diff --git a/test/src/hooks/use-field-state.test.js b/test/src/hooks/use-field-state.test.js
index 181c88a..c5bcbb4 100644
--- a/test/src/hooks/use-field-state.test.js
+++ b/test/src/hooks/use-field-state.test.js
@@ -32,6 +32,30 @@ describe('useFieldState', () => {
expect(result.current.value).toBe('bar');
});
+ it('should return the nested field value', () => {
+ const state = {
+ fields: {
+ values: {
+ foo: {
+ bar: 'baz'
+ }
+ }
+ }
+ };
+
+ const Wrapper = ({ children }) => (
+
+ {children}
+
+ );
+
+ const { result } = renderHook(() => useFieldState('foo.bar'), {
+ wrapper: Wrapper
+ });
+
+ expect(result.current.value).toBe('baz');
+ });
+
it('should return the field error', () => {
const state = {
fields: {
@@ -52,6 +76,30 @@ describe('useFieldState', () => {
expect(result.current.error).toBe('bar');
});
+ it('should return the nested field error', () => {
+ const state = {
+ fields: {
+ errors: {
+ foo: {
+ bar: 'baz'
+ }
+ }
+ }
+ };
+
+ const Wrapper = ({ children }) => (
+
+ {children}
+
+ );
+
+ const { result } = renderHook(() => useFieldState('foo.bar'), {
+ wrapper: Wrapper
+ });
+
+ expect(result.current.error).toBe('baz');
+ });
+
it('should return the field meta state', () => {
const state = {
fields: {
@@ -71,4 +119,28 @@ describe('useFieldState', () => {
expect(result.current.meta).toBe('bar');
});
+
+ it('should return the nested field meta state', () => {
+ const state = {
+ fields: {
+ meta: {
+ foo: {
+ bar: 'baz'
+ }
+ }
+ }
+ };
+
+ const Wrapper = ({ children }) => (
+
+ {children}
+
+ );
+
+ const { result } = renderHook(() => useFieldState('foo.bar'), {
+ wrapper: Wrapper
+ });
+
+ expect(result.current.meta).toBe('baz');
+ });
});
diff --git a/test/src/hooks/use-field.test.js b/test/src/hooks/use-field.test.js
index 1d9b30c..ec7401d 100644
--- a/test/src/hooks/use-field.test.js
+++ b/test/src/hooks/use-field.test.js
@@ -45,6 +45,30 @@ describe('useField', () => {
expect(result.current.value).toBe('bar');
});
+ it('should return the nested field value', () => {
+ const state = {
+ fields: {
+ values: {
+ foo: {
+ bar: 'baz'
+ }
+ }
+ }
+ };
+
+ const Wrapper = ({ children }) => (
+
+
+ {children}
+
+
+ );
+
+ const { result } = renderHook(() => useField('foo.bar'), { wrapper: Wrapper });
+
+ expect(result.current.value).toBe('baz');
+ });
+
it('should return the field error', () => {
const state = {
fields: {
@@ -65,6 +89,32 @@ describe('useField', () => {
expect(result.current.error).toBe('bar');
});
+ it('should return the nested field error', () => {
+ const state = {
+ fields: {
+ errors: {
+ foo: {
+ bar: 'baz'
+ }
+ }
+ }
+ };
+
+ const Wrapper = ({ children }) => (
+
+
+ {children}
+
+
+ );
+
+ const { result } = renderHook(() => useField('foo.bar'), {
+ wrapper: Wrapper
+ });
+
+ expect(result.current.error).toBe('baz');
+ });
+
it('should return the field meta state', () => {
const state = {
fields: {
@@ -85,6 +135,30 @@ describe('useField', () => {
expect(result.current.meta).toBe('bar');
});
+ it('should return the nested field meta state', () => {
+ const state = {
+ fields: {
+ meta: {
+ foo: {
+ bar: 'baz'
+ }
+ }
+ }
+ };
+
+ const Wrapper = ({ children }) => (
+
+
+ {children}
+
+
+ );
+
+ const { result } = renderHook(() => useField('foo.bar'), { wrapper: Wrapper });
+
+ expect(result.current.meta).toBe('baz');
+ });
+
it('should return an `onBlur` action that calls the `blurField` form action with the field name', () => {
const Wrapper = ({ children }) => (
@@ -102,6 +176,25 @@ describe('useField', () => {
expect(actions.blurField).toHaveBeenCalledWith('foo');
});
+ it('should return an `onBlur` action that calls the `blurField` form action with the nested field name', () => {
+ const Wrapper = ({ children }) => (
+
+
+ {children}
+
+
+ );
+
+ const { result } = renderHook(() => useField('foo.bar'), {
+ wrapper: Wrapper
+ });
+
+ result.current.onBlur();
+
+ expect(actions.blurField).toHaveBeenCalledTimes(1);
+ expect(actions.blurField).toHaveBeenCalledWith('foo.bar');
+ });
+
it('should return an `onFocus` action that calls the `focusField` form action with the field name', () => {
const Wrapper = ({ children }) => (
@@ -119,6 +212,25 @@ describe('useField', () => {
expect(actions.focusField).toHaveBeenCalledWith('foo');
});
+ it('should return an `onFocus` action that calls the `focusField` form action with the nested field name', () => {
+ const Wrapper = ({ children }) => (
+
+
+ {children}
+
+
+ );
+
+ const { result } = renderHook(() => useField('foo.bar'), {
+ wrapper: Wrapper
+ });
+
+ result.current.onFocus();
+
+ expect(actions.focusField).toHaveBeenCalledTimes(1);
+ expect(actions.focusField).toHaveBeenCalledWith('foo.bar');
+ });
+
it('should return an `onChange` action that calls the `setFieldValue` form action with the field name and the provided value', () => {
const Wrapper = ({ children }) => (
@@ -135,4 +247,23 @@ describe('useField', () => {
expect(actions.setFieldValue).toHaveBeenCalledTimes(1);
expect(actions.setFieldValue).toHaveBeenCalledWith('foo', 'bar');
});
+
+ it('should return an `onChange` action that calls the `setFieldValue` form action with the nested field name and the provided value', () => {
+ const Wrapper = ({ children }) => (
+
+
+ {children}
+
+
+ );
+
+ const { result } = renderHook(() => useField('foo.bar'), {
+ wrapper: Wrapper
+ });
+
+ result.current.onChange('baz');
+
+ expect(actions.setFieldValue).toHaveBeenCalledTimes(1);
+ expect(actions.setFieldValue).toHaveBeenCalledWith('foo.bar', 'baz');
+ });
});
diff --git a/test/src/hooks/use-form.test.js b/test/src/hooks/use-form.test.js
index 426ec89..8b58b57 100644
--- a/test/src/hooks/use-form.test.js
+++ b/test/src/hooks/use-form.test.js
@@ -24,6 +24,24 @@ describe('useForm hook', () => {
});
});
+ it('should set the initial values of nested objects', () => {
+ const { result } = renderHook(() => useForm({
+ initialValues: {
+ foo: {
+ bar: 'baz'
+ }
+ },
+ jsonSchema: { type: 'object' },
+ onSubmit: () => {}
+ }));
+
+ expect(result.current.state.fields.values).toEqual({
+ foo: {
+ bar: 'baz'
+ }
+ });
+ });
+
it('should start as not active, not dirty, not touched and without errors', () => {
const { result } = renderHook(() => useForm({
jsonSchema: { type: 'object' },
@@ -53,6 +71,25 @@ describe('useForm hook', () => {
expect(onValuesChanged).toHaveBeenCalledWith({ foo: 'bar' });
});
+ it('should call `onValuesChanged` when the form values change and we have nested objects', () => {
+ const onValuesChanged = jest.fn();
+ const { result } = renderHook(() => useForm({
+ jsonSchema: { type: 'object' },
+ onSubmit: () => {},
+ onValuesChanged
+ }));
+
+ act(() => {
+ result.current.fieldActions.setFieldValue('foo.bar', 'baz');
+ });
+
+ expect(onValuesChanged).toHaveBeenCalledWith({
+ foo: {
+ bar: 'baz'
+ }
+ });
+ });
+
describe('blurField', () => {
it('should set the field to inactive and touched', () => {
const { result } = renderHook(() => useForm({
@@ -70,6 +107,22 @@ describe('useForm hook', () => {
});
});
+ it('should set the nested field to inactive and touched', () => {
+ const { result } = renderHook(() => useForm({
+ jsonSchema: { type: 'object' },
+ onSubmit: () => {}
+ }));
+
+ act(() => {
+ result.current.fieldActions.blurField('foo.bar');
+ });
+
+ expect(result.current.state.fields.meta.foo.bar).toEqual({
+ active: false,
+ touched: true
+ });
+ });
+
it('should validate the field value', () => {
const { result } = renderHook(() => useForm({
initialValues: { foo: 1 },
@@ -88,6 +141,36 @@ describe('useForm hook', () => {
expect(result.current.state.fields.errors).toHaveProperty('foo');
});
+
+ it('should validate the nested field value', () => {
+ const { result } = renderHook(() => useForm({
+ initialValues: {
+ foo: {
+ bar: 1
+ }
+ },
+ jsonSchema: {
+ properties: {
+ foo: {
+ properties: {
+ bar: {
+ type: 'string'
+ }
+ },
+ type: 'object'
+ }
+ },
+ type: 'object'
+ },
+ onSubmit: () => {}
+ }));
+
+ act(() => {
+ result.current.fieldActions.blurField('foo.bar');
+ });
+
+ expect(result.current.state.fields.errors).toHaveProperty('foo.bar');
+ });
});
describe('focusField', () => {
@@ -106,6 +189,21 @@ describe('useForm hook', () => {
});
});
+ it('should set the nested field to active', () => {
+ const { result } = renderHook(() => useForm({
+ jsonSchema: { type: 'object' },
+ onSubmit: () => {}
+ }));
+
+ act(() => {
+ result.current.fieldActions.focusField('foo.bar');
+ });
+
+ expect(result.current.state.fields.meta.foo.bar).toEqual({
+ active: true
+ });
+ });
+
it('should not validate the form', () => {
const { result } = renderHook(() => useForm({
initialValues: { foo: 1 },
@@ -124,6 +222,36 @@ describe('useForm hook', () => {
expect(result.current.state.fields.errors).toEqual({});
});
+
+ it('should not validate the form if we have nested objects', () => {
+ const { result } = renderHook(() => useForm({
+ initialValues: {
+ foo: {
+ bar: 1
+ }
+ },
+ jsonSchema: {
+ properties: {
+ foo: {
+ properties: {
+ bar: {
+ type: 'string'
+ }
+ },
+ type: 'object'
+ }
+ },
+ type: 'object'
+ },
+ onSubmit: () => {}
+ }));
+
+ act(() => {
+ result.current.fieldActions.focusField('foo.bar');
+ });
+
+ expect(result.current.state.fields.errors).toEqual({});
+ });
});
describe('registerField', () => {
@@ -144,6 +272,23 @@ describe('useForm hook', () => {
expect(result.current.state.fields.meta).toHaveProperty('foo');
});
+ it('should register the nested field', () => {
+ const { result } = renderHook(() => useForm({
+ jsonSchema: { type: 'object' },
+ onSubmit: () => {}
+ }));
+
+ expect(result.current.state.fields.values).not.toHaveProperty('foo.bar');
+ expect(result.current.state.fields.meta).not.toHaveProperty('foo.bar');
+
+ act(() => {
+ result.current.fieldActions.registerField('foo.bar');
+ });
+
+ expect(result.current.state.fields.values).toHaveProperty('foo.bar');
+ expect(result.current.state.fields.meta).toHaveProperty('foo.bar');
+ });
+
it('should keep initial values', () => {
const { result } = renderHook(() => useForm({
initialValues: { foo: 'bar' },
@@ -158,6 +303,28 @@ describe('useForm hook', () => {
expect(result.current.state.fields.values).toEqual({ foo: 'bar' });
});
+ it('should keep initial values if we have nested objects', () => {
+ const { result } = renderHook(() => useForm({
+ initialValues: {
+ foo: {
+ bar: 'baz'
+ }
+ },
+ jsonSchema: { type: 'object' },
+ onSubmit: () => {}
+ }));
+
+ act(() => {
+ result.current.fieldActions.registerField('foo.bar');
+ });
+
+ expect(result.current.state.fields.values).toEqual({
+ foo: {
+ bar: 'baz'
+ }
+ });
+ });
+
it('should validate the form', () => {
const { result } = renderHook(() => useForm({
initialValues: { foo: 1 },
@@ -176,6 +343,35 @@ describe('useForm hook', () => {
expect(result.current.state.fields.errors).toHaveProperty('foo');
});
+
+ it('should validate the form if we have nested objects', () => {
+ const { result } = renderHook(() => useForm({
+ initialValues: {
+ foo: {
+ bar: 1
+ }
+ },
+ jsonSchema: {
+ properties: {
+ foo: {
+ properties: {
+ bar: {
+ type: 'string'
+ }
+ }
+ }
+ },
+ type: 'object'
+ },
+ onSubmit: () => {}
+ }));
+
+ act(() => {
+ result.current.fieldActions.registerField('foo.bar');
+ });
+
+ expect(result.current.state.fields.errors).toHaveProperty('foo.bar');
+ });
});
describe('reset', () => {
@@ -187,6 +383,26 @@ describe('useForm hook', () => {
act(() => {
result.current.fieldActions.setFieldValue('foo', 'bar');
+ });
+
+ act(() => {
+ result.current.formActions.reset();
+ });
+
+ expect(result.current.state.fields.values).toEqual({});
+ });
+
+ it('should clear the form values if we have nested objects', () => {
+ const { result } = renderHook(() => useForm({
+ jsonSchema: { type: 'object' },
+ onSubmit: () => {}
+ }));
+
+ act(() => {
+ result.current.fieldActions.setFieldValue('foo.bar', 'baz');
+ });
+
+ act(() => {
result.current.formActions.reset();
});
@@ -201,13 +417,36 @@ describe('useForm hook', () => {
}));
act(() => {
- result.current.fieldActions.setFieldValue('foo', 'baz');
+ result.current.fieldActions.setFieldValue('foo', 'bar');
result.current.formActions.reset();
});
expect(result.current.state.fields.values).toEqual({ foo: 'bar' });
});
+ it('should set the initial values if we have nested objects', () => {
+ const { result } = renderHook(() => useForm({
+ initialValues: {
+ foo: {
+ bar: 'baz'
+ }
+ },
+ jsonSchema: { type: 'object' },
+ onSubmit: () => {}
+ }));
+
+ act(() => {
+ result.current.fieldActions.setFieldValue('foo.bar', 'baz');
+ result.current.formActions.reset();
+ });
+
+ expect(result.current.state.fields.values).toEqual({
+ foo: {
+ bar: 'baz'
+ }
+ });
+ });
+
it('should clear the form errors', () => {
const { result } = renderHook(() => useForm({
jsonSchema: {
@@ -227,6 +466,32 @@ describe('useForm hook', () => {
expect(result.current.state.fields.errors).toEqual({});
});
+ it('should clear the form errors if we have nested objects', () => {
+ const { result } = renderHook(() => useForm({
+ jsonSchema: {
+ properties: {
+ foo: {
+ properties: {
+ bar: {
+ type: 'string'
+ }
+ },
+ type: 'object'
+ }
+ },
+ type: 'object'
+ },
+ onSubmit: () => {}
+ }));
+
+ act(() => {
+ result.current.fieldActions.setFieldValue('foo.bar', 1);
+ result.current.formActions.reset();
+ });
+
+ expect(result.current.state.fields.errors).toEqual({});
+ });
+
it('should set all fields to inactive, untouched and pristine', () => {
const { result } = renderHook(() => useForm({
jsonSchema: { type: 'object' },
@@ -248,6 +513,30 @@ describe('useForm hook', () => {
}
});
});
+
+ it('should set all nested fields to inactive, untouched and pristine', () => {
+ const { result } = renderHook(() => useForm({
+ jsonSchema: { type: 'object' },
+ onSubmit: () => {}
+ }));
+
+ act(() => {
+ result.current.fieldActions.blurField('foo.bar');
+ result.current.fieldActions.setFieldValue('foo.bar', 'baz');
+ result.current.fieldActions.focusField('foo.bar');
+ result.current.formActions.reset();
+ });
+
+ expect(result.current.state.fields.meta).toEqual({
+ foo: {
+ bar: {
+ active: false,
+ dirty: false,
+ touched: false
+ }
+ }
+ });
+ });
});
describe('setFieldValue', () => {
@@ -264,6 +553,23 @@ describe('useForm hook', () => {
expect(result.current.state.fields.values).toEqual({ foo: 'bar' });
});
+ it('should set the nested field value', () => {
+ const { result } = renderHook(() => useForm({
+ jsonSchema: { type: 'object' },
+ onSubmit: () => {}
+ }));
+
+ act(() => {
+ result.current.fieldActions.setFieldValue('foo.bar', 'baz');
+ });
+
+ expect(result.current.state.fields.values).toEqual({
+ foo: {
+ bar: 'baz'
+ }
+ });
+ });
+
it('should set the field to dirty', () => {
const { result } = renderHook(() => useForm({
jsonSchema: { type: 'object' },
@@ -279,6 +585,21 @@ describe('useForm hook', () => {
}));
});
+ it('should set the nested field to dirty', () => {
+ const { result } = renderHook(() => useForm({
+ jsonSchema: { type: 'object' },
+ onSubmit: () => {}
+ }));
+
+ act(() => {
+ result.current.fieldActions.setFieldValue('foo.bar', 'baz');
+ });
+
+ expect(result.current.state.fields.meta.foo.bar).toEqual(expect.objectContaining({
+ dirty: true
+ }));
+ });
+
it('should validate the form', () => {
const { result } = renderHook(() => useForm({
jsonSchema: {
@@ -296,6 +617,31 @@ describe('useForm hook', () => {
expect(result.current.state.fields.errors).toHaveProperty('foo');
});
+
+ it('should validate the form if we have nested objects', () => {
+ const { result } = renderHook(() => useForm({
+ jsonSchema: {
+ properties: {
+ foo: {
+ properties: {
+ bar: {
+ type: 'string'
+ }
+ },
+ type: 'object'
+ }
+ },
+ type: 'object'
+ },
+ onSubmit: () => {}
+ }));
+
+ act(() => {
+ result.current.fieldActions.setFieldValue('foo.bar', 1);
+ });
+
+ expect(result.current.state.fields.errors).toHaveProperty('foo.bar');
+ });
});
describe('submit', () => {
@@ -319,13 +665,76 @@ describe('useForm hook', () => {
});
});
+ it('should call the `onSubmit` option with the form values and actions if we have nested objects', async () => {
+ const onSubmit = jest.fn();
+ const { result, waitForNextUpdate } = renderHook(() => useForm({
+ initialValues: {
+ foo: {
+ bar: 'baz'
+ }
+ },
+ jsonSchema: { type: 'object' },
+ onSubmit
+ }));
+
+ act(() => {
+ result.current.formActions.submit();
+ });
+
+ await waitForNextUpdate();
+
+ expect(onSubmit).toHaveBeenCalledTimes(1);
+ expect(onSubmit).toHaveBeenCalledWith({
+ foo: {
+ bar: 'baz'
+ }
+ }, {
+ reset: expect.any(Function)
+ });
+ });
+
it('should not call `onSubmit` when the form has errors', () => {
const onSubmit = jest.fn();
const { rerender, result } = renderHook(() => useForm({
initialValues: { foo: 1 },
jsonSchema: {
properties: {
- foo: { type: 'string' }
+ foo: { type: 'string' }
+ },
+ type: 'object'
+ },
+ onSubmit
+ }));
+
+ act(() => {
+ result.current.fieldActions.blurField('foo');
+ result.current.formActions.submit();
+ });
+
+ // Force rerender to ensure effect is called.
+ rerender();
+
+ expect(onSubmit).not.toHaveBeenCalled();
+ });
+
+ it('should not call `onSubmit` when the form has errors and we have nested objects', () => {
+ const onSubmit = jest.fn();
+ const { rerender, result } = renderHook(() => useForm({
+ initialValues: {
+ foo: {
+ bar: 1
+ }
+ },
+ jsonSchema: {
+ properties: {
+ foo: {
+ properties: {
+ bar: {
+ type: 'string'
+ }
+ },
+ type: 'object'
+ }
},
type: 'object'
},
@@ -333,7 +742,7 @@ describe('useForm hook', () => {
}));
act(() => {
- result.current.fieldActions.blurField('foo');
+ result.current.fieldActions.blurField('foo.bar');
result.current.formActions.submit();
});
@@ -380,6 +789,27 @@ describe('useForm hook', () => {
expect(result.current.state.fields.values).toEqual({ foo: 'bar' });
});
+ it('should not reset the form values if we have nested objects', async () => {
+ const onSubmit = jest.fn();
+ const { result, waitForNextUpdate } = renderHook(() => useForm({
+ jsonSchema: { type: 'object' },
+ onSubmit
+ }));
+
+ act(() => {
+ result.current.fieldActions.setFieldValue('foo.bar', 'baz');
+ result.current.formActions.submit();
+ });
+
+ await waitForNextUpdate();
+
+ expect(result.current.state.fields.values).toEqual({
+ foo: {
+ bar: 'baz'
+ }
+ });
+ });
+
it('should set all fields to touched', async () => {
const { result, waitForNextUpdate } = renderHook(() => useForm({
jsonSchema: { type: 'object' },
@@ -388,7 +818,7 @@ describe('useForm hook', () => {
act(() => {
result.current.fieldActions.registerField('foo');
- result.current.formActions.submit();
+ result.current.formActions.submit('foo');
});
await waitForNextUpdate();
@@ -400,6 +830,28 @@ describe('useForm hook', () => {
});
});
+ it('should set all nested fields to touched', async () => {
+ const { result, waitForNextUpdate } = renderHook(() => useForm({
+ jsonSchema: { type: 'object' },
+ onSubmit: () => {}
+ }));
+
+ act(() => {
+ result.current.fieldActions.registerField('foo.bar');
+ result.current.formActions.submit();
+ });
+
+ await waitForNextUpdate();
+
+ expect(result.current.state.fields.meta).toEqual({
+ foo: {
+ bar: expect.objectContaining({
+ touched: true
+ })
+ }
+ });
+ });
+
it('should validate the form', () => {
const { result } = renderHook(() => useForm({
initialValues: { foo: 1 },
@@ -419,6 +871,36 @@ describe('useForm hook', () => {
expect(result.current.state.fields.errors).toHaveProperty('foo');
});
+ it('should validate the form if we have nested objects', () => {
+ const { result } = renderHook(() => useForm({
+ initialValues: {
+ foo: {
+ bar: 1
+ }
+ },
+ jsonSchema: {
+ properties: {
+ foo: {
+ properties: {
+ bar: {
+ type: 'string'
+ }
+ },
+ type: 'object'
+ }
+ },
+ type: 'object'
+ },
+ onSubmit: () => {}
+ }));
+
+ act(() => {
+ result.current.formActions.submit();
+ });
+
+ expect(result.current.state.fields.errors).toHaveProperty('foo.bar');
+ });
+
it('should reset the form if the passed `reset` action is called', async () => {
const onSubmit = jest.fn((values, { reset }) => {
reset();
@@ -441,6 +923,28 @@ describe('useForm hook', () => {
expect(result.current.state.fields.values).toEqual({});
});
+ it('should reset the form if the passed `reset` action is called and if we have nested objects', async () => {
+ const onSubmit = jest.fn((values, { reset }) => {
+ reset();
+
+ return Promise.resolve();
+ });
+
+ const { result, waitForNextUpdate } = renderHook(() => useForm({
+ jsonSchema: { type: 'object' },
+ onSubmit
+ }));
+
+ act(() => {
+ result.current.fieldActions.setFieldValue('foo.bar', 'baz');
+ result.current.formActions.submit();
+ });
+
+ await waitForNextUpdate();
+
+ expect(result.current.state.fields.values).toEqual({});
+ });
+
it('should change the foo value when set field value with any value', () => {
const stateReducer = (state, action) => {
const { type } = action;
@@ -482,6 +986,67 @@ describe('useForm hook', () => {
expect(result.current.state.fields.values.bar).toEqual('bar');
});
+ it('should change the foo.bar value when set field value with any value', () => {
+ const stateReducer = (state, action) => {
+ const { type } = action;
+
+ switch (type) {
+ case actionTypes.SET_FIELD_VALUE:
+ return merge({}, state, {
+ fields: {
+ values: {
+ foo: {
+ bar: 'baz'
+ }
+ }
+ }
+ });
+
+ default:
+ return state;
+ }
+ };
+
+ const { result } = renderHook(() => useForm({
+ initialValues: {
+ foo: {
+ bar: 1
+ }
+ },
+ jsonSchema: {
+ properties: {
+ foo: {
+ properties: {
+ bar: {
+ type: 'string'
+ }
+ },
+ type: 'object'
+ },
+ qux: {
+ properties: {
+ quux: {
+ type: 'string'
+ }
+ },
+ type: 'object'
+ }
+ },
+ type: 'object'
+ },
+ onSubmit: () => {},
+ stateReducer
+ }));
+
+ act(() => {
+ result.current.fieldActions.setFieldValue('foo.bar', 'foo.bar');
+ result.current.fieldActions.setFieldValue('qux.quux', 'qux.quux');
+ });
+
+ expect(result.current.state.fields.values.foo.bar).toEqual('baz');
+ expect(result.current.state.fields.values.qux.quux).toEqual('qux.quux');
+ });
+
it('should validate the field with custom format', () => {
const { result } = renderHook(() => useForm({
initialValues: {
@@ -521,6 +1086,61 @@ describe('useForm hook', () => {
});
});
+ it('should validate the nested field with custom format', () => {
+ const { result } = renderHook(() => useForm({
+ initialValues: {
+ bar: {
+ baz: '123'
+ },
+ foo: {
+ qux: '123'
+ }
+ },
+ jsonSchema: {
+ properties: {
+ bar: {
+ properties: {
+ baz: {
+ format: 'baz',
+ type: 'string'
+ }
+ },
+ type: 'object'
+ },
+ foo: {
+ properties: {
+ qux: {
+ format: 'qux',
+ type: 'string'
+ }
+ },
+ type: 'object'
+ }
+ },
+ type: 'object'
+ },
+ onSubmit: () => {},
+ validationOptions: {
+ formats: {
+ baz: value => !isNaN(Number(value)),
+ qux: () => false
+ }
+ }
+ }));
+
+ act(() => {
+ result.current.formActions.submit();
+ });
+
+ expect(result.current.state.fields.errors).toEqual({
+ foo: {
+ qux: {
+ rule: 'format'
+ }
+ }
+ });
+ });
+
it('should validate with custom keywords', () => {
const { result } = renderHook(() => useForm({
initialValues: {
@@ -565,6 +1185,67 @@ describe('useForm hook', () => {
}
});
});
+
+ it('should validate with custom keywords if we have nested objects', () => {
+ const { result } = renderHook(() => useForm({
+ initialValues: {
+ bar: {
+ baz: '123'
+ },
+ foo: {
+ qux: '123'
+ }
+ },
+ jsonSchema: {
+ properties: {
+ bar: {
+ properties: {
+ baz: {
+ isBaz: true,
+ type: 'string'
+ }
+ },
+ type: 'object'
+ },
+ foo: {
+ properties: {
+ qux: {
+ isQux: true,
+ type: 'string'
+ }
+ },
+ type: 'object'
+ }
+ },
+ type: 'object'
+ },
+ onSubmit: () => {},
+ validationOptions: {
+ keywords: {
+ isBaz: {
+ type: 'string',
+ validate: () => true
+ },
+ isQux: {
+ type: 'string',
+ validate: () => false
+ }
+ }
+ }
+ }));
+
+ act(() => {
+ result.current.formActions.submit();
+ });
+
+ expect(result.current.state.fields.errors).toEqual({
+ foo: {
+ qux: {
+ rule: 'isQux'
+ }
+ }
+ });
+ });
});
describe('form meta', () => {
@@ -581,6 +1262,19 @@ describe('useForm hook', () => {
expect(result.current.state.meta.active).toBe(true);
});
+ it('should set the form as active when a nested field is active', () => {
+ const { result } = renderHook(() => useForm({
+ jsonSchema: { type: 'object' },
+ onSubmit: () => {}
+ }));
+
+ act(() => {
+ result.current.fieldActions.focusField('foo.bar');
+ });
+
+ expect(result.current.state.meta.active).toBe(true);
+ });
+
it('should set the form as dirty when a field is dirty', () => {
const { result } = renderHook(() => useForm({
jsonSchema: { type: 'object' },
@@ -594,6 +1288,19 @@ describe('useForm hook', () => {
expect(result.current.state.meta.dirty).toBe(true);
});
+ it('should set the form as dirty when a nested field is dirty', () => {
+ const { result } = renderHook(() => useForm({
+ jsonSchema: { type: 'object' },
+ onSubmit: () => {}
+ }));
+
+ act(() => {
+ result.current.fieldActions.setFieldValue('foo.bar', 'baz');
+ });
+
+ expect(result.current.state.meta.dirty).toBe(true);
+ });
+
it('should set the form as touched when a field is touched', () => {
const { result } = renderHook(() => useForm({
jsonSchema: { type: 'object' },
@@ -606,6 +1313,19 @@ describe('useForm hook', () => {
expect(result.current.state.meta.touched).toBe(true);
});
+
+ it('should set the form as touched when a nested field is touched', () => {
+ const { result } = renderHook(() => useForm({
+ jsonSchema: { type: 'object' },
+ onSubmit: () => {}
+ }));
+
+ act(() => {
+ result.current.fieldActions.blurField('foo.bar');
+ });
+
+ expect(result.current.state.meta.touched).toBe(true);
+ });
});
describe('custom validate', () => {
@@ -626,6 +1346,27 @@ describe('useForm hook', () => {
expect(validate).toHaveBeenCalledWith(jsonSchema, { foo: 'bar' }, 'qux');
});
+ it('should be called when a nested field value changes', () => {
+ const validate = jest.fn(() => ({}));
+ const jsonSchema = { type: 'object' };
+ const { result } = renderHook(() => useForm({
+ jsonSchema,
+ onSubmit: () => {},
+ validate,
+ validationOptions: 'qux'
+ }));
+
+ act(() => {
+ result.current.fieldActions.setFieldValue('foo.bar', 'baz');
+ });
+
+ expect(validate).toHaveBeenCalledWith(jsonSchema, {
+ foo: {
+ bar: 'baz'
+ }
+ }, 'qux');
+ });
+
it('should set errors to an empty object when validate returns undefined', () => {
const { result } = renderHook(() => useForm({
jsonSchema: { type: 'object' },
@@ -639,5 +1380,19 @@ describe('useForm hook', () => {
expect(result.current.state.fields.errors).toEqual({});
});
+
+ it('should set errors to an empty object when validate returns undefined and we have nested objects', () => {
+ const { result } = renderHook(() => useForm({
+ jsonSchema: { type: 'object' },
+ onSubmit: () => {},
+ validate: () => {}
+ }));
+
+ act(() => {
+ result.current.fieldActions.setFieldValue('foo.bar', 'baz');
+ });
+
+ expect(result.current.state.fields.errors).toEqual({});
+ });
});
});