From d1b8f2dfab942c2a1517b628ab62ccd010fbffb3 Mon Sep 17 00:00:00 2001
From: Tifenn Guillas <tifenn.guillas@gmail.com>
Date: Wed, 6 Oct 2021 14:35:44 +0200
Subject: [PATCH] WIP: Tests on search type components

---
 .../search-type/list.component.spec.ts        |  82 +++++++++++++
 .../criteria/search-type/list.component.ts    |  12 +-
 .../search-type/operator.component.spec.ts    |  36 ++++++
 .../search-type/operator.component.ts         |   8 +-
 .../search-type/radio.component.spec.ts       |  82 +++++++++++++
 .../criteria/search-type/radio.component.ts   |  12 +-
 .../select-multiple.component.spec.ts         |  88 ++++++++++++++
 .../search-type/select-multiple.component.ts  |  14 ++-
 .../search-type/select.component.spec.ts      | 108 ++++++++++++++++++
 .../criteria/search-type/select.component.ts  |  12 +-
 .../search-type/time.component.spec.ts        |  83 ++++++++++----
 .../criteria/search-type/time.component.ts    |   2 +-
 12 files changed, 490 insertions(+), 49 deletions(-)
 create mode 100644 client/src/app/instance/search/components/criteria/search-type/list.component.spec.ts
 create mode 100644 client/src/app/instance/search/components/criteria/search-type/operator.component.spec.ts
 create mode 100644 client/src/app/instance/search/components/criteria/search-type/radio.component.spec.ts
 create mode 100644 client/src/app/instance/search/components/criteria/search-type/select-multiple.component.spec.ts
 create mode 100644 client/src/app/instance/search/components/criteria/search-type/select.component.spec.ts

diff --git a/client/src/app/instance/search/components/criteria/search-type/list.component.spec.ts b/client/src/app/instance/search/components/criteria/search-type/list.component.spec.ts
new file mode 100644
index 00000000..ea009ff9
--- /dev/null
+++ b/client/src/app/instance/search/components/criteria/search-type/list.component.spec.ts
@@ -0,0 +1,82 @@
+import { Component, ViewChild } from '@angular/core';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+
+import { ListComponent } from './list.component';
+import { ListCriterion } from '../../../../store/models/criterion';
+
+describe('[Instance][Search][Component][Criteria][SearchType] ListComponent', () => {
+    @Component({
+        selector: `app-host`,
+        template: `
+            <app-list 
+                    [id]="id"
+                    [label]="label"
+                    [placeholder]="placeholder"
+                    [criterion]="criterion">
+            </app-list>`
+    })
+    class TestHostComponent {
+        @ViewChild(ListComponent, { static: false })
+        public testedComponent: ListComponent;
+        public id: number = undefined;
+        public label: string = undefined;
+        public placeholder: string = undefined;
+        public criterion: ListCriterion = undefined;
+    }
+
+    let testHostComponent: TestHostComponent;
+    let testHostFixture: ComponentFixture<TestHostComponent>;
+    let testedComponent: ListComponent;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            declarations: [
+                ListComponent,
+                TestHostComponent
+            ],
+            imports: [
+                FormsModule,
+                ReactiveFormsModule
+            ]
+        });
+        testHostFixture = TestBed.createComponent(TestHostComponent);
+        testHostComponent = testHostFixture.componentInstance;
+        testHostFixture.detectChanges();
+        testedComponent = testHostComponent.testedComponent;
+    });
+
+    it('should create the component', () => {
+        expect(testedComponent).toBeTruthy();
+    });
+
+    it('should call ngOnChanges and apply changes', () => {
+        const spy = jest.spyOn(testedComponent, 'ngOnChanges');
+        testHostComponent.criterion = { id: testedComponent.id, type: 'list', values: ['1', '2'] } as ListCriterion;
+        testHostFixture.detectChanges();
+        expect(testedComponent.form.controls.list.value).toEqual('1\n2');
+        expect(testedComponent.form.disabled).toBeTruthy();
+        testHostComponent.criterion = undefined;
+        testHostFixture.detectChanges();
+        expect(testedComponent.form.controls.list.value).toBeNull();
+        expect(testedComponent.form.enabled).toBeTruthy();
+        expect(spy).toHaveBeenCalledTimes(2);
+    });
+
+    it('#getPlaceholder() should fill the placeholder if defined', () => {
+        testedComponent.placeholder = 'placeholder';
+        expect(testedComponent.getPlaceholder()).toEqual('placeholder');
+    });
+
+    it('#getPlaceholder() should not fill the placeholder if not defined', () => {
+        expect(testedComponent.getPlaceholder()).toEqual('');
+    });
+
+    it('raises the add criterion event when clicked', () => {
+        testedComponent.id = 1;
+        testedComponent.form.controls.list.setValue('1\n2');
+        const expectedCriterion = { id: testedComponent.id, type: 'list', values: ['1', '2'] } as ListCriterion;
+        testedComponent.addCriterion.subscribe((event: ListCriterion) => expect(event).toEqual(expectedCriterion));
+        testedComponent.emitAdd();
+    });
+});
diff --git a/client/src/app/instance/search/components/criteria/search-type/list.component.ts b/client/src/app/instance/search/components/criteria/search-type/list.component.ts
index 9887535b..ed743f79 100644
--- a/client/src/app/instance/search/components/criteria/search-type/list.component.ts
+++ b/client/src/app/instance/search/components/criteria/search-type/list.component.ts
@@ -12,16 +12,18 @@ import { FormGroup, FormControl, Validators } from '@angular/forms';
 
 import { ListCriterion, Criterion } from 'src/app/instance/store/models';
 
+/**
+ * @class
+ * @classdesc List search type component.
+ *
+ * @implements OnChanges
+ */
 @Component({
     selector: 'app-list',
     templateUrl: 'list.component.html',
     styleUrls: ['operator.component.scss'],
     changeDetection: ChangeDetectionStrategy.OnPush,
 })
-/**
- * @class
- * @classdesc List search type component.
- */
 export class ListComponent implements OnChanges {
     @Input() id: number;
     @Input() label: string;
@@ -34,7 +36,7 @@ export class ListComponent implements OnChanges {
         list: new FormControl('', [Validators.required])
     });
 
-    ngOnChanges(changes: SimpleChanges) {
+    ngOnChanges(changes: SimpleChanges): void {
         if (changes.criterion && changes.criterion.currentValue) {
             const criterion = changes.criterion.currentValue as ListCriterion;
 
diff --git a/client/src/app/instance/search/components/criteria/search-type/operator.component.spec.ts b/client/src/app/instance/search/components/criteria/search-type/operator.component.spec.ts
new file mode 100644
index 00000000..e9eb1da2
--- /dev/null
+++ b/client/src/app/instance/search/components/criteria/search-type/operator.component.spec.ts
@@ -0,0 +1,36 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { OperatorComponent } from './operator.component';
+
+describe('[Instance][Search][Component][Criteria][SearchType] OperatorComponent', () => {
+    let component: OperatorComponent;
+    let fixture: ComponentFixture<OperatorComponent>;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            declarations: [OperatorComponent]
+        });
+        fixture = TestBed.createComponent(OperatorComponent);
+        component = fixture.componentInstance;
+    });
+
+    it('should create the component', () => {
+        expect(component).toBeTruthy();
+    });
+
+    it('raises the changeOperator event when the value change', () => {
+        component.changeOperator.subscribe((event: string) => expect(event).toEqual('eq'));
+        component.emitChange('eq');
+    });
+
+    it('#getLabel() should return the correct operator form label', () => {
+        expect(component.getLabel('eq')).toBe('=');
+        expect(component.getLabel('neq')).toBe('≠');
+        expect(component.getLabel('gt')).toBe('>');
+        expect(component.getLabel('gte')).toBe('>=');
+        expect(component.getLabel('lt')).toBe('<');
+        expect(component.getLabel('lte')).toBe('<=');
+        expect(component.getLabel('lk')).toBe('like');
+        expect(component.getLabel('nlk')).toBe('not like');
+    });
+});
diff --git a/client/src/app/instance/search/components/criteria/search-type/operator.component.ts b/client/src/app/instance/search/components/criteria/search-type/operator.component.ts
index 32b44df3..1d0ac866 100644
--- a/client/src/app/instance/search/components/criteria/search-type/operator.component.ts
+++ b/client/src/app/instance/search/components/criteria/search-type/operator.component.ts
@@ -9,16 +9,16 @@
 
 import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core';
 
+/**
+ * @class
+ * @classdesc Operator component.
+ */
 @Component({
     selector: 'app-operator',
     templateUrl: 'operator.component.html',
     styleUrls: ['operator.component.scss'],
     changeDetection: ChangeDetectionStrategy.OnPush
 })
-/**
- * @class
- * @classdesc Operator component.
- */
 export class OperatorComponent {
     @Input() operator: string;
     @Input() searchType: string;
diff --git a/client/src/app/instance/search/components/criteria/search-type/radio.component.spec.ts b/client/src/app/instance/search/components/criteria/search-type/radio.component.spec.ts
new file mode 100644
index 00000000..c6abb7b3
--- /dev/null
+++ b/client/src/app/instance/search/components/criteria/search-type/radio.component.spec.ts
@@ -0,0 +1,82 @@
+import { Component, Input, ViewChild } from '@angular/core';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+
+import { RadioComponent } from './radio.component';
+import { FieldCriterion } from '../../../../store/models/criterion';
+import { Option } from '../../../../../metamodel/models';
+
+describe('[Instance][Search][Component][Criteria][SearchType] RadioComponent', () => {
+    @Component({
+        selector: `app-host`,
+        template: `
+            <app-radio 
+                    [id]="id"
+                    [label]="label"
+                    [operator]="operator"
+                    [options]="options"
+                    [criterion]="criterion">
+            </app-radio>`
+    })
+    class TestHostComponent {
+        @ViewChild(RadioComponent, { static: false })
+        public testedComponent: RadioComponent;
+        public id: number = undefined;
+        public label: string = undefined;
+        public operator: string = undefined;
+        public options: Option[] = undefined;
+        public criterion: FieldCriterion = undefined;
+    }
+
+    let testHostComponent: TestHostComponent;
+    let testHostFixture: ComponentFixture<TestHostComponent>;
+    let testedComponent: RadioComponent;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            declarations: [
+                RadioComponent,
+                TestHostComponent
+            ],
+            imports: [
+                FormsModule,
+                ReactiveFormsModule
+            ]
+        });
+        testHostFixture = TestBed.createComponent(TestHostComponent);
+        testHostComponent = testHostFixture.componentInstance;
+        testHostFixture.detectChanges();
+        testedComponent = testHostComponent.testedComponent;
+    });
+
+    it('should create the component', () => {
+        expect(testedComponent).toBeTruthy();
+    });
+
+    it('should call ngOnChanges and apply changes', () => {
+        const spy = jest.spyOn(testedComponent, 'ngOnChanges');
+        testHostComponent.criterion = { id: 1, type: 'field', operator: 'eq', value: 'three' } as FieldCriterion;
+        testHostFixture.detectChanges();
+        expect(testedComponent.form.controls.radio.value).toEqual('three');
+        expect(testedComponent.form.disabled).toBeTruthy();
+        testHostComponent.criterion = undefined;
+        testHostFixture.detectChanges();
+        expect(testedComponent.form.controls.radio.value).toBeNull();
+        expect(testedComponent.form.enabled).toBeTruthy();
+        expect(spy).toHaveBeenCalledTimes(2);
+    });
+
+    it('raises the add criterion event when clicked', () => {
+        testedComponent.id = 1;
+        testedComponent.operator = 'eq';
+        testedComponent.form.controls.radio.setValue('three');
+        testedComponent.options = [
+            { label: 'One', value: 'one', display: 1 },
+            { label: 'Two', value: 'two', display: 2 },
+            { label: 'Three', value: 'three', display: 3 }
+        ];
+        const expectedCriterion = { id: testedComponent.id, type: 'field', operator: 'eq', value: 'three' } as FieldCriterion;
+        testedComponent.addCriterion.subscribe((event: FieldCriterion) => expect(event).toEqual(expectedCriterion));
+        testedComponent.emitAdd();
+    });
+});
diff --git a/client/src/app/instance/search/components/criteria/search-type/radio.component.ts b/client/src/app/instance/search/components/criteria/search-type/radio.component.ts
index 8e43ccce..c61794ad 100644
--- a/client/src/app/instance/search/components/criteria/search-type/radio.component.ts
+++ b/client/src/app/instance/search/components/criteria/search-type/radio.component.ts
@@ -13,15 +13,17 @@ import { FormGroup, FormControl, Validators } from '@angular/forms';
 import { Criterion, FieldCriterion } from 'src/app/instance/store/models';
 import { Option } from 'src/app/metamodel/models';
 
+/**
+ * @class
+ * @classdesc Radio search type component.
+ *
+ * @implements OnChanges
+ */
 @Component({
     selector: 'app-radio',
     templateUrl: 'radio.component.html',
     changeDetection: ChangeDetectionStrategy.OnPush
 })
-/**
- * @class
- * @classdesc Radio search type component.
- */
 export class RadioComponent implements OnChanges {
     @Input() id: number;
     @Input() operator: string;
@@ -35,7 +37,7 @@ export class RadioComponent implements OnChanges {
         radio: new FormControl('', [Validators.required])
     });
 
-    ngOnChanges(changes: SimpleChanges) {
+    ngOnChanges(changes: SimpleChanges): void {
         if (changes.criterion && changes.criterion.currentValue) {
             const criterion = this.criterion as FieldCriterion;
 
diff --git a/client/src/app/instance/search/components/criteria/search-type/select-multiple.component.spec.ts b/client/src/app/instance/search/components/criteria/search-type/select-multiple.component.spec.ts
new file mode 100644
index 00000000..b89f4faf
--- /dev/null
+++ b/client/src/app/instance/search/components/criteria/search-type/select-multiple.component.spec.ts
@@ -0,0 +1,88 @@
+import { Component, ViewChild } from '@angular/core';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+
+import { NgSelectModule } from '@ng-select/ng-select';
+
+import { SelectMultipleComponent } from './select-multiple.component';
+import {  SelectMultipleCriterion } from '../../../../store/models/criterion';
+import { Option } from '../../../../../metamodel/models';
+
+describe('[Instance][Search][Component][Criteria][SearchType] SelectMultipleComponent', () => {
+    @Component({
+        selector: `app-host`,
+        template: `
+            <app-select-multiple 
+                    [id]="id"
+                    [label]="label"
+                    [options]="options"
+                    [criterion]="criterion">
+            </app-select-multiple>`
+    })
+    class TestHostComponent {
+        @ViewChild(SelectMultipleComponent, { static: false })
+        public testedComponent: SelectMultipleComponent;
+        public id: number = undefined;
+        public label: string = undefined;
+        public options: Option[] = undefined;
+        public criterion: SelectMultipleCriterion = undefined;
+    }
+
+    let testHostComponent: TestHostComponent;
+    let testHostFixture: ComponentFixture<TestHostComponent>;
+    let testedComponent: SelectMultipleComponent;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            declarations: [
+                SelectMultipleComponent,
+                TestHostComponent
+            ],
+            imports: [
+                NgSelectModule,
+                FormsModule,
+                ReactiveFormsModule
+            ]
+        });
+        testHostFixture = TestBed.createComponent(TestHostComponent);
+        testHostComponent = testHostFixture.componentInstance;
+        testHostFixture.detectChanges();
+        testedComponent = testHostComponent.testedComponent;
+    });
+
+    it('should create the component', () => {
+        expect(testedComponent).toBeTruthy();
+    });
+
+    it('should call ngOnChanges and apply changes', () => {
+        const spy = jest.spyOn(testedComponent, 'ngOnChanges');
+        const options: Option[] = [
+            { label: 'One', value: 'one', display: 1 },
+            { label: 'Two', value: 'two', display: 2 },
+            { label: 'Three', value: 'three', display: 3 }
+        ];
+        testHostComponent.criterion = { id: 1, type: 'multiple', options } as SelectMultipleCriterion;
+        testHostFixture.detectChanges();
+        expect(testedComponent.form.controls.select.value).toEqual(['one', 'two', 'three']);
+        expect(testedComponent.form.disabled).toBeTruthy();
+        testHostComponent.criterion = undefined;
+        testHostFixture.detectChanges();
+        expect(testedComponent.form.controls.select.value).toBeNull();
+        expect(testedComponent.form.enabled).toBeTruthy();
+        expect(spy).toHaveBeenCalledTimes(2);
+    });
+
+    it('raises the add criterion event when clicked', () => {
+        testedComponent.id = 1;
+        testedComponent.options = [
+            { label: 'One', value: 'one', display: 1 },
+            { label: 'Two', value: 'two', display: 2 },
+            { label: 'Three', value: 'three', display: 3 }
+        ];
+        testedComponent.form.controls.select.setValue(['three']);
+        const expectedValue = [{ label: 'Three', value: 'three', display: 3 }];
+        const expectedCriterion = { id: testedComponent.id, type: 'multiple', options: expectedValue } as SelectMultipleCriterion;
+        testedComponent.addCriterion.subscribe((event: SelectMultipleCriterion) => expect(event).toEqual(expectedCriterion));
+        testedComponent.emitAdd();
+    });
+});
diff --git a/client/src/app/instance/search/components/criteria/search-type/select-multiple.component.ts b/client/src/app/instance/search/components/criteria/search-type/select-multiple.component.ts
index 650ac066..630b13a0 100644
--- a/client/src/app/instance/search/components/criteria/search-type/select-multiple.component.ts
+++ b/client/src/app/instance/search/components/criteria/search-type/select-multiple.component.ts
@@ -13,16 +13,18 @@ import { FormGroup, FormControl, Validators } from '@angular/forms';
 import { Criterion, SelectMultipleCriterion } from 'src/app/instance/store/models';
 import { Option } from 'src/app/metamodel/models';
 
+/**
+ * @class
+ * @classdesc Select multiple search type component.
+ *
+ * @implements OnChanges
+ */
 @Component({
     selector: 'app-select-multiple',
     templateUrl: 'select-multiple.component.html',
     styleUrls: ['operator.component.scss'],
     changeDetection: ChangeDetectionStrategy.OnPush
 })
-/**
- * @class
- * @classdesc Select multiple search type component.
- */
 export class SelectMultipleComponent implements OnChanges {
     @Input() id: number;
     @Input() label: string;
@@ -35,7 +37,7 @@ export class SelectMultipleComponent implements OnChanges {
         select: new FormControl('', [Validators.required])
     });
 
-    ngOnChanges(changes: SimpleChanges) {
+    ngOnChanges(changes: SimpleChanges): void {
         if (changes.criterion && changes.criterion.currentValue) {
             const multipleCriterion = this.criterion as SelectMultipleCriterion;
 
@@ -59,7 +61,7 @@ export class SelectMultipleComponent implements OnChanges {
         const values = this.form.value.select as string[];
 
         const options = this.options.filter(option => values.includes(option.value));
-        const ms = {id: this.id, type: 'multiple', options};
+        const ms = { id: this.id, type: 'multiple', options };
         this.addCriterion.emit(ms);
     }
 }
diff --git a/client/src/app/instance/search/components/criteria/search-type/select.component.spec.ts b/client/src/app/instance/search/components/criteria/search-type/select.component.spec.ts
new file mode 100644
index 00000000..b4496d00
--- /dev/null
+++ b/client/src/app/instance/search/components/criteria/search-type/select.component.spec.ts
@@ -0,0 +1,108 @@
+import { Component, Input, ViewChild } from '@angular/core';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+
+import { NgSelectModule } from '@ng-select/ng-select';
+import { TooltipModule } from 'ngx-bootstrap/tooltip';
+
+import { SelectComponent } from './select.component';
+import { FieldCriterion } from '../../../../store/models/criterion';
+import { Option } from '../../../../../metamodel/models';
+
+describe('[Instance][Search][Component][Criteria][SearchType] SelectComponent', () => {
+    @Component({
+        selector: `app-host`,
+        template: `
+            <app-select 
+                    [id]="id"
+                    [label]="label"
+                    [operator]="operator"
+                    [options]="options"
+                    [criterion]="criterion"
+                    [advancedForm]="advancedForm">
+            </app-select>`
+    })
+    class TestHostComponent {
+        @ViewChild(SelectComponent, { static: false })
+        public testedComponent: SelectComponent;
+        public id: number = undefined;
+        public label: string = undefined;
+        public operator: string = undefined;
+        public options: Option[] = undefined;
+        public criterion: FieldCriterion = undefined;
+        public advancedForm: boolean = false;
+    }
+
+    @Component({ selector: 'app-operator', template: '' })
+    class OperatorStubComponent {
+        @Input() operator: string;
+        @Input() searchType: string;
+        @Input() advancedForm: boolean;
+        @Input() disabled: boolean;
+    }
+
+    @Component({ selector: 'app-help-like', template: '' })
+    class HelpLikeStubComponent { }
+
+    let testHostComponent: TestHostComponent;
+    let testHostFixture: ComponentFixture<TestHostComponent>;
+    let testedComponent: SelectComponent;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            declarations: [
+                SelectComponent,
+                TestHostComponent,
+                OperatorStubComponent,
+                HelpLikeStubComponent
+            ],
+            imports: [
+                NgSelectModule,
+                FormsModule,
+                ReactiveFormsModule,
+                TooltipModule.forRoot()
+            ]
+        });
+        testHostFixture = TestBed.createComponent(TestHostComponent);
+        testHostComponent = testHostFixture.componentInstance;
+        testHostFixture.detectChanges();
+        testedComponent = testHostComponent.testedComponent;
+    });
+
+    it('should create the component', () => {
+        expect(testedComponent).toBeTruthy();
+    });
+
+    it('should call ngOnChanges and apply changes', () => {
+        const spy = jest.spyOn(testedComponent, 'ngOnChanges');
+        testHostComponent.criterion = { id: 1, type: 'field', operator: 'eq', value: 'three' } as FieldCriterion;
+        testHostFixture.detectChanges();
+        expect(testedComponent.form.controls.select.value).toEqual('three');
+        expect(testedComponent.form.disabled).toBeTruthy();
+        testHostComponent.criterion = undefined;
+        testHostFixture.detectChanges();
+        expect(testedComponent.form.controls.select.value).toBeNull();
+        expect(testedComponent.form.enabled).toBeTruthy();
+        expect(spy).toHaveBeenCalledTimes(2);
+    });
+
+    it('#changeOperator() should change the operator', () => {
+        expect(testedComponent.operator).toBeUndefined();
+        testedComponent.changeOperator('toto');
+        expect(testedComponent.operator).toBe('toto');
+    });
+
+    it('raises the add criterion event when clicked', () => {
+        testedComponent.id = 1;
+        testedComponent.operator = 'eq';
+        testedComponent.form.controls.select.setValue('three');
+        testedComponent.options = [
+            { label: 'One', value: 'one', display: 1 },
+            { label: 'Two', value: 'two', display: 2 },
+            { label: 'Three', value: 'three', display: 3 }
+        ];
+        const expectedCriterion = { id: testedComponent.id, type: 'field', operator: 'eq', value: 'three' } as FieldCriterion;
+        testedComponent.addCriterion.subscribe((event: FieldCriterion) => expect(event).toEqual(expectedCriterion));
+        testedComponent.emitAdd();
+    });
+});
diff --git a/client/src/app/instance/search/components/criteria/search-type/select.component.ts b/client/src/app/instance/search/components/criteria/search-type/select.component.ts
index 26dda32e..489df7ea 100644
--- a/client/src/app/instance/search/components/criteria/search-type/select.component.ts
+++ b/client/src/app/instance/search/components/criteria/search-type/select.component.ts
@@ -13,15 +13,17 @@ import { FormGroup, FormControl, Validators } from '@angular/forms';
 import { Criterion, FieldCriterion } from 'src/app/instance/store/models';
 import { Option } from 'src/app/metamodel/models';
 
+/**
+ * @class
+ * @classdesc Select search type component.
+ *
+ * @implements OnChanges
+ */
 @Component({
     selector: 'app-select',
     templateUrl: 'select.component.html',
     changeDetection: ChangeDetectionStrategy.OnPush
 })
-/**
- * @class
- * @classdesc Select search type component.
- */
 export class SelectComponent implements OnChanges {
     @Input() id: number;
     @Input() operator: string;
@@ -38,7 +40,7 @@ export class SelectComponent implements OnChanges {
     
     disabledOperator: boolean;
 
-    ngOnChanges(changes: SimpleChanges) {
+    ngOnChanges(changes: SimpleChanges): void {
         if (changes.criterion && changes.criterion.currentValue) {
             this.form.controls.select.setValue(changes.criterion.currentValue.value);
             this.form.disable();
diff --git a/client/src/app/instance/search/components/criteria/search-type/time.component.spec.ts b/client/src/app/instance/search/components/criteria/search-type/time.component.spec.ts
index d5f3df13..4b627ac6 100644
--- a/client/src/app/instance/search/components/criteria/search-type/time.component.spec.ts
+++ b/client/src/app/instance/search/components/criteria/search-type/time.component.spec.ts
@@ -1,16 +1,34 @@
-import { Component, Input } from '@angular/core';
+import { Component, Input, ViewChild } from '@angular/core';
 import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
-import { FormsModule, ReactiveFormsModule, FormControl } from '@angular/forms';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
 
-
-import { AccordionModule } from 'ngx-bootstrap/accordion';
+import { NgSelectModule } from '@ng-select/ng-select';
 
 import { TimeComponent } from './time.component';
-import { NgSelectModule } from '@ng-select/ng-select';
 import { FieldCriterion } from '../../../../store/models/criterion';
 
 describe('[Instance][Search][Component][Criteria][SearchType] TimeComponent', () => {
+    @Component({
+        selector: `app-host`,
+        template: `
+            <app-time 
+                    [id]="id"
+                    [label]="label"
+                    [operator]="operator"
+                    [criterion]="criterion"
+                    [advancedForm]="advancedForm">
+            </app-time>`
+    })
+    class TestHostComponent {
+        @ViewChild(TimeComponent, { static: false })
+        public testedComponent: TimeComponent;
+        public id: number = undefined;
+        public label: string = undefined;
+        public operator: string = undefined;
+        public criterion: FieldCriterion = undefined;
+        public advancedForm: boolean = false;
+    }
+
     @Component({ selector: 'app-operator', template: '' })
     class OperatorStubComponent {
         @Input() operator: string;
@@ -19,13 +37,15 @@ describe('[Instance][Search][Component][Criteria][SearchType] TimeComponent', ()
         @Input() disabled: boolean;
     }
 
-    let component: TimeComponent;
-    let fixture: ComponentFixture<TimeComponent>;
+    let testHostComponent: TestHostComponent;
+    let testHostFixture: ComponentFixture<TestHostComponent>;
+    let testedComponent: TimeComponent;
 
     beforeEach(() => {
         TestBed.configureTestingModule({
             declarations: [
                 TimeComponent,
+                TestHostComponent,
                 OperatorStubComponent
             ],
             imports: [
@@ -34,34 +54,51 @@ describe('[Instance][Search][Component][Criteria][SearchType] TimeComponent', ()
                 ReactiveFormsModule
             ]
         });
-        fixture = TestBed.createComponent(TimeComponent);
-        component = fixture.componentInstance;
+        testHostFixture = TestBed.createComponent(TestHostComponent);
+        testHostComponent = testHostFixture.componentInstance;
+        testHostFixture.detectChanges();
+        testedComponent = testHostComponent.testedComponent;
     });
 
     it('should create the component', () => {
-        expect(component).toBeTruthy();
+        expect(testedComponent).toBeTruthy();
+    });
+
+    it('should call ngOnChanges and apply changes', () => {
+        const spy = jest.spyOn(testedComponent, 'ngOnChanges');
+        testHostComponent.criterion = { id: 1, type: 'field', operator: 'eq', value: '15:47' } as FieldCriterion;
+        testHostFixture.detectChanges();
+        expect(testedComponent.form.controls.hh.value).toEqual('15');
+        expect(testedComponent.form.controls.mm.value).toEqual('47');
+        expect(testedComponent.form.disabled).toBeTruthy();
+        testHostComponent.criterion = undefined;
+        testHostFixture.detectChanges();
+        expect(testedComponent.form.controls.hh.value).toBeNull();
+        expect(testedComponent.form.controls.hh.value).toBeNull();
+        expect(testedComponent.form.enabled).toBeTruthy();
+        expect(spy).toHaveBeenCalledTimes(2);
     });
 
     it('#changeOperator() should change the operator', () => {
-        expect(component.operator).toBeUndefined();
-        component.changeOperator('toto');
-        expect(component.operator).toBe('toto');
+        expect(testedComponent.operator).toBeUndefined();
+        testedComponent.changeOperator('toto');
+        expect(testedComponent.operator).toBe('toto');
     });
 
     it('raises the add criterion event when clicked', () => {
-        component.id = 1;
+        testedComponent.id = 1;
         const operator = 'eq';
-        component.operator = operator;
-        component.form.controls.hh.setValue('15');
-        component.form.controls.mm.setValue('47');
-        const expectedCriterion = { id: component.id, type: 'field', operator, value: '15:47' } as FieldCriterion;
-        component.addCriterion.subscribe((event: FieldCriterion) => expect(event).toEqual(expectedCriterion));
-        component.emitAdd();
+        testedComponent.operator = operator;
+        testedComponent.form.controls.hh.setValue('15');
+        testedComponent.form.controls.mm.setValue('47');
+        const expectedCriterion = { id: testedComponent.id, type: 'field', operator, value: '15:47' } as FieldCriterion;
+        testedComponent.addCriterion.subscribe((event: FieldCriterion) => expect(event).toEqual(expectedCriterion));
+        testedComponent.emitAdd();
     });
 
     it('#initTime(t) should return an array of string with 2 digits from 0 to t', () => {
         const n = 10;
-        expect(component.initTime(n).length).toEqual(n);
-        expect(component.initTime(n)[5]).toEqual('05');
+        expect(testedComponent.initTime(n).length).toEqual(n);
+        expect(testedComponent.initTime(n)[5]).toEqual('05');
     });
 });
diff --git a/client/src/app/instance/search/components/criteria/search-type/time.component.ts b/client/src/app/instance/search/components/criteria/search-type/time.component.ts
index 9205c7a6..8c4a4e59 100644
--- a/client/src/app/instance/search/components/criteria/search-type/time.component.ts
+++ b/client/src/app/instance/search/components/criteria/search-type/time.component.ts
@@ -43,7 +43,7 @@ export class TimeComponent implements OnChanges {
         mm: new FormControl('', [Validators.required])
     });
 
-    ngOnChanges(changes: SimpleChanges) {
+    ngOnChanges(changes: SimpleChanges): void {
         if (changes.criterion && changes.criterion.currentValue) {
             const criterion = changes.criterion.currentValue as FieldCriterion;
 
-- 
GitLab