/* FILE TYPE: JEST COMPONENT TEST SAMPLE */

import { Component, DebugElement, Input } from '@angular/core';
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
// Use Typescript path mapping to import test helpers.
import { CoreModule } from '@app/core/core.module';
import { Page } from '@testing';
import { ComponentSuite, ComponentSuiteElements } from '@testing';
import { cold, getTestScheduler } from 'jasmine-marbles';
import { of } from 'rxjs/observable/of';
import { MyAsyncService } from './my-async.service';
import { MyFromModuleService } from './my-from-module.service';
import { MyComponent } from './my.component';
import { MyService } from './my.service';

/* HERE YOUR STUBS AND MOCKS */
/*
  Use stub components for shallow testing.
  Do not use NO_ERRORS_SCHEMA.
*/
@Component({ selector: 'child-comp', template: '' })
class ChildStubComponent {
  @Input() prop: string;
}

const myAsyncService = jest.fn<MyAsyncService>(() => ({
  myAsyncServiceMethod: jest.fn(() => of([]))
}));

const optionsMock = {
  option1: 'option1',
  option2: 'option2',
  option3: 'option3'
};

const myFromModuleServiceMock = jest.fn<MyFromModuleService>(() => ({
  optionsObj: optionsMock,
  promiseMethod: jest.fn(() => Promise.resolve()),
  observableMethod: jest.fn(() => of([])),
  voidMethod: jest.fn()
}));

/* HERE YOUR SUITES (describe) */
describe('MyComponent', () => {
  /* Declare all variables that you need for your specs. */
  let fixture: ComponentFixture<MyComponent>;
  let els: ComponentSuiteElements<MyComponent>;
  let page: Page;
  let myService: MyService;
  // Methods from mocked services.
  let myAsyncServiceMethod: any;
  // Other values.
  let myValue: string;

  beforeEach(async(() => {
    /* Initialized here variables that are not depending on the fixture. */
    myAsyncServiceMethod = new myAsyncService().myAsyncServiceMethod;
    myValue = 'myValue';
    TestBed.configureTestingModule({
      imports: [CoreModule],
      declarations: [MyComponent, ChildStubComponent],
      providers: [MyService, { provide: MyAsyncService, useValue: myAsyncService }]
    })
      .overrideComponent(MyComponent, {
        set: {
          providers: [{ provide: MyFromModuleService, useClass: myFromModuleServiceMock }]
        }
      })
      .compileComponents()
      .then(() => {
        fixture = TestBed.createComponent(MyComponent);
        els = new ComponentSuite<MyComponent>(fixture).elements;
        /* User the Page class (or extend it) to encapsulate component's complexity. */
        page = new Page(component, debugEl, nativeEl);
        /* 
          We can also have non-mocked services here.
          For mocked services we directly test on their mocked methods.
        */
        myService = debugEl.injector.get(MyService);
      });
  }));

  /* HERE YOUR SPECS (it -> expect) */
  it('should create', () => {
    /*
      Detect changes on every spec and not on the beforeEach()
      since you might also want to check some logic on the component's constructor().
    */
    fixture.detectChanges();
    /* We use Jest snapshot testing instead of the usual matcher. */
    expect(fixture).toMatchSnapshot();
  });
  
  /* Shallow test example with ComponentSuite helper class */
  it('should generate as many titles as contents', () => {
    fixture.detectChanges();

    const titles: number = els.host.debugEl.queryAll(By.css('.title')).length;
    const contents: number = els.host.component.contents.toArray().length;

    expect(titles).toEqual(contents);
  });

  /* Work with jasmine marbles to test observables. */
  it('should test with jasmine marbles', () => {
    const q$ = cold('---x|', { x: myValue });
    myAsyncServiceMethod.and.returnValue(q$);

    fixture.detectChanges(); // ngOnInit()
    expect(els.host.nativeEl.textContent).toBe('...');

    getTestScheduler().flush(); // flush the observables

    fixture.detectChanges(); // update view
    expect(els.host.nativeEl.textContent).toBe(myValue);
  });
});