Skip to content

Commit

Permalink
Merge pull request #856 from aneoconsulting/autocomplete-filters-inputs
Browse files Browse the repository at this point in the history
feat: autocomplete filters inputs
  • Loading branch information
ngruelaneo authored Jan 8, 2024
2 parents e6f2dce + 5df35b2 commit be71b69
Show file tree
Hide file tree
Showing 12 changed files with 377 additions and 111 deletions.
5 changes: 5 additions & 0 deletions src/app/applications/services/applications-filters.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,9 @@ export class ApplicationsFiltersService {
throw new Error(`Unknown filter type: ${filterFor} ${filterField}}`);
}
}

retrieveField(filterField: string): ApplicationFilterField {
const values = Object.values(this.#rootField);
return values.findIndex(value => value.toLowerCase() === filterField.toLowerCase());
}
}
16 changes: 5 additions & 11 deletions src/app/components/filters/filters-dialog-and.component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,33 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TestBed } from '@angular/core/testing';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { DATA_FILTERS_SERVICE } from '@app/tokens/filters.token';
import { IconsService } from '@services/icons.service';
import { FiltersDialogAndComponent } from './filters-dialog-and.component';

describe('FiltersDialogAndComponent', () => {
let component: FiltersDialogAndComponent<number, number>;
let fixture: ComponentFixture<FiltersDialogAndComponent<number, number>>;
let removeChangeSpy: jest.SpyInstance;

beforeEach(async () => {
await TestBed.configureTestingModule({
component = TestBed.configureTestingModule({
imports: [BrowserAnimationsModule],
providers: [
FiltersDialogAndComponent,
IconsService,
{ provide: DATA_FILTERS_SERVICE, useValue: {
retrieveFiltersDefinitions: jest.fn(() => {
return [];
})
}),
retrieveLabel: jest.fn(),
}}
]
}).compileComponents();
});

beforeEach(() => {
fixture = TestBed.createComponent(FiltersDialogAndComponent<number, number>);
component = fixture.componentInstance;
}).inject(FiltersDialogAndComponent<number, number>);
component.filter = {
field: 1,
for: 'root',
operator: 0,
value: 1
};
fixture.detectChanges();
removeChangeSpy = jest.spyOn(component.removeChange, 'emit');
});

Expand Down
210 changes: 154 additions & 56 deletions src/app/components/filters/filters-dialog-filter-field.component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,44 @@
import { KeyValue } from '@angular/common';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TestBed } from '@angular/core/testing';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { DATA_FILTERS_SERVICE } from '@app/tokens/filters.token';
import { FilterDefinition } from '@app/types/filter-definition';
import { Filter, FilterInputOutput } from '@app/types/filters';
import { Filter, FilterInputOutput, FilterValueOptions } from '@app/types/filters';
import { FiltersService } from '@services/filters.service';
import { FiltersDialogFilterFieldComponent } from './filters-dialog-filter-field.component';

describe('FiltersDialogFilterFieldComponent', () => {
let component: FiltersDialogFilterFieldComponent<number, number>;
let fixture: ComponentFixture<FiltersDialogFilterFieldComponent<number, number>>;

const mockDataFiltersService = {
retrieveFiltersDefinitions: jest.fn(() => {
return filterDefinitions;
}),
retrieveLabel: jest.fn()
retrieveLabel: jest.fn((value: string) => {
const labels = Object.values(propertiesLabel);
return labels.map(label => label.toLowerCase() === value.toLowerCase());
}),
retrieveField: jest.fn((value: string) => {
const values = Object.values(propertiesLabel);
return values.findIndex(label => label.toLowerCase() === value.toLowerCase());
})
};
const allStatuses = [
{key: 0, value: 'Creation'},
{key: 1, value: 'Submitted'},
{key: 2, value: 'Ended'}
];

const propertiesLabel: {[key: number]: string} = {
0: 'undefined',
1:'status',
2:'task id',
3:'children',
4:'size',
5:'-',
6:'Start to End duration',
7:'Created at',
};

const filterDefinitions: FilterDefinition<number, number>[] = [
{
field: 4,
Expand All @@ -26,10 +49,7 @@ describe('FiltersDialogFilterFieldComponent', () => {
field: 1,
type: 'status',
for: 'root',
statuses: [
{key: 'status1', value: '2'},
{key: 'status2', value: '2'}
]
statuses: allStatuses
},
{
field: 2,
Expand Down Expand Up @@ -59,35 +79,136 @@ describe('FiltersDialogFilterFieldComponent', () => {
];

beforeEach(async () => {
await TestBed.configureTestingModule({
component = TestBed.configureTestingModule({
imports: [ BrowserAnimationsModule ],
providers: [
FiltersDialogFilterFieldComponent,
FiltersService,
{ provide: DATA_FILTERS_SERVICE, useValue: mockDataFiltersService }
]
}).compileComponents();
});
}).inject(FiltersDialogFilterFieldComponent);

beforeEach(() => {
fixture = TestBed.createComponent(FiltersDialogFilterFieldComponent<number, number>);
component = fixture.componentInstance;

component.filter = {
field: 1,
for: 'root',
operator: 0,
value: 'someValue'
};
component.first = true;

fixture.detectChanges();
component.ngOnInit();
});

it('should run', () => {
expect(component).toBeTruthy();
});

describe('ngOnInit', () => {
describe('filteredProperties', () => {
it('should filter properly', () => {
component.filteredProperties.subscribe(value => {
expect(value).toEqual(['status', 'Start to End duration', 'Created at']);
});
component.propertyFormControl.setValue('at');
component.propertyFormControl.updateValueAndValidity({emitEvent: true});
});

it('should return all the list in case of null value', () => {
component.filteredProperties.subscribe(value => {
expect(value).toEqual(Object.values(propertiesLabel));
});
component.propertyFormControl.setValue(null);
component.propertyFormControl.updateValueAndValidity({emitEvent: true});
});
});

describe('filteredOperators', () => {
it('should filter properly', () => {
component.filteredOperators.subscribe(value => {
expect(value).toEqual(['equal', 'not equal']);
});
component.propertyFormControl.setValue('equal');
component.propertyFormControl.updateValueAndValidity({emitEvent: true});
});

it('should return all the list in case of null value', () => {
component.filteredOperators.subscribe(value => {
expect(value).toEqual(['equal', 'not equal']);
});
component.propertyFormControl.setValue(null);
component.propertyFormControl.updateValueAndValidity({emitEvent: true});
});
});

describe('filteredStatuses', () => {
it('should filter properly', () => {
component.filteredStatuses.subscribe(value => {
expect(value).toEqual(['sumbitted', 'Ended']);
});
component.propertyFormControl.setValue('ed');
component.propertyFormControl.updateValueAndValidity({emitEvent: true});
});

it('should return all the list in case of null value', () => {
component.filteredStatuses.subscribe(value => {
expect(value).toEqual(Object.values(allStatuses));
});
component.propertyFormControl.setValue(null);
component.propertyFormControl.updateValueAndValidity({emitEvent: true});
});
});
});

describe('retrieveStatusKey', () => {
it('should retrieve the key of the status', () => {
expect(component.retrieveStatusKey(allStatuses[0].value)).toEqual(0);
});

it('should return null if no label is provided', () => {
expect(component.retrieveStatusKey(null)).toEqual(null);
});

it('should return null if the label does not exists', () => {
expect(component.retrieveStatusKey('Unexisting')).toEqual(null);
});
});

describe('retrieveStatusLabel', () => {
it('should retrieve status label', () => {
expect(component.retrieveStatusLabel(1)).toEqual('Submitted');
});
it('should retrieve empty status label when no status is found', () => {
expect(component.retrieveStatusLabel(null)).toEqual('');
});
it('should return empty string if allStatus is undefined', () => {
component.allStatuses = undefined as unknown as FilterValueOptions;
expect(component.retrieveStatusLabel(1)).toEqual('');
});
});

describe('onPropertyChange', () => {
it('should update filter', () => {
component.propertyFormControl.setValue('Created at');
component.onPropertyChange();
expect(component.filter).toEqual({
for: 'root',
field: 7,
operator: null,
value: null
});
});

it('should not update anything if the field does not exists', () => {
component.propertyFormControl.setValue('Unexisting');
component.onPropertyChange();
expect(component.filter).toEqual({
field: 1,
for: 'root',
operator: 0,
value: 'someValue'
});
});
});

//TODO: security type check
it('should retrieve the label', () => {
component.retrieveLabel(filterDefinitions[0]);
Expand All @@ -97,21 +218,11 @@ describe('FiltersDialogFilterFieldComponent', () => {
);
});

//TODO: security type check
it('should change the component filter on field change', () => {
component.onFieldChange('options-2');
expect(component.filter).toEqual({
field: 2,
for: 'options',
operator: 0,
value: 'someValue'
});
});

//TODO: security type check
it('should change the operator of the filter', () => {
component.onOperatorChange('3');
expect(component.filter.operator).toEqual(3);
component.operatorFormControl.setValue('Equal');
component.onOperatorChange();
expect(component.filter.operator).toEqual(0);
});

//TODO: security type check
Expand Down Expand Up @@ -160,6 +271,15 @@ describe('FiltersDialogFilterFieldComponent', () => {
component.onInputChange(inputEvent);
expect(component.filter.value).toEqual(94350);
});

it('should change the filter value to status if one status is passed', () => {
const inputEvent: FilterInputOutput = {
type: 'status',
value: 'Submitted'
};
component.onInputChange(inputEvent);
expect(component.filter.value).toEqual(1);
});
});

describe('findInput', () => {
Expand Down Expand Up @@ -265,10 +385,7 @@ describe('FiltersDialogFilterFieldComponent', () => {
expect(component.findInput(statusFilter)).toEqual({
type: 'status',
value: 'myStatus',
statuses: [
{key: 'status1', value: '2'},
{key: 'status2', value: '2'}
]
statuses: allStatuses
});
});

Expand All @@ -283,10 +400,7 @@ describe('FiltersDialogFilterFieldComponent', () => {
expect(component.findInput(statusFilter)).toEqual({
type: 'status',
value: null,
statuses: [
{key: 'status1', value: '2'},
{key: 'status2', value: '2'}
]
statuses: allStatuses
});
});
});
Expand Down Expand Up @@ -400,10 +514,7 @@ describe('FiltersDialogFilterFieldComponent', () => {
operator: 1,
value: 'myStatus'
};
expect(component.findStatuses(statusFilter)).toEqual([
{key: 'status1', value: '2'},
{key: 'status2', value: '2'}
]);
expect(component.findStatuses(statusFilter)).toEqual(allStatuses);
});

it('should return an empty status list if the filter has no field', () => {
Expand Down Expand Up @@ -446,17 +557,4 @@ describe('FiltersDialogFilterFieldComponent', () => {
};
expect(component.findOperator(filter)).toEqual(new FiltersService()['filterNumberOperators']);
});

it('should track by field', () => {
expect(component.trackByField(0, filterDefinitions[0])).toEqual('root4');
});

it('should track by operator', () => {
const operator: KeyValue<string, string> = {
key: 'greater than',
value: '0'
};

expect(component.trackByOperator(0, operator)).toBe(operator.key);
});
});
Loading

1 comment on commit be71b69

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lines Statements Branches Functions
Coverage: 92%
92.27% (2472/2679) 77.44% (467/603) 85.84% (643/749)

JUnit

Tests Skipped Failures Errors Time
793 0 💤 0 ❌ 0 🔥 53.646s ⏱️
Files coverage (92%)
File% Stmts% Branch% Funcs% LinesUncovered Line #s
All files92.2777.4485.8492.2 
applications100100100100 
   index.component.ts100100100100 
applications/components96.3890.910097.53 
   table.component.ts96.3890.910097.53223, 237
applications/services93.0580.9584.6194.11 
   applications-filters.service.ts86.9510071.4290.4778–79
   applications-grpc.service.ts10066.6610010068–69
   applications-index.service.ts93.18086.6692.8571–75
components96.7584.7291.5397.09 
   actions-toolbar-group.component.ts100100100100 
   actions-toolbar.component.ts100100100100 
   auto-refresh-button.component.ts1006010010041, 64
   auto-refresh-dialog.component.ts100100100100 
   columns-button.component.ts100100100100 
   columns-modify-dialog.component.ts94.599089.4710092
   count-tasks-by-status.component.ts1005010010048
   page-header.component.ts8010008039
   page-section-header.component.ts8010008031
   page-section.component.ts100100100100 
   refresh-button.component.ts100100100100 
   share-url.component.ts92.851007592.337
   show-actions.component.ts86.36077.2786.04135–136, 206–221
   show-card-content.component.ts100100100100 
   show-card.component.ts7510007537–38
   show-page.component.ts100100100100 
   spinner.component.ts100100100100 
   table-actions-toolbar.component.ts100100100100 
   table-container.component.ts100100100100 
   view-tasks-by-status-dialog.component.ts100100100100 
   view-tasks-by-status.component.ts100100100100 
components/filters97.9486.610097.84 
   filters-chips.component.ts100100100100 
   filters-dialog-and.component.ts100100100100 
   filters-dialog-filter-field.component.ts96.0685.2410095.72125, 135, 145, 201, 320
   filters-dialog-input.component.ts97.4381.8110097.36158
   filters-dialog-or.component.ts100100100100 
   filters-dialog.component.ts100100100100 
   filters-toolbar.component.ts100100100100 
components/navigation98.4210092.8598.38 
   add-external-service-dialog.component.ts100100100100 
   edit-external-service-dialog.component.ts100100100100 
   form-external-service.component.ts100100100100 
   manage-external-services-dialog.component.ts100100100100 
   navigation.component.ts95.3810076.9295.23232, 263, 292
   theme-selector.component.ts100100100100 
components/table58.620057.14 
   table-empty-data.component.ts100100100100 
   table-inspect-object-dialog.component.ts41.66100041.6636–50
   table-inspect-object.component.ts64.280064.2838–55
dashboard97.5371.4210097.5 
   index.component.ts97.5371.4210097.5212, 250
dashboard/components99.0998.0797.7799.07 
   add-line-dialog.component.ts100100100100 
   add-statuses-group-dialog.component.ts100100100100 
   edit-name-line-dialog.component.ts100100100100 
   edit-status-group-dialog.component.ts100100100100 
   form-name-line.component.ts95.6510083.3395.65115
   form-statuses-group.component.ts100100100100 
   line.component.ts98.885.719598.78272
   manage-groups-dialog.component.ts100100100100 
   reorganize-lines-dialog.component.ts100100100100 
   split-lines-dialog.component.ts100100100100 
   statuses-group-card.component.ts97.9110010097.72165
dashboard/services100100100100 
   dashboard-index.service.ts100100100100 
   dashboard-storage.service.ts100100100100 
directives50100050 
   no-wrap.directive.ts501000505–6
healthcheck100100100100 
   index.component.ts100100100100 
healthcheck/services100100100100 
   healthcheck-grpc.service.ts100100100100 
   healthcheck-index.service.ts100100100100 
partitions/components97.481.8110097.36 
   table.component.ts97.481.8110097.36223, 255
partitions/services15.620012.9 
   partitions-index.service.ts15.620012.99–119
pipes250018.18 
   duration.pipe.ts16.660011.767–27
   empty-cell.pipe.ts5000406–10
results/components100100100100 
   table.component.ts100100100100 
results/services30.76011.1127.02 
   results-index.service.ts18.180015.629–118
   results-statuses.service.ts100100100100 
services98.3694.3995.7898.24 
   auto-refresh.service.ts100100100100 
   default-config.service.ts10050100100193
   environment.service.ts80100507519
   filters.service.ts100100100100 
   icons.service.ts100100100100 
   navigation.service.ts10080100100116
   notification.service.ts100100100100 
   query-params.service.ts100100100100 
   share-url.service.ts100100100100 
   storage.service.ts98.0310010097.9595
   table-storage.service.ts500042.8511–31
   table-url.service.ts100100100100 
   table.service.ts100100100100 
   tasks-by-status.service.ts100100100100 
   user-grpc.service.ts100100100100 
   user.service.ts100100100100 
   utils.service.ts100100100100 
   versions-grpc.service.ts100100100100 
   versions.service.ts1007010010014, 25, 32
sessions/components96.6210093.196.62 
   table.component.ts96.6210093.196.62244, 366–367
sessions/services44.7311.5323.5243.05 
   sessions-filters.service.ts85.18607588145–151
   sessions-index.service.ts11.62009.529–166
   sessions-statuses.service.ts100100100100 
tasks/components97.0810093.9397.08 
   table.component.ts97.0810093.9397.08269, 400–401
tasks/services72.7252.547572.38 
   tasks-filters.service.ts88.881007592197–198
   tasks-grpc.service.ts20.680017.8512–150
   tasks-index.service.ts91.4889.6588.8891.3117, 183–188
   tasks-statuses.service.ts100100100100 
tokens100100100100 
   filters.token.ts100100100100 
types100100100100 
   filter-definition.ts100100100100 

Please sign in to comment.