angular/forms';
import { SimpleChange } from '@angular/core';

import { of } from 'rxjs';

import { configureTestSuite } from './../../../../util-test/util-expect.spec';

import { PoDynamicFormFieldsBaseComponent } from './po-dynamic-form-fields-base.component';
import { PoDynamicFormFieldsComponent } from './po-dynamic-form-fields.component';
import { PoDynamicModule } from '../../po-dynamic.module';

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

  let nativeElement: any;

  configureTestSuite(() => {
    TestBed.configureTestingModule({
      imports: [FormsModule, ReactiveFormsModule, PoDynamicModule],
      providers: [{ provide: NgForm, useValue: new NgForm(null, null) }]
    });
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(PoDynamicFormFieldsComponent);
    component = fixture.componentInstance;
    component['form'] = <any>{ touched: true };

    fixture.detectChanges();

    nativeElement = fixture.debugElement.nativeElement;
  });

  it('should be created', () => {
    expect(component).toBeTruthy();
    expect(component instanceof PoDynamicFormFieldsBaseComponent).toBeTruthy();
  });

  describe('Methods:', () => {
    it('ngOnChanges: should call `getVisibileFields` and set `visibileFields` if `changes.fields` is defined', () => {
      const fields = [{ property: 'name' }];
      component.visibleFields = [];

      spyOn(component, <any>'getVisibleFields').and.returnValue(fields);

      component.ngOnChanges(<any>{ fields });

      expect(component['getVisibleFields']).toHaveBeenCalled();
      expect(component.visibleFields).toEqual(fields);
    });

    it('ngOnChanges: shouldn`t call `getVisibileFields` if `changes.fields` is undefined', () => {
      component.visibleFields = [];

      spyOn(component, <any>'getVisibleFields');

      component.ngOnChanges({});

      expect(component['getVisibleFields']).not.toHaveBeenCalled();
      expect(component.visibleFields).toEqual([]);
    });

    it('trackBy: should return index', () => {
      const index = 1;
      expect(component.trackBy(index)).toBe(index);
    });

    it('focus: should call `fieldComponent.focus` if find field by property param.', () => {
      const fieldComponent = { focus: () => {}, name: 'newField' };
      component.components = <any>[fieldComponent];

      const spyOnFocus = spyOn(fieldComponent, 'focus');
      component.focus('newField');

      expect(spyOnFocus).toHaveBeenCalled();
    });

    it('focus: shouldn´t call `fieldsComponent.focus` if not find field by property param.', () => {
      const fieldComponent = { focus: () => {}, name: 'newField' };
      component.components = <any>[fieldComponent];

      const spyOnFocus = spyOn(fieldComponent, 'focus');
      component.focus('otherField');

      expect(spyOnFocus).not.toHaveBeenCalled();
    });

    describe('onChangeField', () => {
      const changedFieldIndex = 0;

      it('should call `getField` with `propertyOfVisibleFields` if field type boolean', () => {
        const fakeVisibleField = { property: 'test1', validate: 'teste', type: 'boolean' };

        component['previousValue']['test1'] = 'value';
        component['value']['test1'] = 'new value';

        const field = { changedField: fakeVisibleField, changedFieldIndex };
        component['form'] = <any>{ touched: false };

        spyOn(component, <any>'getField').and.returnValue(field);
        component.onChangeField(fakeVisibleField);

        expect(component['getField']).toHaveBeenCalledWith(fakeVisibleField.property);
      });

      it(`shouldn't call 'getField' with 'propertyOfVisibleFields' if field type not boolean and 'form.touched' is false`, () => {
        const fakeVisibleField = { property: 'test1', validate: 'teste' };

        component['previousValue']['test1'] = 'value';
        component['value']['test1'] = 'new value';

        const field = { changedField: fakeVisibleField, changedFieldIndex };
        component['form'] = <any>{ touched: false };

        spyOn(component, <any>'getField').and.returnValue(field);
        component.onChangeField(fakeVisibleField);

        expect(component['getField']).not.toHaveBeenCalledWith(fakeVisibleField.property);
      });

      it('should call `getField` with `propertyOfVisibleFields` if field value changed', () => {
        const fakeVisibleField = { property: 'test1', validate: 'teste' };

        component['previousValue']['test1'] = 'value';
        component['value']['test1'] = 'new value';

        const field = { changedField: fakeVisibleField, changedFieldIndex };

        spyOn(component, <any>'getField').and.returnValue(field);
        component.onChangeField(fakeVisibleField);

        expect(component['getField']).toHaveBeenCalledWith(fakeVisibleField.property);
      });

      it('should call `validateField` if `changedField.validate` has value and field value changed', async () => {
        const fakeVisibleField = { property: 'test1', validate: 'teste' };

        const field = { changedField: fakeVisibleField, changedFieldIndex };

        component['previousValue']['test1'] = 'value';
        component['value']['test1'] = 'new value';

        spyOn(component, <any>'getField').and.returnValue(field);
        spyOn(component, <any>'validateField');

        await component.onChangeField(fakeVisibleField);

        expect(component['validateField']).toHaveBeenCalledWith(fakeVisibleField, changedFieldIndex, fakeVisibleField);
      });

      it('should call `triggerValidationOnForm` if field value changed', async () => {
        const fakeVisibleField = { property: 'test1' };

        const field = { changedField: { property: 'test1' }, changedFieldIndex: 0 };

        component['previousValue']['test1'] = 'value';
        component['value']['test1'] = 'new value';

        spyOn(component, <any>'triggerValidationOnForm');
        spyOn(component, <any>'getField').and.returnValue(field);

        await component.onChangeField(fakeVisibleField);

        expect(component['triggerValidationOnForm']).toHaveBeenCalledWith(field.changedFieldIndex);
      });

      it('shouldn`t call `triggerValidationOnForm` and `getField` if field value not changed', async () => {
        const fakeVisibleField = { property: 'test1' };

        component['previousValue']['test1'] = 'value';
        component['value']['test1'] = 'value';

        spyOn(component, <any>'triggerValidationOnForm');
        spyOn(component, <any>'getField');

        await component.onChangeField(fakeVisibleField);

        expect(component['triggerValidationOnForm']).not.toHaveBeenCalled();
        expect(component['getField']).not.toHaveBeenCalled();
      });

      it('should update all previous value with new value', async () => {
        const fakeVisibleField = { property: 'test1' };
        const previousValue = { test1: 'value1' };
        const newValue = { test1: 'new value1', test2: 'value2' };

        component['previousValue'] = previousValue;
        component['value'] = newValue;

        spyOn(component, <any>'triggerValidationOnForm');
        spyOn(component, <any>'getField').and.returnValue({ changedField: {} });
        spyOn(component, 'updatePreviousValue').and.callThrough();

        await component.onChangeField(fakeVisibleField);

        expect(component.updatePreviousValue).toHaveBeenCalled();
        expect(component['previousValue']).toEqual(newValue);
      });

      it('should emit `formValidate` if the changed field is included in `validateFields`', async () => {