diff --git a/src/index.test.ts b/src/index.test.ts index f524a94f..a785afc5 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -28,6 +28,8 @@ describe('Public API', () => { 'capitalize', 'formatCurrency', 'parseCurrency', + 'isValidRenavam', + 'isValidPlate', ]; Object.keys(API).forEach((method) => { diff --git a/src/utilities/index.ts b/src/utilities/index.ts index fe776ed1..54317c14 100644 --- a/src/utilities/index.ts +++ b/src/utilities/index.ts @@ -1,4 +1,6 @@ export { isValid as isValidIE } from './inscricao-estadual'; +export { isValid as isValidPlate } from './plate'; +export { isValid as isValidRenavam } from './renavam'; export { isValid as isValidPIS } from './pis'; export { isValid as isValidPhone, isValidMobilePhone, isValidLandlinePhone } from './phone'; export { isValid as isValidEmail } from './email'; diff --git a/src/utilities/plate/index.test.ts b/src/utilities/plate/index.test.ts new file mode 100644 index 00000000..54ddc3ff --- /dev/null +++ b/src/utilities/plate/index.test.ts @@ -0,0 +1,84 @@ +import { isValid } from '.'; + +describe('isValid', () => { + describe('should return false', () => { + test('when is a empty string', () => { + expect(isValid('')).toBe(false); + }); + + test('when is null', () => { + expect(isValid(null as any)).toBe(false); + }); + + test('when is undefined', () => { + expect(isValid(undefined as any)).toBe(false); + }); + + test('when is a boolean', () => { + expect(isValid(true as any)).toBe(false); + expect(isValid(false as any)).toBe(false); + }); + + test('when is a object', () => { + expect(isValid({} as any)).toBe(false); + }); + + test('when is a array', () => { + expect(isValid([] as any)).toBe(false); + }); + + test(`when dont match with CPF length`, () => { + expect(isValid('IWH-86288')).toBe(false); + }); + + test('when contains only letters', () => { + expect(isValid('IWH')).toBe(false); + }); + + test('when contains only numbers', () => { + expect(isValid('8628')).toBe(false); + }); + + test('when is invalid mercosul plate', () => { + expect(isValid('IWH-8628', { mercosul: true })).toBe(false); + }); + + test('when is invalid motorcycle mercosul plate', () => { + expect(isValid('AAA-1A11', { mercosul: true, motorcycle: true })).toBe(false); + }); + + test('when options params is not pass in mercosul plate', () => { + expect(isValid('AAA-1A11')).toBe(false); + }); + + test('when options params is not pass in motorcycle mercosul plate', () => { + expect(isValid('AAA-11A1', { mercosul: true })).toBe(false); + }); + }); + + describe('should return true', () => { + test('when is a valid plate without mask', () => { + expect(isValid('IWH8628')).toBe(true); + }); + + test('when is a valid plate with mask', () => { + expect(isValid('IWH-8628')).toBe(true); + }); + + test('when is a valid mercosul plate', () => { + expect(isValid('AAA-1A11', { mercosul: true })).toBe(true); + }); + + test('when is a valid motorcycle mercosul plate', () => { + expect(isValid('AAA-11A1', { mercosul: true, motorcycle: true })).toBe(true); + }); + + test('when is a valid mercosul plate without mask', () => { + expect(isValid('AAA1A11', { mercosul: true })).toBe(true); + }); + + test('when is a valid motorcycle mercosul plate without mask', () => { + expect(isValid('AAA11A1', { mercosul: true, motorcycle: true })).toBe(true); + }); + }); +}); diff --git a/src/utilities/plate/index.ts b/src/utilities/plate/index.ts new file mode 100644 index 00000000..e442e169 --- /dev/null +++ b/src/utilities/plate/index.ts @@ -0,0 +1,18 @@ +export type Options = { + mercosul?: boolean; + motorcycle?: boolean; +}; + +export function isValid(input: string, options?: Options) { + if (!input || typeof input !== 'string') return false; + + const plate = input.replace('-', ''); + + if (options?.mercosul && options?.motorcycle) { + return /^[a-zA-Z]{3}[0-9]{2}[a-zA-Z]{1}[0-9]{1}$/.test(plate); + } else if (options?.mercosul) { + return /^[a-zA-Z]{3}[0-9]{1}[a-zA-Z]{1}[0-9]{2}$/.test(plate); + } else { + return /^[a-zA-Z]{3}[0-9]{4}$/.test(plate); + } +} diff --git a/src/utilities/renavam/index.test.ts b/src/utilities/renavam/index.test.ts new file mode 100644 index 00000000..70455393 --- /dev/null +++ b/src/utilities/renavam/index.test.ts @@ -0,0 +1,56 @@ +import { isValid, LENGTH } from '.'; + +describe('isValid', () => { + describe('should return false', () => { + test('when is a empty string', () => { + expect(isValid('')).toBe(false); + }); + + test('when is null', () => { + expect(isValid(null as any)).toBe(false); + }); + + test('when is undefined', () => { + expect(isValid(undefined as any)).toBe(false); + }); + + test('when is a boolean', () => { + expect(isValid(true as any)).toBe(false); + expect(isValid(false as any)).toBe(false); + }); + + test('when is a object', () => { + expect(isValid({} as any)).toBe(false); + }); + + test('when is a array', () => { + expect(isValid([] as any)).toBe(false); + }); + + test(`when dont match with CPF length (${LENGTH})`, () => { + expect(isValid('123456')).toBe(false); + }); + + test('when contains only letters or special characters', () => { + expect(isValid('abcabcabcde')).toBe(false); + }); + + test('when is a invalid Renavam', () => { + expect(isValid('95965727047')).toBe(false); + }); + + test('when is a invalid Renavam test numbers with letters', () => { + expect(isValid('foo391.838.38test0-66')).toBe(false); + }); + }); + + describe('should return true', () => { + test('when is a Renavam valid without mask', () => { + expect(isValid('95965727048')).toBe(true); + }); + + test('when is a valid Renavam with mask', () => { + expect(isValid('9596572704-8')).toBe(true); + }); + }); +}); diff --git a/src/utilities/renavam/index.ts b/src/utilities/renavam/index.ts new file mode 100644 index 00000000..069dc7e7 --- /dev/null +++ b/src/utilities/renavam/index.ts @@ -0,0 +1,39 @@ +//base on https://github.com/eliseuborges/Renavam/blob/master/Renavam.js +import { onlyNumbers } from '../../helpers'; + +export const MIN_LENGTH = 9; +export const LENGTH = 11; +export const SUM = 284; + +export function isValid(input: string) { + if (!input || typeof input !== 'string' || input.length < MIN_LENGTH) return false; + + const numeric = onlyNumbers(input.padStart(11, '0')); + + if (!numeric.match('[0-9]{11}')) return false; + + const resultWithoutDigit = numeric.substring(0, 10).split('').reverse().join(''); + const sum = sumCalculationRest(resultWithoutDigit); + const realDigit = parseInt(input.substring(input.length - 1, input.length)); + + return sum === realDigit; +} + +function sumCalculationRest(input: string) { + let sum = 0; + let multiple = 2; + + for (let i = 0; i < 10; i++) { + sum += parseInt(input.substring(i, i + 1)) * multiple; + + if (multiple >= 9) { + multiple = 2; + } else { + multiple++; + } + } + + sum = LENGTH - (sum % LENGTH); + + return sum; +}