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
2 changes: 1 addition & 1 deletion 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.8",
"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.8",
"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,7 @@
<div class="auto-complete-container">
<input
id="inputLocationSearch"
(keyup)="RemoveInvalidString($event)"
(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,23 @@ 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();
})
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { iif, Observable, of } from 'rxjs';
import { debounceTime, map, switchMap, tap } from 'rxjs/operators';
import {
Expand Down Expand Up @@ -33,12 +33,13 @@ export class SearchLocationComponent implements OnInit {
@Output() public locationSelected = new EventEmitter<LocationByEPIMMSModel>();
@Output() public locationTermSearchInputChanged: EventEmitter<string> = new EventEmitter<string>();
@Output() public searchLocationChanged: EventEmitter<void> = new EventEmitter<void>();
public searchTermFormControl = new FormControl('');
public searchTermFormControl = new FormControl('', Validators.pattern('/[^a-zA-Z -]/'));
public readonly minSearchCharacters = 3;
public term: string = '';
private pReset: boolean = true;
public filteredList$: Observable<LocationByEPIMMSModel[] | boolean>;
private readonly debounceTimeInput = 300;
private binaryValue = '';

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,16 @@ export class SearchLocationComponent implements OnInit {
location => !selectedLocations.map(selectedLocation => selectedLocation.epimms_id).includes(location.epimms_id) && location[this.propertyNameFilter]
);
}

public RemoveInvalidString(formInputValue: any) {
OgunyemiO marked this conversation as resolved.
Show resolved Hide resolved
const element = formInputValue.target as HTMLInputElement;
const newInputValue = element.value;
OgunyemiO marked this conversation as resolved.
Show resolved Hide resolved
if (/[^a-zA-Z -]/.test(newInputValue)) {
element.value = this.binaryValue;
} else {
this.binaryValue = newInputValue;
}
}


}