From 926bc35841482c2ef599ef17c669c4410c202bbe Mon Sep 17 00:00:00 2001 From: Ben Kiarie <2597305+Benmuiruri@users.noreply.github.com> Date: Tue, 11 Feb 2025 09:29:19 +0300 Subject: [PATCH] fix(#236): update titleCase to handle hyphens and parenthesis (#252) * chore: update titleCase * chore: add more tests * chore: refactor --- src/validation/validator-name.ts | 34 +++++++++++++++++++++++++------ test/lib/credentials-file.spec.ts | 4 ++-- test/lib/manage-hierarchy.spec.ts | 6 +++--- test/validation.spec.ts | 7 +++++-- 4 files changed, 38 insertions(+), 13 deletions(-) diff --git a/src/validation/validator-name.ts b/src/validation/validator-name.ts index b518d197..8dbba314 100644 --- a/src/validation/validator-name.ts +++ b/src/validation/validator-name.ts @@ -36,12 +36,34 @@ export default class ValidatorName implements IValidator { } private titleCase(value: string): string { - const words = value.toLowerCase().split(' '); - const titleCase = (word: string) => word[0].toUpperCase() + word.slice(1); - const isRomanNumeral = /^[ivx]+$/ig; - const titleCased = words + if (!value) { + return ''; + } + + const titleCase = (word: string) => word[0]?.toUpperCase() + word.slice(1); + + const isRomanNumeral = /^[ivx]+$/i; + const hasForwardSlash = /\//g; + const hasApostrophe = /\s*'\s*/g; + const hasParentheses = /\(([^)]+)\)/g; + const hasExtraSpaces = /\s+/g; + + const splitAndProcess = (value: string, delimiter: string) => { + return value + .split(delimiter) + .map(word => word.match(isRomanNumeral) ? word.toUpperCase() : titleCase(word)) + .join(delimiter); + }; + + return value.toLowerCase() + .replace(hasForwardSlash, ' / ') + .replace(hasApostrophe, '\'') + .replace(hasParentheses, match => `(${titleCase(match.slice(1, -1))})`) + .split(' ') .filter(Boolean) - .map(word => word.match(isRomanNumeral) ? word.toUpperCase() : titleCase(word)).join(' '); - return titleCased.replace(/ '/g, '\''); + .map(word => splitAndProcess(word, '-')) + .join(' ') + .replace(hasExtraSpaces, ' ') + .trim(); } } diff --git a/test/lib/credentials-file.spec.ts b/test/lib/credentials-file.spec.ts index b8329a85..f4214abc 100644 --- a/test/lib/credentials-file.spec.ts +++ b/test/lib/credentials-file.spec.ts @@ -35,7 +35,7 @@ describe('lib/credentials-file.ts', () => { expect(actual).to.deep.eq([{ filename: 'contacttype-name.csv', content: `friendly replacement,friendly PARENT,friendly GRANDPARENT,friendly,name,phone,role,username,password -,Parent-name,,Place,contact,0712 344321,role,, +,Parent-Name,,Place,contact,0712 344321,role,, ` }]); }); @@ -61,7 +61,7 @@ describe('lib/credentials-file.ts', () => { expect(actual).to.deep.eq([{ filename: 'contacttype-name.csv', content: `friendly replacement,friendly PARENT,friendly GRANDPARENT,friendly,name,phone,role,username,password -,Parent-name,,Place,contact,,role,, +,Parent-Name,,Place,contact,,role,, ` }]); }); diff --git a/test/lib/manage-hierarchy.spec.ts b/test/lib/manage-hierarchy.spec.ts index edb12c12..bb668d49 100644 --- a/test/lib/manage-hierarchy.spec.ts +++ b/test/lib/manage-hierarchy.spec.ts @@ -48,7 +48,7 @@ describe('lib/manage-hierarchy.ts', () => { const sessionCache = new SessionCache(); const jobParams = await ManageHierarchyLib.getJobDetails(formData, contactType, sessionCache, chtApiWithDocs()); - expect(jobParams).to.have.property('jobName').that.equals('move_[From Sub.C-h-u]_to_[To Sub]'); + expect(jobParams).to.have.property('jobName').that.equals('move_[From Sub.C-H-U]_to_[To Sub]'); expect(jobParams).to.have.property('jobData').that.deep.include({ action: 'move', sourceId: 'from-chu-id', @@ -113,7 +113,7 @@ describe('lib/manage-hierarchy.ts', () => { const sessionCache = new SessionCache(); const jobParams = await ManageHierarchyLib.getJobDetails(formData, contactType, sessionCache, chtApiWithDocs()); - expect(jobParams).to.have.property('jobName').that.equals('merge_[From Sub.C-h-u]_to_[To Sub.Destination]'); + expect(jobParams).to.have.property('jobName').that.equals('merge_[From Sub.C-H-U]_to_[To Sub.Destination]'); expect(jobParams).to.have.property('jobData').that.deep.include({ action: 'merge', sourceId: 'from-chu-id', @@ -135,7 +135,7 @@ describe('lib/manage-hierarchy.ts', () => { const sessionCache = new SessionCache(); const jobParams = await ManageHierarchyLib.getJobDetails(formData, contactType, sessionCache, chtApiWithDocs()); - expect(jobParams).to.have.property('jobName').that.equals('delete_[From Sub.C-h-u]'); + expect(jobParams).to.have.property('jobName').that.equals('delete_[From Sub.C-H-U]'); expect(jobParams).to.have.property('jobData').that.deep.include({ action: 'delete', sourceId: 'from-chu-id', diff --git a/test/validation.spec.ts b/test/validation.spec.ts index a85ee3a2..a2339ada 100644 --- a/test/validation.spec.ts +++ b/test/validation.spec.ts @@ -45,9 +45,12 @@ const scenarios: Scenario[] = [ { type: 'name', prop: undefined, isValid: false, error: 'Required' }, { type: 'name', prop: 'abc', isValid: true, formatted: 'Abc' }, { type: 'name', prop: 'a b c', isValid: true, formatted: 'A B C' }, - { type: 'name', prop: 'Mr. Sand(m-a-n)', isValid: true, formatted: 'Mr Sand(m-a-n)' }, - { type: 'name', prop: 'WELDON KO(E)CH \n', isValid: true, formatted: 'Weldon Ko(e)ch' }, + { type: 'name', prop: 'Mr. Sand(m-a-n)', isValid: true, formatted: 'Mr Sand(M-A-N)' }, + { type: 'name', prop: 'WELDON KO(E)CH \n', isValid: true, formatted: 'Weldon Ko(E)ch' }, { type: 'name', prop: 'S \'am \'s', isValid: true, formatted: 'S\'am\'s' }, + { type: 'name', prop: 'this-is-(a-place)', isValid: true, formatted: 'This-Is-(A-Place)' }, + { type: 'name', prop: 'mr. chp-(per-son)', isValid: true, formatted: 'Mr Chp-(Per-Son)' }, + { type: 'name', prop: 'ma-#ma-(pa-pa)', isValid: true, formatted: 'Ma-Ma-(Pa-Pa)' }, { type: 'name', prop: 'KYAMBOO/KALILUNI', isValid: true, formatted: 'Kyamboo / Kaliluni' }, { type: 'name', prop: 'NZATANI / ILALAMBYU', isValid: true, formatted: 'Nzatani / Ilalambyu' }, { type: 'name', prop: 'Sam\'s CHU', propertyParameter: ['CHU', 'Comm Unit'], isValid: true, formatted: 'Sam\'s' },