Skip to content

Commit

Permalink
adds custom onBlur
Browse files Browse the repository at this point in the history
  • Loading branch information
vitkon committed Nov 26, 2017
1 parent a752e9e commit bd687b7
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 74 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ It does not modify the component class passed to it; instead, it *returns* a new

* [`formConfig: IFormConfig`] \(*Object*): An object contains initial configuration for the form

- `initialModel: Partial<T>` — object provides initial values to the form fields
- `middleware: (props: T) => any` — function transforms props passed to the wrapped component
- `onInputBlur: (e: React.ForcusEvent<any>) => any` — function is called on every blur on an input field within the form. Adding a custom `onBlur` to the input field itself is not recommended, use this method instead

## Validation

Expand Down
4 changes: 4 additions & 0 deletions src/FormContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ const makeWrapper = <T extends {}>(config: IFormConfig) => (WrappedComponent: an
bindToBlurEvent = (e: React.FocusEvent<any>) => {
const target = e.target as HTMLInputElement;
this.setFieldToTouched(target.name as keyof T);

if (config.onInputBlur) {
config.onInputBlur(e);
}
}

bindInput = (name: keyof T) => ({
Expand Down
147 changes: 73 additions & 74 deletions src/__tests__/FormContainer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,116 +3,115 @@ import * as ReactDOM from 'react-dom';
import { mount } from 'enzyme';
import { connectForm } from '../FormContainer';

const setupTest = (formConfig = {}) => {
const MockComponent = ({ formMethods: { bindInput }, form }) => (
<div>
<input {...bindInput('foo') } />
</div>
);
const WrapperComponent = connectForm([], formConfig)(MockComponent);
const wrapperComponent = mount(
<WrapperComponent />
);
const wrappedComponent = wrapperComponent.find(MockComponent);
const input = wrapperComponent.find('input');

return {
wrapperComponent,
wrappedComponent,
input
};
};

describe('Form container', () => {
it('should export connectForm function by default', () => {
expect(connectForm).toBeDefined;
});

describe('render', () => {
let wrapperComponent;
let wrappedComponent;
let input;

beforeEach(() => {
const MockComponent = ({ formMethods: { bindInput } }) => (
<div>
<input {...bindInput('foo') } />
</div>
);

const initialModel = {
foo: 'bananas'
}

const formConfig = {
initialModel: {
foo: 'bananas'
},
middleware: (props: any) => ({ ...props, bar: 'baz' })
}
const WrapperComponent = connectForm([], formConfig)(MockComponent);
wrapperComponent = mount(
<WrapperComponent />
);
wrappedComponent = wrapperComponent.find(MockComponent);
input = wrapperComponent.find('input');
});

it('should render the wrapped component', () => {
const { wrappedComponent } = setupTest();
expect(wrappedComponent.length).toEqual(1);
});

it('should set initial state', () => {
const { wrapperComponent } = setupTest();
const state: any = wrapperComponent.state();

expect(state.model).toEqual({});
expect(state.touched).toEqual({});
expect(Object.keys(state.inputs)).toEqual(['foo']);
});

it('should render the input', () => {
const { input } = setupTest();
expect(input.length).toEqual(1);
});

describe('wrapper props', () => {
it('should have form prop', () => {
expect(wrappedComponent.props()).toHaveProperty('form');
});

it('should have form model prop', () => {
expect(wrappedComponent.props().form).toHaveProperty('model');
});
it('should have required form props', () => {
const { wrapperComponent, wrappedComponent, input } = setupTest();
const requiredProps: ReadonlyArray<string> = ['model', 'inputs', 'touched'];

it('should have form inputs prop', () => {
expect(wrappedComponent.props().form).toHaveProperty('inputs');
expect(wrappedComponent.props()).toHaveProperty('form');
requiredProps.forEach(prop => expect(wrappedComponent.props().form).toHaveProperty(prop))
});

it('should have form touched prop', () => {
expect(wrappedComponent.props().form).toHaveProperty('touched');
});
it('should have required formMethods props', () => {
const { wrapperComponent, wrappedComponent, input } = setupTest();
const requiredProps: ReadonlyArray<string> = ['bindInput', 'bindToChangeEvent', 'setProperty', 'setModel', 'setFieldToTouched'];

it('should have formMethods prop', () => {
expect(wrappedComponent.props()).toHaveProperty('formMethods');
});

it('should have formMethods bindInput prop', () => {
expect(wrappedComponent.props().formMethods).toHaveProperty('bindInput');
});

it('should have formMethods bindToChangeEvent prop', () => {
expect(wrappedComponent.props().formMethods).toHaveProperty('bindToChangeEvent');
});

it('should have formMethods setProperty prop', () => {
expect(wrappedComponent.props().formMethods).toHaveProperty('setProperty');
});

it('should have formMethods setModel prop', () => {
expect(wrappedComponent.props().formMethods).toHaveProperty('setModel');
});

it('should have formMethods setFieldToTouched prop', () => {
expect(wrappedComponent.props().formMethods).toHaveProperty('setFieldToTouched');
});

it('should get run provided middleware', () => {
expect(wrappedComponent.props()).toHaveProperty('bar');
requiredProps.forEach(prop => expect(wrappedComponent.props().formMethods).toHaveProperty(prop))
});
});

describe('bindInput', () => {
it('should have an input with a name', () => {
it('should have an input with a name and a value', () => {
const { input } = setupTest();
expect(input.prop('name')).toEqual('foo');
expect(input.prop('value')).toEqual('');
});

it('should get the value of the input', () => {
expect(input.prop('value')).toEqual('bananas');
});
it('should change the value of the input', () => {
const { wrapperComponent, input } = setupTest();
const newValue = 'apples';

it('should get the value of the input', () => {
expect(input.prop('value')).toEqual('bananas');
input.simulate('change', { target: { name: input.prop('name'), value: 'apples' } });
expect(input.prop('value')).toEqual('');
input.simulate('change', { target: { name: input.prop('name'), value: newValue } });
const updatedInput = wrapperComponent.find('input');
expect(updatedInput.prop('value')).toEqual('apples');
expect(updatedInput.prop('value')).toEqual(newValue);
});

it('should set field to touched on blur', () => {
const { wrapperComponent, input } = setupTest();

expect(wrapperComponent.state('touched')).toEqual({});
input.simulate('blur');
expect(wrapperComponent.state('touched')).toEqual({ foo: true });
});
});

describe('formConfig', () => {
it('should should set initial model when it is provided', () => {
const foo = 'bananas';
const initialModel = { foo }
const { input } = setupTest({ initialModel });
expect(input.prop('value')).toEqual(foo);
});

it('should call custom onInputBlur when it is provided', () => {
const onInputBlur = jest.fn();
const { wrapperComponent, input } = setupTest({ onInputBlur });
input.simulate('blur');
expect(onInputBlur).toHaveBeenCalled;
});

it('should call middleware when it is provided', () => {
const middleware = (props: any) => ({ ...props, bar: 'baz' });
const { wrappedComponent } = setupTest({ middleware });
expect(wrappedComponent.props()).toHaveProperty('bar');
});
});
})
});
1 change: 1 addition & 0 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,6 @@ export interface IFormProps<T = any> {

export interface IFormConfig<T = any> {
initialModel?: Partial<T>;
onInputBlur?: (e: React.FocusEvent<any>) => any;
middleware?: (props: T) => any;
}

0 comments on commit bd687b7

Please sign in to comment.