diff --git a/projects/templates/src/lib/components/po-page-dynamic-search/po-page-dynamic-search.component.ts b/projects/templates/src/lib/components/po-page-dynamic-search/po-page-dynamic-search.component.ts index 6154811b9..d29df7df4 100644 --- a/projects/templates/src/lib/components/po-page-dynamic-search/po-page-dynamic-search.component.ts +++ b/projects/templates/src/lib/components/po-page-dynamic-search/po-page-dynamic-search.component.ts @@ -5,7 +5,8 @@ import { OnDestroy, ChangeDetectorRef, AfterViewInit, - SimpleChanges + SimpleChanges, + OnChanges } from '@angular/core'; import { Observable, Subscription } from 'rxjs'; @@ -53,7 +54,7 @@ type UrlOrPoCustomizationFunction = string | (() => PoPageDynamicSearchOptions); }) export class PoPageDynamicSearchComponent extends PoPageDynamicSearchBaseComponent - implements OnInit, OnDestroy, AfterViewInit + implements OnInit, OnDestroy, AfterViewInit, OnChanges { @ViewChild(PoAdvancedFilterComponent, { static: true }) poAdvancedFilter: PoAdvancedFilterComponent; @ViewChild(PoPageListComponent, { static: true }) poPageList: PoPageListComponent; diff --git a/projects/ui/src/lib/components/po-dynamic/po-dynamic-form/po-dynamic-form-field.interface.ts b/projects/ui/src/lib/components/po-dynamic/po-dynamic-form/po-dynamic-form-field.interface.ts index 68d391ca3..1ca8ef673 100644 --- a/projects/ui/src/lib/components/po-dynamic/po-dynamic-form/po-dynamic-form-field.interface.ts +++ b/projects/ui/src/lib/components/po-dynamic/po-dynamic-form/po-dynamic-form-field.interface.ts @@ -781,4 +781,14 @@ export interface PoDynamicFormField extends PoDynamicField { * **Componente compatível**: `po-upload` */ onUpload?: Function; + + /** + * + * Define que o filtro no primeiro clique será removido. + * + * > Caso o combo tenha um valor padrão de inicialização, o primeiro clique + * no componente retornará todos os itens da lista e não apenas o item inicialiazado. + * + */ + removeInitialFilter?: boolean; } diff --git a/projects/ui/src/lib/components/po-dynamic/po-dynamic-form/po-dynamic-form-fields/po-dynamic-form-fields.component.html b/projects/ui/src/lib/components/po-dynamic/po-dynamic-form/po-dynamic-form-fields/po-dynamic-form-fields.component.html index dae2c551b..ea7295e5d 100644 --- a/projects/ui/src/lib/components/po-dynamic/po-dynamic-form/po-dynamic-form-fields/po-dynamic-form-fields.component.html +++ b/projects/ui/src/lib/components/po-dynamic/po-dynamic-form/po-dynamic-form-fields/po-dynamic-form-fields.component.html @@ -275,6 +275,7 @@ [p-disabled-tab-filter]="field.disabledTabFilter" [p-debounce-time]="field.debounceTime" [p-change-on-enter]="field.changeOnEnter" + [p-remove-initial-filter]="field.removeInitialFilter" > diff --git a/projects/ui/src/lib/components/po-field/po-combo/po-combo-base.component.ts b/projects/ui/src/lib/components/po-field/po-combo/po-combo-base.component.ts index b9401e2bb..b1ebed000 100644 --- a/projects/ui/src/lib/components/po-field/po-combo/po-combo-base.component.ts +++ b/projects/ui/src/lib/components/po-field/po-combo/po-combo-base.component.ts @@ -909,6 +909,14 @@ export abstract class PoComboBaseComponent implements ControlValueAccessor, OnIn if (validValue(value) && !this.service && this.comboOptionsList && this.comboOptionsList.length) { const option = this.getOptionFromValue(value, this.comboOptionsList); this.updateSelectedValue(option); + + this.comboOptionsList = this.comboOptionsList.map((option: any) => { + if (this.isEqual(option[this.dynamicValue], value)) { + return { ...option, selected: true }; + } + return option; + }); + this.updateComboList(); this.removeInitialFilter = false; return; diff --git a/projects/ui/src/lib/components/po-field/po-combo/po-combo.component.spec.ts b/projects/ui/src/lib/components/po-field/po-combo/po-combo.component.spec.ts index c8d4953cf..f17628108 100644 --- a/projects/ui/src/lib/components/po-field/po-combo/po-combo.component.spec.ts +++ b/projects/ui/src/lib/components/po-field/po-combo/po-combo.component.spec.ts @@ -171,6 +171,24 @@ describe('PoComboComponent:', () => { expect(fakeThis.applyFilter).toHaveBeenCalled(); }); + it('should reset filter and ensure hasNext is true when isFirstFilter and removeInitialFilter are true', () => { + const fakeThis = { + isFirstFilter: true, + removeInitialFilter: true, + selectedValue: true, + defaultService: component.defaultService, + applyFilter: component.applyFilter, + setScrollingControl: component['setScrollingControl'] + }; + + spyOn(fakeThis, 'applyFilter'); + spyOn(fakeThis, 'setScrollingControl'); + component.applyFilterInFirstClick.call(fakeThis); + + expect(component.options).toEqual([]); + expect(fakeThis.defaultService.hasNext).toBeTruthy(); + }); + it('shouldn`t call applyFilter', () => { const fakeThis = { isFirstFilter: true, @@ -183,6 +201,45 @@ describe('PoComboComponent:', () => { expect(fakeThis.applyFilter).not.toHaveBeenCalled(); }); + it('should update cacheOptions with selected item based on getInputValue', () => { + spyOn(component, 'getInputValue').and.returnValue('Option 2'); + + (component as any).comboOptionsList = [{ label: 'Option 1' }, { label: 'Option 2' }, { label: 'Option 3' }]; + component.updateCacheOptions(); + + expect(component.cacheOptions).toEqual([ + { label: 'Option 1' }, + { label: 'Option 2', selected: true }, + { label: 'Option 3' } + ]); + }); + + it('should not update cacheOptions on subsequent calls when isFirstFilter is false', () => { + const items = [...component['comboOptionsList']]; + const value = 'Option 3'; + + component.isFirstFilter = false; + component.cacheOptions = [{ label: 'Option 1', selected: false }]; + + component.setOptionsByApplyFilter(value, items); + + expect(component.cacheOptions).toEqual([{ label: 'Option 1', selected: false }]); + }); + + it('should call prepareOptions and controlComboVisibility with the correct parameters', () => { + const items = [...component['comboOptionsList']]; + const value = 'Option 1'; + const reset = true; + + spyOn(component as any, 'prepareOptions').and.callThrough(); + spyOn(component, 'controlComboVisibility').and.callThrough(); + + component.setOptionsByApplyFilter(value, items, reset); + + expect(component['prepareOptions']).toHaveBeenCalledWith(items); + expect(component.controlComboVisibility).toHaveBeenCalledWith(true, reset); + }); + it('should show combo and save the cache', () => { component.isFirstFilter = true; component.options = [{ label: '1', value: '1' }]; @@ -1522,37 +1579,64 @@ describe('PoComboComponent - with service:', () => { expect(fakeThis.service.getFilteredData).not.toHaveBeenCalled(); }); - it('applyFilter: should call PoComboFilterService.getFilteredData() with correct parameters when hasNext is true has page and pageSize', () => { + it('applyFilter: should set hasNext true if removeInitialFilter is true', () => { const fakeThis: any = { - controlComboVisibility: () => {}, - setOptionsByApplyFilter: () => {}, + removeInitialFilter: true, + controlComboVisibility: jasmine.createSpy('controlComboVisibility'), + setOptionsByApplyFilter: jasmine.createSpy('setOptionsByApplyFilter'), fieldLabel: 'label', - filterParams: 'filterParams', // Replace with your filterParams value + filterParams: 'filterParams', + isServerSearching: false, service: { - getFilteredData: () => {} + getFilteredData: jasmine.createSpy('getFilteredData').and.returnValue({ + subscribe: (success: Function, error: Function) => success([]) + }) }, defaultService: { - hasNext: true + hasNext: false }, - infiniteScroll: true, - page: 1, - pageSize: 10 + focusItem: jasmine.createSpy('focusItem'), + onErrorFilteredData: jasmine.createSpy('onErrorFilteredData') }; - spyOn(fakeThis.service, 'getFilteredData').and.returnValue(of()); // Using of() to create an empty observable - const applyFilterValue = 'applyFilterValue'; // Replace with your applyFilterValue + const applyFilterValue = 'applyFilterValue'; component.applyFilter.apply(fakeThis, [applyFilterValue]); - const expectedParam = { - property: 'label', - value: applyFilterValue, + expect(fakeThis.service.getFilteredData).toHaveBeenCalledWith( + { property: 'label', value: applyFilterValue }, + 'filterParams' + ); + }); + + it('applyFilter: Should call the service getFilteredData method with the correct parameters when removeInitialFilter is true and infiniteScroll is enabled.', () => { + const fakeThis: any = { + removeInitialFilter: true, + controlComboVisibility: jasmine.createSpy('controlComboVisibility'), + setOptionsByApplyFilter: jasmine.createSpy('setOptionsByApplyFilter'), + infiniteScroll: true, page: 1, - pageSize: 10 + pageSize: 1, + fieldLabel: 'label', + filterParams: 'filterParams', + isServerSearching: false, + service: { + getFilteredData: jasmine.createSpy('getFilteredData').and.returnValue({ + subscribe: (success: Function, error: Function) => success([]) + }) + }, + defaultService: { + hasNext: false + }, + focusItem: jasmine.createSpy('focusItem'), + onErrorFilteredData: jasmine.createSpy('onErrorFilteredData') }; + const applyFilterValue = 'applyFilterValue'; + component.applyFilter.apply(fakeThis, [applyFilterValue]); + expect(fakeThis.service.getFilteredData).toHaveBeenCalledWith( - expectedParam, - 'filterParams' // Replace with your filterParams value + { property: 'label', value: applyFilterValue, page: 1, pageSize: 1 }, + 'filterParams' ); }); diff --git a/projects/ui/src/lib/components/po-field/po-combo/po-combo.component.ts b/projects/ui/src/lib/components/po-field/po-combo/po-combo.component.ts index 49ff698f5..083cddd6b 100644 --- a/projects/ui/src/lib/components/po-field/po-combo/po-combo.component.ts +++ b/projects/ui/src/lib/components/po-field/po-combo/po-combo.component.ts @@ -363,6 +363,10 @@ export class PoComboComponent extends PoComboBaseComponent implements AfterViewI } applyFilter(value: string, reset: boolean = false, isArrowDown?: boolean) { + if (this.removeInitialFilter) { + this.defaultService.hasNext = true; + } + if (this.defaultService.hasNext) { this.controlComboVisibility(false, reset); this.isServerSearching = true; @@ -397,8 +401,7 @@ export class PoComboComponent extends PoComboBaseComponent implements AfterViewI if (this.isFirstFilter) { this.isFirstFilter = !this.isFirstFilter; - - this.cacheOptions = this.comboOptionsList; + this.updateCacheOptions(); } } @@ -456,7 +459,9 @@ export class PoComboComponent extends PoComboBaseComponent implements AfterViewI } applyFilterInFirstClick() { - if (this.isFirstFilter && !this.selectedValue) { + const isEmptyFirstFilter = this.isFirstFilter && !this.selectedValue; + + if (this.removeInitialFilter || isEmptyFirstFilter) { this.options = []; const scrollingControl = this.setScrollingControl(); this.applyFilter('', scrollingControl); @@ -583,6 +588,12 @@ export class PoComboComponent extends PoComboBaseComponent implements AfterViewI this.inputEl.nativeElement.focus(); } + updateCacheOptions(): void { + this.cacheOptions = this.comboOptionsList.map(item => + item.label === this.getInputValue() ? { ...item, selected: true } : item + ); + } + private adjustContainerPosition() { this.controlPosition.adjustPosition(poComboContainerPositionDefault); } diff --git a/projects/ui/src/lib/components/po-listbox/po-item-list/po-item-list.component.spec.ts b/projects/ui/src/lib/components/po-listbox/po-item-list/po-item-list.component.spec.ts index cc2857355..381921264 100644 --- a/projects/ui/src/lib/components/po-listbox/po-item-list/po-item-list.component.spec.ts +++ b/projects/ui/src/lib/components/po-listbox/po-item-list/po-item-list.component.spec.ts @@ -32,6 +32,35 @@ describe('PoItemListComponent', () => { }); describe('Methods:', () => { + describe('shouldUpdateSelected', () => { + it('should return true when searchValue and label from changes exist and have equal values', () => { + const changes = { + searchValue: { currentValue: 'test', previousValue: null, firstChange: true, isFirstChange: () => true }, + label: { currentValue: 'test', previousValue: null, firstChange: true, isFirstChange: () => true } + }; + component.ngOnChanges(changes); + + expect(component['shouldUpdateSelected'](changes)).toBeTrue(); + }); + + it('should return false when searchValue and label from changes exist but have different values', () => { + const changes = { + searchValue: { currentValue: 'test', previousValue: null, firstChange: true, isFirstChange: () => true }, + label: { currentValue: 'different', previousValue: null, firstChange: true, isFirstChange: () => true } + }; + + expect(component['shouldUpdateSelected'](changes)).toBeFalse(); + }); + + it('should return false when searchValue is not present in changes', () => { + const changes = { + label: { currentValue: 'test', previousValue: null, firstChange: true, isFirstChange: () => true } + }; + + expect(component['shouldUpdateSelected'](changes)).toBeFalse(); + }); + }); + describe('onSelectItem:', () => { it('should emit tabsItem when tabHide or disabled is changed', () => { component.isTabs = true; diff --git a/projects/ui/src/lib/components/po-listbox/po-item-list/po-item-list.component.ts b/projects/ui/src/lib/components/po-listbox/po-item-list/po-item-list.component.ts index a3933c2c7..3b2fbbb3e 100644 --- a/projects/ui/src/lib/components/po-listbox/po-item-list/po-item-list.component.ts +++ b/projects/ui/src/lib/components/po-listbox/po-item-list/po-item-list.component.ts @@ -22,7 +22,14 @@ export class PoItemListComponent extends PoItemListBaseComponent implements OnCh super(); } + private shouldUpdateSelected(changes: SimpleChanges): boolean { + return !!(changes.searchValue && changes.label && changes.searchValue.currentValue === changes.label.currentValue); + } + ngOnChanges(changes: SimpleChanges): void { + if (this.shouldUpdateSelected(changes)) { + this.selected = true; + } if (this.isTabs) { if (changes.tabHide?.currentValue || changes.disabled?.currentValue) { this.tabsItem.emit(this.item);