Skip to content
This repository has been archived by the owner on Nov 6, 2020. It is now read-only.

Commit

Permalink
feat: synchronous validators (#164)
Browse files Browse the repository at this point in the history
* refactor(required): use different definition of a valid value

* refactor: update compose-validators test step descriptions

* refactor(email): update to case-insensitive regex and allow empty string

* feat(validators): introduce alphaNumeric validator and test

* refactor: extract is-empty helper from required

* chore(validators): fix test step descriptions for alpha-numeric

* feat(validators): introduce boolean validator and tests

* chore: stumped out various validators

* refactor: keep all validation helpers in a single file

* chore: introduce test helper to run a validator for an array of values

* feat(validators): introduce alphaNumeric and test

* feat(validators): introduce boolean and test

* feat(validators): introduce createCharacterLengthRange and test

* feat(validators): introduce createNumberRange and test

* chore: fix import to restore build

* refactor(validators): improve email validation logic and tests

* fix(cypress): adjust error message in e2e tests so they pass again

* feat(validators): introduce create-equal-to and tests

* chore(validators): fix typos in the test descriptions

* chore(validators): amend text in createEqualTo

* feat(validators): introduce integer and tests

* feat(validators): introduce number and test

* feat(validators): introduce password and test

* refactor(validators): extract repeated test into helper function

* feat(validators): introduce createPattern and test

* feat(validators): introduce internationalPhoneNumber and test

* feat(validators): introduce string and test

* feat(validators): introduce url and test

* feat(validators): introduce username and test

* refactor(validators): validate range-fn arguments and delegate to helper

* feat(validators): introduce createMinNumber and test

* feat(validators): introduce createMinNumber and test

* feat(validators): introduce createMaxCharacterLength and test

* feat(validators): introduce createMinCharacterLength and test

* test(create-character-length-range): throws an error with invalid args

* fix(create-equal-to): throw error for invalid argument incl test

* fix(create-pattern): throw error for invalid pattern argument incl test

* chore(validators): add regex attribution to url

* chore(validators): removed redundant helpers

* chore(validators): export validators from `./src/validators/index.js`

* chore(validators): fix typo

* chore(validators): fix import paths and generate translations

* fix(validators): alphaNumeric should allow spaces

* fix(validators): remove check on other field being empty

* fix(validators): use zero as lower bound in createMaxCharacterLength

* fix(validators): rename password to dhis2Password to signify intent

* fix(validators): rename username to dhis2Username to signify intent

* fix(validators): replace broken imports

* fix(validators): only strip leading plus signs from intl phone number

* refactor(validators): rename `required` to `hasValue`

* fix(validators): correct broken import

Co-authored-by: Jan-Gerke Salomon <Mohammer5@users.noreply.github.com>
  • Loading branch information
HendrikThePendric and Mohammer5 authored Jan 7, 2020
1 parent 80f17e1 commit 75cd2ea
Show file tree
Hide file tree
Showing 53 changed files with 1,368 additions and 45 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
coverage/
3 changes: 2 additions & 1 deletion cypress/integration/CheckboxGroup/Displays_error/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import '../common'
import { Then } from 'cypress-cucumber-preprocessor/steps'
import { requiredMessage } from '../../../../src/validators/required.js'

Then('an error message is shown', () => {
cy.get('.error').should('contain', 'Required')
cy.get('.error').should('contain', requiredMessage)
})
5 changes: 3 additions & 2 deletions cypress/integration/Input/Displays_error/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Given, Then } from 'cypress-cucumber-preprocessor/steps'
import { requiredMessage } from '../../../../src/validators/required.js'

Given('an empty, required Input is rendered', () => {
cy.visitStory('Input', 'Required')
cy.visitStory('Testing:Input', 'Required')
cy.verifyFormValue('agree', undefined)
})

Then('an error message is shown', () => {
cy.get('.error').should('contain', 'Required')
cy.get('.error').should('contain', requiredMessage)
})
3 changes: 2 additions & 1 deletion cypress/integration/MultiSelect/Displays_error/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import '../common'
import { Then } from 'cypress-cucumber-preprocessor/steps'
import { requiredMessage } from '../../../../src/validators/required'

Then('an error message is shown', () => {
cy.get('.error').should('contain', 'Required')
cy.get('.error').should('contain', requiredMessage)
})
3 changes: 2 additions & 1 deletion cypress/integration/RadioGroup/Displays_error/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import '../common'
import { Then } from 'cypress-cucumber-preprocessor/steps'
import { requiredMessage } from '../../../../src/validators/required'

Then('an error message is shown', () => {
cy.get('.error').should('contain', 'Required')
cy.get('.error').should('contain', requiredMessage)
})
3 changes: 2 additions & 1 deletion cypress/integration/SingleSelect/Displays_error/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import '../common'
import { Then } from 'cypress-cucumber-preprocessor/steps'
import { requiredMessage } from '../../../../src/validators/required'

Then('an error message is shown', () => {
cy.get('.error').should('contain', 'Required')
cy.get('.error').should('contain', requiredMessage)
})
3 changes: 2 additions & 1 deletion cypress/integration/TextArea/Displays_error/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Given, Then } from 'cypress-cucumber-preprocessor/steps'
import { requiredMessage } from '../../../../src/validators/required'

Given('an empty, required TextArea is rendered', () => {
cy.visitStory('TextArea', 'Required')
cy.verifyFormValue('agree', undefined)
})

Then('an error message is shown', () => {
cy.get('.error').should('contain', 'Required')
cy.get('.error').should('contain', requiredMessage)
})
81 changes: 77 additions & 4 deletions i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"POT-Creation-Date: 2019-11-06T15:56:26.431Z\n"
"PO-Revision-Date: 2019-11-06T15:56:26.431Z\n"
"POT-Creation-Date: 2019-12-17T14:35:58.462Z\n"
"PO-Revision-Date: 2019-12-17T14:35:58.462Z\n"

msgid "Upload file"
msgstr ""
Expand All @@ -20,8 +20,81 @@ msgstr ""
msgid "No file(s) selected yet"
msgstr ""

msgid "Not a valid e-mail"
msgid "Please provide an alpha-numeric value"
msgstr ""

msgid "Required"
msgid "Please provide a boolean value"
msgstr ""

msgid "Please enter between {{lowerBound}} and {{upperBound}} characters"
msgstr ""

msgid ""
"Please make sure the value of this input matches the value in "
"\"{{otherField}}\"."
msgstr ""

msgid "Please enter a maximum of {{upperBound}} characters"
msgstr ""

msgid "Please enter a number with a maximum of {{upperBound}}"
msgstr ""

msgid "Please enter at least {{lowerBound}} characters"
msgstr ""

msgid "Please enter a number of at least {{lowerBound}}"
msgstr ""

msgid "Please enter a number between {{lowerBound}} and {{upperBound}}"
msgstr ""

msgid ""
"Please make sure the value of this input matches the pattern "
"{{patternString}}."
msgstr ""

msgid "Please provide a valid email address"
msgstr ""

msgid "Please provide a round number without decimals"
msgstr ""

msgid "Please provide a valid international phone number."
msgstr ""

msgid "Please provide a number"
msgstr ""

msgid "Password should be a string"
msgstr ""

msgid "Password should be at least 8 characters long"
msgstr ""

msgid "Password should be no longer than 34 characters"
msgstr ""

msgid "Password should contain at least one lowercase letter"
msgstr ""

msgid "Password should contain at least one UPPERCASE letter"
msgstr ""

msgid "Password should contain at least one number"
msgstr ""

msgid "Password should have at least one special character"
msgstr ""

msgid "This is a required field"
msgstr ""

msgid "Please provide a string"
msgstr ""

msgid "Please provide a valid url"
msgstr ""

msgid "Please provide a username between 1 and 255 characters"
msgstr ""
27 changes: 25 additions & 2 deletions src/locales/en/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,29 @@
"Upload files": "",
"Remove": "",
"No file(s) selected yet": "",
"Not a valid e-mail": "",
"Required": ""
"Please provide an alpha-numeric value": "",
"Please provide a boolean value": "",
"Please enter between {{lowerBound}} and {{upperBound}} characters": "",
"Please make sure the value of this input matches the value in \"{{otherField}}\".": "",
"Please enter a maximum of {{upperBound}} characters": "",
"Please enter a number with a maximum of {{upperBound}}": "",
"Please enter at least {{lowerBound}} characters": "",
"Please enter a number of at least {{lowerBound}}": "",
"Please enter a number between {{lowerBound}} and {{upperBound}}": "",
"Please make sure the value of this input matches the pattern {{patternString}}.": "",
"Please provide a valid email address": "",
"Please provide a round number without decimals": "",
"Please provide a valid international phone number.": "",
"Please provide a number": "",
"Password should be a string": "",
"Password should be at least 8 characters long": "",
"Password should be no longer than 34 characters": "",
"Password should contain at least one lowercase letter": "",
"Password should contain at least one UPPERCASE letter": "",
"Password should contain at least one number": "",
"Password should have at least one special character": "",
"This is a required field": "",
"Please provide a string": "",
"Please provide a valid url": "",
"Please provide a username between 1 and 255 characters": ""
}
26 changes: 26 additions & 0 deletions src/validators/__test__/alphaNumeric.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { alphaNumeric, invalidAlphaNumericMessage } from '../alphaNumeric.js'
import { testValidatorValues, allowsEmptyValues } from './helpers/index.js'

describe('validator: alphaNumeric', () => {
allowsEmptyValues(alphaNumeric)

describe('allows alpha-numeric values', () => {
testValidatorValues(alphaNumeric, undefined, [
'123456',
'abcdef',
'a1b2c3',
'A1B2C3d4e5',
'I have spaces',
])
})

describe('rejects non-alpha-numeric values', () => {
testValidatorValues(alphaNumeric, invalidAlphaNumericMessage, [
'.,/|~',
true,
false,
0,
1,
])
})
})
20 changes: 20 additions & 0 deletions src/validators/__test__/boolean.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { boolean, invalidBooleanMessage } from '../boolean'
import { testValidatorValues, allowsEmptyValues } from './helpers/index.js'

describe('validator: boolean', () => {
allowsEmptyValues(boolean)

describe('allows boolean values', () => {
testValidatorValues(boolean, undefined, [true, false])
})

describe('rejects non-boolean values', () => {
testValidatorValues(boolean, invalidBooleanMessage, [
'text',
3,
{},
[],
() => {},
])
})
})
16 changes: 8 additions & 8 deletions src/validators/__test__/composeValidators.test.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import { composeValidators } from '../composeValidators'
import { required, requiredMessage } from '../required'
import { hasValue, hasValueMessage } from '../hasValue'
import { email, invalidEmailMessage } from '../email'

describe('composeValidators', () => {
it('should return undefined', () => {
const validator = composeValidators(required, email)
const validator = composeValidators(hasValue, email)

it('should return undefined for valid values', () => {
expect(validator('test@dhis2.org')).toBe(undefined)
})

it('should return required', () => {
const validator = composeValidators(required, email)
it('should return the required message for empty values', () => {
const validator = composeValidators(hasValue, email)

expect(validator('')).toBe(requiredMessage)
expect(validator('')).toBe(hasValueMessage)
})

it('should return invalid e-mail', () => {
const validator = composeValidators(required, email)
it('should return invalid e-mail message for malformed strings', () => {
const validator = composeValidators(hasValue, email)

expect(validator('test@dhis2.')).toBe(invalidEmailMessage)
})
Expand Down
56 changes: 56 additions & 0 deletions src/validators/__test__/createCharacterLengthRange.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { createCharacterLengthRange } from '../createCharacterLengthRange.js'
import { testValidatorValues, allowsEmptyValues } from './helpers/index.js'
import { requiredArgumentErrorMessage } from '../helpers/index.js'

describe('validator: createCharacterLengthRange', () => {
const betweenSixAndTenChars = createCharacterLengthRange(6, 10)
const inValidMsg = 'Please enter between 6 and 10 characters'

it('should throw an error when lower or upper bound are not a number', () => {
expect(() => {
createCharacterLengthRange(undefined, undefined)
}).toThrowError(requiredArgumentErrorMessage)
expect(() => {
createCharacterLengthRange('test', 'test')
}).toThrowError(requiredArgumentErrorMessage)
expect(() => {
createCharacterLengthRange(1, undefined)
}).toThrowError(requiredArgumentErrorMessage)
expect(() => {
createCharacterLengthRange(undefined, 0)
}).toThrowError(requiredArgumentErrorMessage)
})

it('should create a function', () => {
expect(typeof betweenSixAndTenChars).toEqual('function')
})

allowsEmptyValues(betweenSixAndTenChars)

describe('allows within-range strings', () => {
testValidatorValues(betweenSixAndTenChars, undefined, [
'abcdef', // 6
'abcdefgh',
'abcdefghij', // 10
])
})

describe('rejects non-string values', () => {
testValidatorValues(betweenSixAndTenChars, inValidMsg, [
true,
3,
{},
[],
() => {},
])
})

describe('rejects out-of-range strings', () => {
testValidatorValues(betweenSixAndTenChars, inValidMsg, [
'a',
'abcde', // 5
'abcdefghijk', // 11
'abcdefghijklmnopqrstuvw',
])
})
})
43 changes: 43 additions & 0 deletions src/validators/__test__/createEqualTo.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { createEqualTo } from '../createEqualTo.js'
import { allowsEmptyValues } from './helpers/index.js'
import { requiredArgumentErrorMessage } from '../helpers/index.js'

describe('validator: createEqualTo', () => {
const equalToFoo = createEqualTo('foo')

it('should throw an error when key is not a string', () => {
expect(() => {
createEqualTo(undefined)
}).toThrowError(requiredArgumentErrorMessage)
expect(() => {
createEqualTo({})
}).toThrowError(requiredArgumentErrorMessage)
})

it('should create a function', () => {
expect(typeof equalToFoo).toEqual('function')
})

allowsEmptyValues(equalToFoo)

it('should return undefined when the fields have equal values', () => {
const sameValue = 'abcde'

expect(equalToFoo(sameValue, { foo: sameValue })).toEqual(undefined)
})

it('should return an error string when the fields have inequal values', () => {
const inValidFooMsg =
'Please make sure the value of this input matches the value in "foo".'

expect(equalToFoo('this', { foo: 'that' })).toEqual(inValidFooMsg)
})

it('should use the property description in the error string if provided', () => {
const equalToBar = createEqualTo('bar', 'Barista')
const inValidBarMsg =
'Please make sure the value of this input matches the value in "Barista".'

expect(equalToBar('this', { bar: 'that' })).toEqual(inValidBarMsg)
})
})
24 changes: 24 additions & 0 deletions src/validators/__test__/createMaxCharacterLength.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { createMaxCharacterLength } from '../createMaxCharacterLength.js'
import { testValidatorValues } from './helpers/index.js'

describe('validator: createMaxCharacterLength', () => {
const maxSixChars = createMaxCharacterLength(6)
const errorMessage = 'Please enter a maximum of 6 characters'

/*
* Since createMaxCharacterLength calls createNumberRange internally
* a lot of things have been tested there and here we focus
* purely on the bounderies
*/

describe('allows strings with a lower or equal length than the upper bound', () => {
testValidatorValues(maxSixChars, undefined, ['a', '123456'])
})

describe('rejects strings a length above the upper bound', () => {
testValidatorValues(maxSixChars, errorMessage, [
'1234567',
'some even longer text here....',
])
})
})
Loading

0 comments on commit 75cd2ea

Please sign in to comment.