Repository: fabricjs/fabric.js
Pull request #9336 (2023-09-14 06:31:51)

fix(Canvas): `setActiveObject` should update `canvas#_activeSelection`
before:
```
const rect1 = new Rect({ top: 1, left: 1, width: 1, height: 1 });
const rect2 = new Rect({ top: 1, left: 1, width: 1, height: 1 });
canvas.add(rect1, rect2);

const arr = canvas.getObjects();
const ac = new ActiveSelection(arr, { canvas });
canvas.setActiveObject(ac);
console.log(canvas.getActiveObjects()); // [ActiveSelection]

```


after:
```
const rect1 = new Rect({ top: 1, left: 1, width: 1, height: 1 });
const rect2 = new Rect({ top: 1, left: 1, width: 1, height: 1 });
canvas.add(rect1, rect2);

const arr = canvas.getObjects();
const ac = new ActiveSelection(arr, { canvas });
canvas.setActiveObject(ac);
console.log(canvas.getActiveObjects()); // [Rect, Rect]

```

asturur: eh right.
So what are we fixing is not getObjects but the fact that using setActiveObject on an active selection right now bring to unexpected results.
ok this is mostly good, but let's start from the title that should be fix _setActiveObject.

Said so the `readOnly` was put there for a reason, i m not sure if we want to forbid devs to swap the active selection now
zhe-he: If we want to ensure that activeSelection is only readonly, we need to split the object when setting it, and we also need to consider event triggering, something like this?

```
setActiveObject(object: FabricObject) {
    ..
    if (object instanceof ActiveSelection) {
          const objs = object.getObjects();
          const lastObjs = this._activeSelection.getObjects();
          const dels = [];
          for (const item of lastObjs) {
              const fIndex = objs.indexOf(item);
              if (fIndex != -1) {
                   objs.splice(fIndex, 1);
                   continue;
              }
              dels.push(item);
          }
          const adds = objs;
          for (const item of dels) {
               this._activeSelection.remove(item);
          }
          this._activeSelection.multiSelectAdd(adds);
    }
     ...
},
_setActiveObject(object: FabricObject) {
   ...
    if (object instanceof ActiveSelection) {
          this._activeSelection.removeAll();
         this._activeSelection.add(object.getObjects());
    }
   ...
}
```
The incomplete code above will trigger add and delete callback events.
So do we need the _add and _remove methods to support addition and deletion without triggering events?

ShaMan123: > If we want to ensure that activeSelection is only readonly

There is no need, I didn't consider this use case when I made it readonly
ShaMan123: Ahh I think we need to call `removeAll` `dispose` and to unset the canvas ref on the old active selection
zhe-he: If the user actively chooses to cancel.
```
canvas.setActiveObject(A);         // fire: selection:created
canvas._discardActiveObject();
canvas.setActiveObject(B);         // fire: selection:updated
```

```
canvas.setActiveObject(A);        // fire: selection:created
canvas.discardActiveObject();   // fire: selection:cleared
canvas.setActiveObject(B);        // fire: selection:updated
```
Do we need to help the user cancel it when setting it up? 
If we help the user cancel when setting, do we need to trigger the element's remove event?
ShaMan123: good questions.
I am sure that if the new active selection if not equal to the prev active seelection we need to clean the prev one.
I would say that if the active object was an active selection and after calling `setActiveObject` it remains an active selection (even if a different one) the only selection event that should fire is `selection:updated` but that is only my opinion.
ShaMan123: Also consider
```
canvas.setActiveObject(A);        // fire: selection:created
canvas.setActiveObject(B);        // fire: selection:updated??
```
zhe-he: em... Do I need to modify and submit it like this?

```
setActiveObject(object: FabricObject, e?: TPointerEvent) {
    // we can't inline this, since _setActiveObject will change what getActiveObjects returns
    const currentActives = this.getActiveObjects();
    const discardSelectionObjects = this._getDiscardSelectionObjects(object);
    discardSelectionObjects.forEach(o => {
      if (currentActives.includes(o)) return;
      currentActives.push(o);
    });
    const selected = this._setActiveObject(object, e);
    this._fireSelectionEvents(currentActives, e);
    return selected;
  }

  private _getDiscardSelectionObjects(object: FabricObject) {
    if (!(object instanceof ActiveSelection)) return [];
    const last = this._activeSelection.getObjects();
    const cur = object.getObjects();
    const res = [];
    for (const item of last) {
      if (cur.includes(item)) continue;
      res.push(item);
    }
    return res;
  }

```
ShaMan123: 

> I am sure that if the new active selection if not equal to the prev active selection we need to clean the prev one.

My bad, this is incorrect. If an object was present in the active selection it should be removed by fabric once added to a new one, I think I need to fix a small thing about that.
So we should only unset the canvas ref on the prev active selection.

We should add a test for this specific case to see all is as expected including fired events.

asturur: Yes i didn't mean it needs to be readonly, i meant to bring up the point that there is a readonly and to think why.
ShaMan123: There is a bug I will look into because I think I caused it.
But also we still need to relove the events etc.
ShaMan123: > There is a bug I will look into because I think I caused it. But also we still need to relove the events etc.

Fixed.
The selection events are not related because they use `getActiveObjects` and there are tests for that
ShaMan123: > > There is a bug I will look into because I think I caused it. But also we still need to relove the events etc.
> 
> Fixed. The selection events are not related because they use `getActiveObjects` and there are tests for that

@asturur I have found another related bug handled in #9349 
I can merge it here but do not want to pollute this PR
zhe-he: > The change request i made is optional, it doesn't change anything is just some bytes saved

done.
asturur: Since my english is never understood, from now on i will close PRs that try to fix 2 bugs in one pr.
Let's keep this to one bug.
ShaMan123: > Since my english is never understood, from now on i will close PRs that try to fix 2 bugs in one pr. Let's keep this to one bug.

Your English is well understood most of the time
I have opened a PR
ShaMan123: @asturur you want anything else? Or I merge
ShaMan123: @zhe-he Thanks, XieXie

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 52d43f42d..d549e657b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,11 @@
 # Changelog
 
+- fix(Canvas): `setActiveObject` should update `canvas#_activeSelection` [#9336](https://github.com/fabricjs/fabric.js/pull/9336)
+
 ## [next]
 
+- patch(Coords): calc oCoords only with canvas ref [#9380](https://github.com/fabricjs/fabric.js/pull/9380)
+- patch(Control): pass object to `calcCornerCoords` [#9376](https://github.com/fabricjs/fabric.js/pull/9376)
 - fix(Canvas): invalidate `_objectsToRender` on stack change [#9387](https://github.com/fabricjs/fabric.js/pull/9387)
 - ci(e2e): fix babel compiling error [#9388](https://github.com/fabricjs/fabric.js/pull/9388)
 - Breaking: Remove node 14 [#9383](https://github.com/fabricjs/fabric.js/pull/9383)
@@ -16,6 +20,7 @@
 - chore(): cleanup logs and error messages [#9369](https://github.com/fabricjs/fabric.js/pull/9369)
 - feature(Object) BREAKING: Remove lines parameter from object.containsPoint [#9375](https://github.com/fabricjs/fabric.js/pull/9375)
 - patch(Control): move hit detection to shouldActivate [#9374](https://github.com/fabricjs/fabric.js/pull/9374)
+- fix(Control): method binding for mouseUpHandler, mouseDownHandler, and actionHandler [#9370](https://github.com/fabricjs/fabric.js/pull/9370)
 - fix(StaticCanvas): disposing animations [#9361](https://github.com/fabricjs/fabric.js/pull/9361)
 - fix(IText): cursor width under group [#9341](https://github.com/fabricjs/fabric.js/pull/9341)
 - TS(Canvas): constructor optional el [#9348](https://github.com/fabricjs/fabric.js/pull/9348)
diff --git a/src/canvas/Canvas.ts b/src/canvas/Canvas.ts
index 27df5efd1..603c71831 100644
--- a/src/canvas/Canvas.ts
+++ b/src/canvas/Canvas.ts
@@ -844,12 +844,12 @@ export class Canvas extends SelectableCanvas implements CanvasOptions {
         this.setActiveObject(target, e);
         shouldRender = true;
       } else {
-        const control = target.controls[corner as string];
+        const control = target.controls[corner];
         const mouseUpHandler =
           control && control.getMouseUpHandler(e, target, control);
         if (mouseUpHandler) {
           pointer = this.getPointer(e);
-          mouseUpHandler(e, transform!, pointer.x, pointer.y);
+          mouseUpHandler.call(control, e, transform!, pointer.x, pointer.y);
         }
       }
       target.isMoving = false;
@@ -871,7 +871,13 @@ export class Canvas extends SelectableCanvas implements CanvasOptions {
           );
       pointer = pointer || this.getPointer(e);
       originalMouseUpHandler &&
-        originalMouseUpHandler(e, transform, pointer.x, pointer.y);
+        originalMouseUpHandler.call(
+          originalControl,
+          e,
+          transform,
+          pointer.x,
+          pointer.y
+        );
     }
     this._setCursorFromEvent(e, target);
     this._handleEvent(e, 'up', LEFT_CLICK, isClick);
@@ -1132,7 +1138,13 @@ export class Canvas extends SelectableCanvas implements CanvasOptions {
           mouseDownHandler =
             control && control.getMouseDownHandler(e, target, control);
         if (mouseDownHandler) {
-          mouseDownHandler(e, this._currentTransform!, pointer.x, pointer.y);
+          mouseDownHandler.call(
+            control,
+            e,
+            this._currentTransform!,
+            pointer.x,
+            pointer.y
+          );
         }
       }
     }
diff --git a/src/canvas/SelectableCanvas.ts b/src/canvas/SelectableCanvas.ts
index 2eb88f853..98a11ce72 100644
--- a/src/canvas/SelectableCanvas.ts
+++ b/src/canvas/SelectableCanvas.ts
@@ -293,7 +293,7 @@ export class SelectableCanvas<EventSpec extends CanvasEvents = CanvasEvents>
   protected declare _isCurrentlyDrawing: boolean;
   declare freeDrawingBrush?: BaseBrush;
   declare _activeObject?: FabricObject;
-  protected readonly _activeSelection: ActiveSelection;
+  protected _activeSelection: ActiveSelection;
 
   constructor(
     el?: string | HTMLCanvasElement,
@@ -598,7 +598,7 @@ export class SelectableCanvas<EventSpec extends CanvasEvents = CanvasEvents>
       control = !!corner && target.controls[corner],
       actionHandler =
         alreadySelected && control
-          ? control.getActionHandler(e, target, control)
+          ? control.getActionHandler(e, target, control)?.bind(control)
           : dragHandler,
       action = getActionFromCorner(alreadySelected, corner, e, target),
       origin = this._getOriginFromCorner(target, corner),
@@ -1086,6 +1086,12 @@ export class SelectableCanvas<EventSpec extends CanvasEvents = CanvasEvents>
     }
     this._activeObject = object;
 
+    if (object instanceof ActiveSelection && this._activeSelection !== object) {
+      this._activeSelection = object;
+      object.set('canvas', this);
+      object.setCoords();
+    }
+
     return true;
   }
 
@@ -1161,7 +1167,7 @@ export class SelectableCanvas<EventSpec extends CanvasEvents = CanvasEvents>
    */
   destroy() {
     // dispose of active selection
-    const activeSelection = this._activeSelection!;
+    const activeSelection = this._activeSelection;
     activeSelection.removeAll();
     // @ts-expect-error disposing
     this._activeSelection = undefined;
diff --git a/src/controls/Control.spec.ts b/src/controls/Control.spec.ts
new file mode 100644
index 000000000..6ddc39224
--- /dev/null
+++ b/src/controls/Control.spec.ts
@@ -0,0 +1,41 @@
+import { Canvas } from '../canvas/Canvas';
+import { FabricObject } from '../shapes/Object/FabricObject';
+import { Control } from './Control';
+
+describe('Controls', () => {
+  test('method binding', () => {
+    const actionHandler = jest.fn();
+    const mouseDownHandler = jest.fn();
+    const mouseUpHandler = jest.fn();
+
+    const control = new Control({
+      actionHandler,
+      mouseDownHandler,
+      mouseUpHandler,
+    });
+
+    const target = new FabricObject({
+      controls: { test: control, test2: control },
+    });
+    jest.spyOn(target, '_findTargetCorner').mockReturnValue('test');
+    jest.spyOn(target, 'getActiveControl').mockReturnValue('test');
+
+    const canvas = new Canvas();
+    canvas.setActiveObject(target);
+
+    const downEvent = new MouseEvent('mousedown', { clientX: 0, clientY: 0 });
+    const moveEvent = new MouseEvent('mousemove', { clientX: 0, clientY: 0 });
+    const upEvent = new MouseEvent('mouseup', { clientX: 0, clientY: 0 });
+
+    canvas.getSelectionElement().dispatchEvent(downEvent);
+    // eslint-disable-next-line no-restricted-globals
+    const doc = document;
+    doc.dispatchEvent(moveEvent);
+    canvas._currentTransform!.corner = 'test2';
+    doc.dispatchEvent(upEvent);
+
+    expect(mouseDownHandler.mock.contexts).toEqual([control]);
+    expect(actionHandler.mock.contexts).toEqual([control]);
+    expect(mouseUpHandler.mock.contexts).toEqual([control, control]);
+  });
+});
diff --git a/src/controls/Control.ts b/src/controls/Control.ts
index acc3b13c3..98091469c 100644
--- a/src/controls/Control.ts
+++ b/src/controls/Control.ts
@@ -138,7 +138,7 @@ export class Control {
    */
   withConnection = false;
 
-  constructor(options: Partial<Control>) {
+  constructor(options?: Partial<Control>) {
     Object.assign(this, options);
   }
 
@@ -313,7 +313,8 @@ export class Control {
     objectCornerSize: number,
     centerX: number,
     centerY: number,
-    isTouch: boolean
+    isTouch: boolean,
+    fabricObject: InteractiveFabricObject
   ) {
     const t = multiplyTransformMatrixArray([
       createTranslateMatrix(centerX, centerY),
diff --git a/src/shapes/ActiveSelection.spec.ts b/src/shapes/ActiveSelection.spec.ts
index bba71f692..82088805a 100644
--- a/src/shapes/ActiveSelection.spec.ts
+++ b/src/shapes/ActiveSelection.spec.ts
@@ -1,5 +1,6 @@
 import { Canvas } from '../canvas/Canvas';
 import { ActiveSelection } from './ActiveSelection';
+import { Group } from './Group';
 import { FabricObject } from './Object/FabricObject';
 
 describe('ActiveSelection', () => {
@@ -86,4 +87,37 @@ describe('ActiveSelection', () => {
     expect(canvas.getActiveSelection().lineCoords).toMatchSnapshot();
     expect(canvas.getActiveSelection().aCoords).toMatchSnapshot();
   });
+
+  it('`setActiveObject` should update the active selection ref on canvas if it changed', () => {
+    const canvas = new Canvas(null);
+    const obj1 = new FabricObject();
+    const obj2 = new FabricObject();
+    canvas.add(obj1, obj2);
+    const activeSelection = new ActiveSelection([obj1, obj2]);
+    const spy = jest.spyOn(activeSelection, 'setCoords');
+    canvas.setActiveObject(activeSelection);
+    expect(canvas.getActiveSelection()).toBe(activeSelection);
+    expect(canvas.getActiveObjects()).toEqual([obj1, obj2]);
+    expect(spy).toHaveBeenCalled();
+    expect(activeSelection.canvas).toBe(canvas);
+
+    spy.mockClear();
+    canvas.setActiveObject(activeSelection);
+    expect(spy).not.toHaveBeenCalled();
+  });
+
+  it('transferring an object between active selections keeps its owning group', () => {
+    const object = new FabricObject();
+    const group = new Group([object]);
+    const activeSelection1 = new ActiveSelection([object]);
+    const activeSelection2 = new ActiveSelection();
+    expect(object.group).toBe(activeSelection1);
+    expect(object.getParent(true)).toBe(group);
+    activeSelection2.add(object);
+    expect(object.group).toBe(activeSelection2);
+    expect(object.getParent(true)).toBe(group);
+    activeSelection2.removeAll();
+    expect(object.group).toBe(group);
+    expect(object.getParent(true)).toBe(group);
+  });
 });
diff --git a/src/shapes/ActiveSelection.ts b/src/shapes/ActiveSelection.ts
index 71bc0eada..cd5adfde4 100644
--- a/src/shapes/ActiveSelection.ts
+++ b/src/shapes/ActiveSelection.ts
@@ -86,7 +86,9 @@ export class ActiveSelection extends Group {
       //  save ref to group for later in order to return to it
       const parent = object.group;
       parent._exitGroup(object);
-      object.__owningGroup = parent;
+      // make sure we are setting the correct owning group
+      // in case `object` is transferred between active selections
+      !(parent instanceof ActiveSelection) && (object.__owningGroup = parent);
     }
     this._enterGroup(object, removeParentTransform);
     return true;
diff --git a/src/shapes/Object/FabricObject.spec.ts b/src/shapes/Object/FabricObject.spec.ts
new file mode 100644
index 000000000..cd011e706
--- /dev/null
+++ b/src/shapes/Object/FabricObject.spec.ts
@@ -0,0 +1,16 @@
+import { FabricObject } from './FabricObject';
+
+describe('FabricObject', () => {
+  it('setCoords should calculate control coords only if canvas ref is set', () => {
+    const object = new FabricObject();
+    expect(object.aCoords).toBeUndefined();
+    expect(object.oCoords).toBeUndefined();
+    object.setCoords();
+    expect(object.aCoords).toBeDefined();
+    expect(object.oCoords).toBeUndefined();
+    object.canvas = jest.fn();
+    object.setCoords();
+    expect(object.aCoords).toBeDefined();
+    expect(object.oCoords).toBeDefined();
+  });
+});
diff --git a/src/shapes/Object/InteractiveObject.spec.ts b/src/shapes/Object/InteractiveObject.spec.ts
index 65b8ae849..72bf246a5 100644
--- a/src/shapes/Object/InteractiveObject.spec.ts
+++ b/src/shapes/Object/InteractiveObject.spec.ts
@@ -1,11 +1,13 @@
 import { radiansToDegrees } from '../../util';
 import { Group } from '../Group';
+import { Canvas } from '../../canvas/Canvas';
 import { FabricObject } from './FabricObject';
 import type { TOCoord } from './InteractiveObject';
 
 describe('Object', () => {
   describe('setCoords for objects inside group with rotation', () => {
     it('all corners are rotated as much as the object total angle', () => {
+      const canvas = new Canvas();
       const object = new FabricObject({
         left: 25,
         top: 60,
@@ -21,6 +23,7 @@ describe('Object', () => {
         interactive: true,
         subTargetCheck: true,
       });
+      canvas.add(group);
       group.setCoords();
       const objectAngle = Math.round(object.getTotalAngle());
       expect(objectAngle).toEqual(35);
diff --git a/src/shapes/Object/InteractiveObject.ts b/src/shapes/Object/InteractiveObject.ts
index 9c3d160ad..f3a0c16a8 100644
--- a/src/shapes/Object/InteractiveObject.ts
+++ b/src/shapes/Object/InteractiveObject.ts
@@ -284,14 +284,16 @@ export class InteractiveFabricObject<
       this.cornerSize,
       position.x,
       position.y,
-      false
+      false,
+      this
     );
     const touchCorner = control.calcCornerCoords(
       angle,
       this.touchCornerSize,
       position.x,
       position.y,
-      true
+      true,
+      this
     );
     return { corner, touchCorner };
   }
@@ -307,7 +309,7 @@ export class InteractiveFabricObject<
   setCoords(): void {
     super.setCoords();
     // set coordinates of the draggable boxes in the corners used to scale/rotate the image
-    this.oCoords = this.calcOCoords();
+    this.canvas && (this.oCoords = this.calcOCoords());
   }
 
   /**


Test patch:
diff --git a/test/unit/group.js b/test/unit/group.js
index 2ca68bd8b..db8448770 100644
--- a/test/unit/group.js
+++ b/test/unit/group.js
@@ -741,9 +741,9 @@
         rect2 = new fabric.Rect({ top: 5, left: 5, width: 2, height: 2, strokeWidth: 0, fill: 'red', opacity: 1, objectCaching: false}),
         group = new fabric.Group([rect1]);
 
-    var coords = group.oCoords;
+    var coords = group.aCoords;
     group.add(rect2);
-    var newCoords = group.oCoords;
+    var newCoords = group.aCoords;
     assert.notEqual(coords, newCoords, 'object coords have been recalculated - add');
   });
 
@@ -781,9 +781,9 @@
         rect2 = new fabric.Rect({ top: 5, left: 5, width: 2, height: 2, strokeWidth: 0, fill: 'red', opacity: 1, objectCaching: false}),
         group = new fabric.Group([rect1, rect2]);
 
-    var coords = group.oCoords;
+    var coords = group.aCoords;
     group.remove(rect2);
-    var newCoords = group.oCoords;
+    var newCoords = group.aCoords;
     assert.notEqual(coords, newCoords, 'object coords have been recalculated - remove');
   });
 
diff --git a/test/unit/object_geometry.js b/test/unit/object_geometry.js
index 561d82802..2df36fa0a 100644
--- a/test/unit/object_geometry.js
+++ b/test/unit/object_geometry.js
@@ -244,7 +244,7 @@
   });
 
   QUnit.test('setCoords', function(assert) {
-    var cObj = new fabric.Object({ left: 150, top: 150, width: 100, height: 100, strokeWidth: 0});
+    var cObj = new fabric.Object({ left: 150, top: 150, width: 100, height: 100, strokeWidth: 0,canvas:{}});
     assert.ok(typeof cObj.setCoords === 'function');
     cObj.setCoords();
     assert.equal(cObj.oCoords.tl.x, 150);
diff --git a/test/unit/object_interactivity.js b/test/unit/object_interactivity.js
index c0426fcc9..b608f27c9 100644
--- a/test/unit/object_interactivity.js
+++ b/test/unit/object_interactivity.js
@@ -98,7 +98,7 @@
   });
 
   QUnit.test('corner coords', function(assert) {
-    var cObj = new fabric.Object({ top: 10, left: 10, width: 10, height: 10, strokeWidth: 0 });
+    var cObj = new fabric.Object({ top: 10, left: 10, width: 10, height: 10, strokeWidth: 0, canvas: {} });
     cObj.setCoords();
 
     assert.equal(cObj.oCoords.tl.corner.tl.x.toFixed(2), 3.5);
@@ -151,7 +151,7 @@
     sharedControls.bl.sizeX = 30;
     sharedControls.bl.sizeY = 10;
 
-    var cObj = new fabric.Object({ top: 10, left: 10, width: 10, height: 10, strokeWidth: 0, controls: sharedControls });
+    var cObj = new fabric.Object({ top: 10, left: 10, width: 10, height: 10, strokeWidth: 0, controls: sharedControls, canvas: {} });
     cObj.setCoords();
 
     assert.equal(cObj.oCoords.tl.corner.tl.x.toFixed(2), 3.5);
@@ -201,7 +201,7 @@
   });
 
   QUnit.test('_findTargetCorner', function(assert) {
-    var cObj = new fabric.Object({ top: 10, left: 10, width: 30, height: 30, strokeWidth: 0 });
+    var cObj = new fabric.Object({ top: 10, left: 10, width: 30, height: 30, strokeWidth: 0, canvas: {}});
     assert.ok(typeof cObj._findTargetCorner === 'function', '_findTargetCorner should exist');
     cObj.setCoords();
     cObj.canvas = {
@@ -220,7 +220,7 @@
   });
 
   QUnit.test('_findTargetCorner for touches', function(assert) {
-    var cObj = new fabric.Object({ top: 10, left: 10, width: 30, height: 30, strokeWidth: 0 });
+    var cObj = new fabric.Object({ top: 10, left: 10, width: 30, height: 30, strokeWidth: 0, canvas: {} });
     cObj.setCoords();
     cObj.canvas = {
       getActiveObject() { return cObj }
@@ -241,7 +241,7 @@
 
 
   QUnit.test('_findTargetCorner for non active object', function (assert) {
-    var cObj = new fabric.Object({ top: 10, left: 10, width: 30, height: 30, strokeWidth: 0 });
+    var cObj = new fabric.Object({ top: 10, left: 10, width: 30, height: 30, strokeWidth: 0, canvas: {} });
     assert.ok(typeof cObj._findTargetCorner === 'function', '_findTargetCorner should exist');
     cObj.setCoords();
     cObj.canvas = {
@@ -251,7 +251,7 @@
   });
 
   QUnit.test('_findTargetCorner for non visible control', function (assert) {
-    var cObj = new fabric.Object({ top: 10, left: 10, width: 30, height: 30, strokeWidth: 0 });
+    var cObj = new fabric.Object({ top: 10, left: 10, width: 30, height: 30, strokeWidth: 0, canvas: {} });
     assert.ok(typeof cObj._findTargetCorner === 'function', '_findTargetCorner should exist');
     cObj.setCoords();
     cObj.canvas = {