redux-form
Version:
A higher order component decorator for forms using Redux and React
839 lines (805 loc) • 20.5 kB
JavaScript
import expect, {createSpy} from 'expect';
import readField from '../readField';
const noop = () => null;
const createRestorableSpy = (fn) => {
return createSpy(fn, function restore() {
this.calls = [];
});
};
describe('readField', () => {
const blur = createRestorableSpy();
const change = createRestorableSpy();
const focus = createRestorableSpy();
const defaultProps = {
asyncBlurFields: [],
blur,
change,
focus,
form: {},
initialValues: {},
readonly: false,
addArrayValue: noop,
removeArrayValue: noop,
fields: []
};
const expectField = ({field, name, value, dirty, touched, visited, error, initialValue, readonly, checked}) => {
expect(field)
.toExist()
.toBeA('object');
expect(field.name).toBe(name);
expect(field.value).toEqual(value);
if (readonly) {
expect(field.onBlur).toNotExist();
expect(field.onChange).toNotExist();
expect(field.onDragStart).toNotExist();
expect(field.onDrop).toNotExist();
expect(field.onFocus).toNotExist();
expect(field.onUpdate).toNotExist();
} else {
expect(field.onBlur).toBeA('function');
expect(field.onChange).toBeA('function');
expect(field.onDragStart).toBeA('function');
expect(field.onDrop).toBeA('function');
expect(field.onFocus).toBeA('function');
expect(field.onUpdate).toBeA('function');
expect(field.onUpdate).toBe(field.onChange);
// call blur
expect(blur.calls.length).toBe(0);
field.onBlur('newValue');
expect(blur.calls.length).toBe(1);
expect(blur)
.toHaveBeenCalled()
.toHaveBeenCalledWith(name, 'newValue');
// call change
expect(change.calls.length).toBe(0);
field.onChange('newValue');
expect(change.calls.length).toBe(1);
expect(change)
.toHaveBeenCalled()
.toHaveBeenCalledWith(name, 'newValue');
// call focus
expect(focus.calls.length).toBe(0);
field.onFocus();
expect(focus.calls.length).toBe(1);
expect(focus).toHaveBeenCalled();
}
expect(field.defaultChecked).toBe(initialValue === true);
expect(field.defaultValue).toBe(initialValue);
expect(field.initialValue).toBe(initialValue);
expect(field.error).toBe(error);
expect(field.valid).toBe(!error);
expect(field.invalid).toBe(!!error);
expect(field.dirty).toBe(dirty);
expect(field.pristine).toBe(!dirty);
expect(field.touched).toBe(touched);
expect(field.visited).toBe(visited);
expect(field.checked).toBe(checked);
blur.restore();
change.restore();
focus.restore();
};
it('should initialize a simple field', () => {
const fields = {};
readField({}, 'foo', undefined, fields, {}, undefined, false, defaultProps);
expectField({
field: fields.foo,
name: 'foo',
value: undefined,
dirty: false,
touched: false,
visited: false,
error: undefined,
initialValue: undefined,
readonly: false
});
});
it('should read a simple field', () => {
const fields = {};
readField({
foo: {
value: 'bar'
}
}, 'foo', undefined, fields, {}, undefined, false, defaultProps);
expectField({
field: fields.foo,
name: 'foo',
value: 'bar',
dirty: true,
touched: false,
visited: false,
error: undefined,
initialValue: undefined,
readonly: false
});
});
it('should read a simple field with initial values', () => {
const fields = {};
readField({
foo: {
value: 'bar',
initial: 'dog'
}
}, 'foo', undefined, fields, {}, undefined, false, {
...defaultProps,
initialValues: {foo: 'cat'}
});
expectField({
field: fields.foo,
name: 'foo',
value: 'bar',
dirty: true,
touched: false,
visited: false,
error: undefined,
initialValue: 'dog', // state.initial should override prop
readonly: false
});
});
it('should read a simple field with sync errors', () => {
const fields = {};
readField({
foo: {
value: 'bar'
}
}, 'foo', undefined, fields, {
foo: 'fooError'
}, undefined, false, defaultProps);
expectField({
field: fields.foo,
name: 'foo',
value: 'bar',
dirty: true,
touched: false,
visited: false,
error: 'fooError',
initialValue: undefined,
readonly: false
});
});
it('should set checked for boolean value', () => {
const fields = {};
readField({
foo: {
value: true
}
}, 'foo', undefined, fields, {}, undefined, false, defaultProps);
expectField({
field: fields.foo,
name: 'foo',
value: true,
dirty: true,
touched: false,
visited: false,
error: undefined,
initialValue: undefined,
readonly: false,
checked: true
});
readField({
foo: {
value: false
}
}, 'foo', undefined, fields, {}, undefined, false, defaultProps);
expectField({
field: fields.foo,
name: 'foo',
value: false,
dirty: true,
touched: false,
visited: false,
error: undefined,
initialValue: undefined,
readonly: false,
checked: false
});
});
it('should update simple fields', () => {
const fields = {};
readField({
foo: {
value: 'bar'
}
}, 'foo', undefined, fields, {}, undefined, false, defaultProps);
expectField({
field: fields.foo,
name: 'foo',
value: 'bar',
dirty: true,
touched: false,
visited: false,
error: undefined,
initialValue: undefined,
readonly: false
});
const beforeField = fields.foo;
readField({
foo: {
value: 'dog'
}
}, 'foo', undefined, fields, {}, undefined, false, defaultProps);
expectField({
field: fields.foo,
name: 'foo',
value: 'dog',
dirty: true,
touched: false,
visited: false,
error: undefined,
initialValue: undefined,
readonly: false
});
const afterField = fields.foo;
expect(beforeField).toNotBe(afterField); // field instance should be different
});
it('should initialize a nested field', () => {
const fields = {};
readField({}, 'foo.baz', undefined, fields, {}, undefined, false, defaultProps);
expectField({
field: fields.foo.baz,
name: 'foo.baz',
value: undefined,
dirty: false,
touched: false,
visited: false,
error: undefined,
initialValue: undefined,
readonly: false
});
});
it('should read a nested field', () => {
const fields = {};
readField({
foo: {
baz: {
value: 'bar'
}
}
}, 'foo.baz', undefined, fields, {}, undefined, false, defaultProps);
expectField({
field: fields.foo.baz,
name: 'foo.baz',
value: 'bar',
dirty: true,
touched: false,
visited: false,
error: undefined,
initialValue: undefined,
readonly: false
});
});
it('should read a nested field with initial value', () => {
const fields = {};
readField({
foo: {
baz: {
value: 'bar',
initial: 'dog'
}
}
}, 'foo.baz', undefined, fields, {}, undefined, false, {
...defaultProps,
initialValues: {
foo: {
baz: 'cat'
}
}
});
expectField({
field: fields.foo.baz,
name: 'foo.baz',
value: 'bar',
dirty: true,
touched: false,
visited: false,
error: undefined,
initialValue: 'dog', // state.initial should override prop
readonly: false
});
});
it('should read a nested field with sync errors', () => {
const fields = {};
readField({
foo: {
baz: {
value: 'bar'
}
}
}, 'foo.baz', undefined, fields, {
foo: {
baz: 'bazError'
}
}, undefined, false, defaultProps);
expectField({
field: fields.foo.baz,
name: 'foo.baz',
value: 'bar',
dirty: true,
touched: false,
visited: false,
error: 'bazError',
initialValue: undefined,
readonly: false
});
});
it('should update a nested field', () => {
const fields = {};
readField({
foo: {
baz: {
value: 'bar'
}
}
}, 'foo.baz', undefined, fields, {
foo: {
baz: 'bazError'
}
}, undefined, false, defaultProps);
expectField({
field: fields.foo.baz,
name: 'foo.baz',
value: 'bar',
dirty: true,
touched: false,
visited: false,
error: 'bazError',
initialValue: undefined,
readonly: false
});
const beforeFoo = fields.foo;
const beforeField = fields.foo.baz;
readField({
foo: {
baz: {
value: 'barNew'
}
}
}, 'foo.baz', undefined, fields, {
foo: {
baz: 'bazError'
}
}, undefined, false, defaultProps);
expectField({
field: fields.foo.baz,
name: 'foo.baz',
value: 'barNew',
dirty: true,
touched: false,
visited: false,
error: 'bazError',
initialValue: undefined,
readonly: false
});
const afterFoo = fields.foo;
const afterField = fields.foo.baz;
expect(beforeFoo).toNotBe(afterFoo); // field container instance should be same
expect(beforeField).toNotBe(afterField); // field instance should be different
});
it('should initialize an array field', () => {
const fields = {};
readField({}, 'foo[]', undefined, fields, {}, undefined, false, defaultProps);
expect(fields.foo).toBeA('array');
expect(fields.foo[0]).toBe(undefined);
});
it('should read an array field', () => {
const fields = {};
readField({
foo: [
{value: 'bar'},
{value: 'baz'}
]
}, 'foo[]', undefined, fields, {}, undefined, false, defaultProps);
expectField({
field: fields.foo[0],
name: 'foo[0]',
value: 'bar',
dirty: true,
touched: false,
visited: false,
error: undefined,
initialValue: undefined,
readonly: false
});
expectField({
field: fields.foo[1],
name: 'foo[1]',
value: 'baz',
dirty: true,
touched: false,
visited: false,
error: undefined,
initialValue: undefined,
readonly: false
});
expect(fields.foo[2]).toBe(undefined);
});
it('should read an array field with an initial value', () => {
const fields = {};
readField({
foo: [
{value: 'bar'},
{value: 'baz'}
]
}, 'foo[]', undefined, fields, {}, undefined, false, {
...defaultProps,
initialValues: {
foo: ['cat1', 'cat2']
}
});
expectField({
field: fields.foo[0],
name: 'foo[0]',
value: 'bar',
dirty: true,
touched: false,
visited: false,
error: undefined,
initialValue: 'cat1',
readonly: false
});
expectField({
field: fields.foo[1],
name: 'foo[1]',
value: 'baz',
dirty: true,
touched: false,
visited: false,
error: undefined,
initialValue: 'cat2',
readonly: false
});
});
it('should read an array field with sync errors', () => {
const fields = {};
readField({
foo: [
{value: 'bar'},
{value: 'baz'}
]
}, 'foo[]', undefined, fields, {
foo: ['error1', 'error2']
}, undefined, false, defaultProps);
expectField({
field: fields.foo[0],
name: 'foo[0]',
value: 'bar',
dirty: true,
touched: false,
visited: false,
error: 'error1',
initialValue: undefined,
readonly: false
});
expectField({
field: fields.foo[1],
name: 'foo[1]',
value: 'baz',
dirty: true,
touched: false,
visited: false,
error: 'error2',
initialValue: undefined,
readonly: false
});
});
it('should update an array field', () => {
const fields = {};
readField({
foo: [
{value: 'bar'},
{value: 'baz'}
]
}, 'foo[]', undefined, fields, {
foo: ['error1', 'error2']
}, undefined, false, defaultProps);
expectField({
field: fields.foo[0],
name: 'foo[0]',
value: 'bar',
dirty: true,
touched: false,
visited: false,
error: 'error1',
initialValue: undefined,
readonly: false
});
expectField({
field: fields.foo[1],
name: 'foo[1]',
value: 'baz',
dirty: true,
touched: false,
visited: false,
error: 'error2',
initialValue: undefined,
readonly: false
});
const beforeArray = fields.foo;
const before1 = fields.foo[0];
const before2 = fields.foo[1];
readField({
foo: [
{value: 'barNew'},
{value: 'bazNew'}
]
}, 'foo[]', undefined, fields, {
foo: ['error1', 'error2']
}, undefined, false, defaultProps);
expectField({
field: fields.foo[0],
name: 'foo[0]',
value: 'barNew',
dirty: true,
touched: false,
visited: false,
error: 'error1',
initialValue: undefined,
readonly: false
});
expectField({
field: fields.foo[1],
name: 'foo[1]',
value: 'bazNew',
dirty: true,
touched: false,
visited: false,
error: 'error2',
initialValue: undefined,
readonly: false
});
const afterArray = fields.foo;
const after1 = fields.foo[0];
const after2 = fields.foo[1];
expect(beforeArray).toBe(afterArray); // array should be same instance
expect(before1).toNotBe(after1); // field instance should be different
expect(before2).toNotBe(after2); // field instance should be different
});
it('should allow an array field to add a value', () => {
const spy = createSpy();
const fields = {};
readField({
foo: [
{value: 'bar'},
{value: 'baz'}
]
}, 'foo[]', undefined, fields, {}, undefined, false, {
...defaultProps,
addArrayValue: spy
});
fields.foo.addField('rabbit');
expect(spy)
.toHaveBeenCalled()
.toHaveBeenCalledWith('foo', 'rabbit', undefined, []);
});
it('should allow an array field to add a deeply nested value', () => {
const spy = createSpy();
const fields = {};
readField({
foo: [
{
bar: [
{ baz: 'foo[0].bar[0].baz' },
{ baz: 'foo[0].bar[1].baz' }
]
},
{
bar: [
{ baz: 'foo[1].bar[0].baz' },
{ baz: 'foo[1].bar[1].baz' }
]
}
]
}, 'foo[]', undefined, fields, {}, undefined, false, {
...defaultProps,
addArrayValue: spy,
fields: [
'foo[].bar[].baz'
]
});
fields.foo.addField('rabbit');
expect(spy)
.toHaveBeenCalled()
.toHaveBeenCalledWith('foo', 'rabbit', undefined, ['bar[].baz']);
});
it('should allow an array field to remove a value', () => {
const spy = createSpy();
const fields = {};
readField({
foo: [
{value: 'bar'},
{value: 'baz'}
]
}, 'foo[]', undefined, fields, {}, undefined, false, {
...defaultProps,
removeArrayValue: spy
});
fields.foo.removeField(1);
expect(spy)
.toHaveBeenCalled()
.toHaveBeenCalledWith('foo', 1);
});
it('should remove array field when it is no longer in the store', () => {
const fields = {};
readField({
foo: [
{value: 'bar'},
{value: 'baz'}
]
}, 'foo[]', undefined, fields, {}, undefined, false, defaultProps);
expect(fields.foo.length).toBe(2);
expect(fields.foo[0].value).toBe('bar');
expect(fields.foo[1].value).toBe('baz');
readField({
foo: [
{value: 'bar'}
]
}, 'foo[]', undefined, fields, {}, undefined, false, defaultProps);
expect(fields.foo.length).toBe(1);
expect(fields.foo[0].value).toBe('bar');
});
it('should initialize a mixed field with empty state', () => {
const fields = {};
readField({}, 'pig.foo[].dog.cat[].rat', undefined, fields, {}, undefined, false, defaultProps);
expect(fields.pig).toBeA('object');
expect(fields.pig.foo).toBeA('array');
expect(fields.pig.foo[0]).toBe(undefined);
});
it('should read a mixed field', () => {
const fields = {};
readField({
pig: {
foo: [
{
dog: {
cat: [
{
rat: {
value: 'hello' // that's deep, baby!
}
}
]
}
}
]
}
}, 'pig.foo[].dog.cat[].rat', undefined, fields, {}, undefined, false, defaultProps);
expect(fields.pig).toBeA('object');
expect(fields.pig.foo).toBeA('array');
expect(fields.pig.foo[0].dog).toBeA('object');
expect(fields.pig.foo[0].dog.cat).toBeA('array');
expect(fields.pig.foo[0].dog.cat[0]).toBeA('object');
expect(fields.pig.foo[0].dog.cat[0].rat).toBeA('object');
expectField({
field: fields.pig.foo[0].dog.cat[0].rat,
name: 'pig.foo[0].dog.cat[0].rat',
value: 'hello',
dirty: true,
touched: false,
visited: false,
error: undefined,
initialValue: undefined,
readonly: false
});
});
it('should read an array field with an initial value', () => {
const fields = {};
readField({
pig: {
foo: [
{
dog: {
cat: [
{
rat: {
value: 'hello'
}
}
]
}
}
]
}
}, 'pig.foo[].dog.cat[].rat', undefined, fields, {}, undefined, false, {
...defaultProps,
initialValues: {
pig: {
foo: [
{
dog: {
cat: [
{rat: 'initVal'}
]
}
}
]
}
}
});
expectField({
field: fields.pig.foo[0].dog.cat[0].rat,
name: 'pig.foo[0].dog.cat[0].rat',
value: 'hello',
dirty: true,
touched: false,
visited: false,
error: undefined,
initialValue: 'initVal',
readonly: false
});
});
it('should read a mixed field with sync errors', () => {
const fields = {};
readField({
pig: {
foo: [
{
dog: {
cat: [
{
rat: {
value: 'hello'
}
}
]
}
}
]
}
}, 'pig.foo[].dog.cat[].rat', undefined, fields, {
pig: {
foo: [
{
dog: {
cat: [
{rat: 'syncError'}
]
}
}
]
}
}, undefined, false, defaultProps);
expectField({
field: fields.pig.foo[0].dog.cat[0].rat,
name: 'pig.foo[0].dog.cat[0].rat',
value: 'hello',
dirty: true,
touched: false,
visited: false,
error: 'syncError',
initialValue: undefined,
readonly: false
});
});
it('should allow an array value', () => {
const fields = {};
readField({
foo: {
value: [1, 2]
}
}, 'foo', undefined, fields, {}, undefined, false, defaultProps);
expectField({
field: fields.foo,
name: 'foo',
value: [1, 2],
dirty: true,
touched: false,
visited: false,
error: undefined,
initialValue: undefined,
readonly: false
});
});
it('should not provide mutators when readonly', () => {
const fields = {};
readField({}, 'foo', undefined, fields, {}, undefined, false, {
...defaultProps,
readonly: true
});
const field = fields.foo;
expect(field.onBlur).toNotExist();
expect(field.onChange).toNotExist();
expect(field.onDragStart).toNotExist();
expect(field.onDrop).toNotExist();
expect(field.onFocus).toNotExist();
expect(field.onUpdate).toNotExist();
});
});