Skip to content

Commit

Permalink
feat(validation): validateAge, validateDate, validateFileSize, valida…
Browse files Browse the repository at this point in the history
…teFileType, validateMaxStringLength, validateMinStringLength, validateMaxNumber, validateMinNumber, validateRegExp, validateValueRequired, validateEmail, validatePostcode
  • Loading branch information
Benjamin Strauß authored and b-strauss committed Feb 11, 2018
1 parent 8c3f94b commit 43928eb
Show file tree
Hide file tree
Showing 4 changed files with 387 additions and 0 deletions.
240 changes: 240 additions & 0 deletions src/validation/validation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
goog.module('clulib.validation');

const {objectToMap} = goog.require('clulib.collections');

/**
* Map of postcodes by country.
*
* @type {Map<string, RegExp>}
*/
const postcodes = objectToMap({
// Argentina
'ar': /^[B-T]{1}\\d{4}[A-Z]{3}$/i,
// Austria
'at': /^[0-9]{4}$/i,
// Australia
'au': /^[2-9][0-9]{2,3}$/i,
// Belgium
'be': /^[1-9][0-9]{3}$/i,
// Canada
'ca': /^[a-z][0-9][a-z][ \t-]*[0-9][a-z][0-9]$/i,
// Switzerland
'ch': /^[0-9]{4}$/i,
// China
'cn': /^[0-9]{6}$/i,
// Germany
'de': /^[0-9]{5}$/i,
// Denmark
'dk': /^(DK-)?[0-9]{4}$/i,
// Estonia
'ee': /^[0-9]{5}$/i,
// Spain
'es': /^[0-4][0-9]{4}$/i,
// Finland
'fi': /^(FI-)?[0-9]{5}$/i,
// France
'fr': /^(0[1-9]|[1-9][0-9])[0-9][0-9][0-9]$/i,
// Great Britain
// eslint-disable-next-line max-len
'gb': /^(([gG][iI][rR] {0,}0[aA]{2})|((([a-pr-uwyzA-PR-UWYZ][a-hk-yA-HK-Y]?[0-9][0-9]?)|(([a-pr-uwyzA-PR-UWYZ][0-9][a-hjkstuwA-HJKSTUW])|([a-pr-uwyzA-PR-UWYZ][a-hk-yA-HK-Y][0-9][abehmnprv-yABEHMNPRV-Y]))) {0,}[0-9][abd-hjlnp-uw-zABD-HJLNP-UW-Z]{2}))$/,
// India
'in': /^[1-9]{1}[0-9]{2}(\\s|-)?[0-9]{3}$/i,
// Italy
'it': /^[0-9]{5}$/i,
// Iceland
'is': /^[0-9]{3}$/i,
// Latvia
'lv': /^(LV-)?[1-9][0-9]{3}$/i,
// Mexico
'mx': /^[0-9]{5}$/i,
// Netherlands
'nl': /^[0-9]{4}.?[a-z]{2}$/i,
// Norway
'no': /^[0-9]{4}$/i,
// New Zealand
'nz': /^[0-9]{4}$/i,
// Poland
'pl': /^[0-9]{2}-[0-9]{3}$/i,
// Portugal
'pt': /^[0-9]{4}-[0-9]{3}$/i,
// Romania
'ro': /^[0-9]{6}$/i,
// Russian Federation
'ru': /^[0-9]{6}$/i,
// Sweden
'se': /^[0-9]{3}\s?[0-9]{2}$/i,
// Turkey
'tr': /^[0-9]{5}$/i,
// United States
'us': /^[0-9]{5}((-| )[0-9]{4})?$/i
});

/**
* Validates for a min age.
*
* @param {Date} birthDate The birthdate
* @param {number} minAge Min. age in years
* @returns {boolean} True if the date is older than minAge
*/
function validateAge (birthDate, minAge) {
const now = new Date();
const deadlineDate = new Date(now.getFullYear() - minAge, now.getMonth(), now.getDate());

return deadlineDate.getTime() >= birthDate.getTime();
}

/**
* Checks if a date is valid and exists.
*
* @param {number} year Full year
* @param {number} month Month, 1 = january
* @param {number} day Day
* @returns {boolean} True if the date is valid
*/
function validateDate (year, month, day) {
month -= 1;

const date = new Date(year, month, day);

return date.getFullYear() === year
&& date.getMonth() === month
&& date.getDate() === day;
}

/**
* Checks if a File does not exceed a maximum size.
*
* @param {Blob} blob File object
* @param {number} maxSize Max size in Megabytes
* @returns {boolean} True if the file is not too large
*/
function validateFileSize (blob, maxSize) {
const bytes = maxSize * 1000000;

return blob.size <= bytes;
}

/**
* Checks if a File is of certain mime types.
*
* @param {Blob} blob File object
* @param {Array<string>} mimeTypes allowed mime types
* @returns {boolean} True if the File has one of the mime types
*/
function validateFileType (blob, mimeTypes) {
return mimeTypes.reduce((old, currentMimeType) => {
return blob.type === currentMimeType || old;
}, false);
}

/**
* Validates a string for max length, does not count whitespace.
*
* @param {string} string
* @param {number} maxLength
* @returns {boolean}
*/
function validateMaxStringLength (string, maxLength) {
string = string.trim();
return validateMaxNumber(string.length, maxLength);
}

/**
* Validates a string for min length, does not count whitespace.
*
* @param {string} string
* @param {number} minLength
* @returns {boolean}
*/
function validateMinStringLength (string, minLength) {
string = string.trim();
return validateMinNumber(string.length, minLength);
}

/**
* Validates a number for max value.
*
* @param {number} number
* @param {number} maxValue
* @returns {boolean}
*/
function validateMaxNumber (number, maxValue) {
return number <= maxValue;
}

/**
* Validates a number for min value.
*
* @param {number} number
* @param {number} minValue
* @returns {boolean}
*/
function validateMinNumber (number, minValue) {
return number >= minValue;
}

/**
* Validates a string with a regular expression.
*
* @param {string} string
* @param {RegExp} regExp
* @returns {boolean}
*/
function validateRegExp (string, regExp) {
return regExp.test(string);
}

/**
* Validates a value that is required (neither null or undefined, or not empty if it's a string).
*
* @param {?} value
* @returns {boolean}
*/
function validateValueRequired (value) {
if (goog.isString(value))
return validateMinStringLength(value, 1);
else
return value != null;
}

/**
* Validates if an email adress is valid.
*
* @param {string} string
* @returns {boolean}
*/
function validateEmail (string) {
// eslint-disable-next-line no-useless-escape,max-len
return validateRegExp(string, /^[a-z0-9!#$%&\'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&\'*+\/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i);
}

/**
* Validates a postcode by country array (see {@link postcodes} for values).
*
* @param {string} string
* @param {Array<string>} countries An array of country codes.
* @returns {boolean}
*/
function validatePostcode (string, countries) {
return countries.reduce((old, country) => {
if (!postcodes.has(country))
throw new Error(`Postcode for country '${country}' is not defined.`);
const postcodeRegExp = postcodes.get(country);
return old || validateRegExp(string, postcodeRegExp);
}, false);
}

exports = {
validateAge,
validateDate,
validateFileSize,
validateFileType,
validateMaxStringLength,
validateMinStringLength,
validateMaxNumber,
validateMinNumber,
validateRegExp,
validateValueRequired,
validateEmail,
validatePostcode
};
File renamed without changes.
2 changes: 2 additions & 0 deletions test/test_main.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const resourceBundleMain = goog.require('test.clulib.l10n.ResourceBundle');
const resourceManagerMain = goog.require('test.clulib.l10n.ResourceManager');
const httpRequestMain = goog.require('test.clulib.net.http_request');
const mathMain = goog.require('test.clulib.math');
const validationMain = goog.require('test.clulib.validation');

renderingMain();
arrayMain();
Expand All @@ -23,3 +24,4 @@ resourceBundleMain();
resourceManagerMain();
httpRequestMain();
mathMain();
validationMain();
145 changes: 145 additions & 0 deletions test/validation/validation_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
goog.module('test.clulib.validation');

const ResponseType = goog.require('goog.net.XhrIo.ResponseType');

const validation = goog.require('clulib.validation');
const {basePath} = goog.require('testing.environment');
const http = goog.require('clulib.net.http_request');

/**
* @returns {Promise<Blob>}
*/
async function getBlob () {
const request = http.httpRequest(dummyJsonUrl, 'GET', null, ResponseType.BLOB);
const result = await request.promise;

return result.response;
}

const dummyJsonUrl = `${basePath}/test-assets/json/dummy.json`;

exports = function () {
describe('clulib.validation', () => {
describe('validateAge', () => {
it('should validate a min birthdate', () => {
jasmine.clock().install();

// mock 2020-01-02
jasmine.clock().mockDate(new Date(2020, 0, 2));
expect(validation.validateAge(new Date(2002, 0, 3), 18)).toBe(false);
expect(validation.validateAge(new Date(2002, 0, 2), 18)).toBe(true);
expect(validation.validateAge(new Date(2002, 0, 1), 18)).toBe(true);

// check for people born on leap year (29. February)
// mock 2034-02-28
jasmine.clock().mockDate(new Date(2034, 1, 28));
expect(validation.validateAge(new Date(2016, 1, 29), 18)).toBe(false);

jasmine.clock().mockDate(new Date(2034, 2, 1));
expect(validation.validateAge(new Date(2016, 1, 29), 18)).toBe(true);

jasmine.clock().uninstall();
});
});

describe('validateDate', () => {
it('should validate a date', () => {
expect(validation.validateDate(2000, 1, 1)).toBe(true);
// leap year
expect(validation.validateDate(2016, 2, 29)).toBe(true);

expect(validation.validateDate(2015, 2, 29)).toBe(false);
expect(validation.validateDate(2015, 0, 10)).toBe(false);
expect(validation.validateDate(2015, 20, 20)).toBe(false);
});
});

describe('validateFileSize', () => {
it('should validate for file size', async () => {
const blob = await getBlob();

expect(validation.validateFileSize(blob, 100 / 1000000)).toBe(true);
expect(validation.validateFileSize(blob, 20 / 1000000)).toBe(false);
});
});

describe('validateFileType', () => {
it('should validate for file type', async () => {
const blob = await getBlob();

expect(validation.validateFileType(blob, ['application/json'])).toBe(true);
expect(validation.validateFileType(blob, ['application/javascript'])).toBe(false);
});
});

describe('validateMaxStringLength', () => {
it('should validate a string for max length', () => {
expect(validation.validateMaxStringLength('ab', 3)).toBe(true);
expect(validation.validateMaxStringLength('abc', 3)).toBe(true);
expect(validation.validateMaxStringLength('abcd', 3)).toBe(false);
});
});

describe('validateMinStringLength', () => {
it('should validate a string for min length', () => {
expect(validation.validateMinStringLength('ab', 3)).toBe(false);
expect(validation.validateMinStringLength('abc', 3)).toBe(true);
expect(validation.validateMinStringLength('abcd', 3)).toBe(true);
});
});

describe('validateMaxNumber', () => {
it('should validate a number for max value', () => {
expect(validation.validateMaxNumber(1, 2)).toBe(true);
expect(validation.validateMaxNumber(2, 2)).toBe(true);
expect(validation.validateMaxNumber(3, 2)).toBe(false);
});
});

describe('validateMinNumber', () => {
it('should validate a number for min value', () => {
expect(validation.validateMinNumber(1, 2)).toBe(false);
expect(validation.validateMinNumber(2, 2)).toBe(true);
expect(validation.validateMinNumber(3, 2)).toBe(true);
});
});

describe('validateRegExp', () => {
it('should validate a regular expression', () => {
expect(validation.validateRegExp('aa', new RegExp('[a-z]'))).toBe(true);
expect(validation.validateRegExp('AA', new RegExp('[a-z]'))).toBe(false);

expect(validation.validateRegExp('AA', new RegExp('[a-z]', 'i'))).toBe(true);

expect(validation.validateRegExp('AA', /[a-z]/i)).toBe(true);
});
});

describe('validateValueRequired', () => {
it('should validate for a required value', () => {
expect(validation.validateValueRequired(null)).toBe(false);
expect(validation.validateValueRequired(undefined)).toBe(false);
expect(validation.validateValueRequired('')).toBe(false);
expect(validation.validateValueRequired('abc')).toBe(true);
expect(validation.validateValueRequired(10)).toBe(true);
});
});

describe('validateEmail', () => {
it('should validate for a valid email adress', () => {
expect(validation.validateEmail('')).toBe(false);
expect(validation.validateEmail('afda@@adfad.com')).toBe(false);
expect(validation.validateEmail('email@example.com')).toBe(true);
});
});

describe('validatePostcode', () => {
it('should validate for a valid postcode', () => {
expect(validation.validatePostcode('000-00', ['de'])).toBe(false);
expect(validation.validatePostcode('12345', ['de'])).toBe(true);
expect(validation.validatePostcode('12345', ['de', 'gb'])).toBe(true);
expect(validation.validatePostcode('AA00 0AA', ['de', 'gb'])).toBe(true);
});
});
});
};

0 comments on commit 43928eb

Please sign in to comment.