text: topSuite("Ext.draw.sprite.Sprite", ['Ext.draw.*'], function() {
      beforeEach(function() {
          // Silence warnings regarding Sencha download server
          spyOn(Ext.log, 'warn');
      });
  
      describe('setAttributes', function () {
          var draw;
  
          afterEach(function () {
              Ext.destroy(draw);
          });
  
          it('should filter out attributes properly', function () {
              // This actually tests many things: the setAttributes method of the sprite,
              // the abstract and Animation modifiers and the Animator class.
              var animationend;
  
              draw = new Ext.draw.Container({
                  renderTo: document.body,
                  width: 200,
                  height: 200
              });
              var surface = draw.getSurface();
              var circle = new Ext.draw.sprite.Circle({
                  r: 10,
                  cx: 100,
                  cy: 100,
                  fillStyle: 'red',
                  strokeStyle: 'none'
              });
  
              var animation = circle.getAnimation();
              animation.setDuration(250);
              animation.on('animationend', function () {
                  animationend = true;
              });
  
              surface.add(circle);
  
              circle.setAttributes({
                  r: 90
              });
  
              surface.renderFrame();
  
              waitsFor(function () {
                  return animationend;
              });
  
              runs(function () {
                  expect(circle.attr.r).toBe(90);
                  animationend = false;
                  circle.setAttributes({
                      r: 50
                  });
                  // The call below should compare the value being set with the target value
                  // at the end of animation, not the current value, which is still 90.
                  // If this is buggy, the attribute won't be set and the animation to 50
                  // will be performed instead.
                  circle.setAttributes({
                      r: 90
                  });
                  surface.renderFrame();
              });
  
              waitsFor(function () {
                  return animationend;
              });
  
              runs(function () {
                  expect(circle.attr.r).toBe(90);
              });
          });
      });
  
      describe('surface', function () {
          var surface;
  
          it('should remove itself from the old surface', function () {
              surface = new Ext.draw.Surface({
                  items: {
                      type: 'rect',
                      id: 'rect',
                      x: 50,
                      y: 50,
                      width: 100,
                      height: 100,
                      fillStyle: 'orange'
                  }
              });
              var sprite = surface.get('rect');
              expect(surface.getItems().length).toBe(1);
              sprite.setSurface(null);
              expect(surface.getItems().length).toBe(0);
          });
  
          afterEach(function () {
              Ext.destroy(surface);
          });
      });
  
      describe('transformation matrix calculation', function () {
          describe('default centers of scaling and rotation', function () {
              it('should apply transformation in the following order: scale, rotate, translate', function () {
                  var theta = Math.PI / 2,
                      sin = Math.sin(theta),
                      cos = Math.cos(theta),
                      left = 100,
                      top = 100,
                      width = 100,
                      height = 100,
                      sx = 2,
                      sy = 0.5,
                      tx = 100,
                      ty = 50,
                      centerX = left + width / 2,
                      centerY = top + height / 2;
  
                  var rect = new Ext.draw.sprite.Rect({
                      x: left,
                      y: top,
                      width: width,
                      height: height,
                      rotationRads: theta,
                      scalingX: sx,
                      scalingY: sy,
                      translationX: tx,
                      translationY: ty
                  });
  
                  var referenceMatrix = [
                      cos * sx, sin * sx,
                      -sin * sy, cos * sy,
                      cos * (centerX * (1 - sx) - centerX) - sin * (centerY * (1 - sy) - centerY) + centerX + tx,
                      sin * (centerX * (1 - sx) - centerX) + cos * (centerY * (1 - sy) - centerY) + centerY + ty
                  ];
  
                  rect.applyTransformations(true);
                  expect(rect.attr.matrix.elements).toEqual(referenceMatrix);
              });
          });
          describe('custom centers of scaling and rotation', function () {
              it('should apply transformation in the following order: scale, rotate, translate', function () {
                  var theta = Math.PI / 2,
                      sin = Math.sin(theta),
                      cos = Math.cos(theta),
                      left = 100,
                      top = 100,
                      width = 100,
                      height = 100,
                      sx = 2,
                      sy = 0.5,
                      tx = 100,
                      ty = 50,
                      scalingCenterX = 50,
                      scalingCenterY = 50,
                      rotationCenterX = 150,
                      rotationCenterY = 150;
  
                  var rect = new Ext.draw.sprite.Rect({
                      x: left,
                      y: top,
                      width: width,
                      height: height,
                      rotationRads: theta,
                      scalingX: sx,
                      scalingY: sy,
                      translationX: tx,
                      translationY: ty,
                      rotationCenterX: rotationCenterX,
                      rotationCenterY: rotationCenterY,
                      scalingCenterX: scalingCenterX,
                      scalingCenterY: scalingCenterY
                  });
  
                  var referenceMatrix = [
                      cos * sx, sin * sx,
                      -sin * sy, cos * sy,
                      cos * (scalingCenterX * (1 - sx) - rotationCenterX) - sin * (scalingCenterY * (1 - sy) - rotationCenterY) + rotationCenterX + tx,
                      sin * (scalingCenterX * (1 - sx) - rotationCenterX) + cos * (scalingCenterY * (1 - sy) - rotationCenterY) + rotationCenterY + ty
                  ];
  
                  rect.applyTransformations(true);
                  expect(rect.attr.matrix.elements).toEqual(referenceMatrix);
              });
          });
      });
  
      describe('setTransform', function () {
          // This is a result of scaling by (2.5, 7.5), rotating by Math.PI/4 and translating by (3,4).
          var elements = [1.76776695, 1.76776695, -5.30330086, 5.30330086, 3, 4],
              sprite;
  
          beforeEach(function () {
              sprite = new Ext.draw.sprite.Rect();
          });
  
          afterEach(function () {
              Ext.destroy(sprite);
          });
  
          it("should use the given elements for the transformation matrix of the sprite", function () {
              sprite.setTransform(elements);
              var matrixElements = sprite.attr.matrix.elements;
  
              expect(matrixElements).toEqual(elements);
          });
          it("should mark the sprite and its parent as dirty", function () {
              var drawContainer = new Ext.draw.Container({
                  renderTo: Ext.getBody(),
                  width: 200,
                  height: 200
              });
              var surface = drawContainer.getSurface();
              expect(surface.getDirty()).toBe(false);
              surface.add(sprite);
              expect(surface.getDirty()).toBe(true);
              surface.renderFrame();
              expect(surface.getDirty()).toBe(false);
              sprite.setTransform(elements);
              expect(sprite.attr.dirty).toBe(true);
              expect(sprite.getParent().getDirty()).toBe(true);
  
              drawContainer.destroy();
          });
          it("should properly calculate the inverse matrix from the given matrix", function () {
              sprite.setTransform(elements);
              var inverseMatrixElements = sprite.attr.inverseMatrix.elements,
                  precision = 8;
  
              expect(inverseMatrixElements[0]).toBeCloseTo(0.28284271, precision);
              expect(inverseMatrixElements[1]).toBeCloseTo(-0.0942809, precision);
              expect(inverseMatrixElements[2]).toBeCloseTo(0.28284271, precision);
              expect(inverseMatrixElements[3]).toBeCloseTo(0.0942809, precision);
              expect(inverseMatrixElements[4]).toBeCloseTo(-1.97989899, precision);
              expect(inverseMatrixElements[5]).toBeCloseTo(-0.0942809, precision);
          });
          it("should mark bbox transform as dirty", function () {
              sprite.setTransform(elements);
              expect(sprite.attr.bbox.transform.dirty).toBe(true);
          });
          it("should not update the transformation attributes by default", function () {
              var attr = sprite.attr,
                  rotationRads = attr.rotationRads,
                  rotationCenterX = attr.rotationCenterX,
                  rotationCenterY = attr.rotationCenterY,
                  scalingX = attr.scalingX,
                  scalingY = attr.scalingY,
                  scalingCenterX = attr.scalingCenterX,
                  scalingCenterY = attr.scalingCenterY,
                  translationX = attr.translationX,
                  translationY = attr.translationY;
  
              sprite.setTransform(elements);
  
              expect(attr.rotationRads).toEqual(rotationRads);
              expect(attr.rotationCenterX).toEqual(rotationCenterX);
              expect(attr.rotationCenterY).toEqual(rotationCenterY);
              expect(attr.scalingX).toEqual(scalingX);
              expect(attr.scalingY).toEqual(scalingY);
              expect(attr.scalingCenterX).toEqual(scalingCenterX);
              expect(attr.scalingCenterY).toEqual(scalingCenterY);
              expect(attr.translationX).toEqual(translationX);
              expect(attr.translationY).toEqual(translationY);
          });
          it("should update the transformation attributes, if explicitly asked", function () {
              var attr = sprite.attr,
                  precision = 8;
  
              sprite.setTransform(elements, true);
  
              expect(attr.rotationRads).toBeCloseTo(Math.PI / 4, precision);
              expect(attr.rotationCenterX).toEqual(0);
              expect(attr.rotationCenterY).toEqual(0);
              expect(attr.scalingX).toBeCloseTo(2.5, precision);
              expect(attr.scalingY).toBeCloseTo(7.5, precision);
              expect(attr.scalingCenterX).toEqual(0);
              expect(attr.scalingCenterY).toEqual(0);
              expect(attr.translationX).toEqual(3);
              expect(attr.translationY).toEqual(4);
          });
          it("should not modify the given array", function () {
              sprite.setTransform(elements);
              sprite.attr.matrix.rotate(Math.PI / 4);
  
              expect(elements).toEqual([1.76776695, 1.76776695, -5.30330086, 5.30330086, 3, 4]);
          });
          it("should return the sprite itself", function () {
              var result = sprite.transform([1, 0, 0, 1, 100, 100]);
              expect(result).toEqual(sprite);
          });
      });
  
      describe('resetTransform', function () {
          var spriteConfig = {
              type: 'rect',
              x: 0,
              y: 0,
              width: 100,
              height: 100,
              rotationCenterX: 0,
              rotationCenterY: 0,
              rotationRads: Math.PI / 3,
              scalingCenterX: 0,
              scalingCenterY: 0,
              scalingX: 2,
              scalingY: 3,
              translationX: 50,
              translationY: 50
          };
  
          it("should mark the sprite and its parent as dirty", function () {
              var drawContainer = new Ext.draw.Container({
                  renderTo: Ext.getBody(),
                  width: 200,
                  height: 200
              });
              var surface = drawContainer.getSurface();
              expect(surface.getDirty()).toBe(false);
              var sprite = surface.add(spriteConfig);
              expect(surface.getDirty()).toBe(true);
              surface.renderFrame();
              expect(surface.getDirty()).toBe(false);
              sprite.resetTransform();
              expect(sprite.attr.dirty).toBe(true);
              expect(sprite.getParent().getDirty()).toBe(true);
  
              drawContainer.destroy();
          });
  
          it("should reset the transformation matrix and its reverse to the identity matrix", function () {
              var sprite = new Ext.draw.sprite.Rect(spriteConfig),
                  identityMatrixElements = [1, 0, 0, 1, 0, 0];
  
              sprite.applyTransformations(true);
              expect(sprite.attr.matrix.elements).not.toEqual(identityMatrixElements);
              sprite.resetTransform();
  
              expect(sprite.attr.matrix.elements).toEqual(identityMatrixElements);
              expect(sprite.attr.inverseMatrix.elements).toEqual(identityMatrixElements);
  
              sprite.destroy();
          });
          it("should return the sprite itself", function () {
              var sprite = new Ext.draw.sprite.Rect(),
                  result = sprite.transform([1, 0, 0, 1, 100, 100]);
  
              expect(result).toEqual(sprite);
  
              sprite.destroy();
          });
      });
  
      describe('transform', function () {
          it("should multiply the given matrix with the current transformation matrix", function () {
              var sprite = new Ext.draw.sprite.Rect(),
                  precision = 12;
  
              sprite.attr.matrix.elements = [1, 2, 3, 4, 5, 6];
              sprite.transform([1, 2, 3, 4, 5, 6]);
  
              expect(sprite.attr.matrix.elements).toEqual([7, 10, 15, 22, 28, 40]);
  
              var inverseMatrixElements = sprite.attr.inverseMatrix.elements;
  
              expect(inverseMatrixElements[0]).toBeCloseTo(5.5, precision);
              expect(inverseMatrixElements[1]).toBeCloseTo(-2.5, precision);
              expect(inverseMatrixElements[2]).toBeCloseTo(-3.75, precision);
              expect(inverseMatrixElements[3]).toBeCloseTo(1.75, precision);
              expect(inverseMatrixElements[4]).toBeCloseTo(-4, precision);
              expect(inverseMatrixElements[5]).toBeCloseTo(0, precision);
          });
          it("should pre-multiply the current matrix with the given matrix", function () {
              var sprite = new Ext.draw.sprite.Rect(),
                  scale = [2, 0, 0, 3, 0, 0],
                  translate = [1, 0, 0, 1, 100, 100],
                  p = [2, 4],
                  tp;
  
              // Initially, sprite's matrix is identity matrix.
  
              // First scale the grid, then translate.
              // S * T * I = identity.prepend(translate).prepend(scale)
              sprite.transform(translate).transform(scale);
              expect(sprite.attr.matrix.elements).toEqual([2, 0, 0, 3, 200, 300]);
              tp = sprite.attr.matrix.transformPoint(p);
              // Transformed point in original grid coordinates.
              expect(tp).toEqual([204, 312]);
  
              sprite.resetTransform();
  
              // First translate the grid, then scale.
              // T * S * I = identity.prepend(scale).prepend(translate)
              sprite.transform(scale).transform(translate);
              expect(sprite.attr.matrix.elements).toEqual([2, 0, 0, 3, 100, 100]);
              tp = sprite.attr.matrix.transformPoint(p);
              expect(tp).toEqual([104, 112]);
  
              sprite.destroy();
          });
          it("should return the sprite itself", function () {
              var sprite = new Ext.draw.sprite.Rect(),
                  result = sprite.transform([1, 0, 0, 1, 100, 100]);
  
              expect(result).toEqual(sprite);
  
              sprite.destroy();
          });
      });
  
      describe('remove', function () {
          it("should remove itself from the surface, returning itself or null (if already removed)", function () {
              var surface = new Ext.draw.Surface({}),
                  sprite = new Ext.draw.sprite.Rect({}),
                  id = sprite.getId(),
                  result;
  
              surface.add(sprite);
              result = sprite.remove();
  
              expect(surface.getItems().length).toBe(0);
              expect(surface.get(id)).toBe(undefined);
              expect(result).toEqual(sprite);
  
              result = sprite.remove(); // sprite with no surface, expect not to throw
              expect(result).toBe(null);
  
              sprite.destroy();
              surface.destroy();
          });
      });
  
      describe('destroy', function () {
          it("should remove itself from the surface", function () {
              var surface = new Ext.draw.Surface({}),
                  sprite = new Ext.draw.sprite.Rect({}),
                  id = sprite.getId();
  
              surface.add(sprite);
              sprite.destroy();
  
              expect(surface.getItems().length).toBe(0);
              expect(surface.get(id)).toBe(undefined);
  
              surface.destroy();
          });
      });
  
      describe("isVisible", function () {
          var none = 'none',
              rgba_none = 'rgba(0,0,0,0)',
              sprite, surface, container;
  
          beforeEach(function () {
              container = new Ext.draw.Container({
                  renderTo: Ext.getBody()
              });
              surface = new Ext.draw.Surface();
              sprite = new Ext.draw.sprite.Rect({
                  hidden: false,
                  globalAlpha: 1,
                  fillOpacity: 1,
                  strokeOpacity: 1,
                  fillStyle: 'red',
                  strokeStyle: 'red'
              });
              surface.add(sprite);
              container.add(surface);
          });
  
          afterEach(function () {
              Ext.destroy(sprite, surface, container);
          });
  
          it("should return true if the sprite belongs to a visible parent, false otherwise", function () {
              expect(sprite.isVisible()).toBe(true);
  
              surface.remove(sprite);
              expect(sprite.isVisible()).toBe(false);
  
              var instancing = new Ext.draw.sprite.Instancing({
                  template: sprite
              });
              surface.add(instancing);
              expect(sprite.isVisible()).toBe(true);
  
              instancing.destroy();
          });
  
          it("should return false if the sprite belongs to a parent that doesn't belong to a surface", function () {
              var instancing = new Ext.draw.sprite.Instancing({
                  template: sprite
              });
              expect(sprite.isVisible()).toBe(false);
          });
  
          it("should return false in case the sprite is hidden", function () {
              sprite.hide();
              expect(sprite.isVisible()).toBe(false);
          });
  
          it("should return false in case the sprite has no fillStyle and strokeStyle, true otherwise", function () {
              sprite.setAttributes({
                  fillStyle: none
              });
              expect(sprite.isVisible()).toBe(true);
  
              sprite.setAttributes({
                  fillStyle: rgba_none
              });
              expect(sprite.isVisible()).toBe(true);
  
              sprite.setAttributes({
                  fillStyle: 'red',
                  strokeStyle: none
              });
              expect(sprite.isVisible()).toBe(true);
  
              sprite.setAttributes({
                  strokeStyle: rgba_none
              });
              expect(sprite.isVisible()).toBe(true);
  
              sprite.setAttributes({
                  fillStyle: none,
                  strokeStyle: none
              });
              expect(sprite.isVisible()).toBe(false);
  
              sprite.setAttributes({
                  fillStyle: none,
                  strokeStyle: rgba_none
              });
              expect(sprite.isVisible()).toBe(false);
  
              sprite.setAttributes({
                  fillStyle: rgba_none,
                  strokeStyle: none
              });
              expect(sprite.isVisible()).toBe(false);
  
              sprite.setAttributes({
                  fillStyle: rgba_none,
                  strokeStyle: rgba_none
              });
              expect(sprite.isVisible()).toBe(false);
          });
  
          it("should return false if the globalAlpha attribute is zero", function () {
              sprite.setAttributes({
                  globalAlpha: 0
              });
              expect(sprite.isVisible()).toBe(false);
          });
  
          it("should return false if both fill and stroke are completely transparent, true otherwise", function () {
              sprite.setAttributes({
                  fillOpacity: 0,
                  strokeOpacity: 0
              });
              expect(sprite.isVisible()).toBe(false);
  
              sprite.setAttributes({
                  fillOpacity: 0,
                  strokeOpacity: 0.01
              });
              expect(sprite.isVisible()).toBe(true);
  
              sprite.setAttributes({
                  fillOpacity: 0.01,
                  strokeOpacity: 0
              });
              expect(sprite.isVisible()).toBe(true);
          });
      });
  
      describe("hitTest", function () {
          var sprite, surface, container;
  
          beforeEach(function () {
              container = new Ext.draw.Container({
                  renderTo: Ext.getBody()
              });
              surface = new Ext.draw.Surface();
              sprite = new Ext.draw.sprite.Circle({
                  hidden: false,
                  globalAlpha: 1,
                  fillOpacity: 1,
                  strokeOpacity: 1,
                  fillStyle: 'red',
                  strokeStyle: 'red',
                  r: 100,
                  cx: 100,
                  cy: 100
              });
              surface.add(sprite);
              container.add(surface);
          });
  
          afterEach(function () {
              Ext.destroy(sprite, surface, container);
          });
  
          it("should return an object with the 'sprite' property set to the sprite itself, " +
              "if the sprite is visible and its bounding box is hit", function () {
              // Testing hitTest method of the abstract Sprite class.
              // Even though, (10,10) is not inside the circle, it's inside it's bounding box.
              var result = Ext.draw.sprite.Sprite.prototype.hitTest.call(sprite, [10, 10]);
              expect(result && result.sprite).toBe(sprite);
          });
  
          it("should return null, if the sprite's bounding box is hit, but the sprite is not visible", function () {
              var originalMethod = sprite.isVisible;
              sprite.isVisible = function () { return false; };
              var result = Ext.draw.sprite.Sprite.prototype.hitTest.call(sprite, [10, 10]);
              expect(result).toBe(null);
              sprite.isVisible = originalMethod;
          });
  
          it("should return null, if the sprite is visible, but it's bounding box is not hit", function () {
              var result = Ext.draw.sprite.Sprite.prototype.hitTest.call(sprite, [210, 210]);
              expect(result).toBe(null);
          });
      });
  
      describe("getAnimation", function () {
          it("should return the stored reference to the sprite's animation modifier", function () {
              var sprite = new Ext.draw.sprite.Rect();
  
              expect(sprite.getAnimation()).toEqual(sprite.modifiers.animation);
          });
      });
  
      describe("setAnimation", function () {
          it("should set the config of the Animation modifier of a sprite", function () {
              var sprite = new Ext.draw.sprite.Rect();
              var config = {
                  duration: 2000,
                  easing: 'bounceOut',
                  customEasings: {
                      x: 'linear'
                  },
                  customDurations: {
                      y: 1000
                  }
              };
  
              sprite.setAnimation(config);
  
              var actualConfig = sprite.modifiers.animation.getInitialConfig();
              expect(actualConfig.duration).toEqual(config.duration);
              expect(actualConfig.easing).toEqual(config.easing);
              expect(actualConfig.customEasings).toEqual(config.customEasings);
              expect(actualConfig.customDurations).toEqual(config.customDurations);
          });
      });
  
  });
  