d2-ui
Version:
462 lines (354 loc) • 16.3 kB
JavaScript
import React from 'react';
import { shallow } from 'enzyme';
import TextField from 'material-ui/TextField/TextField';
import FormBuilder from '../FormBuilder.component';
import { getStubContext } from '../../../config/inject-theme';
import AsyncValidatorRunner from '../AsyncValidatorRunner';
describe('FormBuilder component', () => {
let formComponent;
const renderComponent = (props, children) => {
return shallow(<FormBuilder {...props}>{children}</FormBuilder>, {
context: getStubContext(),
});
};
beforeEach(() => {
const fields = [
{ name: 'name', component: TextField, value: 'John' },
];
function onUpdateField(fieldName, value) {
fields[0].value = value;
}
formComponent = renderComponent({ fields, onUpdateField });
});
it('should have a asyncFieldValidator availble on the component', () => {
expect(formComponent.instance().asyncValidationRunner).toBeInstanceOf(AsyncValidatorRunner);
});
describe('field rendering', () => {
it('should render a the Textfield', () => {
const textField = formComponent.find(TextField);
expect(textField).toHaveLength(1);
});
it('should pass the value to the Textfield', () => {
const textField = formComponent.find(TextField);
expect(textField.props().value).toBe('John');
});
it('should pass the new value to the TextField when props has changed', () => {
formComponent.setProps({
fields: [
{ name: 'name', component: TextField, value: 'Mike' },
],
});
formComponent.update();
const textField = formComponent.find(TextField);
expect(textField.props().value).toBe('Mike');
});
});
describe('field rendering with sync validator', () => {
let fields;
let onUpdateFieldSpy;
let onUpdateFormStatus;
beforeEach(() => {
fields = [
{
name: 'name',
component: TextField,
value: 'John',
validators: [
{
validator: jest.fn(value => !!value),
message: 'field_is_required',
},
],
},
];
function onUpdateField(fieldName, value) {
fields[0].value = value;
}
onUpdateFieldSpy = jest.fn(onUpdateField);
onUpdateFormStatus = jest.fn();
formComponent = renderComponent({
fields,
onUpdateField: onUpdateFieldSpy,
onUpdateFormStatus,
});
});
it('should mark the field as valid', () => {
const textField = formComponent.find(TextField);
expect(textField.props().errorText).toBe(undefined);
});
describe('with invalid result', () => {
beforeEach(() => {
formComponent.setProps({
fields: [
{
name: 'name',
component: TextField,
value: '',
validators: [
{
validator: jest.fn(value => !!value),
message: 'field_is_required',
},
],
},
],
});
});
it('should mark the field as invalid', () => {
expect(formComponent.state().fields.name.valid).toBe(false);
});
it('should pass the error message to the text field', () => {
expect(formComponent.find(TextField).first().props().errorText).toBe('field_is_required');
});
it('should mark the form as invalid', () => {
expect(formComponent.state().form.valid).toBe(false);
});
});
describe('when changing textfield value', () => {
it('should run the validator on value change', () => {
const textField = formComponent.find(TextField);
textField.props().onChange({ target: { value: 'John1' } });
expect(fields[0].validators[0].validator).toHaveBeenCalledWith('John1', expect.any(Object));
});
it('should emit the value of the field from the form', () => {
const textField = formComponent.find(TextField);
textField.props().onChange({ target: { value: 'John1' } });
expect(onUpdateFieldSpy).toHaveBeenCalledWith('name', 'John1');
});
it('should run the validator with an empty value', () => {
const textField = formComponent.find(TextField);
textField.props().onChange({ target: { value: '' } });
expect(fields[0].validators[0].validator).toHaveBeenCalledWith('', expect.any(Object));
});
it('should still emit the value from the form when it is invalid', () => {
const textField = formComponent.find(TextField);
textField.props().onChange({ target: { value: 'ddd' } });
expect(onUpdateFieldSpy).toHaveBeenCalledWith('name', 'ddd');
});
it('should not run the validator when the values are the same', () => {
const textField = formComponent.find(TextField);
textField.props().onChange({ target: { value: 'John' } });
expect(fields[0].validators[0].validator).not.toHaveBeenCalled();
});
it('should still emit the value when the values are the same', () => {
const textField = formComponent.find(TextField);
textField.props().onChange({ target: { value: 'John' } });
expect(onUpdateFieldSpy).toHaveBeenCalledWith('name', 'John');
});
});
describe('when changing an `object` value', () => {
// Fake object field
const ObjectField = () => (<div />);
beforeEach(() => {
fields = [
{
name: 'mapValues',
component: ObjectField,
value: new Map([['key1', 'value1'], ['key2', 'value2']]),
validators: [
{
validator: jest.fn(value => value.size >= 2),
message: 'field_is_required',
},
],
},
];
function onUpdateField(fieldName, value) {
fields[0].value = value;
}
onUpdateFieldSpy = jest.fn(onUpdateField);
onUpdateFormStatus = jest.fn();
formComponent = renderComponent({
fields,
onUpdateField: onUpdateFieldSpy,
onUpdateFormStatus,
});
});
it('should run the validator even when the value is the same', () => {
const objectField = formComponent.find(ObjectField);
objectField.props().onChange({ target: { value: fields[0].value } });
expect(fields[0].validators[0].validator).toHaveBeenCalled();
});
it('should emit the value when the values are the same', () => {
const objectField = formComponent.find(ObjectField);
objectField.props().onChange({ target: { value: fields[0].value } });
expect(onUpdateFieldSpy).toHaveBeenCalledWith('mapValues', fields[0].value);
expect(onUpdateFieldSpy.mock.calls[0][1].get('key1')).toBe('value1');
expect(onUpdateFieldSpy.mock.calls[0][1].get('key2')).toBe('value2');
});
});
describe('onUpdateFormStatus callback', () => {
it('should emit the formStatus when a field was changed and the value is valid', () => {
const textField = formComponent.find(TextField);
textField.props().onChange({ target: { value: 'John1' } });
expect(onUpdateFormStatus).toHaveBeenCalledWith({ pristine: false, valid: true, validating: false });
});
it('should emit the formStatus when a field was changed but the value is invalid', () => {
const textField = formComponent.find(TextField);
textField.props().onChange({ target: { value: '' } });
expect(onUpdateFormStatus).toHaveBeenCalledWith({ pristine: false, valid: false, validating: false });
});
});
});
describe('field rendering with async validator', () => {
let fields;
let onUpdateFieldSpy;
let onUpdateFormStatus;
let asyncValidationRunnerMock;
beforeEach(() => {
fields = [
{
name: 'name',
component: TextField,
value: 'John',
asyncValidators: [
jest.fn(() => new Promise(() => {})),
],
},
];
function onUpdateField(fieldName, value) {
fields[0].value = value;
}
onUpdateFieldSpy = jest.fn(onUpdateField);
onUpdateFormStatus = jest.fn();
asyncValidationRunnerMock = {
run() {
return this;
},
listenToValidatorsFor: jest.fn().mockReturnValue({
subscribe(callback) {
callback({ fieldName: 'name', isValid: true });
},
}),
};
formComponent = renderComponent({
fields,
onUpdateField: onUpdateFieldSpy,
onUpdateFormStatus,
asyncValidationRunner: asyncValidationRunnerMock,
});
});
it('should emit the formStatus with validating set to true before running the async validators', () => {
const textField = formComponent.find(TextField);
textField.props().onChange({ target: { value: 'John1' } });
expect(onUpdateFormStatus).toHaveBeenCalledWith({ pristine: false, valid: true, validating: true });
});
// TODO: Incorrectly(?) sets pristine state to true?
it('should emit the formStatus after the async validators complete', (done) => {
const textField = formComponent.find(TextField);
fields[0].asyncValidators[0] = jest.fn(() => Promise.resolve('Success'));
formComponent = renderComponent({
fields,
onUpdateField: onUpdateFieldSpy,
onUpdateFormStatus,
});
textField.props().onChange({ target: { value: 'John1' } });
expect(onUpdateFormStatus).toHaveBeenCalledWith({ pristine: false, valid: true, validating: true });
setTimeout(() => {
expect(onUpdateFormStatus).toHaveBeenCalledWith({ pristine: true, valid: true, validating: false });
formComponent.update();
done();
}, 5);
});
// TODO: Incorrectly(?) sets pristine state to true?
it('should emit the formStatus after the async validators failed', (done) => {
const textField = formComponent.find(TextField);
fields[0].asyncValidators[0] = jest.fn(() => Promise.reject('Failure'));
asyncValidationRunnerMock.listenToValidatorsFor
.mockReturnValue({
subscribe(callback) {
callback({ fieldName: 'name', isValid: false });
},
});
formComponent = renderComponent({
fields,
onUpdateField: onUpdateFieldSpy,
onUpdateFormStatus,
});
textField.props().onChange({ target: { value: 'John1' } });
expect(onUpdateFormStatus).toHaveBeenCalledWith({ pristine: false, valid: true, validating: true });
setTimeout(() => {
expect(onUpdateFormStatus).toHaveBeenCalledWith({ pristine: true, valid: false, validating: false });
formComponent.update();
done();
}, 5);
});
it('should call the onUpdateField after the async validators completed', (done) => {
const textField = formComponent.find(TextField);
fields[0].asyncValidators[0] = jest.fn(() => Promise.resolve('Success'));
formComponent = renderComponent({
fields,
onUpdateField: onUpdateFieldSpy,
onUpdateFormStatus,
});
textField.props().onChange({ target: { value: 'John1' } });
setTimeout(() => {
expect(onUpdateFieldSpy).toHaveBeenCalledWith('name', 'John1');
formComponent.update();
done();
}, 5);
});
it('should call the onUpdateField after the async validators failed', (done) => {
const textField = formComponent.find(TextField);
fields[0].asyncValidators[0] = jest.fn(() => Promise.reject('Failure'));
formComponent = renderComponent({
fields,
onUpdateField: onUpdateFieldSpy,
onUpdateFormStatus,
});
textField.props().onChange({ target: { value: 'John1' } });
setTimeout(() => {
expect(onUpdateFieldSpy).toHaveBeenCalledWith('name', 'John1');
formComponent.update();
done();
}, 5);
});
it('should cancel the running async validators when the value is changed again', () => {
const textField = formComponent.find(TextField);
fields[0].asyncValidators[0] = jest.fn(() => new Promise(() => {}));
formComponent = renderComponent({
fields,
onUpdateField: onUpdateFieldSpy,
onUpdateFormStatus,
});
jest.spyOn(FormBuilder.prototype, 'cancelAsyncValidators');
textField.props().onChange({ target: { value: 'John1' } });
textField.props().onChange({ target: { value: 'John12' } });
expect(formComponent.instance().cancelAsyncValidators).toHaveBeenCalled();
});
});
describe('when changeEvent is blur', () => {
let fields;
let onUpdateFieldSpy;
let onUpdateFormStatus;
beforeEach(() => {
fields = [
{
name: 'name',
component: TextField,
value: 'John',
props: {
changeEvent: 'onBlur',
},
},
];
function onUpdateField(fieldName, value) {
fields[0].value = value;
}
onUpdateFieldSpy = jest.fn(onUpdateField);
onUpdateFormStatus = jest.fn();
formComponent = renderComponent({
fields,
onUpdateField: onUpdateFieldSpy,
onUpdateFormStatus,
});
});
it('should not run the handler for onChange', () => {
jest.spyOn(FormBuilder.prototype, 'updateFieldState');
const textField = formComponent.find(TextField);
textField.props().onChange({ target: { value: 'John2' } });
textField.props().onBlur({ target: { value: 'John2' } });
expect(onUpdateFieldSpy).toHaveBeenCalledWith('name', 'John2');
});
});
});