import {TestBed, inject, fakeAsync, tick} from '@angular/core/testing';
import {ScrollDispatchModule} from './public-api';
import {ViewportRuler, VIEWPORT_RULER_PROVIDER} from './viewport-ruler';
import {dispatchFakeEvent} from '@angular/cdk/testing';


// For all tests, we assume the browser window is 1024x786 (outerWidth x outerHeight).
// The karma config has been set to this for local tests, and it is the default size
// for tests on CI (both SauceLabs and Browserstack).

// While we know the *outer* window width/height, the innerWidth and innerHeight depend on the
// the size of the individual browser's chrome, so we have to use window.innerWidth and
// window.innerHeight in the unit test instead of hard-coded values.

describe('ViewportRuler', () => {
  let ruler: ViewportRuler;

  let startingWindowWidth = window.innerWidth;
  let startingWindowHeight = window.innerHeight;

  // Create a very large element that will make the page scrollable.
  let veryLargeElement: HTMLElement = document.createElement('div');
  veryLargeElement.style.width = '6000px';
  veryLargeElement.style.height = '6000px';

  beforeEach(() => TestBed.configureTestingModule({
    imports: [ScrollDispatchModule],
    providers: [VIEWPORT_RULER_PROVIDER]
  }));

  beforeEach(inject([ViewportRuler], (viewportRuler: ViewportRuler) => {
    ruler = viewportRuler;
    scrollTo(0, 0);
  }));

  afterEach(() => {
    ruler.ngOnDestroy();
  });

  it('should get the viewport size', () => {
    let size = ruler.getViewportSize();
    expect(size.width).toBe(window.innerWidth);
    expect(size.height).toBe(window.innerHeight);
  });

  it('should get the viewport bounds when the page is not scrolled', () => {
    let bounds = ruler.getViewportRect();
    expect(bounds.top).toBe(0);
    expect(bounds.left).toBe(0);
    expect(bounds.bottom).toBe(window.innerHeight);
    expect(bounds.right).toBe(window.innerWidth);
  });

  it('should get the viewport bounds when the page is scrolled', () => {
    document.body.appendChild(veryLargeElement);

    scrollTo(1500, 2000);

    let bounds = ruler.getViewportRect();

    // In the iOS simulator (BrowserStack & SauceLabs), adding the content to the
    // body causes karma's iframe for the test to stretch to fit that content once we attempt to
    // scroll the page. Setting width / height / maxWidth / maxHeight on the iframe does not
    // successfully constrain its size. As such, skip assertions in environments where the
    // window size has changed since the start of the test.
    if (window.innerWidth > startingWindowWidth || window.innerHeight > startingWindowHeight) {
      document.body.removeChild(veryLargeElement);
      return;
    }

    expect(bounds.top).toBe(2000);
    expect(bounds.left).toBe(1500);
    expect(bounds.bottom).toBe(2000 + window.innerHeight);
    expect(bounds.right).toBe(1500 + window.innerWidth);

    document.body.removeChild(veryLargeElement);
  });

  it('should get the bounds based on client coordinates when the page is pinch-zoomed', () => {
    // There is no API to make the browser pinch-zoom, so there's no real way to automate
    // tests for this behavior. Leaving this test here as documentation for the behavior.
  });

  it('should get the scroll position when the page is not scrolled', () => {
    let scrollPos = ruler.getViewportScrollPosition();
    expect(scrollPos.top).toBe(0);
    expect(scrollPos.left).toBe(0);
  });

  it('should get the scroll position when the page is scrolled', () => {
    document.body.appendChild(veryLargeElement);

    scrollTo(1500, 2000);

    // In the iOS simulator (BrowserStack & SauceLabs), adding the content to the
    // body causes karma's iframe for the test to stretch to fit that content once we attempt to
    // scroll the page. Setting width / height / maxWidth / maxHeight on the iframe does not
    // successfully constrain its size. As such, skip assertions in environments where the
    // window size has changed since the start of the test.
    if (window.innerWidth > startingWindowWidth || window.innerHeight > startingWindowHeight) {
      document.body.removeChild(veryLargeElement);
      return;
    }

    let scrollPos = ruler.getViewportScrollPosition();
    expect(scrollPos.top).toBe(2000);
    expect(scrollPos.left).toBe(1500);

    document.body.removeChild(veryLargeElement);
  });

  describe('changed event', () => {
    it('should dispatch an event when the window is resized', () => {
      const spy = jasmine.createSpy('viewport changed spy');
      const subscription = ruler.change(0).subscribe(spy);

      dispatchFakeEvent(window, 'resize');
      expect(spy).toHaveBeenCalled();
      subscription.unsubscribe();
    });

    it('should dispatch an event when the orientation is changed', () => {
      const spy = jasmine.createSpy('viewport changed spy');
      const subscription = ruler.change(0).subscribe(spy);

      dispatchFakeEvent(window, 'orientationchange');
      expect(spy).toHaveBeenCalled();
      subscription.unsubscribe();
    });

    it('should be able to throttle the callback', fakeAsync(() => {
      const spy = jasmine.createSpy('viewport changed spy');
      const subscription = ruler.change(1337).subscribe(spy);

      dispatchFakeEvent(window, 'resize');
      expect(spy).not.toHaveBeenCalled();

      tick(1337);

      expect(spy).toHaveBeenCalledTimes(1);
      subscription.unsubscribe();
    }));
  });
});
