Skip to content

Commit

Permalink
feat(#55): add select_one and select_multiple
Browse files Browse the repository at this point in the history
  • Loading branch information
inromualdo committed Mar 14, 2024
1 parent ad6acb6 commit f6370ee
Show file tree
Hide file tree
Showing 9 changed files with 107 additions and 18 deletions.
4 changes: 2 additions & 2 deletions src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -43,7 +43,7 @@ export type ContactProperty = {
property_name: string;
type: string;
required: boolean;
parameter? : string | string[];
parameter? : string | string[] | object;
errorDescription? : string;
};

Expand Down
6 changes: 4 additions & 2 deletions src/lib/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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(),
};

Expand Down
2 changes: 1 addition & 1 deletion src/lib/validator-regex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
35 changes: 35 additions & 0 deletions src/lib/validator-select_multiple.ts
Original file line number Diff line number Diff line change
@@ -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';
}

}
26 changes: 26 additions & 0 deletions src/lib/validator-select_one.ts
Original file line number Diff line number Diff line change
@@ -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';
}

}
14 changes: 13 additions & 1 deletion src/liquid/components/contact_type_property.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,24 @@
{{include.prop.friendly_name}}
</label>
<div class="control">
{% if include.prop.type == 'select_one' or include.prop.type == 'select_multiple' %}
<div class="select">
<select name="{{ prop_name }}" {% if include.prop.type == 'select_multiple' %}multiple{% endif %}>
{% for params in include.prop.parameter %}
<option value="{{ params[0] }}" {% if data[prop_name] == params[0] or data[prop_name] contains params[0] %}selected{% endif %}>
{{ params[1] }}
</option>
{% endfor %}
</select>
</div>
{% else %}
<input
name="{{ prop_name }}"
type="{% if include.prop.type == 'dob' %}date{% else %}text{% endif %}"
class="input"
{% if false and include.prop.type == 'regex' %} pattern="{{ include.prop.parameter }}" {% endif %}
{% if data[prop_name] %} value="{{ data[prop_name] }}" {% endif %}
/>
{% endif %}
</div>
</div>
</div>
9 changes: 8 additions & 1 deletion src/liquid/components/list_cell.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@
<a href="https://{{ session.authInfo.domain }}/#/contacts/{{ include.linkTo.id }}" target="_blank">
{{ include.values[include.property.property_name] }}
</a>
{% elsif include.property.type == 'select_one' %}
<span class="tag is-light">{{ include.property.parameter[include.values[include.property.property_name]] }}</span>
{% elsif include.property.type == 'select_multiple' %}
{% assign values = include.values[include.property.property_name] | split: " " %}
{% for value in values %}
<span class="tag is-light">{{include.property.parameter[value]}}</span>
{% endfor %}
{% else %}
{{ include.values[include.property.property_name] }}
{% endif %}
Expand All @@ -19,4 +26,4 @@
<span class="material-symbols-outlined">cloud_off</span>
{% endif %}
{% endif %}
</td>
</td>
18 changes: 11 additions & 7 deletions test/lib/validation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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', () => {
Expand Down
11 changes: 7 additions & 4 deletions test/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down Expand Up @@ -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 => {
Expand All @@ -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'),
Expand All @@ -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'),
Expand Down Expand Up @@ -95,14 +97,15 @@ 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,
parameter,
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',
Expand Down

0 comments on commit f6370ee

Please sign in to comment.