Repository: hustcc/jest-canvas-mock
Pull request #26 (2019-02-02 14:19:57)

[RFC] External Blob Support
Developers who want to actually mock their fetch calls using the `jest-fetch-mock` package will run into problems with `createImageBitmap`. Calling `response.toBlob()` will return a `Blob`-like object, however the fetch implementation is not aware of the environment's `Blob` constructor.

Lastly, I don't have faith that https://github.com/jefflau/jest-fetch-mock/issues/105 will yield something helpful.

This pull request contains a small fixes that assume `jest-fetch-mock` won't fall through. Instead we should do duck typing.

# Blob Duck Typing

Since it's possible to inspect the `blob.constructor.name` property, we can do this:

```ts
if (img instanceof Blob || (img && img.constructor && img.constructor.name === "Blob")) validImage = true;
```

I think these changes are safe. and likely will not provide false positives on class types. However, I would like some comments on this if I can get any. 

jtenner: @hustcc can I ask your opinion?
jtenner: closes #24 #25 #27 and #28

Patch:
diff --git a/src/classes/CanvasRenderingContext2D.js b/src/classes/CanvasRenderingContext2D.js
index 361ae8a..404c30f 100644
--- a/src/classes/CanvasRenderingContext2D.js
+++ b/src/classes/CanvasRenderingContext2D.js
@@ -12,14 +12,20 @@ function parseCSSColor(value) {
   }
 
   if (result.hex) {
+    const hex = result.hex;
+
+    // shorthand #ABC
+    if (hex[1] === hex[2] && hex[3] === hex[4] && hex[5] === hex[6]) {
+      return "#" + hex[1] + hex[3] + hex[5];
+    }
     return result.hex;
   }
 
   return void 0;
 }
 
-const testFuncs = ['setTransform', 'getTransform', 'getImageData', 'save', 'restore', 'createPattern', 'createRadialGradient', 'addHitRegion', 'arc', 'arcTo', 'beginPath', 'clip', 'closePath', 'scale', 'stroke', 'clearHitRegions', 'clearRect', 'fillRect', 'strokeRect', 'rect', 'resetTransform', 'translate', 'moveTo', 'lineTo', 'bezierCurveTo', 'createLinearGradient', 'ellipse', 'measureText', 'rotate', 'drawImage', 'drawFocusIfNeeded', 'isPointInPath', 'isPointInStroke', 'putImageData', 'strokeText', 'fillText', 'quadraticCurveTo', 'removeHitRegion', 'fill', 'transform', 'scrollPathIntoView', 'createImageData'];
-const compositeOperations = ['source-over', 'source-in', 'source-out', 'source-atop', 'destination-over', 'destination-in', 'destination-out', 'destination-atop', 'lighter', 'copy', 'xor', 'multiply', 'screen', 'overlay', 'darken', 'lighten', 'color-dodge', 'color-burn', 'hard-light', 'soft-light', 'difference', 'exclusion', 'hue', 'saturation', 'color', 'luminosity', 'fillText', 'strokeText'];
+const testFuncs = ['setLineDash', 'getLineDash', 'setTransform', 'getTransform', 'getImageData', 'save', 'restore', 'createPattern', 'createRadialGradient', 'addHitRegion', 'arc', 'arcTo', 'beginPath', 'clip', 'closePath', 'scale', 'stroke', 'clearHitRegions', 'clearRect', 'fillRect', 'strokeRect', 'rect', 'resetTransform', 'translate', 'moveTo', 'lineTo', 'bezierCurveTo', 'createLinearGradient', 'ellipse', 'measureText', 'rotate', 'drawImage', 'drawFocusIfNeeded', 'isPointInPath', 'isPointInStroke', 'putImageData', 'strokeText', 'fillText', 'quadraticCurveTo', 'removeHitRegion', 'fill', 'transform', 'scrollPathIntoView', 'createImageData'];
+const compositeOperations = ['source-over', 'source-in', 'source-out', 'source-atop', 'destination-over', 'destination-in', 'destination-out', 'destination-atop', 'lighter', 'copy', 'xor', 'multiply', 'screen', 'overlay', 'darken', 'lighten', 'color-dodge', 'color-burn', 'hard-light', 'soft-light', 'difference', 'exclusion', 'hue', 'saturation', 'color', 'luminosity'];
 
 export default class CanvasRenderingContext2D {
   _directionStack = ['inherit'];
diff --git a/src/mock/createImageBitmap.js b/src/mock/createImageBitmap.js
index d1f9f19..2afecdc 100644
--- a/src/mock/createImageBitmap.js
+++ b/src/mock/createImageBitmap.js
@@ -9,7 +9,8 @@ export default jest.fn(function createImageBitmap(img, sx, sy, sWidth, sHeight,
     if (img instanceof HTMLImageElement) validImage = true;
     if (img instanceof HTMLVideoElement) validImage = true;
     if (img instanceof HTMLCanvasElement) validImage = true;
-    if (img instanceof Blob) validImage = true;
+    // checking constructor name is the only reliable way to verify the object's constructing class is "blob-like"
+    if (img instanceof Blob || (img && img.constructor && img.constructor.name === "Blob")) validImage = true;
     if (img instanceof ImageBitmap) validImage = true;
     if (img instanceof ImageData) validImage = true;
     if (!validImage) return reject(new TypeError('Failed to execute \'createImageBitmap\' on \'Window\': The provided value is not of type \'(HTMLImageElement or SVGImageElement or HTMLVideoElement or HTMLCanvasElement or Blob or ImageData or ImageBitmap or OffscreenCanvas)\''));


Test patch:
diff --git a/__tests__/classes/CanvasRenderingContext2D.fillStyle.js b/__tests__/classes/CanvasRenderingContext2D.fillStyle.js
index 0130374..b63efd3 100644
--- a/__tests__/classes/CanvasRenderingContext2D.fillStyle.js
+++ b/__tests__/classes/CanvasRenderingContext2D.fillStyle.js
@@ -11,7 +11,7 @@ beforeEach(() => {
 describe('fillStyle', () => {
   it('should parse a css color string \'blue\'', () => {
     ctx.fillStyle = 'blue';
-    expect(ctx.fillStyle).toBe('#0000ff');
+    expect(ctx.fillStyle).toBe('#00f');
   });
 
   it('should not parse invalid colors', () => {
@@ -28,7 +28,7 @@ describe('fillStyle', () => {
     ctx.fillStyle = 'green';
     ctx.save();
     ctx.fillStyle = 'red';
-    expect(ctx.fillStyle).toBe('#ff0000');
+    expect(ctx.fillStyle).toBe('#f00');
     ctx.restore();
     expect(ctx.fillStyle).toBe('#008000');
   });
diff --git a/__tests__/classes/CanvasRenderingContext2D.shadowColor.js b/__tests__/classes/CanvasRenderingContext2D.shadowColor.js
index 03f907b..2a48bcc 100644
--- a/__tests__/classes/CanvasRenderingContext2D.shadowColor.js
+++ b/__tests__/classes/CanvasRenderingContext2D.shadowColor.js
@@ -11,7 +11,7 @@ beforeEach(() => {
 describe('shadowColor', () => {
   it('should parse a css color string \'blue\'', () => {
     ctx.shadowColor = 'blue';
-    expect(ctx.shadowColor).toBe('#0000ff');
+    expect(ctx.shadowColor).toBe('#00f');
   });
 
   it('should not parse invalid colors', () => {
@@ -28,7 +28,7 @@ describe('shadowColor', () => {
     ctx.shadowColor = 'green';
     ctx.save();
     ctx.shadowColor = 'red';
-    expect(ctx.shadowColor).toBe('#ff0000');
+    expect(ctx.shadowColor).toBe('#f00');
     ctx.restore();
     expect(ctx.shadowColor).toBe('#008000');
   });
diff --git a/__tests__/classes/CanvasRenderingContext2D.strokeStyle.js b/__tests__/classes/CanvasRenderingContext2D.strokeStyle.js
index f171851..4636450 100644
--- a/__tests__/classes/CanvasRenderingContext2D.strokeStyle.js
+++ b/__tests__/classes/CanvasRenderingContext2D.strokeStyle.js
@@ -11,7 +11,7 @@ beforeEach(() => {
 describe('strokeStyle', () => {
   it('should parse a css color string \'blue\'', () => {
     ctx.strokeStyle = 'blue';
-    expect(ctx.strokeStyle).toBe('#0000ff');
+    expect(ctx.strokeStyle).toBe('#00f');
   });
 
   it('should not parse invalid colors', () => {
@@ -28,7 +28,7 @@ describe('strokeStyle', () => {
     ctx.strokeStyle = 'green';
     ctx.save();
     ctx.strokeStyle = 'red';
-    expect(ctx.strokeStyle).toBe('#ff0000');
+    expect(ctx.strokeStyle).toBe('#f00');
     ctx.restore();
     expect(ctx.strokeStyle).toBe('#008000');
   });