import {async, ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing';
import {Component, ViewChild} from '@angular/core';
import {By} from '@angular/platform-browser';
import {dispatchFakeEvent, dispatchMouseEvent} from '@angular/cdk/testing';
import {Direction, Directionality} from '@angular/cdk/bidi';
import {Subject} from 'rxjs/Subject';
import {MatTabLink, MatTabNav, MatTabsModule} from '../index';


describe('MatTabNavBar', () => {
  let dir: Direction = 'ltr';
  let dirChange = new Subject();

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [MatTabsModule],
      declarations: [
        SimpleTabNavBarTestApp,
        TabLinkWithNgIf,
        TabLinkWithTabIndexBinding,
        TabLinkWithNativeTabindexAttr,
      ],
      providers: [
        {provide: Directionality, useFactory: () => ({
          value: dir,
          change: dirChange.asObservable()
        })},
      ]
    });

    TestBed.compileComponents();
  }));

  describe('basic behavior', () => {
    let fixture: ComponentFixture<SimpleTabNavBarTestApp>;

    beforeEach(() => {
      fixture = TestBed.createComponent(SimpleTabNavBarTestApp);
      fixture.detectChanges();
    });

    it('should change active index on click', () => {
      // select the second link
      let tabLink = fixture.debugElement.queryAll(By.css('a'))[1];
      tabLink.nativeElement.click();
      expect(fixture.componentInstance.activeIndex).toBe(1);

      // select the third link
      tabLink = fixture.debugElement.queryAll(By.css('a'))[2];
      tabLink.nativeElement.click();
      expect(fixture.componentInstance.activeIndex).toBe(2);
    });

    it('should add the active class if active', () => {
      let tabLink1 = fixture.debugElement.queryAll(By.css('a'))[0];
      let tabLink2 = fixture.debugElement.queryAll(By.css('a'))[1];
      const tabLinkElements = fixture.debugElement.queryAll(By.css('a'))
        .map(tabLinkDebugEl => tabLinkDebugEl.nativeElement);

      tabLink1.nativeElement.click();
      fixture.detectChanges();
      expect(tabLinkElements[0].classList.contains('mat-tab-label-active')).toBeTruthy();
      expect(tabLinkElements[1].classList.contains('mat-tab-label-active')).toBeFalsy();

      tabLink2.nativeElement.click();
      fixture.detectChanges();
      expect(tabLinkElements[0].classList.contains('mat-tab-label-active')).toBeFalsy();
      expect(tabLinkElements[1].classList.contains('mat-tab-label-active')).toBeTruthy();

    });

    it('should add the disabled class if disabled', () => {
      const tabLinkElements = fixture.debugElement.queryAll(By.css('a'))
        .map(tabLinkDebugEl => tabLinkDebugEl.nativeElement);

      expect(tabLinkElements.every(tabLinkEl => !tabLinkEl.classList.contains('mat-tab-disabled')))
        .toBe(true, 'Expected every tab link to not have the disabled class initially');

      fixture.componentInstance.disabled = true;
      fixture.detectChanges();

      expect(tabLinkElements.every(tabLinkEl => tabLinkEl.classList.contains('mat-tab-disabled')))
        .toBe(true, 'Expected every tab link to have the disabled class if set through binding');
    });

    it('should update aria-disabled if disabled', () => {
      const tabLinkElements = fixture.debugElement.queryAll(By.css('a'))
        .map(tabLinkDebugEl => tabLinkDebugEl.nativeElement);

      expect(tabLinkElements.every(tabLink => tabLink.getAttribute('aria-disabled') === 'false'))
        .toBe(true, 'Expected aria-disabled to be set to "false" by default.');

      fixture.componentInstance.disabled = true;
      fixture.detectChanges();

      expect(tabLinkElements.every(tabLink => tabLink.getAttribute('aria-disabled') === 'true'))
        .toBe(true, 'Expected aria-disabled to be set to "true" if link is disabled.');
    });

    it('should update the disableRipple property on each tab link', () => {
      const tabLinkElements = fixture.debugElement.queryAll(By.directive(MatTabLink))
        .map(tabLinkDebug => tabLinkDebug.componentInstance) as MatTabLink[];

      expect(tabLinkElements.every(tabLink => !tabLink.disableRipple))
        .toBe(true, 'Expected every tab link to have ripples enabled');

      fixture.componentInstance.disableRipple = true;
      fixture.detectChanges();

      expect(tabLinkElements.every(tabLink => tabLink.disableRipple))
        .toBe(true, 'Expected every tab link to have ripples disabled');
    });

    it('should update the tabindex if links are disabled', () => {
      const tabLinkElements = fixture.debugElement.queryAll(By.css('a'))
        .map(tabLinkDebugEl => tabLinkDebugEl.nativeElement);

      expect(tabLinkElements.every(tabLink => tabLink.tabIndex === 0))
        .toBe(true, 'Expected element to be keyboard focusable by default');

      fixture.componentInstance.disabled = true;
      fixture.detectChanges();

      expect(tabLinkElements.every(tabLink => tabLink.tabIndex === -1))
        .toBe(true, 'Expected element to no longer be keyboard focusable if disabled.');
    });

    it('should show ripples for tab links', () => {
      const tabLink = fixture.debugElement.nativeElement.querySelector('.mat-tab-link');

      dispatchMouseEvent(tabLink, 'mousedown');
      dispatchMouseEvent(tabLink, 'mouseup');

      expect(tabLink.querySelectorAll('.mat-ripple-element').length)
        .toBe(1, 'Expected one ripple to show up if user clicks on tab link.');
    });

    it('should be able to disable ripples on a tab link', () => {
      const tabLinkDebug = fixture.debugElement.query(By.css('a'));
      const tabLinkElement = tabLinkDebug.nativeElement;
      const tabLinkInstance = tabLinkDebug.injector.get(MatTabLink);

      tabLinkInstance.disableRipple = true;

      dispatchMouseEvent(tabLinkElement, 'mousedown');
      dispatchMouseEvent(tabLinkElement, 'mouseup');

      expect(tabLinkElement.querySelectorAll('.mat-ripple-element').length)
        .toBe(0, 'Expected no ripple to show up if ripples are disabled.');
    });

    it('should re-align the ink bar when the direction changes', () => {
      const inkBar = fixture.componentInstance.tabNavBar._inkBar;

      spyOn(inkBar, 'alignToElement');

      dirChange.next();
      fixture.detectChanges();

      expect(inkBar.alignToElement).toHaveBeenCalled();
    });

    it('should re-align the ink bar when the tabs list change', () => {
      const inkBar = fixture.componentInstance.tabNavBar._inkBar;

      spyOn(inkBar, 'alignToElement');

      fixture.componentInstance.tabs = [1, 2, 3, 4];
      fixture.detectChanges();

      expect(inkBar.alignToElement).toHaveBeenCalled();
    });

    it('should re-align the ink bar when the tab labels change the width', done => {
      const inkBar = fixture.componentInstance.tabNavBar._inkBar;

      const spy = spyOn(inkBar, 'alignToElement').and.callFake(() => {
        expect(spy.calls.any()).toBe(true);
        done();
      });

      fixture.componentInstance.label = 'label change';
      fixture.detectChanges();

      expect(spy.calls.any()).toBe(false);
    });

    it('should re-align the ink bar when the window is resized', fakeAsync(() => {
      const inkBar = fixture.componentInstance.tabNavBar._inkBar;

      spyOn(inkBar, 'alignToElement');

      dispatchFakeEvent(window, 'resize');
      tick(150);
      fixture.detectChanges();

      expect(inkBar.alignToElement).toHaveBeenCalled();
    }));
  });

  it('should clean up the ripple event handlers on destroy', () => {
    let fixture: ComponentFixture<TabLinkWithNgIf> = TestBed.createComponent(TabLinkWithNgIf);
    fixture.detectChanges();

    let link = fixture.debugElement.nativeElement.querySelector('.mat-tab-link');

    fixture.componentInstance.isDestroyed = true;
    fixture.detectChanges();

    dispatchMouseEvent(link, 'mousedown');

    expect(link.querySelector('.mat-ripple-element'))
      .toBeFalsy('Expected no ripple to be created when ripple target is destroyed.');
  });

  it('should support the native tabindex attribute', () => {
      const fixture = TestBed.createComponent(TabLinkWithNativeTabindexAttr);
    fixture.detectChanges();

    const tabLink = fixture.debugElement.query(By.directive(MatTabLink)).injector.get(MatTabLink);

    expect(tabLink.tabIndex)
      .toBe(5, 'Expected the tabIndex to be set from the native tabindex attribute.');
  });

  it('should support binding to the tabIndex', () => {
    const fixture = TestBed.createComponent(TabLinkWithTabIndexBinding);
    fixture.detectChanges();

    const tabLink = fixture.debugElement.query(By.directive(MatTabLink)).injector.get(MatTabLink);

    expect(tabLink.tabIndex).toBe(0, 'Expected the tabIndex to be set to 0 by default.');

    fixture.componentInstance.tabIndex = 3;
    fixture.detectChanges();

    expect(tabLink.tabIndex).toBe(3, 'Expected the tabIndex to be have been set to 3.');
  });
});

@Component({
  selector: 'test-app',
  template: `
    <nav mat-tab-nav-bar [disableRipple]="disableRipple">
      <a mat-tab-link
         *ngFor="let tab of tabs; let index = index"
         [active]="activeIndex === index"
         [disabled]="disabled"
         (click)="activeIndex = index">
        Tab link {{label}}
      </a>
    </nav>
  `
})
class SimpleTabNavBarTestApp {
  @ViewChild(MatTabNav) tabNavBar: MatTabNav;

  label = '';
  disabled: boolean = false;
  disableRipple: boolean = false;
  tabs = [0, 1, 2];

  activeIndex = 0;
}

@Component({
  template: `
    <nav mat-tab-nav-bar>
      <a mat-tab-link *ngIf="!isDestroyed">Link</a>
    </nav>
  `
})
class TabLinkWithNgIf {
  isDestroyed = false;
}

@Component({
  template: `
    <nav mat-tab-nav-bar>
      <a mat-tab-link [tabIndex]="tabIndex">TabIndex Link</a>
    </nav>
  `
})
class TabLinkWithTabIndexBinding {
  tabIndex = 0;
}

@Component({
  template: `
    <nav mat-tab-nav-bar>
      <a mat-tab-link tabindex="5">Link</a>
    </nav>
  `
})
class TabLinkWithNativeTabindexAttr {}
