Repository: hustcc/jest-canvas-mock
Pull request #29 (2019-02-18 18:54:26)

fix error desc, add two generic tests
This pull request aims to clean up the error descriptions, and verify error types. It also comes with a few generic tests that should have been added to begin with.

- [x] Verify Error Descriptions
- [x] Verify Error Parameters (e.g. DOMException)
- [x] Add CanvasGradient tests and conform to HTML Standard specification


Patch:
diff --git a/src/classes/CanvasGradient.js b/src/classes/CanvasGradient.js
index e392a08..2285332 100644
--- a/src/classes/CanvasGradient.js
+++ b/src/classes/CanvasGradient.js
@@ -1,10 +1,18 @@
-
+import parseColor from 'parse-color';
 
 export default class CanvasGradient {
   constructor() {
     this.addColorStop = jest.fn(this.addColorStop.bind(this));
   }
-  addColorStop() {
-
+  addColorStop(offset, color) {
+    const numoffset = Number(offset);
+    const colorstr = String(color);
+    if (!Number.isFinite(numoffset) || numoffset < 0 || numoffset > 1) {
+      throw new DOMException('IndexSizeError', 'Failed to execute \'addColorStop\' on \'CanvasGradient\': The provided value (\'' + numoffset + '\') is outside the range (0.0, 1.0)');
+    }
+    const output = parseColor(colorstr);
+    if (!output.hex) {
+      throw new SyntaxError('Failed to execute \'addColorStop\' on \'CanvasGradient\': The value provided (\'' + color + '\') could not be parsed as a color.')
+    }
   }
 }
diff --git a/src/classes/CanvasRenderingContext2D.js b/src/classes/CanvasRenderingContext2D.js
index 404c30f..92b88e9 100644
--- a/src/classes/CanvasRenderingContext2D.js
+++ b/src/classes/CanvasRenderingContext2D.js
@@ -16,7 +16,7 @@ function parseCSSColor(value) {
 
     // shorthand #ABC
     if (hex[1] === hex[2] && hex[3] === hex[4] && hex[5] === hex[6]) {
-      return "#" + hex[1] + hex[3] + hex[5];
+      return '#' + hex[1] + hex[3] + hex[5];
     }
     return result.hex;
   }
@@ -93,7 +93,7 @@ export default class CanvasRenderingContext2D {
 
     if (Number.isFinite(cpx1Result) && Number.isFinite(cpx2Result) && Number.isFinite(cpy1Result) && Number.isFinite(cpy2Result)) {
       const radiusResult = Number(radius);
-      if (Number.isFinite(radiusResult) && radiusResult < 0) throw new TypeError('Failed to execute \'arc\' on \'' + this.constructor.name + '\': The radius provided (' + radius + ') is negative.');
+      if (Number.isFinite(radiusResult) && radiusResult < 0) throw new TypeError('Failed to execute \'arcTo\' on \'' + this.constructor.name + '\': The radius provided (' + radius + ') is negative.');
     }
   }
 
@@ -160,7 +160,7 @@ export default class CanvasRenderingContext2D {
     if (type === 'repeat' || type === 'repeat-x' || type === 'repeat-y' || type === 'no-repeat') {
       if (image instanceof HTMLImageElement) return new CanvasPattern();
       if (image instanceof ImageBitmap) {
-        if (image._closed) throw new DOMException('ValidationError', 'DOMException: Failed to execute \'createPattern\' on \'CanvasRenderingContext2D\': The image source is detached.');
+        if (image._closed) throw new DOMException('SyntaxError', 'DOMException: Failed to execute \'createPattern\' on \'CanvasRenderingContext2D\': The image source is detached.');
         return new CanvasPattern();
       }
       if (image instanceof HTMLVideoElement) return new CanvasPattern();
@@ -227,7 +227,7 @@ export default class CanvasRenderingContext2D {
 
     if (img instanceof HTMLImageElement) return;
     if (img instanceof ImageBitmap) {
-      if (img._closed) throw new DOMException('ValidationError', 'DOMException: Failed to execute \'drawImage\' on \'CanvasRenderingContext2D\': The image source is detached.');
+      if (img._closed) throw new DOMException('InvalidStateError', 'DOMException: Failed to execute \'drawImage\' on \'CanvasRenderingContext2D\': The image source is detached.');
       return;
     }
     if (img instanceof HTMLVideoElement) return;
@@ -416,7 +416,7 @@ export default class CanvasRenderingContext2D {
   }
 
   measureText(text) {
-    if (arguments.length < 1) throw new TypeError('VM5906 pen.js:2 Uncaught TypeError: Failed to execute \'measureText\' on \'' + this.constructor.name + '\': 1 argument required, but only 0 present.');
+    if (arguments.length < 1) throw new TypeError('Failed to execute \'measureText\' on \'' + this.constructor.name + '\': 1 argument required, but only 0 present.');
     return new TextMetrics(String(text));
   }
 
@@ -447,7 +447,7 @@ export default class CanvasRenderingContext2D {
   }
 
   rect(x, y, width, height) {
-    if (arguments.length < 4) throw new TypeError('Uncaught TypeError: Failed to execute \'rect\' on \'' + this.constructor.name + '\': 4 arguments required, but only ' + arguments.length + ' present.');
+    if (arguments.length < 4) throw new TypeError('Failed to execute \'rect\' on \'' + this.constructor.name + '\': 4 arguments required, but only ' + arguments.length + ' present.');
   }
 
   removeHitRegion(id) {
@@ -490,7 +490,7 @@ export default class CanvasRenderingContext2D {
   }
 
   rotate(angle) {
-    if (arguments.length < 1) throw new TypeError('VM6715 pen.js:2 Uncaught TypeError: Failed to execute \'rotate\' on \'' + this.constructor.name + '\': 1 argument required, but only 0 present.');
+    if (arguments.length < 1) throw new TypeError('Failed to execute \'rotate\' on \'' + this.constructor.name + '\': 1 argument required, but only 0 present.');
     angle = Number(angle);
     if (!Number.isFinite(angle)) return;
     const a = this._transformStack[this._stackIndex][0];
@@ -532,7 +532,7 @@ export default class CanvasRenderingContext2D {
   }
 
   scale(x, y) {
-    if (arguments.length < 2) throw new TypeError('TypeError: Failed to execute \'scale\' on \'' + this.constructor.name + '\': 2 arguments required, but only ' + arguments.length + ' present.');
+    if (arguments.length < 2) throw new TypeError('Failed to execute \'scale\' on \'' + this.constructor.name + '\': 2 arguments required, but only ' + arguments.length + ' present.');
     const xResult = Number(x);
     const yResult = Number(y);
 
@@ -661,7 +661,7 @@ export default class CanvasRenderingContext2D {
   }
 
   strokeRect(x, y, width, height) {
-    if (arguments.length < 4) throw new TypeError('Uncaught TypeError: Failed to execute \'strokeRect\' on \'' + this.constructor.name + '\': 4 arguments required, but only ' + arguments.length + ' present.');
+    if (arguments.length < 4) throw new TypeError('Failed to execute \'strokeRect\' on \'' + this.constructor.name + '\': 4 arguments required, but only ' + arguments.length + ' present.');
   }
 
   set strokeStyle(value) {
@@ -730,7 +730,7 @@ export default class CanvasRenderingContext2D {
   }
 
   translate(x, y) {
-    if (arguments.length < 2) throw new TypeError('TypeError: Failed to execute \'translate\' on \'' + this.constructor.name + '\': 2 arguments required, but only ' + arguments.length + ' present.');
+    if (arguments.length < 2) throw new TypeError('Failed to execute \'translate\' on \'' + this.constructor.name + '\': 2 arguments required, but only ' + arguments.length + ' present.');
     const xResult = Number(x);
     const yResult = Number(y);
     const a = this._transformStack[this._stackIndex][0];


Test patch:
diff --git a/__tests__/classes/CanvasGradient.js b/__tests__/classes/CanvasGradient.js
index fbcb4bb..53da7f9 100644
--- a/__tests__/classes/CanvasGradient.js
+++ b/__tests__/classes/CanvasGradient.js
@@ -9,8 +9,8 @@ beforeEach(() => {
 describe('CanvasGradient', () => {
   test('CanvasGradient', () => {
     expect(grd).toBeDefined();
-    grd.addColorStop();
-    expect(grd.addColorStop).toBeCalled();
+    grd.addColorStop(1.0, "blue");
+    expect(grd.addColorStop).toBeCalledWith(1.0, 'blue');
 
     const grd2 = ctx.createLinearGradient(2, 3, 4, 5);
     expect(grd2.addColorStop).not.toBeCalled();
@@ -21,4 +21,20 @@ describe('CanvasGradient', () => {
     const grd2 = ctx.createLinearGradient(1, 2, 3, 4);
     expect(grd1.addColorStop).not.toBe(grd2.addColorStop);
   });
+
+  [Infinity, NaN, -Infinity].forEach(value => {
+    test('CanvasGradient should throw if offset is ' + value, () => {
+      expect(() => {
+        var grd = ctx.createLinearGradient(1, 2, 3, 4);
+        grd.addColorStop(value, "blue");
+      }).toThrow(DOMException);
+    });
+  });
+
+  test('CanvasGradient should throw if color cannot be parsed', () => {
+    expect(() => {
+      var grd = ctx.createLinearGradient(1, 2, 3, 4);
+      grd.addColorStop(1, "badcolor");
+    }).toThrow(SyntaxError);
+  });
 });
diff --git a/__tests__/classes/CanvasRenderingContext2D.js b/__tests__/classes/CanvasRenderingContext2D.js
index bcace12..773e8a7 100644
--- a/__tests__/classes/CanvasRenderingContext2D.js
+++ b/__tests__/classes/CanvasRenderingContext2D.js
@@ -16,4 +16,12 @@ describe('CanvasRenderingContext2D prototype', () => {
   it('should have a canvas property', () => {
     expect(ctx.canvas).toBe(canvas);
   });
+
+  it('should have a getContext function', () => {
+    expect(canvas.getContext).toBeCalled();
+  });
+
+  it('should be defined on the prototype', () => {
+    expect(HTMLCanvasElement.prototype.getContext).toBeCalled();
+  });
 });