text: // Copyright (c) 2017 Uber Technologies, Inc.
  //
  // Licensed under the Apache License, Version 2.0 (the "License");
  // you may not use this file except in compliance with the License.
  // You may obtain a copy of the License at
  //
  // http://www.apache.org/licenses/LICENSE-2.0
  //
  // Unless required by applicable law or agreed to in writing, software
  // distributed under the License is distributed on an "AS IS" BASIS,
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  // See the License for the specific language governing permissions and
  // limitations under the License.
  
  import DraggableManager from './DraggableManager';
  import EUpdateTypes from './EUpdateTypes';
  
  describe('DraggableManager', () => {
    const baseClientX = 100;
    // left button mouse events have `.button === 0`
    const baseMouseEvt = { button: 0, clientX: baseClientX };
    const tag = 'some-tag';
    let bounds;
    let getBounds;
    let ctorOpts;
    let instance;
  
    function startDragging(dragManager) {
      dragManager.handleMouseDown({ ...baseMouseEvt, type: 'mousedown' });
      expect(dragManager.isDragging()).toBe(true);
    }
  
    beforeEach(() => {
      bounds = {
        clientXLeft: 50,
        maxValue: 0.9,
        minValue: 0.1,
        width: 100,
      };
      getBounds = jest.fn(() => bounds);
      ctorOpts = {
        getBounds,
        tag,
        onMouseEnter: jest.fn(),
        onMouseLeave: jest.fn(),
        onMouseMove: jest.fn(),
        onDragStart: jest.fn(),
        onDragMove: jest.fn(),
        onDragEnd: jest.fn(),
        resetBoundsOnResize: false,
      };
      instance = new DraggableManager(ctorOpts);
    });
  
    describe('_getPosition()', () => {
      it('invokes the getBounds ctor argument', () => {
        instance._getPosition(0);
        expect(ctorOpts.getBounds.mock.calls).toEqual([[tag]]);
      });
  
      it('converts clientX to x and [0, 1] value', () => {
        const left = 100;
        const pos = instance._getPosition(left);
        expect(pos).toEqual({
          x: left - bounds.clientXLeft,
          value: (left - bounds.clientXLeft) / bounds.width,
        });
      });
  
      it('clamps x and [0, 1] value based on getBounds().minValue', () => {
        const left = 0;
        const pos = instance._getPosition(left);
        expect(pos).toEqual({
          x: bounds.minValue * bounds.width,
          value: bounds.minValue,
        });
      });
  
      it('clamps x and [0, 1] value based on getBounds().maxValue', () => {
        const left = 10000;
        const pos = instance._getPosition(left);
        expect(pos).toEqual({
          x: bounds.maxValue * bounds.width,
          value: bounds.maxValue,
        });
      });
    });
  
    describe('window resize event listener', () => {
      it('is added in the ctor iff `resetBoundsOnResize` param is truthy', () => {
        const oldFn = window.addEventListener;
        window.addEventListener = jest.fn();
  
        ctorOpts.resetBoundsOnResize = false;
        instance = new DraggableManager(ctorOpts);
        expect(window.addEventListener.mock.calls).toEqual([]);
        ctorOpts.resetBoundsOnResize = true;
        instance = new DraggableManager(ctorOpts);
        expect(window.addEventListener.mock.calls).toEqual([['resize', expect.any(Function)]]);
  
        window.addEventListener = oldFn;
      });
  
      it('is removed in `.dispose()` iff `resetBoundsOnResize` param is truthy', () => {
        const oldFn = window.removeEventListener;
        window.removeEventListener = jest.fn();
  
        ctorOpts.resetBoundsOnResize = false;
        instance = new DraggableManager(ctorOpts);
        instance.dispose();
        expect(window.removeEventListener.mock.calls).toEqual([]);
        ctorOpts.resetBoundsOnResize = true;
        instance = new DraggableManager(ctorOpts);
        instance.dispose();
        expect(window.removeEventListener.mock.calls).toEqual([['resize', expect.any(Function)]]);
  
        window.removeEventListener = oldFn;
      });
    });
  
    describe('minor mouse events', () => {
      it('throws an error for invalid event types', () => {
        const type = 'invalid-event-type';
        const throwers = [
          () => instance.handleMouseEnter({ ...baseMouseEvt, type }),
          () => instance.handleMouseMove({ ...baseMouseEvt, type }),
          () => instance.handleMouseLeave({ ...baseMouseEvt, type }),
        ];
        throwers.forEach((thrower) => expect(thrower).toThrow());
      });
  
      it('does nothing if already dragging', () => {
        startDragging(instance);
        expect(getBounds.mock.calls.length).toBe(1);
  
        instance.handleMouseEnter({ ...baseMouseEvt, type: 'mouseenter' });
        instance.handleMouseMove({ ...baseMouseEvt, type: 'mousemove' });
        instance.handleMouseLeave({ ...baseMouseEvt, type: 'mouseleave' });
        expect(ctorOpts.onMouseEnter).not.toHaveBeenCalled();
        expect(ctorOpts.onMouseMove).not.toHaveBeenCalled();
        expect(ctorOpts.onMouseLeave).not.toHaveBeenCalled();
  
        const evt = { ...baseMouseEvt, type: 'invalid-type' };
        expect(() => instance.handleMouseEnter(evt)).not.toThrow();
  
        expect(getBounds.mock.calls.length).toBe(1);
      });
  
      it('passes data based on the mouse event type to callbacks', () => {
        const x = baseClientX - bounds.clientXLeft;
        const value = (baseClientX - bounds.clientXLeft) / bounds.width;
        const cases = [
          {
            type: 'mouseenter',
            handler: instance.handleMouseEnter,
            callback: ctorOpts.onMouseEnter,
            updateType: EUpdateTypes.MouseEnter,
          },
          {
            type: 'mousemove',
            handler: instance.handleMouseMove,
            callback: ctorOpts.onMouseMove,
            updateType: EUpdateTypes.MouseMove,
          },
          {
            type: 'mouseleave',
            handler: instance.handleMouseLeave,
            callback: ctorOpts.onMouseLeave,
            updateType: EUpdateTypes.MouseLeave,
          },
        ];
  
        cases.forEach((testCase) => {
          const { type, handler, callback, updateType } = testCase;
          const event = { ...baseMouseEvt, type };
          handler(event);
          expect(callback.mock.calls).toEqual([[{ event, tag, value, x, manager: instance, type: updateType }]]);
        });
      });
    });
  
    describe('drag events', () => {
      let realWindowAddEvent;
      let realWindowRmEvent;
  
      beforeEach(() => {
        realWindowAddEvent = window.addEventListener;
        realWindowRmEvent = window.removeEventListener;
        window.addEventListener = jest.fn();
        window.removeEventListener = jest.fn();
      });
  
      afterEach(() => {
        window.addEventListener = realWindowAddEvent;
        window.removeEventListener = realWindowRmEvent;
      });
  
      it('throws an error for invalid event types', () => {
        expect(() => instance.handleMouseDown({ ...baseMouseEvt, type: 'invalid-event-type' })).toThrow();
      });
  
      describe('mousedown', () => {
        it('is ignored if already dragging', () => {
          startDragging(instance);
          window.addEventListener.mockReset();
          ctorOpts.onDragStart.mockReset();
  
          expect(getBounds.mock.calls.length).toBe(1);
          instance.handleMouseDown({ ...baseMouseEvt, type: 'mousedown' });
          expect(getBounds.mock.calls.length).toBe(1);
  
          expect(window.addEventListener).not.toHaveBeenCalled();
          expect(ctorOpts.onDragStart).not.toHaveBeenCalled();
        });
  
        it('sets `isDragging()` to true', () => {
          instance.handleMouseDown({ ...baseMouseEvt, type: 'mousedown' });
          expect(instance.isDragging()).toBe(true);
        });
  
        it('adds the window mouse listener events', () => {
          instance.handleMouseDown({ ...baseMouseEvt, type: 'mousedown' });
          expect(window.addEventListener.mock.calls).toEqual([
            ['mousemove', expect.any(Function)],
            ['mouseup', expect.any(Function)],
          ]);
        });
      });
  
      describe('mousemove', () => {
        it('is ignored if not already dragging', () => {
          instance._handleDragEvent({ ...baseMouseEvt, type: 'mousemove' });
          expect(ctorOpts.onDragMove).not.toHaveBeenCalled();
          startDragging(instance);
          instance._handleDragEvent({ ...baseMouseEvt, type: 'mousemove' });
          expect(ctorOpts.onDragMove).toHaveBeenCalled();
        });
      });
  
      describe('mouseup', () => {
        it('is ignored if not already dragging', () => {
          instance._handleDragEvent({ ...baseMouseEvt, type: 'mouseup' });
          expect(ctorOpts.onDragEnd).not.toHaveBeenCalled();
          startDragging(instance);
          instance._handleDragEvent({ ...baseMouseEvt, type: 'mouseup' });
          expect(ctorOpts.onDragEnd).toHaveBeenCalled();
        });
  
        it('sets `isDragging()` to false', () => {
          startDragging(instance);
          expect(instance.isDragging()).toBe(true);
          instance._handleDragEvent({ ...baseMouseEvt, type: 'mouseup' });
          expect(instance.isDragging()).toBe(false);
        });
  
        it('removes the window mouse listener events', () => {
          startDragging(instance);
          expect(window.removeEventListener).not.toHaveBeenCalled();
          instance._handleDragEvent({ ...baseMouseEvt, type: 'mouseup' });
          expect(window.removeEventListener.mock.calls).toEqual([
            ['mousemove', expect.any(Function)],
            ['mouseup', expect.any(Function)],
          ]);
        });
      });
  
      it('passes drag event data to the callbacks', () => {
        const x = baseClientX - bounds.clientXLeft;
        const value = (baseClientX - bounds.clientXLeft) / bounds.width;
        const cases = [
          {
            type: 'mousedown',
            handler: instance.handleMouseDown,
            callback: ctorOpts.onDragStart,
            updateType: EUpdateTypes.DragStart,
          },
          {
            type: 'mousemove',
            handler: instance._handleDragEvent,
            callback: ctorOpts.onDragMove,
            updateType: EUpdateTypes.DragMove,
          },
          {
            type: 'mouseup',
            handler: instance._handleDragEvent,
            callback: ctorOpts.onDragEnd,
            updateType: EUpdateTypes.DragEnd,
          },
        ];
  
        cases.forEach((testCase) => {
          const { type, handler, callback, updateType } = testCase;
          const event = { ...baseMouseEvt, type };
          handler(event);
          expect(callback.mock.calls).toEqual([[{ event, tag, value, x, manager: instance, type: updateType }]]);
        });
      });
    });
  });
  