d2-ui
Version:
462 lines (354 loc) • 16.2 kB
JavaScript
import React from 'react';
import FormBuilder from '../../src/forms/FormBuilder.component';
import { shallow } from 'enzyme';
import { getStubContext } from '../../config/inject-theme';
import Textfield from 'material-ui/Textfield/Textfield';
import AsyncValidatorRunner from '../../src/forms/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).to.be.instanceof(AsyncValidatorRunner);
});
describe('field rendering', () => {
it('should render a the Textfield', () => {
const textField = formComponent.find(Textfield);
expect(textField).to.have.length(1);
});
it('should pass the value to the Textfield', () => {
const textField = formComponent.find(Textfield);
expect(textField.props().value).to.equal('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).to.equal('Mike');
});
});
describe('field rendering with sync validator', () => {
let fields;
let onUpdateFieldSpy;
let onUpdateFormStatus;
beforeEach(() => {
fields = [
{
name: 'name',
component: Textfield,
value: 'John',
validators: [
{
validator: sinon.spy((value) => !!value),
message: 'field_is_required',
},
],
},
];
function onUpdateField(fieldName, value) {
fields[0].value = value;
}
onUpdateFieldSpy = sinon.spy(onUpdateField);
onUpdateFormStatus = sinon.spy();
formComponent = renderComponent({
fields,
onUpdateField: onUpdateFieldSpy,
onUpdateFormStatus,
});
});
it('should mark the field as valid', () => {
const textField = formComponent.find(Textfield);
expect(textField.props().errorText).to.be.undefined;
});
describe('with invalid result', () => {
beforeEach(() => {
formComponent.setProps({
fields: [
{
name: 'name',
component: Textfield,
value: '',
validators: [
{
validator: sinon.spy((value) => !!value),
message: 'field_is_required',
},
],
},
],
});
});
it('should mark the field as invalid', () => {
expect(formComponent.state().fields.name.valid).to.be.false;
});
it('should pass the error message to the text field', () => {
expect(formComponent.find(Textfield).first().props().errorText).to.equal('field_is_required');
});
it('should mark the form as invalid', () => {
expect(formComponent.state().form.valid).to.be.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).to.be.calledWith('John1');
});
it('should emit the value of the field from the form', () => {
const textField = formComponent.find(Textfield);
textField.props().onChange({ target: { value: 'John1' }});
expect(onUpdateFieldSpy).to.be.calledWith('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).to.be.calledWith('');
});
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).to.be.calledWith('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.to.be.called;
});
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).to.be.calledWith('name', 'John');
});
});
describe('when changing an `object` value', () => {
// Fake object field
const ObjectField = () => (<div></div>);
beforeEach(() => {
fields = [
{
name: 'mapValues',
component: ObjectField,
value: new Map([['key1', 'value1'], ['key2', 'value2']]),
validators: [
{
validator: sinon.spy((value) => value.size >= 2),
message: 'field_is_required',
},
],
},
];
function onUpdateField(fieldName, value) {
fields[0].value = value;
}
onUpdateFieldSpy = sinon.spy(onUpdateField);
onUpdateFormStatus = sinon.spy();
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).to.be.called;
});
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).to.be.deep.calledWith('mapValues', fields[0].value);
expect(onUpdateFieldSpy.firstCall.args[1].get('key1')).to.equal('value1');
expect(onUpdateFieldSpy.firstCall.args[1].get('key2')).to.equal('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).to.be.calledWith({ 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).to.be.calledWith({ 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: [
sinon.spy(() => new Promise(() => {}))
],
},
];
function onUpdateField(fieldName, value) {
fields[0].value = value;
}
onUpdateFieldSpy = sinon.spy(onUpdateField);
onUpdateFormStatus = sinon.spy();
asyncValidationRunnerMock = {
run() {
return this;
},
listenToValidatorsFor: stub().returns({
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).to.be.calledWith({ 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] = sinon.spy(() => Promise.resolve('Success'));
formComponent = renderComponent({
fields,
onUpdateField: onUpdateFieldSpy,
onUpdateFormStatus,
});
textField.props().onChange({ target: { value: 'John1' }});
expect(onUpdateFormStatus).to.be.calledWith({ pristine: false, valid: true, validating: true });
setTimeout(() => {
expect(onUpdateFormStatus).to.be.calledWith({ 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] = sinon.spy(() => Promise.reject('Failure'));
asyncValidationRunnerMock.listenToValidatorsFor
.returns({
subscribe(callback) {
callback({fieldName: 'name', isValid: false});
},
});
formComponent = renderComponent({
fields,
onUpdateField: onUpdateFieldSpy,
onUpdateFormStatus,
});
textField.props().onChange({ target: { value: 'John1' }});
expect(onUpdateFormStatus).to.be.calledWith({ pristine: false, valid: true, validating: true });
setTimeout(() => {
expect(onUpdateFormStatus).to.be.calledWith({ 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] = sinon.spy(() => Promise.resolve('Success'));
formComponent = renderComponent({
fields,
onUpdateField: onUpdateFieldSpy,
onUpdateFormStatus,
});
textField.props().onChange({ target: { value: 'John1' }});
setTimeout(() => {
expect(onUpdateFieldSpy).to.be.calledWith('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] = sinon.spy(() => Promise.reject('Failure'));
formComponent = renderComponent({
fields,
onUpdateField: onUpdateFieldSpy,
onUpdateFormStatus,
});
textField.props().onChange({ target: { value: 'John1' }});
setTimeout(() => {
expect(onUpdateFieldSpy).to.be.calledWith('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] = sinon.spy(() => new Promise(() => {}));
formComponent = renderComponent({
fields,
onUpdateField: onUpdateFieldSpy,
onUpdateFormStatus,
});
sinon.spy(FormBuilder.prototype, 'cancelAsyncValidators');
textField.props().onChange({ target: { value: 'John1' }});
textField.props().onChange({ target: { value: 'John12' }});
expect(formComponent.instance().cancelAsyncValidators).to.be.called;
});
});
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 = sinon.spy(onUpdateField);
onUpdateFormStatus = sinon.spy();
formComponent = renderComponent({
fields,
onUpdateField: onUpdateFieldSpy,
onUpdateFormStatus,
});
});
it('should not run the handler for onChange', () => {
sinon.spy(FormBuilder.prototype, 'updateFieldState');
const textField = formComponent.find(Textfield);
textField.props().onChange({ target: { value: 'John2' }});
textField.props().onBlur({ target: { value: 'John2' }});
expect(onUpdateFieldSpy).to.be.calledWith('name', 'John2');
});
});
});