From 12b4af27aff4c734cf9930a2c5069e2aef8337dd Mon Sep 17 00:00:00 2001 From: RafaelGondi Date: Tue, 22 Jul 2025 10:49:16 -0300 Subject: [PATCH 1/4] feat: implementa helper pluralize() --- .gitignore | 3 ++ package.json | 2 +- src/components/index.ts | 97 ++++++++++++++++++----------------- src/utils/index.js | 1 + src/utils/pluralize.ts | 44 ++++++++++++++++ tests/utils/pluralize.test.ts | 75 +++++++++++++++++++++++++++ 6 files changed, 173 insertions(+), 49 deletions(-) create mode 100644 src/utils/pluralize.ts create mode 100644 tests/utils/pluralize.test.ts diff --git a/.gitignore b/.gitignore index b2533d2..a0c6379 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ npm-debug.log* package-lock.json /dist /coverage +.yalc +.yalc/* +yalc.lock \ No newline at end of file diff --git a/package.json b/package.json index b63e24d..3c3eb8a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sysvale/show", - "version": "1.19.0", + "version": "1.20.0", "description": "A set of components used at Sysvale", "repository": { "type": "git", diff --git a/src/components/index.ts b/src/components/index.ts index aff42dc..9841b94 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -16,61 +16,62 @@ import { useDialog } from '@/composables/useDialog'; /** Utils */ import { - convertKeysToCamelCase, - convertKeysToSnakeCase, - removeAccents, - generateKey, - toThousands, - setupVeeValidateWrapper, + convertKeysToCamelCase, + convertKeysToSnakeCase, + removeAccents, + generateKey, + toThousands, + setupVeeValidateWrapper, + pluralize, + pluralizeWithCount, } from '../utils'; /* -------*/ -export { useRequest, useDialog }; +export { useRequest, useDialog, pluralize, pluralizeWithCount }; export default { - install(app: any, options = { - veeValidateOptions: {}, - disabledFeatures: [], - }) { - const { veeValidateOptions, disabledFeatures } = options; - const veeValidateWrapper = setupVeeValidateWrapper(veeValidateOptions); + install(app: any, options = { + veeValidateOptions: {}, + disabledFeatures: [], + }) { + const { veeValidateOptions, disabledFeatures } = options; + const veeValidateWrapper = setupVeeValidateWrapper(veeValidateOptions); - if (disabledFeatures.length > 0) { - app.provide('disabledFeatures', options.disabledFeatures); - } + if (disabledFeatures.length > 0) { + app.provide('disabledFeatures', options.disabledFeatures); + } - app.component('ShowRequestProvider', RequestProvider); - app.component('ShowRequestObserver', RequestObserver); - app.component('ShowRequestSelect', RequestSelect); - app.component('ShowForm', Form); - app.component('ShowField', Field); - app.component('ShowFieldArray', FieldArray); - app.component('ShowFormWizard', FormWizard); - app.component('ShowFeatureWrapper', FeatureWrapper); + app.component('ShowRequestProvider', RequestProvider); + app.component('ShowRequestObserver', RequestObserver); + app.component('ShowRequestSelect', RequestSelect); + app.component('ShowForm', Form); + app.component('ShowField', Field); + app.component('ShowFieldArray', FieldArray); + app.component('ShowFormWizard', FormWizard); + app.component('ShowFeatureWrapper', FeatureWrapper); + const utils = { + $showConvertKeysToCamelCase: convertKeysToCamelCase, + $showConvertKeysToSnakeCase: convertKeysToSnakeCase, + $showRemoveAccents: removeAccents, + $showGenerateKey: generateKey, + $showToThousands: toThousands, + }; - const utils = { - $showConvertKeysToCamelCase: convertKeysToCamelCase, - $showConvertKeysToSnakeCase: convertKeysToSnakeCase, - $showRemoveAccents: removeAccents, - $showGenerateKey: generateKey, - $showToThousands: toThousands, - }; + const version = Number(app.version.split('.')[0]); + + if (version <= 2) { + throw new Error('Essa versão só é compatível com projetos que possuem o Vue 3. Para projetos com a Vue 2, utilize a versão 0.3.0 ou inferior'); + } + + if (version > 2) { + // ficará disponível apenas com o uso do Options API + Object.keys(utils).forEach((key: string) => { + app.config.globalProperties[key] = utils[key]; + }); - const version = Number(app.version.split('.')[0]); - - if (version <= 2) { - throw new Error('Essa versão só é compatível com projetos que possuem o Vue 3. Para projetos com a Vue 2, utilize a versão 0.3.0 ou inferior'); - } - - if (version > 2) { - // ficará disponível apenas com o uso do Options API - Object.keys(utils).forEach((key: string) => { - app.config.globalProperties[key] = utils[key]; - }); - - // wrapper do vee-validate - app.config.globalProperties.$showVeeValidate = veeValidateWrapper; - } - }, -} \ No newline at end of file + // wrapper do vee-validate + app.config.globalProperties.$showVeeValidate = veeValidateWrapper; + } + }, +} diff --git a/src/utils/index.js b/src/utils/index.js index bdd4a63..127bbf4 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -4,4 +4,5 @@ export { default as getFirstErrorMessage } from './getFirstErrorMessage'; export { default as removeAccents } from './removeAccents'; export { default as generateKey } from './generateKey'; export { default as toThousands } from './toThousands'; +export { pluralize, pluralizeWithCount } from './pluralize'; export { default as setupVeeValidateWrapper } from './setupVeeValidateWrapper'; diff --git a/src/utils/pluralize.ts b/src/utils/pluralize.ts new file mode 100644 index 0000000..2303cdb --- /dev/null +++ b/src/utils/pluralize.ts @@ -0,0 +1,44 @@ +export function pluralize( + count: number | string | null, + word: string, + suffix: string = '' +): string { + let plural = word; + + if (typeof count === 'string') { + word = count; + count = null; + } + + const absCount = Math.abs(count ?? 2); + console.log('test TS'); + + if (absCount >= 2) { + if (suffix) { // sufixo personalizado fornecido por quem chama a função + plural = word + suffix; + } else if (word.endsWith('s') || word.endsWith('x')) { // Palavras invariáveis terminadas em s ou x + plural = word; + } else if (word.endsWith('ão')) { // Exceções do -ão (padrão geral: -ões) + plural = word.slice(0, -2) + 'ões'; + } else if (word.endsWith('m')) { // -m -> -ns + plural = word.slice(0, -1) + 'ns'; + } else if (word.endsWith('r') || word.endsWith('z')) { // -r, -z -> +es + plural = word + 'es'; + } else if (/[aou]l$/.test(word)) { // -al, -ol, -ul → -ais, -ois, -uis + plural = word.slice(0, -1) + 'is'; + } else if (word.endsWith('el')) { // -el → -éis + plural = word.slice(0, -2) + 'éis'; + } else if (word.endsWith('il')) { // -il → -is (oxítonas) [simplificado] + plural = word.slice(0, -2) + 'is'; + } else if (/[aeiou]$/.test(word)) { // Regra geral para vogais finais + plural = word + 's'; + } + } + + return plural; +} + +export function pluralizeWithCount(...args: Parameters): string { + const [counter] = args; + return `${counter} ${pluralize(...args)}`; +} diff --git a/tests/utils/pluralize.test.ts b/tests/utils/pluralize.test.ts new file mode 100644 index 0000000..0f7da44 --- /dev/null +++ b/tests/utils/pluralize.test.ts @@ -0,0 +1,75 @@ +import { describe, test, expect } from 'vitest'; +import { pluralize, pluralizeWithCount } from '../../src/utils/pluralize'; // Adjust the path + +describe('pluralize()', () => { + test('returns same word if count is 1', () => { + expect(pluralize(1, 'carro')).toBe('carro'); + }); + + test('adds "s" for regular words ending in vowel', () => { + expect(pluralize(2, 'banana')).toBe('bananas'); + }); + + test('uses custom suffix if provided', () => { + expect(pluralize(2, 'livro', 'zinhos')).toBe('livrozinhos'); + }); + + test('does not change words ending in s or x', () => { + expect(pluralize(3, 'tórax')).toBe('tórax'); + expect(pluralize(3, 'lápis')).toBe('lápis'); + }); + + test('converts -ão to -ões', () => { + expect(pluralize(2, 'pão')).toBe('pões'); + }); + + test('converts -m to -ns', () => { + expect(pluralize(2, 'homem')).toBe('homens'); + }); + + test('adds "es" for words ending in r or z', () => { + expect(pluralize(2, 'luz')).toBe('luzes'); + expect(pluralize(2, 'motor')).toBe('motores'); + }); + + test('converts -al, -ol, -ul to -ais, -ois, -uis', () => { + expect(pluralize(2, 'animal')).toBe('animais'); + expect(pluralize(2, 'sol')).toBe('sois'); + expect(pluralize(2, 'azul')).toBe('azuis'); + }); + + test('converts -el to -éis', () => { + expect(pluralize(2, 'papel')).toBe('papéis'); + }); + + test('converts -il to -is', () => { + expect(pluralize(2, 'fuzil')).toBe('fuzis'); + }); + + test('uses plural when count is negative', () => { + expect(pluralize(-3, 'avião')).toBe('aviões'); + }); + + test('does not use plural when count is zero', () => { + expect(pluralize(0, 'mapa')).toBe('mapa'); + }); + + test('defaults count to 2 if null or undefined', () => { + expect(pluralize(null, 'livro')).toBe('livros'); + }); + + test('treats first param as word if count is a string', () => { + expect(pluralize('carro', '', 's')).toBe('carros'); + }); +}); + +describe('pluralizeWithCount()', () => { + test('includes the count in the result', () => { + expect(pluralizeWithCount(1, 'carro')).toBe('1 carro'); + expect(pluralizeWithCount(2, 'carro')).toBe('2 carros'); + }); + + test('works with custom suffix', () => { + expect(pluralizeWithCount(2, 'livro', 'zinhos')).toBe('2 livrozinhos'); + }); +}); From 0b2e9d54a8b4cc05d39a908b746f3dec5ab252c2 Mon Sep 17 00:00:00 2001 From: RafaelGondi Date: Tue, 22 Jul 2025 10:55:06 -0300 Subject: [PATCH 2/4] =?UTF-8?q?chore:=20remove=20coment=C3=A1rio=20e=20adi?= =?UTF-8?q?ciona=20quebra=20de=20linha=20no=20gitignore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 +- src/utils/pluralize.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index a0c6379..0aea038 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,4 @@ package-lock.json /coverage .yalc .yalc/* -yalc.lock \ No newline at end of file +yalc.lock diff --git a/src/utils/pluralize.ts b/src/utils/pluralize.ts index 2303cdb..0f4fdc6 100644 --- a/src/utils/pluralize.ts +++ b/src/utils/pluralize.ts @@ -11,7 +11,6 @@ export function pluralize( } const absCount = Math.abs(count ?? 2); - console.log('test TS'); if (absCount >= 2) { if (suffix) { // sufixo personalizado fornecido por quem chama a função From 47ae1c5e2bd42436fe51e1a9988946421cc7625f Mon Sep 17 00:00:00 2001 From: RafaelGondi Date: Tue, 22 Jul 2025 14:48:59 -0300 Subject: [PATCH 3/4] Implementa pluralizeWords --- src/components/index.ts | 3 +- src/utils/index.js | 2 +- src/utils/pluralize.ts | 98 +++++++++++++++++++++++++---------- tests/utils/pluralize.test.ts | 57 ++++++++++++++++---- 4 files changed, 122 insertions(+), 38 deletions(-) diff --git a/src/components/index.ts b/src/components/index.ts index 9841b94..3b3b308 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -24,10 +24,11 @@ import { setupVeeValidateWrapper, pluralize, pluralizeWithCount, + pluralizeWords, } from '../utils'; /* -------*/ -export { useRequest, useDialog, pluralize, pluralizeWithCount }; +export { useRequest, useDialog, pluralize, pluralizeWithCount, pluralizeWords }; export default { install(app: any, options = { diff --git a/src/utils/index.js b/src/utils/index.js index 127bbf4..b0681db 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -4,5 +4,5 @@ export { default as getFirstErrorMessage } from './getFirstErrorMessage'; export { default as removeAccents } from './removeAccents'; export { default as generateKey } from './generateKey'; export { default as toThousands } from './toThousands'; -export { pluralize, pluralizeWithCount } from './pluralize'; +export { pluralize, pluralizeWithCount, pluralizeWords } from './pluralize'; export { default as setupVeeValidateWrapper } from './setupVeeValidateWrapper'; diff --git a/src/utils/pluralize.ts b/src/utils/pluralize.ts index 0f4fdc6..1329053 100644 --- a/src/utils/pluralize.ts +++ b/src/utils/pluralize.ts @@ -1,9 +1,27 @@ +const irregulars: Record = { + pão: 'pães', + mão: 'mãos', + irmão: 'irmãos', + capitão: 'capitães', + cidadão: 'cidadãos', + cão: 'cães', + alemão: 'alemães', + mal: 'males', + órgão: 'órgãos', + nível: 'níveis' +}; + export function pluralize( - count: number | string | null, + count: number | string | null = null, word: string, - suffix: string = '' + customPlural: string = '', + customIrregulars: Record = {} ): string { - let plural = word; + + let innerIrregulars = { + ...irregulars, + ...customIrregulars + } if (typeof count === 'string') { word = count; @@ -12,32 +30,58 @@ export function pluralize( const absCount = Math.abs(count ?? 2); - if (absCount >= 2) { - if (suffix) { // sufixo personalizado fornecido por quem chama a função - plural = word + suffix; - } else if (word.endsWith('s') || word.endsWith('x')) { // Palavras invariáveis terminadas em s ou x - plural = word; - } else if (word.endsWith('ão')) { // Exceções do -ão (padrão geral: -ões) - plural = word.slice(0, -2) + 'ões'; - } else if (word.endsWith('m')) { // -m -> -ns - plural = word.slice(0, -1) + 'ns'; - } else if (word.endsWith('r') || word.endsWith('z')) { // -r, -z -> +es - plural = word + 'es'; - } else if (/[aou]l$/.test(word)) { // -al, -ol, -ul → -ais, -ois, -uis - plural = word.slice(0, -1) + 'is'; - } else if (word.endsWith('el')) { // -el → -éis - plural = word.slice(0, -2) + 'éis'; - } else if (word.endsWith('il')) { // -il → -is (oxítonas) [simplificado] - plural = word.slice(0, -2) + 'is'; - } else if (/[aeiou]$/.test(word)) { // Regra geral para vogais finais - plural = word + 's'; - } - } + // Quantidades insuficientes para aplicação do plural + if (absCount < 2) return word; + + // Sufixo personalizado fornecido por quem chama a função + if (customPlural) return customPlural; + + // Cenário em que a palavra tem plural irregular + if (innerIrregulars[word]) return innerIrregulars[word]; + + // Palavras invariáveis terminadas em s ou x + if (word.endsWith('s') || word.endsWith('x')) return word; + + // Exceções do -ão (padrão geral: -ões) + if (word.endsWith('ão')) return word.slice(0, -2) + 'ões'; + + // -m -> -ns + if (word.endsWith('m')) return word.slice(0, -1) + 'ns'; + + // -r, -z -> +es + if (word.endsWith('r') || word.endsWith('z')) return word + 'es'; - return plural; + // -ol → -óis + if (word.endsWith('ol')) return word.slice(0, -2) + 'óis'; + + // -al, -ul → -ais, -uis + if (/[au]l$/.test(word)) return word.slice(0, -1) + 'is'; + + // -el → -éis + if (word.endsWith('el')) return word.slice(0, -2) + 'éis'; + + // -il → -is (oxítonas) [simplificado] + if (word.endsWith('il')) return word.slice(0, -2) + 'is'; + + // Regra geral para vogais finais + if (/[aeiou]$/.test(word)) return word + 's'; + + //Fallback + return word; } export function pluralizeWithCount(...args: Parameters): string { - const [counter] = args; - return `${counter} ${pluralize(...args)}`; + const [count] = args; + return `${count} ${pluralize(...args)}`; +} + +export function pluralizeWords( + count: number | string | null, + words: string[], + customPlural: string[] | string = '', + customIrregulars: Record = {} +): string { + let pluralizedWords = words.map((word, index) => pluralize(count, word, customPlural[index] ?? customPlural, customIrregulars)); + + return `${pluralizedWords.join(' ')}`; } diff --git a/tests/utils/pluralize.test.ts b/tests/utils/pluralize.test.ts index 0f7da44..df57620 100644 --- a/tests/utils/pluralize.test.ts +++ b/tests/utils/pluralize.test.ts @@ -1,5 +1,5 @@ import { describe, test, expect } from 'vitest'; -import { pluralize, pluralizeWithCount } from '../../src/utils/pluralize'; // Adjust the path +import { pluralize, pluralizeWords, pluralizeWithCount } from '../../src/utils/pluralize'; // Adjust the path describe('pluralize()', () => { test('returns same word if count is 1', () => { @@ -10,8 +10,9 @@ describe('pluralize()', () => { expect(pluralize(2, 'banana')).toBe('bananas'); }); - test('uses custom suffix if provided', () => { - expect(pluralize(2, 'livro', 'zinhos')).toBe('livrozinhos'); + test('uses custom plural if provided', () => { + expect(pluralize(2, 'livro')).not.toBe('livrozinhos'); + expect(pluralize(2, 'livro', 'livrozinhos')).toBe('livrozinhos'); }); test('does not change words ending in s or x', () => { @@ -20,7 +21,18 @@ describe('pluralize()', () => { }); test('converts -ão to -ões', () => { - expect(pluralize(2, 'pão')).toBe('pões'); + expect(pluralize(2, 'avião')).toBe('aviões'); + }); + + test('converts some irregulars properly', () => { + expect(pluralize(2, 'pão')).toBe('pães'); + expect(pluralize(2, 'alemão')).toBe('alemães'); + }); + + test('convert custom irregulars properly', () => { + expect(pluralize(2, 'cônsul')).not.toBe('cônsules'); + expect(pluralize(2, 'cônsul', undefined, { cônsul: 'cônsules'})).toBe('cônsules'); + expect(pluralize(2, 'maçã', undefined, { maçã: 'maçãs'})).toBe('maçãs'); }); test('converts -m to -ns', () => { @@ -32,9 +44,12 @@ describe('pluralize()', () => { expect(pluralize(2, 'motor')).toBe('motores'); }); - test('converts -al, -ol, -ul to -ais, -ois, -uis', () => { + test('converts-ol to -óis', () => { + expect(pluralize(2, 'sol')).toBe('sóis'); + }); + + test('converts -al, -ul to -ais, -uis', () => { expect(pluralize(2, 'animal')).toBe('animais'); - expect(pluralize(2, 'sol')).toBe('sois'); expect(pluralize(2, 'azul')).toBe('azuis'); }); @@ -59,7 +74,7 @@ describe('pluralize()', () => { }); test('treats first param as word if count is a string', () => { - expect(pluralize('carro', '', 's')).toBe('carros'); + expect(pluralize('carro', '')).toBe('carros'); }); }); @@ -69,7 +84,31 @@ describe('pluralizeWithCount()', () => { expect(pluralizeWithCount(2, 'carro')).toBe('2 carros'); }); - test('works with custom suffix', () => { - expect(pluralizeWithCount(2, 'livro', 'zinhos')).toBe('2 livrozinhos'); + test('works with custom plural', () => { + expect(pluralizeWithCount(2, 'livro', 'livrozinhos')).toBe('2 livrozinhos'); + }); +}); + +describe('pluralizeWords()', () => { + test('works with one word', () => { + expect(pluralizeWords(0, ['papel'])).toBe('papel'); + expect(pluralizeWords(1, ['papel'])).toBe('papel'); + expect(pluralizeWords(2, ['papel'])).toBe('papéis'); + }); + + test('works with two words', () => { + expect(pluralizeWords(0, ['avião', 'antigo'])).toBe('avião antigo'); + expect(pluralizeWords(1, ['avião', 'antigo'])).toBe('avião antigo'); + expect(pluralizeWords(2, ['avião', 'antigo'])).toBe('aviões antigos'); + }); + + test('works with three words', () => { + expect(pluralizeWords(0, ['moto', 'branca', 'escolhida'])).toBe('moto branca escolhida'); + expect(pluralizeWords(1, ['moto', 'branca', 'escolhida'])).toBe('moto branca escolhida'); + expect(pluralizeWords(2, ['moto', 'branca', 'escolhida'])).toBe('motos brancas escolhidas'); + }); + + test('works with custom plurals', () => { + expect(pluralizeWords(2, ['o', 'avião', 'velho'], ['os', 'aviõezinhos', 'velhos'])).toBe('os aviõezinhos velhos'); }); }); From 1c0805e215df60aeb042e224cab6c5a90533317a Mon Sep 17 00:00:00 2001 From: RafaelGondi Date: Tue, 22 Jul 2025 14:52:26 -0300 Subject: [PATCH 4/4] =?UTF-8?q?Adiciona=20mais=20um=20caso=20de=20teste=20?= =?UTF-8?q?para=20garantir=20que=20um=20objeto=20customizado=20de=20plurai?= =?UTF-8?q?s=20n=C3=A3o=20sobrescreve=20o=20objeto=20interno?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/utils/pluralize.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/utils/pluralize.test.ts b/tests/utils/pluralize.test.ts index df57620..b3ba50c 100644 --- a/tests/utils/pluralize.test.ts +++ b/tests/utils/pluralize.test.ts @@ -30,6 +30,7 @@ describe('pluralize()', () => { }); test('convert custom irregulars properly', () => { + expect(pluralize(2, 'órgão')).toBe('órgãos'); expect(pluralize(2, 'cônsul')).not.toBe('cônsules'); expect(pluralize(2, 'cônsul', undefined, { cônsul: 'cônsules'})).toBe('cônsules'); expect(pluralize(2, 'maçã', undefined, { maçã: 'maçãs'})).toBe('maçãs');