Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@ export { default as maskCpfOrCnpj } from './masks/maskCpfOrCnpj';
export { default as maskComplete } from './masks/maskComplete';
export { default as setupMultipleMask } from './masks/setupMultipleMask';
export { default as maskHintBankAccount } from './masks/maskHintBankAccount';
export { default as maskBankBranch } from './masks/maskBankBranch';
export { default as maskBankAccount } from './masks/maskBankAccount';
export type { MaskType, CurrencyMaskOptions, BankCompensationCode } from './masks/types';
export type { MaskValueOptions } from './masks/maskValue';
export type { MaskCompleteOptions } from './masks/maskComplete';

// breakpoints

Expand Down
23 changes: 23 additions & 0 deletions src/masks/maskBankAccount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import VMasker from 'vanilla-masker';
import {
BANK_ACCOUNT_MASKS,
DEFAULT_BANK_ACCOUNT_MASK,
ALPHANUMERIC_BANK_CODES,
} from './masks';
import { stripAlphanumeric, stripNumeric } from './strip';
import type { BankCompensationCode } from './types';

export default function maskBankAccount(
value: string | null | undefined,
compensationCode?: string,
): string {
if (value === null || value === undefined || value === '') return '';

const code = compensationCode as BankCompensationCode;
const pattern = BANK_ACCOUNT_MASKS[code] ?? DEFAULT_BANK_ACCOUNT_MASK;
const stripped = ALPHANUMERIC_BANK_CODES.includes(code)
? stripAlphanumeric(String(value))
: stripNumeric(String(value));

return VMasker.toPattern(stripped, pattern);
}
17 changes: 17 additions & 0 deletions src/masks/maskBankBranch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import VMasker from 'vanilla-masker';
import { BANK_BRANCH_MASKS, DEFAULT_BANK_BRANCH_MASK } from './masks';
import { stripNumeric } from './strip';
import type { BankCompensationCode } from './types';

export default function maskBankBranch(
value: string | null | undefined,
compensationCode?: string,
): string {
if (value === null || value === undefined || value === '') return '';

const stripped = stripNumeric(String(value));
const pattern = BANK_BRANCH_MASKS[compensationCode as BankCompensationCode]
?? DEFAULT_BANK_BRANCH_MASK;

return VMasker.toPattern(stripped, pattern);
}
54 changes: 48 additions & 6 deletions src/masks/maskComplete.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { MASKS } from './masks';
import {
MASKS,
DEFAULT_BANK_BRANCH_MASK,
DEFAULT_BANK_ACCOUNT_MASK,
BANK_BRANCH_MASKS,
BANK_ACCOUNT_MASKS,
ALPHANUMERIC_BANK_CODES,
} from './masks';
import { stripAlphanumeric, stripNumeric, PATTERN_PLACEHOLDER_REGEX } from './strip';
import type { MaskType } from './types';
import type { MaskType, BankCompensationCode } from './types';

const ALPHANUMERIC_MASKS: ReadonlyArray<MaskType> = ['cnpj', 'cpf_cnpj'];

Expand All @@ -9,24 +16,52 @@ const placeholdersInPattern = (pattern: string): number => {
return matches ? matches.length : 0;
};

const patternFor = (type: MaskType): string | null => {
const patternFor = (
type: MaskType,
compensationCode?: string,
): string | null => {
switch (type) {
case 'cpf': return MASKS.CPF;
case 'cnpj': return MASKS.CNPJ;
case 'phone': return MASKS.PHONE;
case 'phone_idd': return MASKS.PHONE_IDD;
case 'zipcode': return MASKS.ZIPCODE;
case 'date': return MASKS.DATE;
case 'barCode': return MASKS.BAR_CODE;
case 'barCodeUtilities': return MASKS.BAR_CODE_UTILITIES;
case 'darf': return MASKS.DARF;
case 'number': return MASKS.NUMBER;
case 'bank_branch': {
if (compensationCode) {
const mapped = BANK_BRANCH_MASKS[compensationCode as BankCompensationCode];
if (mapped) return mapped;
}
return DEFAULT_BANK_BRANCH_MASK;
}
case 'bank_account': {
if (compensationCode) {
const mapped = BANK_ACCOUNT_MASKS[compensationCode as BankCompensationCode];
if (mapped) return mapped;
}
return DEFAULT_BANK_ACCOUNT_MASK;
}
default: return null;
}
};

export default function maskComplete(value: string, type: MaskType): boolean {
export interface MaskCompleteOptions {
compensationCode?: string;
}

export default function maskComplete(
value: string,
type: MaskType,
options?: string | MaskCompleteOptions,
): boolean {
if (!value) return false;

const compensationCode = typeof options === 'string' ? options : options?.compensationCode;

if (type === 'currency' || type === 'percentage') {
return value.length > 0;
}
Expand All @@ -36,11 +71,18 @@ export default function maskComplete(value: string, type: MaskType): boolean {
return stripped.length === 11 || stripped.length === 14;
}

const pattern = patternFor(type);
const pattern = patternFor(type, compensationCode);
if (pattern === null) return false;

const expected = placeholdersInPattern(pattern);
const stripped = ALPHANUMERIC_MASKS.includes(type)

const isAlphanumeric = ALPHANUMERIC_MASKS.includes(type)
|| (
(type === 'bank_account' || type === 'bank_branch')
&& ALPHANUMERIC_BANK_CODES.includes(compensationCode as BankCompensationCode)
);

const stripped = isAlphanumeric
? stripAlphanumeric(value)
: stripNumeric(value);

Expand Down
6 changes: 3 additions & 3 deletions src/masks/maskHintBankAccount.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { BANK_ACCOUNT_MASKS } from './masks';
import { BANK_ACCOUNT_MASKS, DEFAULT_BANK_ACCOUNT_MASK } from './masks';
import type { BankCompensationCode } from './types';

const DEFAULT_HINT = '0000000000-0';

const toHint = (pattern: string): string => pattern.replace(/[9S]/g, '0');

const DEFAULT_HINT = toHint(DEFAULT_BANK_ACCOUNT_MASK);

export default function maskHintBankAccount(compensationCode: string): string {
const pattern = BANK_ACCOUNT_MASKS[compensationCode as BankCompensationCode];

Expand Down
66 changes: 57 additions & 9 deletions src/masks/maskValue.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,60 @@
import VMasker from 'vanilla-masker';
import { MASKS, CURRENCY_MASK_DEFAULTS, PERCENTAGE_MASK_DEFAULTS } from './masks';
import {
MASKS,
CURRENCY_MASK_DEFAULTS,
PERCENTAGE_MASK_DEFAULTS,
DEFAULT_BANK_BRANCH_MASK,
DEFAULT_BANK_ACCOUNT_MASK,
BANK_BRANCH_MASKS,
BANK_ACCOUNT_MASKS,
ALPHANUMERIC_BANK_CODES,
} from './masks';
import { stripAlphanumeric, stripNumeric } from './strip';
import type { MaskType, CurrencyMaskOptions } from './types';
import type { MaskType, CurrencyMaskOptions, BankCompensationCode } from './types';

/**
* Options for `maskValue`. Extends `CurrencyMaskOptions` so that currency/percentage
* formatting options and `compensationCode` (used only by `bank_branch`/`bank_account`)
* can be passed through a single options object.
*/
export interface MaskValueOptions extends CurrencyMaskOptions {
compensationCode?: string;
}

const ALPHANUMERIC_MASKS: ReadonlyArray<MaskType> = ['cnpj', 'cpf_cnpj'];

const stripFor = (value: string, type: MaskType): string => (
ALPHANUMERIC_MASKS.includes(type) ? stripAlphanumeric(value) : stripNumeric(value)
);
const stripFor = (value: string, type: MaskType, compensationCode?: string): string => {
if (ALPHANUMERIC_MASKS.includes(type)) return stripAlphanumeric(value);
if (
(type === 'bank_account' || type === 'bank_branch')
&& ALPHANUMERIC_BANK_CODES.includes(compensationCode as BankCompensationCode)
) {
return stripAlphanumeric(value);
}
return stripNumeric(value);
};

const patternFor = (type: MaskType, stripped: string): string => {
const bankBranchPattern = (code?: string): string => {
if (code) {
const mapped = BANK_BRANCH_MASKS[code as BankCompensationCode];
if (mapped) return mapped;
}
return DEFAULT_BANK_BRANCH_MASK;
};

const bankAccountPattern = (code?: string): string => {
if (code) {
const mapped = BANK_ACCOUNT_MASKS[code as BankCompensationCode];
if (mapped) return mapped;
}
return DEFAULT_BANK_ACCOUNT_MASK;
};

const patternFor = (
type: MaskType,
stripped: string,
compensationCode?: string,
): string => {
if (type === 'cpf_cnpj') {
if (/[A-Z]/.test(stripped)) return MASKS.CNPJ;
return stripped.length <= 11 ? MASKS.CPF : MASKS.CNPJ;
Expand All @@ -19,20 +64,23 @@ const patternFor = (type: MaskType, stripped: string): string => {
case 'cpf': return MASKS.CPF;
case 'cnpj': return MASKS.CNPJ;
case 'phone': return MASKS.PHONE;
case 'phone_idd': return MASKS.PHONE_IDD;
case 'zipcode': return MASKS.ZIPCODE;
case 'date': return MASKS.DATE;
case 'barCode': return MASKS.BAR_CODE;
case 'barCodeUtilities': return MASKS.BAR_CODE_UTILITIES;
case 'darf': return MASKS.DARF;
case 'number': return MASKS.NUMBER;
case 'bank_branch': return bankBranchPattern(compensationCode);
case 'bank_account': return bankAccountPattern(compensationCode);
default: return '';
}
};

export default function maskValue(
value: string | null | undefined,
type: MaskType,
options?: CurrencyMaskOptions,
options?: MaskValueOptions,
): string {
if (value === null || value === undefined || value === '') return '';

Expand All @@ -46,8 +94,8 @@ export default function maskValue(
return VMasker.toMoney(stringValue, { ...PERCENTAGE_MASK_DEFAULTS, ...(options || {}) });
}

const stripped = stripFor(stringValue, type);
const pattern = patternFor(type, stripped);
const stripped = stripFor(stringValue, type, options?.compensationCode);
const pattern = patternFor(type, stripped, options?.compensationCode);

if (!pattern) return stringValue;

Expand Down
15 changes: 13 additions & 2 deletions src/masks/masks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ export const MASKS = {
CNPJ: 'SS.SSS.SSS/SSSS-99',
CPF_CNPJ: '999.999.999-99',
PHONE: '(99) 99999-9999',
PHONE_IDD: '+99 (99) 99999-9999',
ZIPCODE: '99999-999',
DATE: '99/99/9999',
BAR_CODE: '99999.99999 99999.999999 99999.999999 9 99999999999999',
BAR_CODE_UTILITIES: '999999999999 999999999999 999999999999 999999999999',
DARF: '99.999.999-9',
DARF: '99 9 99 999999-99',
NUMBER: '999999999999',
} as const;

Expand All @@ -29,7 +30,15 @@ export const PERCENTAGE_MASK_DEFAULTS: CurrencyMaskOptions = {
zeroCents: false,
};

export const BANK_ACCOUNT_MASKS: Record<BankCompensationCode, string> = {
export const BANK_BRANCH_MASKS: Partial<Record<BankCompensationCode, string>> = {
341: '99999-9',
};

export const DEFAULT_BANK_BRANCH_MASK = '9999';

export const ALPHANUMERIC_BANK_CODES: ReadonlyArray<BankCompensationCode> = ['1'];

export const BANK_ACCOUNT_MASKS: Partial<Record<BankCompensationCode, string>> = {
1: '99999999-S',
33: '99999999-9',
41: '999999999-9',
Expand All @@ -40,3 +49,5 @@ export const BANK_ACCOUNT_MASKS: Record<BankCompensationCode, string> = {
399: '999999-9',
745: '9999999-9',
};

export const DEFAULT_BANK_ACCOUNT_MASK = '999999999999-9';
5 changes: 4 additions & 1 deletion src/masks/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ export type MaskType =
| 'darf'
| 'number'
| 'currency'
| 'percentage';
| 'percentage'
| 'phone_idd'
| 'bank_branch'
| 'bank_account';

export interface CurrencyMaskOptions {
precision?: number;
Expand Down
87 changes: 87 additions & 0 deletions tests/masks/maskBankAccount.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import 'jest';

import { maskBankAccount } from '../../src';

describe('maskBankAccount', () => {
describe('Banco do Brasil (1)', () => {
test('formata conta com dígito', () => {
expect(maskBankAccount('123456789', '1')).toBe('12345678-9');
});
});

describe('Santander (33)', () => {
test('formata conta com dígito', () => {
expect(maskBankAccount('123456789', '33')).toBe('12345678-9');
});
});

describe('Bradesco (237)', () => {
test('formata conta com dígito', () => {
expect(maskBankAccount('12345678', '237')).toBe('1234567-8');
});
});

describe('Itaú (341)', () => {
test('formata conta com dígito', () => {
expect(maskBankAccount('123456', '341')).toBe('12345-6');
});
});

describe('CEF (104)', () => {
test('formata conta longa com dígito', () => {
expect(maskBankAccount('1234567890123', '104')).toBe('123456789012-3');
});
});

describe('Banrisul (41)', () => {
test('formata conta com dígito', () => {
expect(maskBankAccount('1234567890', '41')).toBe('123456789-0');
});
});

describe('HSBC (399)', () => {
test('formata conta com dígito', () => {
expect(maskBankAccount('1234567', '399')).toBe('123456-7');
});
});

describe('Citibank (745)', () => {
test('formata conta com dígito', () => {
expect(maskBankAccount('12345678', '745')).toBe('1234567-8');
});
});

describe('Banco Original (213)', () => {
test('formata conta com dígito', () => {
expect(maskBankAccount('12345678', '213')).toBe('1234567-8');
});
});

describe('noop (banco desconhecido)', () => {
test('usa máscara default (999999999999-9)', () => {
expect(maskBankAccount('1234567890123', '999')).toBe('123456789012-3');
});

test('sem código de compensação usa default', () => {
expect(maskBankAccount('1234567890123')).toBe('123456789012-3');
});
});

describe('edge cases', () => {
test('null retorna empty string', () => {
expect(maskBankAccount(null, '1')).toBe('');
});

test('undefined retorna empty string', () => {
expect(maskBankAccount(undefined, '1')).toBe('');
});

test('empty string retorna empty string', () => {
expect(maskBankAccount('', '1')).toBe('');
});

test('strip de separadores antes de aplicar', () => {
expect(maskBankAccount('1234-5678-9', '33')).toBe('12345678-9');
});
});
});
Loading
Loading