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({}); + }); }); });