From f6370ee05da4e92a8a89b8e1c0313b5fa556b217 Mon Sep 17 00:00:00 2001 From: Romuald DANSOU Date: Wed, 13 Mar 2024 23:19:56 +0000 Subject: [PATCH] feat(#55): add select_one and select_multiple --- src/config/index.ts | 4 +-- src/lib/validation.ts | 6 ++-- src/lib/validator-regex.ts | 2 +- src/lib/validator-select_multiple.ts | 35 +++++++++++++++++++ src/lib/validator-select_one.ts | 26 ++++++++++++++ .../components/contact_type_property.html | 14 +++++++- src/liquid/components/list_cell.html | 9 ++++- test/lib/validation.spec.ts | 18 ++++++---- test/mocks.ts | 11 +++--- 9 files changed, 107 insertions(+), 18 deletions(-) create mode 100644 src/lib/validator-select_multiple.ts create mode 100644 src/lib/validator-select_one.ts diff --git a/src/config/index.ts b/src/config/index.ts index 6be7a1ce..3079904b 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -31,7 +31,7 @@ export type HierarchyConstraint = { property_name: string; type: string; required: boolean; - parameter? : string | string[]; + parameter? : string | string[] | object; errorDescription? : string; contact_type: string; @@ -43,7 +43,7 @@ export type ContactProperty = { property_name: string; type: string; required: boolean; - parameter? : string | string[]; + parameter? : string | string[] | object; errorDescription? : string; }; diff --git a/src/lib/validation.ts b/src/lib/validation.ts index 83715374..22a6e9ec 100644 --- a/src/lib/validation.ts +++ b/src/lib/validation.ts @@ -4,12 +4,13 @@ import RemotePlaceResolver from './remote-place-resolver'; import { RemotePlace } from './cht-api'; import ValidatorDateOfBirth from './validator-dob'; -import ValidatorGender from './validator-gender'; import ValidatorName from './validator-name'; import ValidatorPhone from './validator-phone'; import ValidatorRegex from './validator-regex'; import ValidatorSkip from './validator-skip'; import ValidatorString from './validator-string'; +import ValidatorSelectMultiple from './validator-select_multiple'; +import ValidatorSelectOne from './validator-select_one'; export type ValidationError = { property_name: string; @@ -32,7 +33,8 @@ const TypeValidatorMap: ValidatorMap = { regex: new ValidatorRegex(), phone: new ValidatorPhone(), none: new ValidatorSkip(), - gender: new ValidatorGender(), + select_one: new ValidatorSelectOne(), + select_multiple: new ValidatorSelectMultiple(), dob: new ValidatorDateOfBirth(), }; diff --git a/src/lib/validator-regex.ts b/src/lib/validator-regex.ts index 8ddb418c..8e550f05 100644 --- a/src/lib/validator-regex.ts +++ b/src/lib/validator-regex.ts @@ -13,7 +13,7 @@ export default class ValidatorRegex implements IValidator { throw Error(`property of type regex - 'parameter' should not be an array`); } - const regex = new RegExp(property.parameter); + const regex = new RegExp(property.parameter.toString()); const validatorStr = new ValidatorString(); const altered = validatorStr.format(input); const match = altered.match(regex); diff --git a/src/lib/validator-select_multiple.ts b/src/lib/validator-select_multiple.ts new file mode 100644 index 00000000..377bab9b --- /dev/null +++ b/src/lib/validator-select_multiple.ts @@ -0,0 +1,35 @@ +import {ContactProperty} from '../config'; +import {IValidator} from './validation'; + +export default class ValidatorSelectMultiple implements IValidator { + isValid(input: string, property: ContactProperty): boolean { + // Verify property.parameter is an object and is not null + if (!property?.parameter || typeof property.parameter !== 'object') { + throw new TypeError(`Expected property "parameter" to be an object.`); + } + const validValues = Object.keys(property.parameter); + let selectedValues; + if (Array.isArray(input)) { + selectedValues = input; + } else { + selectedValues = input.split(' ').map(value => value.trim()).filter(value => value !== ''); + } + const invalidValues = selectedValues.filter(value => !validValues.includes(value)); + if (invalidValues.length > 0) { + throw new Error(`Invalid values: ${invalidValues.join(', ')}`); + } + if (selectedValues.length === 0 && property.required) { + throw new Error('Value is required'); + } + return true; + } + + format(input: string): string { + return Array.isArray(input) ? input.join(' ') : input; + } + + get defaultError(): string { + return 'Invalid input'; + } + +} diff --git a/src/lib/validator-select_one.ts b/src/lib/validator-select_one.ts new file mode 100644 index 00000000..15ebd55b --- /dev/null +++ b/src/lib/validator-select_one.ts @@ -0,0 +1,26 @@ +import {ContactProperty} from '../config'; +import {IValidator} from './validation'; +import {property} from "lodash"; + +export default class ValidatorSelectOne implements IValidator { + isValid(input: string, property: ContactProperty): boolean { + if (input.trim().length === 0 && property.required) { + throw new Error('Value is required'); + } + // Verify property.parameter is an object + if (!property?.parameter || typeof property.parameter !== 'object') { + throw new TypeError(`Expected property "parameter" to be an object.`); + } + const validValues = Object.keys(property.parameter); + return validValues.includes(input.trim()); + } + + format(input: string): string { + return input; + } + + get defaultError(): string { + return 'Invalid value selected'; + } + +} diff --git a/src/liquid/components/contact_type_property.html b/src/liquid/components/contact_type_property.html index 3af9065f..6409bbc6 100644 --- a/src/liquid/components/contact_type_property.html +++ b/src/liquid/components/contact_type_property.html @@ -4,6 +4,17 @@ {{include.prop.friendly_name}}
+ {% if include.prop.type == 'select_one' or include.prop.type == 'select_multiple' %} +
+ +
+ {% else %} + {% endif %}
- \ No newline at end of file + diff --git a/src/liquid/components/list_cell.html b/src/liquid/components/list_cell.html index f26e8beb..db757423 100644 --- a/src/liquid/components/list_cell.html +++ b/src/liquid/components/list_cell.html @@ -11,6 +11,13 @@ {{ include.values[include.property.property_name] }} + {% elsif include.property.type == 'select_one' %} + {{ include.property.parameter[include.values[include.property.property_name]] }} + {% elsif include.property.type == 'select_multiple' %} + {% assign values = include.values[include.property.property_name] | split: " " %} + {% for value in values %} + {{include.property.parameter[value]}} + {% endfor %} {% else %} {{ include.values[include.property.property_name] }} {% endif %} @@ -19,4 +26,4 @@ cloud_off {% endif %} {% endif %} - \ No newline at end of file + diff --git a/test/lib/validation.spec.ts b/test/lib/validation.spec.ts index b135f2ab..2e3c0eb7 100644 --- a/test/lib/validation.spec.ts +++ b/test/lib/validation.spec.ts @@ -7,7 +7,7 @@ type Scenario = { type: string; prop: string; isValid: boolean; - propertyParameter?: string | string[]; + propertyParameter?: string | string[] | object; altered?: string; propertyErrorDescription?: string; error?: string; @@ -53,12 +53,16 @@ const scenarios: Scenario[] = [ { type: 'dob', prop: '2016-05-25', isValid: true, altered: '2016-05-25' }, { type: 'dob', prop: ' 20 16- 05- 25 ', isValid: true, altered: '2016-05-25' }, - { type: 'gender', prop: 'Man', isValid: true, altered: 'male' }, - { type: 'gender', prop: 'male', isValid: true, altered: 'male' }, - { type: 'gender', prop: 'F', isValid: true, altered: 'female' }, - { type: 'gender', prop: 'Female', isValid: true, altered: 'female' }, - { type: 'gender', prop: 'Woman', isValid: true, altered: 'female' }, - { type: 'gender', prop: 'X', isValid: false, error: 'male' }, + { type: 'select_one', prop: ' male', isValid: true, propertyParameter: { male: 'Male', female: 'Female' } }, + { type: 'select_one', prop: 'female ', isValid: true, propertyParameter: { male: 'Male', female: 'Female' } }, + { type: 'select_one', prop: 'f', isValid: false, propertyParameter: { male: 'Male', female: 'Female' } }, + { type: 'select_one', prop: '', isValid: false, propertyParameter: { male: 'Male', female: 'Female' } }, + + { type: 'select_multiple', prop: 'male', isValid: true, propertyParameter: { male: 'Male', female: 'Female' } }, + { type: 'select_multiple', prop: 'male female', isValid: true, propertyParameter: { male: 'Male', female: 'Female' } }, + { type: 'select_multiple', prop: ' male female', isValid: true, propertyParameter: { male: 'Male', female: 'Female' } }, + { type: 'select_multiple', prop: 'f,m', isValid: false, propertyParameter: { male: 'Male', female: 'Female' }, error: 'Invalid values' }, + { type: 'select_multiple', prop: '', isValid: false, propertyParameter: { male: 'Male', female: 'Female' }, error: 'required' }, ]; describe('lib/validation.ts', () => { diff --git a/test/mocks.ts b/test/mocks.ts index cf7fa069..c0a6722d 100644 --- a/test/mocks.ts +++ b/test/mocks.ts @@ -3,7 +3,7 @@ import Sinon from 'sinon'; import { ChtApi, RemotePlace } from '../src/lib/cht-api'; import ChtSession from '../src/lib/cht-session'; -import { ContactProperty, ContactType } from '../src/lib/config'; +import { ContactProperty, ContactType } from '../src/config'; import Place from '../src/services/place'; export const mockPlace = (type: ContactType, prop: any) : Place => { @@ -32,7 +32,7 @@ export const mockChtApi: ChtApi = (first: RemotePlace[] = [], second: RemotePlac }); export const mockSimpleContactType = ( - propertyType, + propertyType: string, propertyValidator: string | string[] | undefined, errorDescription?: string ) : ContactType => { @@ -44,6 +44,7 @@ export const mockSimpleContactType = ( contact_type: 'contact-type', user_role: 'role', username_from_place: false, + deactivate_users_on_replace: false, hierarchy: [ { ...mockProperty('name', undefined, 'PARENT'), @@ -60,12 +61,13 @@ export const mockSimpleContactType = ( }; }; -export const mockValidContactType = (propertyType, propertyValidator: string | string[] | undefined) : ContactType => ({ +export const mockValidContactType = (propertyType: string, propertyValidator: string | string[] | undefined) : ContactType => ({ name: 'contacttype-name', friendly: 'friendly', contact_type: 'contact-type', user_role: 'role', username_from_place: false, + deactivate_users_on_replace: false, hierarchy: [ { ...mockProperty('name', undefined, 'PARENT'), @@ -95,7 +97,7 @@ export const mockParentPlace = (parentPlaceType: ContactType, parentName: string return place; }; -export const mockProperty = (type, parameter: string | string[] | undefined, property_name: string = 'prop'): ContactProperty => ({ +export const mockProperty = (type: string, parameter: string | string[] | undefined | object, property_name: string = 'prop'): ContactProperty => ({ friendly_name: 'csv', property_name, type, @@ -103,6 +105,7 @@ export const mockProperty = (type, parameter: string | string[] | undefined, pro required: true }); +// Constructor of class ChtSession is private and only accessible within the class declaration. export const mockChtSession = (userFacilityId: string = '*') : ChtSession => new ChtSession( { friendly: 'domain',