-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
REF: Major refactor on mobx-form and adding tests
- v13.3.6
- v13.3.5
- v13.3.4
- v13.3.3
- v13.3.2
- v13.3.1
- v13.3.0
- v13.2.1
- v13.2.0
- v13.1.1
- v13.1.0
- v13.0.0
- v12.0.4
- v12.0.3
- v12.0.2
- v12.0.1
- v12.0.0
- v11.0.1
- v11.0.0
- v10.1.2
- v10.1.1
- v10.1.0
- v10.0.5
- v10.0.4
- v10.0.3
- v10.0.2
- v10.0.1
- v10.0.0
- v9.0.7
- v9.0.6
- v9.0.5
- v9.0.4
- v9.0.3
- v9.0.2
- v9.0.1
- v9.0.1-beta.1
- v9.0.0
- v8.0.0
- v8.0.0-beta.4
Showing
10 changed files
with
1,486 additions
and
343 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
export const deferred = () => { | ||
let resolve; | ||
let reject; | ||
|
||
const promise = new Promise((resolver, rejector) => { | ||
resolve = resolver; | ||
reject = rejector; | ||
}); | ||
|
||
promise.resolve = arg => resolve(arg); | ||
|
||
promise.reject = arg => reject(arg); | ||
|
||
return promise; | ||
}; | ||
|
||
export const sleep = (timeout = 1000) => | ||
new Promise(resolve => { | ||
setTimeout(resolve, timeout); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,370 @@ | ||
import sleep from 'sleep.async'; | ||
import { createModelFromState } from '../src/FormModel'; | ||
|
||
describe('form-model', () => { | ||
describe('restoreInitialValues', () => { | ||
it('should reset the initial values on the form', () => { | ||
const model = createModelFromState({ valueA: '', valueB: '' }); | ||
|
||
model.updateField('valueA', 'some'); | ||
model.updateField('valueB', 'b'); | ||
|
||
expect(model.valueOf('valueA')).toEqual('some'); | ||
expect(model.valueOf('valueB')).toEqual('b'); | ||
|
||
model.restoreInitialValues(); | ||
|
||
expect(model.valueOf('valueA')).toEqual(''); | ||
expect(model.valueOf('valueB')).toEqual(''); | ||
}); | ||
|
||
it('should reset the initial values on the form even if not empty strings', () => { | ||
const model = createModelFromState({ valueA: [], valueB: [] }); | ||
|
||
model.updateField('valueA', [1, 2, 3]); | ||
model.updateField('valueB', [4, 5, 6]); | ||
|
||
expect(model.valueOf('valueA')).toEqual([1, 2, 3]); | ||
expect(model.valueOf('valueB')).toEqual([4, 5, 6]); | ||
|
||
model.restoreInitialValues(); | ||
|
||
expect(model.valueOf('valueA')).toEqual([]); | ||
expect(model.valueOf('valueB')).toEqual([]); | ||
}); | ||
}); | ||
|
||
describe('serializedData', () => { | ||
it('should return always the data serialized as a Javascript object', () => { | ||
const model = createModelFromState({ name: 'John', lastName: 'Doe' }); | ||
expect(model.serializedData).toEqual({ name: 'John', lastName: 'Doe' }); | ||
|
||
model.updateField('name', 'Jane'); | ||
model.updateField('lastName', 'Martins'); | ||
|
||
expect(model.serializedData).toEqual({ | ||
name: 'Jane', | ||
lastName: 'Martins', | ||
}); | ||
}); | ||
}); | ||
|
||
describe('requiredAreFilled', () => { | ||
it('should be true if all required fields have a value', () => { | ||
const model = createModelFromState( | ||
{ name: '', lastName: '', email: '' }, | ||
{ | ||
name: { required: true, requiredMessage: 'The name is required' }, | ||
lastName: { | ||
required: ({ fields }) => !!fields.name.value, | ||
requiredMessage: 'lastName is required', | ||
}, | ||
email: { | ||
required: ({ fields }) => fields.lastName.value === 'Doo', | ||
requiredMessage: 'Email is required for all Doos', | ||
}, // only required when last name is Doo | ||
}, | ||
); | ||
|
||
expect(model.requiredAreFilled).toBe(false); | ||
expect(model.requiredFields).toEqual(['name']); | ||
|
||
model.updateField('name', 'John'); | ||
|
||
expect(model.requiredAreFilled).toBe(false); // now lastName is also required! | ||
expect(model.requiredFields.sort()).toEqual(['name', 'lastName'].sort()); | ||
|
||
model.updateField('lastName', 'Doo'); | ||
|
||
expect(model.requiredFields.sort()).toEqual(['name', 'lastName', 'email'].sort()); | ||
expect(model.requiredAreFilled).toBe(false); | ||
|
||
model.updateField('email', 'some@email.com'); | ||
expect(model.requiredAreFilled).toBe(true); | ||
}); | ||
|
||
it('should allow the creation of a form that will track if all required fields are filled', async () => { | ||
const model = createModelFromState( | ||
{ name: '', lastName: '', email: '' }, | ||
{ | ||
// using generic validation | ||
name: { required: 'Name is required' }, | ||
// using a custom validation functiont that returns a Boolean | ||
lastName: { | ||
required: 'lastName is required', | ||
validator: field => field.value !== 'Doe', | ||
errorMessage: 'Please do not enter Doe', | ||
}, | ||
// using an async function that throws when it fails, since throws are converted to rejections | ||
// this just works. If validation passed no need to return anything. | ||
email: { | ||
validator: async ({ value }) => { | ||
await sleep(100); | ||
|
||
if (value === 'johndoe@gmail.com') { | ||
throw new Error('Email already used'); | ||
} | ||
}, | ||
}, | ||
}, | ||
); | ||
|
||
expect(model.requiredAreFilled).toEqual(false); | ||
|
||
model.updateField('name', 'Snoopy'); | ||
expect(model.requiredAreFilled).toEqual(false); | ||
|
||
model.updateField('lastName', 'Doo'); | ||
expect(model.requiredAreFilled).toEqual(true); | ||
|
||
await model.validate(); | ||
expect(model.valid).toEqual(true); | ||
|
||
model.updateFrom({ | ||
name: '', | ||
lastName: 'Doe', | ||
email: 'johndoe@gmail.com', | ||
}); | ||
|
||
await model.validate(); | ||
expect(model.valid).toEqual(false); | ||
|
||
expect(model.summary).toEqual(['Name is required', 'Please do not enter Doe', 'Email already used']); | ||
}); | ||
|
||
it('errors thrown inside the validator execution should not break the validation when validation is not async', async () => { | ||
const model = createModelFromState( | ||
{ name: '', lastName: '', email: '' }, | ||
{ | ||
// using generic validation | ||
name: { required: 'Name is required' }, | ||
// using a custom validation functiont that returns a Boolean | ||
lastName: { | ||
required: 'lastName is required', | ||
validator: field => field.value !== 'Doe', | ||
errorMessage: 'Please do not enter Doe', | ||
}, | ||
// using an async function that throws when it fails, since throws are converted to rejections | ||
// this just works. If validation passed no need to return anything. | ||
email: { | ||
validator: ({ value }) => { | ||
if (value === 'johndoe@gmail.com') { | ||
throw new Error('Email already used'); | ||
} | ||
}, | ||
}, | ||
}, | ||
); | ||
|
||
expect(model.requiredAreFilled).toEqual(false); | ||
|
||
model.updateField('name', 'Snoopy'); | ||
expect(model.requiredAreFilled).toEqual(false); | ||
|
||
model.updateField('lastName', 'Doo'); | ||
expect(model.requiredAreFilled).toEqual(true); | ||
|
||
await model.validate(); | ||
expect(model.valid).toEqual(true); | ||
|
||
model.updateFrom({ | ||
name: '', | ||
lastName: 'Doe', | ||
email: 'johndoe@gmail.com', | ||
}); | ||
|
||
await model.validate(); | ||
expect(model.valid).toEqual(false); | ||
|
||
expect(model.summary).toEqual(['Name is required', 'Please do not enter Doe', 'Email already used']); | ||
}); | ||
|
||
it('should allow validators to access fields in the model', async () => { | ||
const model = createModelFromState( | ||
{ name: 'Snoopy', lastName: 'Brown', email: '' }, | ||
{ | ||
// using generic validation | ||
name: { required: 'Name is required' }, | ||
// using a custom validation functiont that returns a Boolean | ||
lastName: { | ||
required: 'lastName is required', | ||
validator: field => field.value !== 'Doe', | ||
errorMessage: 'Please do not enter Doe', | ||
}, | ||
// using an async function that throws when it fails, since throws are converted to rejections | ||
// this just works. If validation passed no need to return anything. | ||
email: { | ||
validator: ({ value = '' }, fields, _model) => { | ||
if (_model.validateEmails) { | ||
if (!(value.indexOf('@') > 1)) { | ||
throw new Error('INVALID_EMAIL'); | ||
} | ||
} | ||
return true; | ||
}, | ||
}, | ||
}, | ||
); | ||
|
||
await model.validate(); | ||
expect(model.valid).toEqual(true); | ||
|
||
model.validateEmails = true; | ||
await model.validate(); | ||
expect(model.valid).toEqual(false); | ||
}); | ||
}); | ||
|
||
describe('addFields', () => { | ||
it('should create a model from an object descriptor', async () => { | ||
const model = createModelFromState({}); | ||
|
||
model.addFields({ | ||
// using generic validation | ||
name: { | ||
value: 'Snoopy', | ||
required: 'Name is required', | ||
}, | ||
// using a custom validation functiont that returns a Boolean | ||
lastName: { | ||
value: 'Brown', | ||
required: 'lastName is required', | ||
validator: field => field.value !== 'Doe', | ||
errorMessage: 'Please do not enter Doe', | ||
}, | ||
email: { | ||
validator: ({ value = '' }, fields, _model) => { | ||
if (_model.validateEmails) { | ||
if (!(value.indexOf('@') > 1)) { | ||
throw new Error('INVALID_EMAIL'); | ||
} | ||
} | ||
return true; | ||
}, | ||
}, | ||
}); | ||
|
||
await model.validate(); | ||
expect(model.valid).toEqual(true); | ||
|
||
model.validateEmails = true; | ||
await model.validate(); | ||
expect(model.valid).toEqual(false); | ||
}); | ||
|
||
it('should create a model from a descriptor of type array', async () => { | ||
const model = createModelFromState({}); | ||
|
||
model.addFields([ | ||
{ | ||
name: 'name', | ||
value: 'Snoopy', | ||
required: 'Name is required', | ||
}, | ||
{ | ||
name: 'lastName', | ||
value: 'Brown', | ||
required: 'lastName is required', | ||
validator: field => field.value !== 'Doe', | ||
errorMessage: 'Please do not enter Doe', | ||
}, | ||
{ | ||
name: 'email', | ||
validator: ({ value = '' }, fields, _model) => { | ||
if (_model.validateEmails) { | ||
if (!(value.indexOf('@') > 1)) { | ||
throw new Error('INVALID_EMAIL'); | ||
} | ||
} | ||
return true; | ||
}, | ||
}, | ||
]); | ||
|
||
expect(model.fields.name.name).toEqual('name'); | ||
expect(model.fields.lastName.name).toEqual('lastName'); | ||
expect(model.fields.email.name).toEqual('email'); | ||
|
||
expect(model.fields.name.value).toEqual('Snoopy'); | ||
expect(model.fields.lastName.value).toEqual('Brown'); | ||
expect(model.fields.email.value).toEqual(undefined); | ||
|
||
await model.validate(); | ||
expect(model.valid).toEqual(true); | ||
|
||
model.validateEmails = true; | ||
await model.validate(); | ||
expect(model.valid).toEqual(false); | ||
}); | ||
|
||
it('should store non recognized fields as meta in the fields', async () => { | ||
const model = createModelFromState({}); | ||
|
||
const descriptorFields = [ | ||
{ | ||
name: 'numOfBedrooms', | ||
value: undefined, | ||
required: '# of bedrooms is required', | ||
meta: { | ||
type: 'Select', | ||
label: '# of Bedrooms', | ||
items: [ | ||
{ id: 'STUDIO', value: 'Studio' }, | ||
{ id: 'ONE_BED', value: 'One bed' }, | ||
{ id: 'TWO_BEDS', value: 'Two beds' }, | ||
{ id: 'THREE_BEDS', value: 'Three beds' }, | ||
{ id: 'FOUR_BEDS', value: 'Four beds' }, | ||
], | ||
}, | ||
}, | ||
{ | ||
name: 'moveInRange', | ||
value: undefined, | ||
required: 'Move-in range is required', | ||
meta: { | ||
type: 'Select', | ||
label: 'When do you plan to rent?', | ||
items: [ | ||
{ id: 'NEXT_4_WEEKS', value: 'Next 4 weeks' }, | ||
{ id: 'NEXT_2_MONTHS', value: 'Next 2 months' }, | ||
{ id: 'NEXT_4_MONTHS', value: 'Next 4 months' }, | ||
{ id: 'BEYOND_4_MONTHS', value: 'Beyond 4 months' }, | ||
{ id: 'I_DONT_KNOW', value: "I don't know" }, | ||
], | ||
}, | ||
}, | ||
{ | ||
name: 'comments', | ||
value: '', | ||
required: 'Comments are required', | ||
meta: { | ||
type: 'TextArea', | ||
label: 'Comments', | ||
}, | ||
}, | ||
]; | ||
|
||
model.addFields(descriptorFields); | ||
|
||
const { fields } = model; | ||
|
||
expect(fields.numOfBedrooms.meta).toEqual(descriptorFields[0].meta); | ||
expect(fields.moveInRange.meta).toEqual(descriptorFields[1].meta); | ||
expect(fields.comments.meta).toEqual(descriptorFields[2].meta); | ||
|
||
await model.validate(); | ||
expect(model.valid).toEqual(false); | ||
|
||
expect(fields.numOfBedrooms.errorMessage).toEqual(descriptorFields[0].required); | ||
expect(fields.moveInRange.errorMessage).toEqual(descriptorFields[1].required); | ||
expect(fields.comments.errorMessage).toEqual(descriptorFields[2].required); | ||
|
||
fields.numOfBedrooms.setValue('STUDIO'); | ||
fields.moveInRange.setValue('NEXT_4_WEEKS'); | ||
fields.comments.setValue('Some comment'); | ||
|
||
await model.validate(); | ||
expect(model.valid).toEqual(true); | ||
}); | ||
}); | ||
}); |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`FormModel A FormModel can be created using createModel, createModelFromState or using the Constructor directly createModel dataIsReady should throw when calling disableFields with an argument different from an array 1`] = `"fieldKeys should be an array with the names of the fields to disable"`; | ||
|
||
exports[`FormModel A FormModel can be created using createModel, createModelFromState or using the Constructor directly createModel dataIsReady should throw when calling disableFields with no parameters 1`] = `"fieldKeys should be an array with the names of the fields to disable"`; | ||
|
||
exports[`FormModel A FormModel can be created using createModel, createModelFromState or using the Constructor directly createModel dataIsReady should throw when calling disableFields with null 1`] = `"fieldKeys should be an array with the names of the fields to disable"`; | ||
|
||
exports[`FormModel A FormModel can be created using createModel, createModelFromState or using the Constructor directly createModel dataIsReady should throw when calling enableFields with an argument different from an array 1`] = `"fieldKeys should be an array with the names of the fields to disable"`; | ||
|
||
exports[`FormModel A FormModel can be created using createModel, createModelFromState or using the Constructor directly createModel dataIsReady should throw when calling enableFields with an argument different from an array 2`] = `"fieldKeys should be an array with the names of the fields to disable"`; | ||
|
||
exports[`FormModel A FormModel can be created using createModel, createModelFromState or using the Constructor directly createModel dataIsReady when trying to disable a field that does not exist should throw an error 1`] = `"Field \\"non existant field\\" not found"`; | ||
|
||
exports[`FormModel A FormModel can be created using createModel, createModelFromState or using the Constructor directly createModel required fields when required fields are missing should contain a summary of missing fields 1`] = ` | ||
Array [ | ||
"The name is required", | ||
"Field: \\"email\\" is required", | ||
] | ||
`; | ||
|
||
exports[`FormModel A FormModel can be created using createModel, createModelFromState or using the Constructor directly createModel required fields when required fields are missing when required property is set to true (boolean) should have an errorMessage on failed fields 1`] = `"Field: \\"name\\" is required"`; | ||
|
||
exports[`FormModel A FormModel can be created using createModel, createModelFromState or using the Constructor directly createModel required fields when required fields are missing when required property is set to true (boolean) should have an errorMessage on failed fields 2`] = `"Field: \\"email\\" is required"`; | ||
|
||
exports[`FormModel Adding fields after model is created should throw when calling addFields with no values 1`] = `"fieldDescriptor has to be an Object or an Array"`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters