Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ex UI 1173 new special character error on location input #441

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
5 changes: 1 addition & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
{
"name": "@hmcts/rpx-xui-common-lib",
"version": "2.0.11",
"engines": {
OgunyemiO marked this conversation as resolved.
Show resolved Hide resolved
"node": ">=18.17.0"
},
"version": "2.0.8-remove-special-characters-allow-space",
"scripts": {
"ng": "ng",
"build:library": "ng build exui-common-lib --configuration production",
Expand Down
2 changes: 1 addition & 1 deletion projects/exui-common-lib/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@hmcts/rpx-xui-common-lib",
"version": "2.0.11",
"version": "2.0.8-remove-special-characters-allow-space",
"peerDependencies": {
"launchdarkly-js-client-sdk": "^2.15.2",
"ngx-pagination": "^3.2.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<div class="auto-complete-container">
<input
id="inputLocationSearch"
#char
(keyup)="removeInvalidString(char.value)"
(input)="onInput()"
[formControl]="searchTermFormControl"
[matAutocomplete]="autoSearchLocation"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,9 +240,9 @@ describe('SearchLocationComponent', () => {
RpxTranslateMockPipe
],
providers: [
{provide: LocationService, useValue: locationServiceMock},
{provide: SessionStorageService, useValue: sessionServiceMock},
{provide: RefDataService, useValue: refDataServiceMock}
{ provide: LocationService, useValue: locationServiceMock },
{ provide: SessionStorageService, useValue: sessionServiceMock },
{ provide: RefDataService, useValue: refDataServiceMock }
],
}).compileComponents();

Expand Down Expand Up @@ -287,16 +287,16 @@ describe('SearchLocationComponent', () => {
describe('lookup by jurisdiction (i.e. servicesField does not exist)', () => {
it('should return false and should not call the api when ' +
'input characters are less then three', fakeAsync(() => {
// @ts-expect-error - private property
const debounceTime = component.debounceTimeInput;
// @ts-expect-error - private property
const debounceTime = component.debounceTimeInput;

component.filteredList$.subscribe((result) => {
expect(result).toBe(false);
});
component.searchTermFormControl.setValue('');
tick(debounceTime);
flush();
}));
component.filteredList$.subscribe((result) => {
expect(result).toBe(false);
});
component.searchTermFormControl.setValue('');
tick(debounceTime);
flush();
}));

it('should call get locations with the correct parameters', () => {
component.serviceIds = 'IA,SSCS';
Expand All @@ -306,22 +306,22 @@ describe('SearchLocationComponent', () => {
// checks that civil added to userLocations as well
component.serviceIds = 'IA,CIVIL';
component.bookingCheck = BookingCheckType.BOOKINGS_AND_BASE;
const bookingsAndBase = [{service: 'IA', locations: [dummyLocations[0]]}, {
const bookingsAndBase = [{ service: 'IA', locations: [dummyLocations[0]] }, {
service: 'CIVIL',
locations: [dummyLocations[1]]
}];
const bookingsAndBaseString = JSON.stringify(bookingsAndBase);
sessionServiceMock.getItem.and.returnValues(bookingsAndBaseString);
component.getLocations('exampleString2');
expect(locationServiceMock.getAllLocations).toHaveBeenCalledWith(
'api/locations/getLocations', 'IA,CIVIL', '', 'exampleString2', [{service: 'IA', locations: [dummyLocations[0]]}, {
'api/locations/getLocations', 'IA,CIVIL', '', 'exampleString2', [{ service: 'IA', locations: [dummyLocations[0]] }, {
service: 'CIVIL',
locations: [dummyLocations[1]]
}]
);
// check user locations filtered for bookable correctly
component.bookingCheck = BookingCheckType.POSSIBLE_BOOKINGS;
const bookableLocations = [{service: 'IA', locations: [dummyLocations[0]]}, {
const bookableLocations = [{ service: 'IA', locations: [dummyLocations[0]] }, {
service: 'CIVIL',
locations: [dummyLocations[1]]
}];
Expand All @@ -345,7 +345,7 @@ describe('SearchLocationComponent', () => {
};
component.form = new FormGroup({
[serviceCodesFormControlName]: new FormControl(
[{key: serviceCodesValues[0], label: 'some label'}, {key: serviceCodesValues[1], label: 'some other label'}]
[{ key: serviceCodesValues[0], label: 'some label' }, { key: serviceCodesValues[1], label: 'some other label' }]
)
});
component.ngOnInit();
Expand All @@ -355,7 +355,7 @@ describe('SearchLocationComponent', () => {
describe('filteredList$', () => {
it('should call getLocationsByServiceCodes as part of the switchMap when subscribing with the service codes from the form', fakeAsync(() => {
component.form.get(serviceCodesFormControlName)
.setValue([{key: serviceCodesValues[0], label: 'some label'}, {
.setValue([{ key: serviceCodesValues[0], label: 'some label' }, {
key: serviceCodesValues[1],
label: 'some other label'
}]);
Expand Down Expand Up @@ -439,15 +439,35 @@ describe('SearchLocationComponent', () => {
// checks that civil added to userLocations as well
component.serviceIds = 'IA,CIVIL';
component.bookingCheck = BookingCheckType.BOOKINGS_AND_BASE;
const emptyLocationString = JSON.stringify([{service: 'IA', locations: []}]);
const emptyLocationString = JSON.stringify([{ service: 'IA', locations: [] }]);
sessionServiceMock.getItem.and.returnValues(emptyLocationString, `["12345"]`, '["CIVIL"]');
component.getLocations('exampleString2');
expect(locationServiceMock.getAllLocations).toHaveBeenCalledWith('api/locations/getLocations', 'IA,CIVIL', '', 'exampleString2', [{service: 'IA', locations: []}]);
expect(locationServiceMock.getAllLocations).toHaveBeenCalledWith('api/locations/getLocations', 'IA,CIVIL', '', 'exampleString2', [{ service: 'IA', locations: [] }]);
// check user locations filtered for bookable correctly
component.bookingCheck = BookingCheckType.POSSIBLE_BOOKINGS;
const bookableLocationString = JSON.stringify([{service: 'IA', locations: [{epimms_id: '12345'}]}, {service: 'CIVIL', locations: [{epimms_id: '32456'}]}]);
const bookableLocationString = JSON.stringify([{ service: 'IA', locations: [{ epimms_id: '12345' }] }, { service: 'CIVIL', locations: [{ epimms_id: '32456' }] }]);
sessionServiceMock.getItem.and.returnValues(bookableLocationString);
component.getLocations('exampleString2');
expect(locationServiceMock.getAllLocations).toHaveBeenCalledWith('api/locations/getLocations', 'IA,CIVIL', '', 'exampleString2', [{service: 'IA', locations: [{epimms_id: '12345'}]}, {service: 'CIVIL', locations: [{epimms_id: '32456'}]}]);
expect(locationServiceMock.getAllLocations).toHaveBeenCalledWith('api/locations/getLocations', 'IA,CIVIL', '', 'exampleString2', [{ service: 'IA', locations: [{ epimms_id: '12345' }] }, { service: 'CIVIL', locations: [{ epimms_id: '32456' }] }]);
});

it('should call RemoveInvalidString on change input', () => {
let spyInvalidString = spyOn(component, 'removeInvalidString');
let input = fixture.debugElement.query(By.css('.govuk-input'));
input.triggerEventHandler('keyup', {});
fixture.detectChanges();
expect(spyInvalidString).toHaveBeenCalled();
});

it('check for invalid regex values', () => {
const value = 'Glasg-0?';
const isCharInValid = component.isValidNameCharacter(value);
expect(isCharInValid).toBeTruthy();
});

it('check for valid regex values', () => {
const value = 'Glasg- \'';
const isCharInValid = component.isValidNameCharacter(value);
expect(isCharInValid).toBeFalsy();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export class SearchLocationComponent implements OnInit {
private pReset: boolean = true;
public filteredList$: Observable<LocationByEPIMMSModel[] | boolean>;
private readonly debounceTimeInput = 300;
public previousValue = '';

public get reset(): boolean {
return this.pReset;
Expand All @@ -51,9 +52,9 @@ export class SearchLocationComponent implements OnInit {
}

constructor(private readonly locationService: LocationService,
private readonly sessionStorageService: SessionStorageService,
private readonly refDataService: RefDataService
) {}
private readonly sessionStorageService: SessionStorageService,
private readonly refDataService: RefDataService
) { }

public ngOnInit(): void {
const searchInputChanges$ = this.searchTermFormControl.valueChanges
Expand All @@ -67,21 +68,21 @@ export class SearchLocationComponent implements OnInit {
switchMap((term: string) => iif(
// Seems more responsive to do length 0 if locationsByServiceCodes are cached
() => (!!term && term.length >= 0),
this.refDataService.getLocationsByServiceCodes(
(this.form.get(this.field.servicesField)?.value as FilterConfigOption[]).map((service) => service.key)
).pipe(
// Filter locations by the search input term and the chosen property name
map((locations) => locations
.filter((location) => location[this.propertyNameFilter].toLowerCase().includes(term.toLowerCase()))),
// Filter out locations that are already selected
map((locations) => this.filterUnselectedLocations(locations, this.selectedLocations, this.singleMode)),
// Filter out duplicate locations (by propertyNameFilter)
map((locations) => locations.filter((location, index, array) =>
index === array.findIndex((item) => item[this.propertyNameFilter] === location[this.propertyNameFilter])
)),
),
// Returns false if the search term is empty to not show the autocomplete field i.e. ngIf should be false
of(false)
this.refDataService.getLocationsByServiceCodes(
(this.form.get(this.field.servicesField)?.value as FilterConfigOption[]).map((service) => service.key)
).pipe(
// Filter locations by the search input term and the chosen property name
map((locations) => locations
.filter((location) => location[this.propertyNameFilter].toLowerCase().includes(term.toLowerCase()))),
// Filter out locations that are already selected
map((locations) => this.filterUnselectedLocations(locations, this.selectedLocations, this.singleMode)),
// Filter out duplicate locations (by propertyNameFilter)
map((locations) => locations.filter((location, index, array) =>
index === array.findIndex((item) => item[this.propertyNameFilter] === location[this.propertyNameFilter])
)),
),
// Returns false if the search term is empty to not show the autocomplete field i.e. ngIf should be false
of(false)
)),
);
} else {
Expand All @@ -100,12 +101,12 @@ export class SearchLocationComponent implements OnInit {

if (this.singleMode && this.selectedLocations.length > 0) {
const location = this.selectedLocations[0];
this.searchTermFormControl.patchValue(location[this.propertyNameFilter], {emitEvent: false, onlySelf: true});
this.searchTermFormControl.patchValue(location[this.propertyNameFilter], { emitEvent: false, onlySelf: true });
}
}

public onSelectedLocation(location: LocationByEPIMMSModel): void {
this.searchTermFormControl.patchValue(location[this.propertyNameFilter], {emitEvent: false, onlySelf: true});
this.searchTermFormControl.patchValue(location[this.propertyNameFilter], { emitEvent: false, onlySelf: true });
this.locationSelected.emit(location);
}
public onInput(): void {
Expand Down Expand Up @@ -147,4 +148,24 @@ export class SearchLocationComponent implements OnInit {
location => !selectedLocations.map(selectedLocation => selectedLocation.epimms_id).includes(location.epimms_id) && location[this.propertyNameFilter]
);
}

public removeInvalidString(formInputValue: string) {
const element = formInputValue;
let newInputValue = element;
if (this.isValidNameCharacter(formInputValue)) {
newInputValue = this.previousValue;
this.searchTermFormControl.setValue(newInputValue);
} else {
this.previousValue = newInputValue
this.searchTermFormControl.patchValue(newInputValue);
}
}

public isValidNameCharacter(value: string): boolean {
let newInputValue = '';
if(value !== undefined) {
newInputValue = value;
}
return (/[^a-zA-Z \s'-]/).test(newInputValue);
}
}
Loading