'use strict';
describe('ngClass', function() {
  var element;
  afterEach(function() {
    dealoc(element);
  });
  it('should add new and remove old classes dynamically', inject(function($rootScope, $compile) {
    element = $compile('
')($rootScope);
    $rootScope.dynClass = 'A';
    $rootScope.$digest();
    expect(element.hasClass('existing')).toBe(true);
    expect(element.hasClass('A')).toBe(true);
    $rootScope.dynClass = 'B';
    $rootScope.$digest();
    expect(element.hasClass('existing')).toBe(true);
    expect(element.hasClass('A')).toBe(false);
    expect(element.hasClass('B')).toBe(true);
    delete $rootScope.dynClass;
    $rootScope.$digest();
    expect(element.hasClass('existing')).toBe(true);
    expect(element.hasClass('A')).toBe(false);
    expect(element.hasClass('B')).toBe(false);
  }));
  it('should support adding multiple classes via an array', inject(function($rootScope, $compile) {
    element = $compile('')($rootScope);
    $rootScope.$digest();
    expect(element.hasClass('existing')).toBeTruthy();
    expect(element.hasClass('A')).toBeTruthy();
    expect(element.hasClass('B')).toBeTruthy();
  }));
  it('should support adding multiple classes conditionally via a map of class names to boolean' +
      'expressions', inject(function($rootScope, $compile) {
    var element = $compile(
        '' +
        '
')($rootScope);
    $rootScope.conditionA = true;
    $rootScope.$digest();
    expect(element.hasClass('existing')).toBeTruthy();
    expect(element.hasClass('A')).toBeTruthy();
    expect(element.hasClass('B')).toBeFalsy();
    expect(element.hasClass('AnotB')).toBeTruthy();
    $rootScope.conditionB = function() { return true; };
    $rootScope.$digest();
    expect(element.hasClass('existing')).toBeTruthy();
    expect(element.hasClass('A')).toBeTruthy();
    expect(element.hasClass('B')).toBeTruthy();
    expect(element.hasClass('AnotB')).toBeFalsy();
  }));
  it('should remove classes when the referenced object is the same but its property is changed',
    inject(function($rootScope, $compile) {
      var element = $compile('')($rootScope);
      $rootScope.classes = { A: true, B: true };
      $rootScope.$digest();
      expect(element.hasClass('A')).toBeTruthy();
      expect(element.hasClass('B')).toBeTruthy();
      $rootScope.classes.A = false;
      $rootScope.$digest();
      expect(element.hasClass('A')).toBeFalsy();
      expect(element.hasClass('B')).toBeTruthy();
  }));
  it('should support adding multiple classes via a space delimited string', inject(function($rootScope, $compile) {
    element = $compile('')($rootScope);
    $rootScope.$digest();
    expect(element.hasClass('existing')).toBeTruthy();
    expect(element.hasClass('A')).toBeTruthy();
    expect(element.hasClass('B')).toBeTruthy();
  }));
  it('should preserve class added post compilation with pre-existing classes', inject(function($rootScope, $compile) {
    element = $compile('')($rootScope);
    $rootScope.dynClass = 'A';
    $rootScope.$digest();
    expect(element.hasClass('existing')).toBe(true);
    // add extra class, change model and eval
    element.addClass('newClass');
    $rootScope.dynClass = 'B';
    $rootScope.$digest();
    expect(element.hasClass('existing')).toBe(true);
    expect(element.hasClass('B')).toBe(true);
    expect(element.hasClass('newClass')).toBe(true);
  }));
  it('should preserve class added post compilation without pre-existing classes"', inject(function($rootScope, $compile) {
    element = $compile('')($rootScope);
    $rootScope.dynClass = 'A';
    $rootScope.$digest();
    expect(element.hasClass('A')).toBe(true);
    // add extra class, change model and eval
    element.addClass('newClass');
    $rootScope.dynClass = 'B';
    $rootScope.$digest();
    expect(element.hasClass('B')).toBe(true);
    expect(element.hasClass('newClass')).toBe(true);
  }));
  it('should preserve other classes with similar name"', inject(function($rootScope, $compile) {
    element = $compile('')($rootScope);
    $rootScope.dynCls = 'panel';
    $rootScope.$digest();
    $rootScope.dynCls = 'foo';
    $rootScope.$digest();
    expect(element[0].className).toBe('ui-panel ui-selected ng-scope foo');
  }));
  it('should not add duplicate classes', inject(function($rootScope, $compile) {
    element = $compile('')($rootScope);
    $rootScope.dynCls = 'panel';
    $rootScope.$digest();
    expect(element[0].className).toBe('panel bar ng-scope');
  }));
  it('should remove classes even if it was specified via class attribute', inject(function($rootScope, $compile) {
    element = $compile('')($rootScope);
    $rootScope.dynCls = 'panel';
    $rootScope.$digest();
    $rootScope.dynCls = 'window';
    $rootScope.$digest();
    expect(element[0].className).toBe('bar ng-scope window');
  }));
  it('should remove classes even if they were added by another code', inject(function($rootScope, $compile) {
    element = $compile('')($rootScope);
    $rootScope.dynCls = 'foo';
    $rootScope.$digest();
    element.addClass('foo');
    $rootScope.dynCls = '';
    $rootScope.$digest();
  }));
  it('should convert undefined and null values to an empty string', inject(function($rootScope, $compile) {
    element = $compile('')($rootScope);
    $rootScope.dynCls = [undefined, null];
    $rootScope.$digest();
  }));
  it('should ngClass odd/even', inject(function($rootScope, $compile) {
    element = $compile('')($rootScope);
    $rootScope.$digest();
    var e1 = jqLite(element[0].childNodes[1]);
    var e2 = jqLite(element[0].childNodes[3]);
    expect(e1.hasClass('existing')).toBeTruthy();
    expect(e1.hasClass('odd')).toBeTruthy();
    expect(e2.hasClass('existing')).toBeTruthy();
    expect(e2.hasClass('even')).toBeTruthy();
  }));
  it('should allow both ngClass and ngClassOdd/Even on the same element', inject(function($rootScope, $compile) {
    element = $compile('' +
      '' +
      '')($rootScope);
    $rootScope.$apply();
    var e1 = jqLite(element[0].childNodes[1]);
    var e2 = jqLite(element[0].childNodes[3]);
    expect(e1.hasClass('plainClass')).toBeTruthy();
    expect(e1.hasClass('odd')).toBeTruthy();
    expect(e1.hasClass('even')).toBeFalsy();
    expect(e2.hasClass('plainClass')).toBeTruthy();
    expect(e2.hasClass('even')).toBeTruthy();
    expect(e2.hasClass('odd')).toBeFalsy();
  }));
  it('should allow both ngClass and ngClassOdd/Even with multiple classes', inject(function($rootScope, $compile) {
    element = $compile('' +
      '' +
      '')($rootScope);
    $rootScope.$apply();
    var e1 = jqLite(element[0].childNodes[1]);
    var e2 = jqLite(element[0].childNodes[3]);
    expect(e1.hasClass('A')).toBeTruthy();
    expect(e1.hasClass('B')).toBeTruthy();
    expect(e1.hasClass('C')).toBeTruthy();
    expect(e1.hasClass('D')).toBeTruthy();
    expect(e1.hasClass('E')).toBeFalsy();
    expect(e1.hasClass('F')).toBeFalsy();
    expect(e2.hasClass('A')).toBeTruthy();
    expect(e2.hasClass('B')).toBeTruthy();
    expect(e2.hasClass('E')).toBeTruthy();
    expect(e2.hasClass('F')).toBeTruthy();
    expect(e2.hasClass('C')).toBeFalsy();
    expect(e2.hasClass('D')).toBeFalsy();
  }));
  it('should reapply ngClass when interpolated class attribute changes', inject(function($rootScope, $compile) {
    element = $compile('')($rootScope);
    $rootScope.$apply(function() {
      $rootScope.cls = "two";
      $rootScope.four = true;
    });
    expect(element).toHaveClass('one');
    expect(element).toHaveClass('two'); // interpolated
    expect(element).toHaveClass('three');
    expect(element).toHaveClass('four');
    $rootScope.$apply(function() {
      $rootScope.cls = "too";
    });
    expect(element).toHaveClass('one');
    expect(element).toHaveClass('too'); // interpolated
    expect(element).toHaveClass('three');
    expect(element).toHaveClass('four'); // should still be there
    expect(element.hasClass('two')).toBeFalsy();
    $rootScope.$apply(function() {
      $rootScope.cls = "to";
    });
    expect(element).toHaveClass('one');
    expect(element).toHaveClass('to'); // interpolated
    expect(element).toHaveClass('three');
    expect(element).toHaveClass('four'); // should still be there
    expect(element.hasClass('two')).toBeFalsy();
    expect(element.hasClass('too')).toBeFalsy();
  }));
  it('should not mess up class value due to observing an interpolated class attribute', inject(function($rootScope, $compile) {
    $rootScope.foo = true;
    $rootScope.$watch("anything", function() {
      $rootScope.foo = false;
    });
    element = $compile('')($rootScope);
    $rootScope.$digest();
    expect(element.hasClass('foo')).toBe(false);
  }));
  it('should update ngClassOdd/Even when model is changed by filtering', inject(function($rootScope, $compile) {
    element = $compile('' +
      '' +
      '')($rootScope);
    $rootScope.items = ['a','b','a'];
    $rootScope.$digest();
    $rootScope.items = ['a','a'];
    $rootScope.$digest();
    var e1 = jqLite(element[0].childNodes[1]);
    var e2 = jqLite(element[0].childNodes[3]);
    expect(e1.hasClass('odd')).toBeTruthy();
    expect(e1.hasClass('even')).toBeFalsy();
    expect(e2.hasClass('even')).toBeTruthy();
    expect(e2.hasClass('odd')).toBeFalsy();
  }));
  it('should update ngClassOdd/Even when model is changed by sorting', inject(function($rootScope, $compile) {
    element = $compile('' +
      '- i' +
      '
')($rootScope);
    $rootScope.items = ['a','b'];
    $rootScope.$digest();
    $rootScope.items = ['b','a'];
    $rootScope.$digest();
    var e1 = jqLite(element[0].childNodes[1]);
    var e2 = jqLite(element[0].childNodes[3]);
    expect(e1.hasClass('odd')).toBeTruthy();
    expect(e1.hasClass('even')).toBeFalsy();
    expect(e2.hasClass('even')).toBeTruthy();
    expect(e2.hasClass('odd')).toBeFalsy();
  }));
});
describe('ngClass animations', function() {
  var body, element, $rootElement;
  it("should avoid calling addClass accidentally when removeClass is going on", function() {
    module('mock.animate');
    inject(function($compile, $rootScope, $animate, $timeout) {
      var element = angular.element('');
      var body = jqLite(document.body);
      body.append(element);
      $compile(element)($rootScope);
      expect($animate.queue.length).toBe(0);
      $rootScope.val = 'one';
      $rootScope.$digest();
      $animate.flushNext('addClass');
      expect($animate.queue.length).toBe(0);
      $rootScope.val = '';
      $rootScope.$digest();
      $animate.flushNext('removeClass'); //only removeClass is called
      expect($animate.queue.length).toBe(0);
      $rootScope.val = 'one';
      $rootScope.$digest();
      $animate.flushNext('addClass');
      expect($animate.queue.length).toBe(0);
      $rootScope.val = 'two';
      $rootScope.$digest();
      $animate.flushNext('removeClass');
      $animate.flushNext('addClass');
      expect($animate.queue.length).toBe(0);
    });
  });
  it("should consider the ngClass expression evaluation before performing an animation", function() {
    //mocks are not used since the enter delegation method is called before addClass and
    //it makes it impossible to test to see that addClass is called first
    module('ngAnimate');
    var digestQueue = [];
    module(function($animateProvider) {
      $animateProvider.register('.crazy', function() {
        return {
          enter : function(element, done) {
            element.data('state', 'crazy-enter');
            done();
          }
        };
      });
      return function($rootScope) {
        var before = $rootScope.$$postDigest;
        $rootScope.$$postDigest = function() {
          var args = arguments;
          digestQueue.push(function() {
            before.apply($rootScope, args);
          });
        };
      };
    });
    inject(function($compile, $rootScope, $rootElement, $animate, $timeout, $document) {
      // Enable animations by triggering the first item in the postDigest queue
      digestQueue.shift()();
      $rootScope.val = 'crazy';
      var element = angular.element('');
      jqLite($document[0].body).append($rootElement);
      $compile(element)($rootScope);
      var enterComplete = false;
      $animate.enter(element, $rootElement, null, function() {
        enterComplete = true;
      });
      //jquery doesn't compare both elements properly so let's use the nodes
      expect(element.parent()[0]).toEqual($rootElement[0]);
      expect(element.hasClass('crazy')).toBe(false);
      expect(enterComplete).toBe(false);
      expect(digestQueue.length).toBe(1);
      $rootScope.$digest();
      $timeout.flush();
      expect(element.hasClass('crazy')).toBe(true);
      expect(enterComplete).toBe(false);
      digestQueue.shift()(); //enter
      expect(digestQueue.length).toBe(0);
      //we don't normally need this, but since the timing between digests
      //is spaced-out then it is required so that the original digestion
      //is kicked into gear
      $rootScope.$digest();
      $timeout.flush();
      expect(element.data('state')).toBe('crazy-enter');
      expect(enterComplete).toBe(true);
    });
  });
  it("should not remove classes if they're going to be added back right after", function() {
    module('mock.animate');
    inject(function($rootScope, $compile, $animate) {
      var className;
      $rootScope.one = true;
      $rootScope.two = true;
      $rootScope.three = true;
      var element = angular.element('');
      $compile(element)($rootScope);
      $rootScope.$digest();
      //this fires twice due to the class observer firing
      className = $animate.flushNext('addClass').params[1];
      expect(className).toBe('one two three');
      expect($animate.queue.length).toBe(0);
      $rootScope.three = false;
      $rootScope.$digest();
      className = $animate.flushNext('removeClass').params[1];
      expect(className).toBe('three');
      expect($animate.queue.length).toBe(0);
      $rootScope.two = false;
      $rootScope.three = true;
      $rootScope.$digest();
      className = $animate.flushNext('removeClass').params[1];
      expect(className).toBe('two');
      className = $animate.flushNext('addClass').params[1];
      expect(className).toBe('three');
      expect($animate.queue.length).toBe(0);
    });
  });
});