text: (function() {
    var canvas = this.canvas = new fabric.Canvas();
    QUnit.module('fabric.Textbox', {
      afterEach: function() {
        canvas.clear();
      }
    });
  
    var TEXTBOX_OBJECT = {
      version: fabric.version,
      type: 'textbox',
      originX: 'left',
      originY: 'top',
      left: 0,
      top: 0,
      width: 20,
      height: 45.2,
      fill: 'rgb(0,0,0)',
      stroke: null,
      strokeWidth: 1,
      strokeDashArray: null,
      strokeLineCap: 'butt',
      strokeDashOffset: 0,
      strokeLineJoin: 'miter',
      strokeMiterLimit: 4,
      scaleX: 1,
      scaleY: 1,
      angle: 0,
      flipX: false,
      flipY: false,
      opacity: 1,
      shadow: null,
      visible: true,
      text: 'x',
      fontSize: 40,
      fontWeight: 'normal',
      fontFamily: 'Times New Roman',
      fontStyle: 'normal',
      lineHeight: 1.16,
      underline: false,
      overline: false,
      linethrough: false,
      textAlign: 'left',
      backgroundColor: '',
      textBackgroundColor: '',
      fillRule: 'nonzero',
      paintFirst: 'fill',
      globalCompositeOperation: 'source-over',
      skewX: 0,
      skewY: 0,
      charSpacing: 0,
      styles: { },
      minWidth: 20,
      splitByGrapheme: false,
      strokeUniform: false,
      path: null,
      direction: 'ltr',
      pathStartOffset:            0,
      pathSide:                   'left',
    };
  
    QUnit.test('constructor', function(assert) {
      var textbox = new fabric.Textbox('test');
      assert.ok(textbox instanceof fabric.Textbox);
      assert.ok(textbox instanceof fabric.IText);
      assert.ok(textbox instanceof fabric.Text);
    });
  
    QUnit.test('constructor with width', function(assert) {
      var textbox = new fabric.Textbox('test', { width: 400 });
      assert.equal(textbox.width, 400, 'width is taken by contstructor');
    });
  
    QUnit.test('constructor with width too small', function(assert) {
      var textbox = new fabric.Textbox('test', { width: 5 });
      assert.equal(Math.round(textbox.width), 56, 'width is calculated by constructor');
    });
  
    QUnit.test('initial properties', function(assert) {
      var textbox = new fabric.Textbox('test');
      assert.equal(textbox.text, 'test');
      assert.equal(textbox.type, 'textbox');
      assert.deepEqual(textbox.styles, { });
      assert.ok(textbox.cacheProperties.indexOf('width') > -1, 'width is in cacheProperties');
    });
  
    QUnit.test('toObject', function(assert) {
      var textbox = new fabric.Textbox('x');
      var obj = textbox.toObject();
      assert.deepEqual(obj, TEXTBOX_OBJECT, 'JSON OUTPUT MATCH');
    });
  
    QUnit.test('fromObject', function(assert) {
      var done = assert.async();
      fabric.Textbox.fromObject(TEXTBOX_OBJECT, function(textbox) {
        assert.equal(textbox.text, 'x', 'properties are respected');
        assert.ok(textbox instanceof fabric.Textbox, 'the generated object is a textbox');
        done();
      });
    });
  
    QUnit.test('isEndOfWrapping', function(assert) {
      var textbox = new fabric.Textbox('a q o m s g\np q r s t w', {
        width: 70,
      });
      assert.equal(textbox.isEndOfWrapping(0), false, 'first line is not end of wrapping');
      assert.equal(textbox.isEndOfWrapping(1), false, 'second line is not end of wrapping');
      assert.equal(textbox.isEndOfWrapping(2), true, 'line before an hard break is end of wrapping');
      assert.equal(textbox.isEndOfWrapping(3), false, 'line 3 is not end of wrapping');
      assert.equal(textbox.isEndOfWrapping(4), false, 'line 4 is not end of wrapping');
      assert.equal(textbox.isEndOfWrapping(5), true, 'last line is end of wrapping');
    });
  
    QUnit.test('_removeExtraneousStyles', function(assert) {
      var textbox = new fabric.Textbox('a q o m s g\np q r s t w', {
        width: 40,
        styles: {
          0: { 0: { fontSize: 4 } },
          1: { 0: { fontSize: 4 } },
          2: { 0: { fontSize: 4 } },
          3: { 0: { fontSize: 4 } },
          4: { 0: { fontSize: 4 } },
          5: { 0: { fontSize: 4 } },
        }
      });
      assert.deepEqual(textbox.styles[3], { 0: { fontSize: 4 } }, 'style line 3 exists');
      assert.deepEqual(textbox.styles[4], { 0: { fontSize: 4 } }, 'style line 4 exists');
      assert.deepEqual(textbox.styles[5], { 0: { fontSize: 4 } }, 'style line 5 exists');
      textbox._removeExtraneousStyles();
      assert.equal(textbox.styles[2], undefined, 'style line 2 has been removed');
      assert.equal(textbox.styles[3], undefined, 'style line 3 has been removed');
      assert.equal(textbox.styles[4], undefined, 'style line 4 has been removed');
      assert.equal(textbox.styles[5], undefined, 'style line 5 has been removed');
    });
  
    QUnit.test('isEmptyStyles', function(assert) {
      var textbox = new fabric.Textbox('x x', { width: 5, styles: { 0: { 0: { fill: 'red' } } } });
      assert.equal(textbox._textLines.length, 2, 'lines are wrapped');
      assert.equal(textbox._unwrappedTextLines.length, 1, 'there is only one text line');
      assert.equal(textbox.isEmptyStyles(), false, 'style is not empty');
      assert.equal(textbox.isEmptyStyles(0), false, 'style is not empty at line 0');
      assert.equal(textbox.isEmptyStyles(1), true, 'style is empty at line 1');
    });
  
    QUnit.test('isEmptyStyles does not crash on null styles', function(assert) {
      var textbox = new fabric.Textbox('x x', { width: 5 });
      textbox.styles = null;
      assert.equal(textbox._textLines.length, 2, 'lines are wrapped');
      assert.equal(textbox._unwrappedTextLines.length, 1, 'there is only one text line');
      assert.equal(textbox.isEmptyStyles(1), true, 'style is empty');
    });
  
    QUnit.test('isEmptyStyles alternate lines', function(assert) {
      var textbox = new fabric.Textbox('xa xb xc xd xe\nya yb', {
        width: 5,
        styles: {
          0: { 0: { fill: 'red' }, 1: { fill: 'blue' }, 9: { fill: 'red' }, 10: { fill: 'blue' } },
          1: { 3: { fill: 'red' }, 4: { fill: 'blue' } },
        }
      });
      assert.equal(textbox._textLines.length, 7, 'lines are wrapped');
      assert.equal(textbox._unwrappedTextLines.length, 2, 'there is only one text line');
      assert.equal(textbox.isEmptyStyles(), false, 'style is not empty');
      assert.equal(textbox.isEmptyStyles(0), false, 'style is not empty at line 0');
      assert.equal(textbox.isEmptyStyles(1), true, 'style is empty at line 1');
      assert.equal(textbox.isEmptyStyles(2), true, 'style is empty at line 2');
      assert.equal(textbox.isEmptyStyles(3), false, 'style is empty at line 3');
      assert.equal(textbox.isEmptyStyles(4), true, 'style is empty at line 4');
      assert.equal(textbox.isEmptyStyles(5), true, 'style is empty at line 5');
      assert.equal(textbox.isEmptyStyles(6), false, 'style is empty at line 6');
    });
    QUnit.test('wrapping with charspacing', function(assert) {
      var textbox = new fabric.Textbox('xa xb xc xd xe ya yb id', {
        width: 190,
      });
      assert.equal(textbox.textLines[0], 'xa xb xc xd', 'first line match expectations');
      textbox.charSpacing = 100;
      textbox.initDimensions();
      assert.equal(textbox.textLines[0], 'xa xb xc', 'first line match expectations spacing 100');
      textbox.charSpacing = 300;
      textbox.initDimensions();
      assert.equal(textbox.textLines[0], 'xa xb', 'first line match expectations spacing 300');
      textbox.charSpacing = 800;
      textbox.initDimensions();
      assert.equal(textbox.textLines[0], 'xa', 'first line match expectations spacing 800');
    });
    QUnit.test('wrapping with charspacing and splitByGrapheme positive', function(assert) {
      var textbox = new fabric.Textbox('xaxbxcxdeyaybid', {
        width: 190,
        splitByGrapheme: true,
        charSpacing: 400
      });
      assert.deepEqual(
        textbox.textLines,
        ['xaxbx', 'cxdey', 'aybid'],
        'lines match splitByGrapheme charSpacing 400'
      );
    });
    QUnit.test('wrapping with charspacing and splitByGrapheme negative', function(assert) {
      var textbox = new fabric.Textbox('xaxbxcxdeyaybid', {
        width: 190,
        splitByGrapheme: true,
        charSpacing: -100
      });
      assert.deepEqual(
        textbox.textLines,
        ['xaxbxcxdeyay', 'bid'],
        'lines match splitByGrapheme charSpacing -100'
      );
    });
    QUnit.test('wrapping with different things', function(assert) {
      var textbox = new fabric.Textbox('xa xb\txc\rxd xe ya yb id', {
        width: 16,
      });
      assert.equal(textbox.textLines[0], 'xa', '0 line match expectations');
      assert.equal(textbox.textLines[1], 'xb', '1 line match expectations');
      assert.equal(textbox.textLines[2], 'xc', '2 line match expectations');
      assert.equal(textbox.textLines[3], 'xd', '3 line match expectations');
      assert.equal(textbox.textLines[4], 'xe', '4 line match expectations');
      assert.equal(textbox.textLines[5], 'ya', '5 line match expectations');
      assert.equal(textbox.textLines[6], 'yb', '6 line match expectations');
    });
    QUnit.test('wrapping with splitByGrapheme', function(assert) {
      var textbox = new fabric.Textbox('xaxbxcxdxeyaybid', {
        width: 1,
        splitByGrapheme: true,
      });
      assert.equal(textbox.textLines[0], 'x', '0 line match expectations splitByGrapheme');
      assert.equal(textbox.textLines[1], 'a', '1 line match expectations splitByGrapheme');
      assert.equal(textbox.textLines[2], 'x', '2 line match expectations splitByGrapheme');
      assert.equal(textbox.textLines[3], 'b', '3 line match expectations splitByGrapheme');
      assert.equal(textbox.textLines[4], 'x', '4 line match expectations splitByGrapheme');
      assert.equal(textbox.textLines[5], 'c', '5 line match expectations splitByGrapheme');
    });
    QUnit.test('wrapping with custom space', function(assert) {
      var textbox = new fabric.Textbox('xa xb xc xd xe ya yb id', {
        width: 2000,
      });
      var line1 = textbox._wrapLine('xa xb xc xd xe ya yb id', 0, 100, 0);
      var expected1 =  [
        ['x', 'a', ' ', 'x', 'b'],
        ['x', 'c', ' ', 'x', 'd'],
        ['x', 'e', ' ', 'y', 'a'],
        ['y', 'b', ' ', 'i', 'd']];
      assert.deepEqual(line1, expected1, 'wrapping without reserved');
      assert.deepEqual(textbox.dynamicMinWidth, 40, 'wrapping without reserved');
      var line2 = textbox._wrapLine('xa xb xc xd xe ya yb id', 0, 100, 50);
      var expected2 =  [
        ['x', 'a'],
        ['x', 'b'],
        ['x', 'c'],
        ['x', 'd'],
        ['x', 'e'],
        ['y', 'a'],
        ['y', 'b'],
        ['i', 'd']];
      assert.deepEqual(line2, expected2, 'wrapping without reserved');
      assert.deepEqual(textbox.dynamicMinWidth, 90, 'wrapping without reserved');
    });
    QUnit.test('wrapping an empty line', function(assert) {
      var textbox = new fabric.Textbox('', {
        width: 10,
      });
      var line1 = textbox._wrapLine('', 0, 100, 0);
      assert.deepEqual(line1, [[]], 'wrapping without splitByGrapheme');
      textbox.splitByGrapheme = true;
      var line2 = textbox._wrapLine('', 0, 100, 0);
      assert.deepEqual(line2, [[]], 'wrapping with splitByGrapheme');
    });
    QUnit.test('texbox will change width from the mr corner', function(assert) {
      var text = new fabric.Textbox('xa xb xc xd xe ya yb id', { strokeWidth: 0 });
      canvas.add(text);
      canvas.setActiveObject(text);
      var canvasEl = canvas.getElement(),
          canvasOffset = fabric.util.getElementOffset(canvasEl);
      var eventStub = {
        clientX: canvasOffset.left + text.width,
        clientY: canvasOffset.top + text.oCoords.mr.corner.tl.y + 1,
        type: 'mousedown',
      };
      var originalWidth = text.width;
      canvas.__onMouseDown(eventStub);
      canvas.__onMouseMove({
        clientX: eventStub.clientX + 20,
        clientY: eventStub.clientY,
        type: 'mousemove',
      });
      canvas.__onMouseUp({
        clientX: eventStub.clientX + 20,
        clientY: eventStub.clientY,
        type: 'mouseup',
      });
      assert.equal(text.width, originalWidth + 20, 'width increased');
    });
    QUnit.test('texbox will change width from the ml corner', function(assert) {
      var text = new fabric.Textbox('xa xb xc xd xe ya yb id', { strokeWidth: 0, left: 40 });
      canvas.add(text);
      canvas.setActiveObject(text);
      var canvasEl = canvas.getElement(),
          canvasOffset = fabric.util.getElementOffset(canvasEl);
      var eventStub = {
        clientX: canvasOffset.left + text.left,
        clientY: canvasOffset.top + text.oCoords.ml.corner.tl.y + 2,
        type: 'mousedown',
      };
      var originalWidth = text.width;
      canvas.__onMouseDown(eventStub);
      canvas.__onMouseMove({
        clientX: eventStub.clientX - 20,
        clientY: eventStub.clientY,
        type: 'mousemove',
      });
      canvas.__onMouseUp({
        clientX: eventStub.clientX + 20,
        clientY: eventStub.clientY,
        type: 'mouseup',
      });
      assert.equal(text.width, originalWidth + 20, 'width increased');
    });
    QUnit.test('_removeExtraneousStyles', function(assert) {
      var iText = new fabric.Textbox('a\nq\qo', { styles: {
        0: { 0: { fontSize: 4 } },
        1: { 0: { fontSize: 4 } },
        2: { 0: { fontSize: 4 } },
        3: { 0: { fontSize: 4 } },
        4: { 0: { fontSize: 4 } },
      } });
      assert.deepEqual(iText.styles[3], { 0: { fontSize: 4 } }, 'style line 3 exists');
      assert.deepEqual(iText.styles[4], { 0: { fontSize: 4 } }, 'style line 4 exists');
      iText._removeExtraneousStyles();
      assert.equal(iText.styles[3], undefined, 'style line 3 has been removed');
      assert.equal(iText.styles[4], undefined, 'style line 4 has been removed');
    });
  
    QUnit.test('get2DCursorLocation with splitByGrapheme', function(assert) {
      var iText = new fabric.Textbox('aaaaaaaaaaaaaaaaaaaaaaaa',
        { width: 60, splitByGrapheme: true });
      var loc = iText.get2DCursorLocation();
  
      // [ [ '由', '石', '墨' ],
      //   [ '分', '裂', '的' ],
      //   [ '石', '墨', '分' ],
      //   [ '裂', '由', '石' ],
      //   [ '墨', '分', '裂' ],
      //   [ '由', '石', '墨' ],
      //   [ '分', '裂', '的' ],
      //   [ '石', '墨', '分' ],
      //   [ '裂' ] ]
  
      assert.equal(loc.lineIndex, 0);
      assert.equal(loc.charIndex, 0);
  
      // '由石墨|分裂的石墨分裂由石墨分裂由石墨分裂的石墨分裂'
      iText.selectionStart = iText.selectionEnd = 4;
      loc = iText.get2DCursorLocation();
  
      assert.equal(loc.lineIndex, 1, 'selection end 4 line 1');
      assert.equal(loc.charIndex, 1, 'selection end 4 char 1');
  
      iText.selectionStart = iText.selectionEnd = 7;
      loc = iText.get2DCursorLocation();
  
      assert.equal(loc.lineIndex, 2, 'selection end 7 line 2');
      assert.equal(loc.charIndex, 1, 'selection end 7 char 1');
  
      iText.selectionStart = iText.selectionEnd = 14;
      loc = iText.get2DCursorLocation();
  
      assert.equal(loc.lineIndex, 4, 'selection end 14 line 4');
      assert.equal(loc.charIndex, 2, 'selection end 14 char 2');
    });
  
    QUnit.test('missingNewlineOffset with splitByGrapheme', function(assert) {
      var textbox = new fabric.Textbox('aaa\naaaaaa\na\naaaaaaaaaaaa\naaa',
        { width: 80, splitByGrapheme: true });
  
      // [ [ 'a', 'a', 'a' ],
      //   [ 'a', 'a', 'a', 'a' ],
      //   [ 'a', 'a' ],
      //   [ 'a' ],
      //   [ 'a', 'a', 'a', 'a' ],
      //   [ 'a', 'a', 'a', 'a' ],
      //   [ 'a', 'a', 'a', 'a' ],
      //   [ 'a', 'a', 'a' ] ]
  
      var offset = textbox.missingNewlineOffset(0);
      assert.equal(offset, 1, 'line 0 is interrupted by a \n so has an offset of 1');
  
      offset = textbox.missingNewlineOffset(1);
      assert.equal(offset, 0, 'line 1 is wrapped without a \n so it does have an extra char count');
    });
  
    QUnit.test('missingNewlineOffset with normal split', function(assert) {
      var texbox = new fabric.Textbox('aaa\naaaaaa\na\naaaaaaaaaaaa\naaa',
        { width: 160 });
  
      var offset = texbox.missingNewlineOffset(0);
      assert.equal(offset, 1, 'it returns always 1');
      var offset = texbox.missingNewlineOffset(1);
      assert.equal(offset, 1, 'it returns always 1');
      var offset = texbox.missingNewlineOffset(2);
      assert.equal(offset, 1, 'it returns always 1');
    });
  
    QUnit.test('_getLineStyle', function(assert) {
      var textbox = new fabric.Textbox('aaa aaq ggg gg\noee eee', {
        styles: {
          1: { 0: { fontSize: 4 } },
        },
        width: 80,
      });
  
      assert.equal(textbox._getLineStyle(0), false, 'wrapped line 0 has no style');
      assert.equal(textbox._getLineStyle(1), false, 'wrapped line 1 has no style');
      assert.equal(textbox._getLineStyle(4), true, 'wrapped line 2 has style');
    });
  
    QUnit.test('_setLineStyle', function(assert) {
      var textbox = new fabric.Textbox('aaa aaq ggg gg\noee eee', {
        styles: {
          1: { 0: { fontSize: 4 } },
        },
        width: 80,
      });
  
      assert.equal(textbox._getLineStyle(0), false, 'wrapped line 0 has no style');
      assert.equal(textbox._getLineStyle(1), false, 'wrapped line 1 has no style');
      assert.equal(textbox._getLineStyle(2), false, 'wrapped line 2 has no style');
      assert.equal(textbox._getLineStyle(3), false, 'wrapped line 3 has no style');
  
      assert.deepEqual(textbox.styles[0], undefined, 'style is undefined');
      textbox._setLineStyle(0);
  
      assert.equal(textbox._getLineStyle(0), true, 'wrapped line 0 has style');
      assert.equal(textbox._getLineStyle(1), true, 'wrapped line 1 has style');
      assert.equal(textbox._getLineStyle(2), true, 'wrapped line 2 has style');
      assert.equal(textbox._getLineStyle(3), true, 'wrapped line 3 has style');
  
      assert.deepEqual(textbox.styles[0], {}, 'style is an empty object');
    });
  
    QUnit.test('_deleteStyleDeclaration', function(assert) {
      var textbox = new fabric.Textbox('aaa aaq ggg gg oee eee', {
        styles: {
          0: {
            0: { fontSize: 4 },
            1: { fontSize: 4 },
            2: { fontSize: 4 },
            3: { fontSize: 4 },
            4: { fontSize: 4 },
            5: { fontSize: 4 },
            6: { fontSize: 4 },
            7: { fontSize: 4 },
            8: { fontSize: 4 },
            9: { fontSize: 4 },
            10: { fontSize: 4 },
            11: { fontSize: 4 },
            12: { fontSize: 4 },
            13: { fontSize: 4 },
            14: { fontSize: 4 },
            15: { fontSize: 4 },
            16: { fontSize: 4 },
          },
        },
        width: 5,
      });
      textbox._deleteStyleDeclaration(2, 2);
      assert.equal(textbox.styles[0][10], undefined, 'style has been removed');
    });
  
    QUnit.test('_setStyleDeclaration', function(assert) {
      var textbox = new fabric.Textbox('aaa aaq ggg gg oee eee', {
        styles: {
          0: {
            0: { fontSize: 4 },
            1: { fontSize: 4 },
            2: { fontSize: 4 },
            3: { fontSize: 4 },
            4: { fontSize: 4 },
            5: { fontSize: 4 },
            6: { fontSize: 4 },
            7: { fontSize: 4 },
            8: { fontSize: 4 },
            9: { fontSize: 4 },
            10: { fontSize: 4 },
            11: { fontSize: 4 },
            12: { fontSize: 4 },
            13: { fontSize: 4 },
            14: { fontSize: 4 },
            15: { fontSize: 4 },
            16: { fontSize: 4 },
          },
        },
        width: 5,
      });
      assert.equal(typeof textbox._setStyleDeclaration, 'function', 'function exists');
      var newStyle = { fontSize: 10 };
      textbox._setStyleDeclaration(2, 2, newStyle);
      assert.equal(textbox.styles[0][10], newStyle, 'style has been changed');
    });
  
    QUnit.test('styleHas', function(assert) {
      var textbox = new fabric.Textbox('aaa aaq ggg gg oee eee', {
        styles: {
          0: {
            0: { fontSize: 4 },
            1: { fontSize: 4 },
            2: { fontSize: 4 },
            4: { fontFamily: 'Arial' },
            5: { fontFamily: 'Arial' },
            6: { fontFamily: 'Arial' },
          },
        },
        width: 5,
      });
      assert.equal(textbox.styleHas('fontSize'), true, 'style has fontSize');
      assert.equal(textbox.styleHas('fontSize', 0), true, 'style has fontSize on line 0');
      // assert.equal(textbox.styleHas('fontSize', 1), false, 'style does not have fontSize on line 1');
      assert.equal(textbox.styleHas('fontFamily'), true, 'style has fontFamily');
      // assert.equal(textbox.styleHas('fontFamily', 0), false, 'style does not have fontFamily on line 0');
      assert.equal(textbox.styleHas('fontFamily', 1), true, 'style has fontFamily on line 1');
    });
  
    QUnit.test('The same text does not need to be wrapped.', function(assert) {
      var str = '0123456789';
      var measureTextbox = new fabric.Textbox(str, {
        fontSize: 20,
        splitByGrapheme: false,
      });
      var newTextbox = new fabric.Textbox(str, {
        width: measureTextbox.width,
        fontSize: 20,
        splitByGrapheme: true,
      });
      assert.equal(newTextbox.textLines.length, measureTextbox.textLines.length, 'The same text is not wrapped');
    });
  
  })();
  