Skip to content

Commit 186ed8f

Browse files
authored
fix: limit select custom field items to 200 (#3450)
1 parent dd93e20 commit 186ed8f

File tree

7 files changed

+83
-46
lines changed

7 files changed

+83
-46
lines changed

src/app/fyle/add-edit-expense/add-edit-expense.page.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1119,6 +1119,7 @@
11191119
[placeholder]="customInput.placeholder | ellipsis: 30"
11201120
[touchedInParent]="customInput.control.controls.value.touched"
11211121
[validInParent]="customInput.control.controls.value.valid"
1122+
[isCustomSelect]="true"
11221123
>
11231124
</app-fy-select>
11241125
</div>

src/app/fyle/add-edit-mileage/add-edit-mileage.page.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,7 @@
604604
[placeholder]="customInput.placeholder | ellipsis: 30"
605605
[touchedInParent]="customInput.control.controls.value.touched"
606606
[validInParent]="customInput.control.controls.value.valid"
607+
[isCustomSelect]="true"
607608
>
608609
</app-fy-select>
609610
</div>

src/app/fyle/add-edit-per-diem/add-edit-per-diem.page.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,7 @@
593593
[placeholder]="customInput.placeholder | ellipsis: 30"
594594
[touchedInParent]="customInput.control.controls.value.touched"
595595
[validInParent]="customInput.control.controls.value.valid"
596+
[isCustomSelect]="true"
596597
>
597598
</app-fy-select>
598599
</div>

src/app/shared/components/fy-select/fy-select-modal/fy-select-modal.component.spec.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,34 @@ describe('FySelectModalComponent', () => {
8181
tick(1000);
8282
}));
8383

84+
it('ngAfterViewInit(): should update filteredOptions$ if isCustomSelect is true', fakeAsync(() => {
85+
component.currentSelection = 'ECONOMY';
86+
component.nullOption = true;
87+
component.customInput = true;
88+
component.defaultLabelProp = 'economy';
89+
component.isCustomSelect = true;
90+
component.options = [
91+
{ label: 'economy', value: 'ECONOMY' },
92+
{ label: 'business', value: 'BUSINESS' },
93+
];
94+
tick();
95+
component.ngAfterViewInit();
96+
inputElement.value = 'econo';
97+
inputElement.dispatchEvent(new Event('keyup'));
98+
99+
component.filteredOptions$.pipe(take(1)).subscribe((res) => {
100+
expect(res).toEqual([
101+
{ label: 'None', value: null },
102+
{ label: 'econo', value: 'econo', selected: false },
103+
{ label: 'economy', value: 'ECONOMY', selected: true },
104+
]);
105+
expect(component.options).toEqual([
106+
{ label: 'economy', value: 'ECONOMY', selected: true },
107+
{ label: 'business', value: 'BUSINESS', selected: false },
108+
]);
109+
});
110+
}));
111+
84112
it('ngAfterViewInit(): should update filteredOptions$ if currentSelection is not equal to any options value', () => {
85113
component.currentSelection = { travel_class: 'ECONOMY', vendor: 'asdf' };
86114
component.nullOption = true;
@@ -193,7 +221,7 @@ describe('FySelectModalComponent', () => {
193221
});
194222

195223
it('should return recently used items from local storage if not available from API', fakeAsync(() => {
196-
const getSpy = recentLocalStorageItemsService.get.and.returnValue(Promise.resolve(localStorageItems));
224+
const getSpy = recentLocalStorageItemsService.get.and.resolveTo(localStorageItems);
197225
component.options = options;
198226
component.recentlyUsed = null;
199227
component.currentSelection = 'BUSINESS';

src/app/shared/components/fy-select/fy-select-modal/fy-select-modal.component.ts

Lines changed: 30 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,8 @@
1-
import {
2-
Component,
3-
OnInit,
4-
ViewChild,
5-
ElementRef,
6-
AfterViewInit,
7-
Input,
8-
ChangeDetectorRef,
9-
TemplateRef,
10-
} from '@angular/core';
1+
import { Component, ViewChild, ElementRef, AfterViewInit, Input, ChangeDetectorRef, TemplateRef } from '@angular/core';
112
import { from, fromEvent, Observable, of } from 'rxjs';
12-
import { map, startWith, distinctUntilChanged, tap, switchMap, shareReplay } from 'rxjs/operators';
3+
import { map, startWith, distinctUntilChanged, switchMap, shareReplay } from 'rxjs/operators';
134
import { ModalController } from '@ionic/angular';
14-
import { isEqual, includes } from 'lodash';
5+
import { isEqual } from 'lodash';
156
import { RecentLocalStorageItemsService } from 'src/app/core/services/recent-local-storage-items.service';
167
import { UtilityService } from 'src/app/core/services/utility.service';
178
import { ExtendedOption, ModalOption, Option } from './fy-select-modal.interface';
@@ -21,7 +12,7 @@ import { ExtendedOption, ModalOption, Option } from './fy-select-modal.interface
2112
templateUrl: './fy-select-modal.component.html',
2213
styleUrls: ['./fy-select-modal.component.scss'],
2314
})
24-
export class FySelectModalComponent implements OnInit, AfterViewInit {
15+
export class FySelectModalComponent implements AfterViewInit {
2516
@ViewChild('searchBar') searchBarRef: ElementRef;
2617

2718
@Input() options: Option[] = [];
@@ -52,6 +43,8 @@ export class FySelectModalComponent implements OnInit, AfterViewInit {
5243

5344
@Input() label: string;
5445

46+
@Input() isCustomSelect = false;
47+
5548
value: string | ModalOption = '';
5649

5750
recentrecentlyUsedItems$: Observable<ExtendedOption[]>;
@@ -63,16 +56,14 @@ export class FySelectModalComponent implements OnInit, AfterViewInit {
6356
private utilityService: UtilityService
6457
) {}
6558

66-
ngOnInit() {}
67-
68-
clearValue() {
59+
clearValue(): void {
6960
this.value = '';
7061
const searchInput = this.searchBarRef.nativeElement as HTMLInputElement;
7162
searchInput.value = '';
7263
searchInput.dispatchEvent(new Event('keyup'));
7364
}
7465

75-
getRecentlyUsedItems() {
66+
getRecentlyUsedItems(): Observable<Option[]> {
7667
// Check if recently items exists from api and set, else, set the recent items from the localStorage
7768
if (this.recentlyUsed) {
7869
return of(this.recentlyUsed);
@@ -92,14 +83,14 @@ export class FySelectModalComponent implements OnInit, AfterViewInit {
9283
}
9384
}
9485

95-
ngAfterViewInit() {
86+
ngAfterViewInit(): void {
9687
if (this.searchBarRef && this.searchBarRef.nativeElement) {
97-
this.filteredOptions$ = fromEvent(this.searchBarRef.nativeElement, 'keyup').pipe(
98-
map((event: any) => event.srcElement.value),
88+
this.filteredOptions$ = fromEvent(this.searchBarRef.nativeElement as HTMLElement, 'keyup').pipe(
89+
map((event: Event) => (event.target as HTMLInputElement).value),
9990
startWith(''),
10091
distinctUntilChanged(),
10192
map((searchText) => {
102-
const initial = [];
93+
const initial: Option[] = [];
10394

10495
if (this.nullOption && this.currentSelection) {
10596
initial.push({ label: 'None', value: null });
@@ -113,6 +104,7 @@ export class FySelectModalComponent implements OnInit, AfterViewInit {
113104
const selectedOption = this.options.find((option) => isEqual(option.value, this.currentSelection));
114105
if (!selectedOption) {
115106
extraOption = extraOption.concat({
107+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
116108
label: this.currentSelection[this.defaultLabelProp],
117109
value: this.currentSelection,
118110
selected: false,
@@ -129,12 +121,14 @@ export class FySelectModalComponent implements OnInit, AfterViewInit {
129121
option.selected = isEqual(option.value, this.currentSelection);
130122
return option;
131123
})
124+
.sort((a, b) => (this.isCustomSelect ? (a.selected === b.selected ? 0 : a.selected ? -1 : 1) : 0))
125+
.slice(0, this.isCustomSelect ? 200 : this.options.length)
132126
);
133127
}),
134128
shareReplay(1)
135129
);
136-
this.recentrecentlyUsedItems$ = fromEvent(this.searchBarRef.nativeElement, 'keyup').pipe(
137-
map((event: any) => event.srcElement.value),
130+
this.recentrecentlyUsedItems$ = fromEvent(this.searchBarRef.nativeElement as HTMLElement, 'keyup').pipe(
131+
map((event: Event) => (event.target as HTMLInputElement).value),
138132
startWith(''),
139133
distinctUntilChanged(),
140134
switchMap((searchText) =>
@@ -146,41 +140,44 @@ export class FySelectModalComponent implements OnInit, AfterViewInit {
146140
shareReplay(1)
147141
);
148142
} else {
149-
const initial = [];
143+
const initial: Option[] = [];
150144

151145
if (this.nullOption && this.currentSelection) {
152146
initial.push({ label: 'None', value: null });
153147
}
154148

155149
this.filteredOptions$ = of(
156150
initial.concat(
157-
this.options.map((option) => {
158-
option.selected = isEqual(option.value, this.currentSelection);
159-
return option;
160-
})
151+
this.options
152+
.map((option) => {
153+
option.selected = isEqual(option.value, this.currentSelection);
154+
return option;
155+
})
156+
.sort((a, b) => (this.isCustomSelect ? (a.selected === b.selected ? 0 : a.selected ? -1 : 1) : 0))
157+
.slice(0, this.isCustomSelect ? 200 : this.options.length)
161158
)
162159
);
163160
}
164161

165162
this.cdr.detectChanges();
166163
}
167164

168-
onDoneClick() {
165+
onDoneClick(): void {
169166
this.modalController.dismiss();
170167
}
171168

172-
onElementSelect(option: ExtendedOption) {
169+
onElementSelect(option: ExtendedOption): void {
173170
if (this.cacheName && option.value) {
174171
option.custom = !this.options.some((internalOption) => internalOption.value !== option.value);
175172
this.recentLocalStorageItemsService.post(this.cacheName, option, 'label');
176173
}
177174
this.modalController.dismiss(option);
178175
}
179176

180-
saveToCacheAndUse() {
177+
saveToCacheAndUse(): void {
181178
const option: ExtendedOption = {
182-
label: this.searchBarRef.nativeElement.value,
183-
value: this.searchBarRef.nativeElement.value,
179+
label: (this.searchBarRef.nativeElement as HTMLInputElement).value,
180+
value: (this.searchBarRef.nativeElement as HTMLInputElement).value,
184181
selected: false,
185182
};
186183
this.onElementSelect(option);

src/app/shared/components/fy-select/fy-select.component.spec.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,19 +50,19 @@ describe('FySelectComponent', () => {
5050
component.touchedInParent = true;
5151

5252
component.validInParent = false;
53-
expect(component.valid).toBe(false);
53+
expect(component.valid).toBeFalse();
5454

5555
component.validInParent = true;
56-
expect(component.valid).toBe(true);
56+
expect(component.valid).toBeTrue();
5757

5858
component.touchedInParent = false;
5959
// valid should return true in any case
6060

6161
component.validInParent = true;
62-
expect(component.valid).toBe(true);
62+
expect(component.valid).toBeTrue();
6363

6464
component.validInParent = false;
65-
expect(component.valid).toBe(true);
65+
expect(component.valid).toBeTrue();
6666
});
6767

6868
it('value: should return innerValue', () => {
@@ -95,13 +95,15 @@ describe('FySelectComponent', () => {
9595
expect(component.displayValue).toEqual('');
9696
expect(component.onChangeCallback).toHaveBeenCalledOnceWith('');
9797
});
98+
9899
it('should set the value and update the displayValue if innerValue and defaultLabelProps is defined', () => {
99100
component.defaultLabelProp = 'vendor';
100101
component.value = { travelClass: 'BUSINESS', vendor: 'vendor1' };
101102
expect(component.innerValue).toEqual({ travelClass: 'BUSINESS', vendor: 'vendor1' });
102103
expect(component.displayValue).toEqual('vendor1');
103104
expect(component.onChangeCallback).toHaveBeenCalledOnceWith({ travelClass: 'BUSINESS', vendor: 'vendor1' });
104105
});
106+
105107
it('should set the value and update the displayValue if none of the conditions holds true', () => {
106108
component.defaultLabelProp = '';
107109
component.value = { travelClass: 'BUSINESS', vendor: 'vendor1' };
@@ -130,8 +132,8 @@ describe('FySelectComponent', () => {
130132
component.registerOnChange(mockCallback);
131133
component.label = 'Payment mode';
132134
const modalSpy = jasmine.createSpyObj('HTMLIonModalElement', ['present', 'onWillDismiss']);
133-
modalController.create.and.returnValue(Promise.resolve(modalSpy));
134-
modalSpy.onWillDismiss.and.returnValue(Promise.resolve({ data: { value: 'UpdatedValue' } }));
135+
modalController.create.and.resolveTo(modalSpy);
136+
modalSpy.onWillDismiss.and.resolveTo({ data: { value: 'UpdatedValue' } });
135137

136138
modalPropertiesService.getModalDefaultProperties.and.returnValue(mockDefaultProperties);
137139
const {
@@ -171,6 +173,7 @@ describe('FySelectComponent', () => {
171173
defaultLabelProp,
172174
recentlyUsed,
173175
label,
176+
isCustomSelect: undefined,
174177
},
175178
mode: 'ios',
176179
...mockDefaultProperties,
@@ -190,8 +193,8 @@ describe('FySelectComponent', () => {
190193
component.registerOnChange(mockCallback);
191194
component.label = 'Travel Class';
192195
const modalSpy = jasmine.createSpyObj('HTMLIonModalElement', ['present', 'onWillDismiss']);
193-
modalController.create.and.returnValue(Promise.resolve(modalSpy));
194-
modalSpy.onWillDismiss.and.returnValue(Promise.resolve({ data: { value: 'UpdatedValue' } }));
196+
modalController.create.and.resolveTo(modalSpy);
197+
modalSpy.onWillDismiss.and.resolveTo({ data: { value: 'UpdatedValue' } });
195198

196199
modalPropertiesService.getModalDefaultProperties.and.returnValue(mockDefaultProperties);
197200
const {
@@ -231,6 +234,7 @@ describe('FySelectComponent', () => {
231234
defaultLabelProp,
232235
recentlyUsed,
233236
label,
237+
isCustomSelect: undefined,
234238
},
235239
mode: 'ios',
236240
...mockDefaultProperties,
@@ -276,12 +280,14 @@ describe('FySelectComponent', () => {
276280
expect(component.innerValue).toEqual('');
277281
expect(component.displayValue).toEqual('');
278282
});
283+
279284
it('should set the value and update the displayValue if innerValue and defaultLabelProps is defined', () => {
280285
component.defaultLabelProp = 'vendor';
281286
component.writeValue({ travelClass: 'BUSINESS', vendor: 'vendor1' });
282287
expect(component.innerValue).toEqual({ travelClass: 'BUSINESS', vendor: 'vendor1' });
283288
expect(component.displayValue).toEqual('vendor1');
284289
});
290+
285291
it('should set the value and update the displayValue if none of the conditions holds true', () => {
286292
component.defaultLabelProp = '';
287293
component.writeValue({ travelClass: 'BUSINESS', vendor: 'vendor1' });

src/app/shared/components/fy-select/fy-select.component.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,16 @@ export class FySelectComponent implements ControlValueAccessor {
4646

4747
@Input() placeholder: string;
4848

49-
@Input() defaultLabelProp;
49+
@Input() defaultLabelProp?: string;
5050

5151
@Input() recentlyUsed: { label: string; value: any; selected?: boolean }[];
5252

5353
@Input() touchedInParent: boolean;
5454

5555
@Input() validInParent: boolean;
5656

57+
@Input() isCustomSelect?: boolean;
58+
5759
displayValue: string | number | boolean;
5860

5961
innerValue: string | Value;
@@ -64,7 +66,7 @@ export class FySelectComponent implements ControlValueAccessor {
6466

6567
constructor(private modalController: ModalController, private modalProperties: ModalPropertiesService) {}
6668

67-
get valid() {
69+
get valid(): boolean {
6870
if (this.touchedInParent) {
6971
return this.validInParent;
7072
} else {
@@ -123,6 +125,7 @@ export class FySelectComponent implements ControlValueAccessor {
123125
showSaveButton: this.showSaveButton,
124126
defaultLabelProp: this.defaultLabelProp,
125127
recentlyUsed: this.recentlyUsed,
128+
isCustomSelect: this.isCustomSelect,
126129
label: this.label,
127130
},
128131
mode: 'ios',
@@ -131,14 +134,14 @@ export class FySelectComponent implements ControlValueAccessor {
131134

132135
await selectionModal.present();
133136

134-
const { data } = await selectionModal.onWillDismiss();
137+
const { data } = (await selectionModal.onWillDismiss()) as { data: { value: string } };
135138

136139
if (data) {
137140
this.value = data.value;
138141
}
139142
}
140143

141-
onBlur() {
144+
onBlur(): void {
142145
this.onTouchedCallback();
143146
}
144147

0 commit comments

Comments
 (0)