linagora/openpaas-esn

View on GitHub
frontend/js/modules/box-overlay/box-overlay.spec.js

Summary

Maintainability
D
2 days
Test Coverage
'use strict';

/* global chai: false, sinon: false */

var expect = chai.expect;

describe('The box-overlay Angular module', function() {

  var $window, $compile, $rootScope, $scope, $timeout, $q, element, deviceDetector, notificationFactory, esnI18nService, DEVICES;

  function compile(html) {
    element = $compile(html)($scope);
    $scope.$digest();

    return element;
  }

  function compileAndClickTheButton(html) {
    compile(html);

    return clickTheButton(element);
  }

  function clickTheButton(button) {
    button.click();
    $rootScope.$digest();
    return button;
  }

  function closeFirstBox() {
    var closeButton = angular.element('.box-overlay-open i.close').first();
    closeButton.triggerHandler('click');
    $timeout.flush();
  }

  function getMaximizeButton() {
    return angular.element('.box-overlay-open .toggle-maximize').first();
  }

  function maximizeFirstBox() {
    getMaximizeButton().click();
  }

  function minimizeFirstBox() {
    angular.element('.box-overlay-open .toggle-minimize').first().click();
  }

  function triggerEventOnFirstInputElement(event) {
    angular.element('.box-overlay-open input').first().trigger({type: event});
    $scope.$digest();
  }

  function touchFirstInputElement() {
    triggerEventOnFirstInputElement('touchstart');
  }

  function focusFirstInputElement() {
    triggerEventOnFirstInputElement('focus');
  }

  function overlays() {
    return angular.element('.box-overlay-open');
  }

  beforeEach(module('jadeTemplates'));

  beforeEach(function() {
    angular.mock.module('esn.box-overlay', function($provide) {
      esnI18nService = {
        translate: function(input) {
          return {
            toString: function() { return input; }
          };
        }
      };

      $provide.value('notificationFactory', notificationFactory = {
        weakError: sinon.spy()
      });
      $provide.value('esnI18nService', esnI18nService);
    });
  });

  afterEach(function() {
    angular.element('.box-overlay-container').remove();
  });

  describe('boxOverlay directive', function() {

    var $httpBackend, ESN_BOX_OVERLAY_EVENTS, ESN_BOX_OVERLAY_MAX_WINDOWS;

    beforeEach(inject(function(_$window_, _$compile_, _$rootScope_, _$httpBackend_, _$timeout_,
        _deviceDetector_, _DEVICES_, _ESN_BOX_OVERLAY_EVENTS_, _ESN_BOX_OVERLAY_MAX_WINDOWS_) {
      $window = _$window_;
      $compile = _$compile_;
      $rootScope = _$rootScope_;
      $scope = $rootScope.$new();
      $httpBackend = _$httpBackend_;
      $timeout = _$timeout_;
      deviceDetector = _deviceDetector_;
      DEVICES = _DEVICES_;
      ESN_BOX_OVERLAY_EVENTS = _ESN_BOX_OVERLAY_EVENTS_;
      ESN_BOX_OVERLAY_MAX_WINDOWS = _ESN_BOX_OVERLAY_MAX_WINDOWS_;

      deviceDetector.device = DEVICES.ANDROID;
    }));

    afterEach(function() {
      overlays().remove(); // Removes all overlays that might have been left in the DOM
    });

    it('should display the overlay when the calling element is clicked', function() {
      compileAndClickTheButton('<button box-overlay />');

      expect(overlays()).to.have.length(1);
    });

    it('should correctly fetch a custom template, and add it to the overlay', function() {
      $httpBackend.expectGET('/path/to/the/template').respond('<div class="i-am-the-template">Test</div>');

      compileAndClickTheButton('<button box-overlay box-template-url="/path/to/the/template" />');
      $httpBackend.flush();

      expect(overlays().find('.i-am-the-template')).to.have.length(1);
    });

    it('should display the title in the overlay', function() {
      compileAndClickTheButton('<button box-overlay box-title="The title !" />');

      expect(overlays().find('.panel-title').text()).to.match(/The title !/);
    });

    it('should not try to focus when no element has the autofocus attr in the template', function() {
      $httpBackend.expectGET('/path/to/the/template').respond('<div class="i-am-the-template">Test</div>');

      compileAndClickTheButton('<button box-overlay box-template-url="/path/to/the/template" />');
      $httpBackend.flush();
      $timeout.flush();

      expect(overlays().find('.i-am-the-template')).to.have.length(1);
    });

    it('should focus an autofocus element found in the template', function() {
      $httpBackend.expectGET('/path/to/the/template').respond('<input class="i-am-the-template" autofocus>Test</input>');

      compileAndClickTheButton('<button box-overlay box-template-url="/path/to/the/template" />');
      $httpBackend.flush();
      $timeout.flush();

      expect(overlays().find('.i-am-the-template')[0]).to.equal(document.activeElement);
    });

    it('should focus the autofocus element of a newly shown overlay', function() {
      $httpBackend.expectGET('/path/to/the/template').respond('<input class="i-am-the-template" autofocus>Test</input>');
      compileAndClickTheButton('<button box-overlay box-template-url="/path/to/the/template" />');
      $httpBackend.flush();
      $timeout.flush();

      $httpBackend.expectGET('/path/to/another/template').respond('<input class="i-am-another-template" autofocus>Test</input>');
      compileAndClickTheButton('<button box-overlay box-template-url="/path/to/another/template" />');
      $httpBackend.flush();
      $timeout.flush();

      expect(overlays().find('.i-am-another-template')[0]).to.equal(document.activeElement);
    });

    it('should accept to open ESN_BOX_OVERLAY_MAX_WINDOWS boxes', function() {
      var notificationCount = 0;

      $rootScope.$on('box-overlay:no-space-left-on-screen', function() {
        notificationCount++;
      });

      var button = compileAndClickTheButton('<button box-overlay />');

      for (var i = 0; i < ESN_BOX_OVERLAY_MAX_WINDOWS; i++) {
        clickTheButton(button);
      }

      expect(overlays()).to.have.length(ESN_BOX_OVERLAY_MAX_WINDOWS);
      expect(notificationCount).to.equal(1);
    });

    it('should not accept to have more than ESN_BOX_OVERLAY_MAX_WINDOWS boxes', function() {
      var button = compileAndClickTheButton('<button box-overlay />');

      for (var i = 0; i < ESN_BOX_OVERLAY_MAX_WINDOWS * 2; i++) {
        clickTheButton(button);
      }

      expect(overlays()).to.have.length(ESN_BOX_OVERLAY_MAX_WINDOWS);
    });

    it('should notify when it cannot open more boxes', function() {
      var button = compileAndClickTheButton('<button box-overlay />');

      for (var i = 0; i < ESN_BOX_OVERLAY_MAX_WINDOWS + 1; i++) {
        clickTheButton(button);
      }

      expect(notificationFactory.weakError).to.have.been.calledWith('', 'Cannot open more than ' + ESN_BOX_OVERLAY_MAX_WINDOWS + ' windows. Please close one and try again');
    });

    it('should not accept to open two boxes with the same identifier', function() {
      var button = compileAndClickTheButton('<button box-overlay box-id="identifier" />');
      clickTheButton(button);

      expect(overlays()).to.have.length(1);
    });

    it('should accept to reopen a box when one has been closed', function() {
      var button = compileAndClickTheButton('<button box-overlay />');

      clickTheButton(button);
      $timeout.flush();
      expect(overlays()).to.have.length(2);

      closeFirstBox();
      expect(overlays()).to.have.length(1);

      clickTheButton(button);
      $timeout.flush();
      expect(overlays()).to.have.length(2);
    });

    it('should set the "maximized" CSS class when the box is maximized', function() {
      compileAndClickTheButton('<button box-overlay />');
      maximizeFirstBox();

      expect(overlays().first().hasClass('maximized')).to.equal(true);
      expect(overlays().first().hasClass('minimized')).to.equal(false);
    });

    it('should set the "minimized" CSS class when the box is minimized', function() {
      compileAndClickTheButton('<button box-overlay />');
      minimizeFirstBox();

      expect(overlays().first().hasClass('minimized')).to.equal(true);
      expect(overlays().first().hasClass('maximized')).to.equal(false);
    });

    it('should minimize other boxes when a box is maximized', function() {
      compileAndClickTheButton('<button box-overlay />');
      compileAndClickTheButton('<button box-overlay />');
      maximizeFirstBox();

      expect(overlays().first().hasClass('maximized')).to.equal(true);
      expect(overlays().first().hasClass('minimized')).to.equal(false);
      expect(overlays().last().hasClass('maximized')).to.equal(false);
      expect(overlays().last().hasClass('minimized')).to.equal(true);
    });

    it('should open in the given initial state when defined', function() {
      compileAndClickTheButton('<button box-overlay box-initial-state="FULL_SCREEN" />');

      expect(angular.element('.full-screen')).to.have.length(1);
    });

    it('should not be closeable when closeable=false is given', function() {
      compileAndClickTheButton('<button box-overlay box-closeable="false" />');

      expect(angular.element('.box-overlay-open i.close')).to.have.length(0);
    });

    it('should be closeable when no closeable=true is given', function() {
      compileAndClickTheButton('<button box-overlay box-closeable="true" />');

      expect(angular.element('.box-overlay-open i.close')).to.have.length(1);
    });

    it('should be closeable when no closeable option is given', function() {
      compileAndClickTheButton('<button box-overlay />');

      expect(angular.element('.box-overlay-open i.close')).to.have.length(1);
    });

    it('should have minimize and maximize buttons when no allowedStates option is given', function() {
      compileAndClickTheButton('<button box-overlay />');

      expect(angular.element('.box-overlay-open .toggle-maximize')).to.have.length(1);
      expect(angular.element('.box-overlay-open .toggle-minimize')).to.have.length(1);
    });

    it('should have maximize button when allowedStates contains MAXIMIZED', function() {
      compileAndClickTheButton('<button box-overlay box-allowed-states="[\'MAXIMIZED\']"/>');

      expect(angular.element('.box-overlay-open .toggle-maximize')).to.have.length(1);
    });

    it('should not have minimize button when allowedStates does not contain MINIMIZED', function() {
      compileAndClickTheButton('<button box-overlay box-allowed-states="[\'MAXIMIZED\']"/>');

      expect(angular.element('.box-overlay-open .toggle-minimize')).to.have.length(0);
    });

    it('should have minimize button when allowedStates contains MINIMIZED', function() {
      compileAndClickTheButton('<button box-overlay box-allowed-states="[\'MINIMIZED\']"/>');

      expect(angular.element('.box-overlay-open .toggle-minimize')).to.have.length(1);
    });

    it('should not have maximize button when allowedStates does not contain MAXIMIZED', function() {
      compileAndClickTheButton('<button box-overlay box-allowed-states="[\'MINIMIZED\']"/>');

      expect(angular.element('.box-overlay-open .toggle-maximize')).to.have.length(0);
    });

    describe('min/maximize buttons should notify child components', function() {

      var mock, unsubscriber;

      beforeEach(function() {
        compileAndClickTheButton('<button box-overlay />');
        mock = sinon.spy();
        unsubscriber = $rootScope.$on(ESN_BOX_OVERLAY_EVENTS.RESIZED, mock);
      });

      afterEach(function() {
        unsubscriber && unsubscriber();
      });

      it('should raise event ESN_BOX_OVERLAY_EVENTS.RESIZED when unminimized', function() {
        minimizeFirstBox();
        maximizeFirstBox();

        expect(mock).to.have.been.calledOnce;
      });

      it('should raise event ESN_BOX_OVERLAY_EVENTS.RESIZED when maximized', function() {
        maximizeFirstBox();

        expect(mock).to.have.been.calledOnce;
      });

      it('should NOT raise event ESN_BOX_OVERLAY_EVENTS.RESIZED when minimized', function() {
        minimizeFirstBox();

        expect(mock).to.not.have.been.called;
      });

      it('should raise event ESN_BOX_OVERLAY_EVENTS.RESIZED when minimized clicked 2 times', function() {
        minimizeFirstBox();
        minimizeFirstBox();

        expect(mock).to.have.been.calledOnce;
      });
    });

    describe('when the device is an ipad', function() {

      beforeEach(function() {
        deviceDetector.device = DEVICES.I_PAD;

        $httpBackend.expectGET('/path/to/the/template').respond('<input>Test</input>');
        compileAndClickTheButton('<button box-overlay box-template-url="/path/to/the/template" />');
        $httpBackend.flush();
      });

      it('should automatically open as maximize', function() {
        expect(overlays().first().hasClass('maximized')).to.equal(true);
      });

      it('should scroll to top when an input is focused', function() {
        $window.scrollTo = sinon.spy();

        focusFirstInputElement();

        expect($window.scrollTo).to.have.been.calledWith(0, 0);
      });

      it('should maximize the box when an input is touched', function() {
        minimizeFirstBox();
        expect(overlays().first().hasClass('maximized')).to.equal(false);

        touchFirstInputElement();

        expect(overlays().first().hasClass('maximized')).to.equal(true);
      });

      it('should do nothing when an input is touched but the box is already maximized', function() {
        maximizeFirstBox();
        $scope.$toggleMaximized = sinon.spy();

        touchFirstInputElement();

        expect($scope.$toggleMaximized).to.have.not.been.called;
      });

    });

  });

  describe('The boxOverlayContainer directive', function() {

    beforeEach(inject(function(_$compile_, _$rootScope_) {
      $compile = _$compile_;
      $rootScope = _$rootScope_;
      $scope = $rootScope.$new();
    }));

    it('should set the "maximized" CSS class when one box is maximized', function() {
      compileAndClickTheButton('<button box-overlay />');
      maximizeFirstBox();

      expect(angular.element('.box-overlay-container').hasClass('maximized')).to.equal(true);
    });

  });

  describe('The boxOverlayOpener service', function() {

    var boxOverlay, boxOverlayReturnValue, boxOverlayOpener;

    beforeEach(angular.mock.module(function($provide) {
      boxOverlay = function(options) {
        boxOverlay.receivedOptions = options;
        return boxOverlayReturnValue;
      };
      $provide.value('$boxOverlay', boxOverlay);
    }));

    beforeEach(inject(function(_$compile_, _$rootScope_, _boxOverlayOpener_) {
      $compile = _$compile_;
      $rootScope = _$rootScope_;
      $scope = $rootScope.$new();
      boxOverlayOpener = _boxOverlayOpener_;
    }));

    it('delegate to $boxOverlay with the given options', function() {
      boxOverlayOpener.open({my: 'super options'});

      expect(boxOverlay.receivedOptions).to.deep.equal({my: 'super options'});
    });

    it('call the show fn if the returned value is defined', function(done) {
      boxOverlayReturnValue = {
        show: done
      };

      boxOverlayOpener.open({});
    });

    it('not try to call the show fn if the returned value is undefined', function() {
      boxOverlayReturnValue = undefined;

      boxOverlayOpener.open({});
    });

  });

  describe('The boxOverlay service', function() {
    var $boxOverlay, $httpBackend;

    beforeEach(inject(function(_$timeout_, _$httpBackend_, _$boxOverlay_, _$q_) {
      $timeout = _$timeout_;
      $httpBackend = _$httpBackend_;
      $boxOverlay = _$boxOverlay_;
      $q = _$q_;
    }));

    beforeEach(function() {
      $httpBackend.expectGET('/path/to/template').respond('');
    });

    it('should update the title', function() {
      var overlay = $boxOverlay({
        id: 0,
        title: 'Default title',
        templateUrl: '/path/to/template'
      });

      overlay.updateTitle('New Title');

      expect(overlay.$scope.title).to.equal('New Title');
    });

    it('should allow hiding and opening through scope functions', function() {
      var overlay = $boxOverlay({
        id: 0,
        title: 'Default title',
        templateUrl: '/path/to/template'
      });

      overlay.$scope.$show();
      $timeout.flush();
      expect(overlay.$isShown).to.equal(true);

      overlay.$scope.$hide();
      $timeout.flush();
      expect(overlay.$isShown).to.equal(false);
    });

    it('should allow hiding and reopening the same overlay over and over again', function() {
      var overlay = $boxOverlay({
        id: 0,
        title: 'Default title',
        templateUrl: '/path/to/template'
      });

      for (var i = 0; i < 10; i++) {
        overlay.$scope.$show();
        $timeout.flush();
        expect(overlay.$isShown).to.equal(true);

        overlay.$scope.$hide();
        $timeout.flush();
        expect(overlay.$isShown).to.equal(false);
      }
    });

    it('should call $q.when when trying to close if no callback has been set', function(done) {
      sinon.spy($q, 'when');

      var target = $boxOverlay({
        id: 0,
        title: 'Default title',
        templateUrl: '/path/to/template'
      });

      target.$scope.$close().then(function() {
        expect($q.when).to.have.been.called;
        done();
      });

      target.$scope.$digest();
    });

    it('should call provided callback when trying to close', function(done) {
      var callback = sinon.stub().returns($q.when());
      var target = $boxOverlay({
        id: 0,
        title: 'Default title',
        templateUrl: '/path/to/template'
      });

      target.$scope.$onTryClose(callback);

      target.$scope.$close().then(function() {
        expect(callback).to.have.been.called;
        done();
      });

      target.$scope.$digest();
    });

    it('should hide the the box overlay when trying to close', function() {
      var target = $boxOverlay({
        id: 0,
        title: 'Default title',
        templateUrl: '/path/to/template'
      });

      sinon.spy(target, 'hide');

      target.$scope.$close();
      expect(target.hide).to.have.been.called;
    });

    it('should destroy overlay when callback resolves', function() {
      var target = $boxOverlay({
        id: 0,
        title: 'Default title',
        templateUrl: '/path/to/template'
      });

      sinon.spy(target, 'destroy');

      target.$scope.$onTryClose(function() {
        return $q.when();
      });

      target.$scope.$close();

      target.$scope.$digest();
      $timeout.flush();

      expect(target.destroy).to.have.been.called;
    });

    it('should not destroy overlay when callback rejects', function() {
      var target = $boxOverlay({
        id: 0,
        title: 'Default title',
        templateUrl: '/path/to/template'
      });

      sinon.spy(target, 'destroy');

      target.$scope.$onTryClose(function() {
        return $q.reject();
      });

      target.$scope.$digest();

      expect($timeout.flush).to.throw();
      expect(target.destroy).not.to.have.been.called;
    });
  });

  describe('The BoxOverlayStateManager factory', function() {
    var BoxOverlayStateManager, stateManager;

    beforeEach(inject(function(_BoxOverlayStateManager_) {
      BoxOverlayStateManager = _BoxOverlayStateManager_;
      stateManager = new BoxOverlayStateManager();
    }));

    it('should call registered functions when toggled', function() {
      var callback = sinon.spy();

      stateManager.registerHandler(callback);
      stateManager.toggle(BoxOverlayStateManager.STATES.NORMAL);

      expect(callback).to.have.been.calledOnce;
    });

    it('should register only functions', function() {
      var callback = 'foobar';

      stateManager.registerHandler(callback);

      expect(function() {stateManager.toggle(BoxOverlayStateManager.STATES.NORMAL);}).to.not.throw();
    });

    it('should have no registered callback by default', function() {
      stateManager = new BoxOverlayStateManager();

      expect(stateManager.callbacks).to.be.an('array').that.is.empty;
    });
  });

});