Repository: hustcc/jest-canvas-mock
Pull request #45 (2019-07-25 21:44:52)

Support for ImageData instantiation using a source array
This PR introduces changes in the `ImageData` class to comply with the [official specification](https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-createimagedata).

## Changes

- `ImageData()` now supports instantiation using the syntax: `new ImageData(array, width [, height]);`.
- More semantic error types.
- Test suite for the class was refactored, each inner `describe` block corresponds to each valid way to instantiate `ImageData`. All tests from the previous version are in the first inner `describe` block.


Patch:
diff --git a/.gitignore b/.gitignore
index 5b95b10..bc7a6d1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,4 +3,5 @@ node_modules
 .DS_STORE
 coverage
 package-lock.json
+yarn.lock
 lib
diff --git a/src/classes/ImageData.js b/src/classes/ImageData.js
index c7f0db6..6541425 100644
--- a/src/classes/ImageData.js
+++ b/src/classes/ImageData.js
@@ -15,13 +15,51 @@ export default class ImageData {
     return this._data;
   }
 
-  constructor(width, height) {
-    if (!Number.isFinite(height)) throw new TypeError('The source height is zero or not a number.');
-    if (height === 0) throw new TypeError('The source height is zero or not a number.');
-    if (!Number.isFinite(width)) throw new TypeError('The source width is zero or not a number.');
-    if (width === 0) throw new TypeError('The source height is zero or not a number.');
-    this._width = width;
-    this._height = height;
-    this._data = new Uint8ClampedArray(width * height * 4);
+  constructor(arr, w, h) {
+    if (arguments.length === 2) {
+      if (arr instanceof Uint8ClampedArray) {
+        if (arr.length === 0) throw new RangeError('Source length must be a positive multiple of 4.');
+        if (arr.length % 4 !== 0) throw new RangeError('Source length must be a positive multiple of 4.');
+
+        if (!Number.isFinite(w)) throw new RangeError('The width is zero or not a number.');
+        if (w === 0) throw new RangeError('The width is zero or not a number.');
+
+        this._width = w;
+        this._height = (arr.length / 4) / w;
+        this._data = arr;
+
+      } else {
+        const width = arr;
+        const height = w;
+
+        if (!Number.isFinite(height)) throw new RangeError('The height is zero or not a number.');
+        if (height === 0) throw new RangeError('The height is zero or not a number.');
+        if (!Number.isFinite(width)) throw new RangeError('The width is zero or not a number.');
+        if (width === 0) throw new RangeError('The width is zero or not a number.');
+
+        this._width = width;
+        this._height = height;
+        this._data = new Uint8ClampedArray(width * height * 4);
+      }
+
+    } else if (arguments.length === 3) {
+      if (!(arr instanceof Uint8ClampedArray)) throw new TypeError('First argument must be a Uint8ClampedArray when using 3 arguments.');
+      if (arr.length === 0) throw new RangeError('Source length must be a positive multiple of 4.');
+      if (arr.length % 4 !== 0) throw new RangeError('Source length must be a positive multiple of 4.');
+
+      if (!Number.isFinite(h)) throw new RangeError('The height is zero or not a number.');
+      if (h === 0) throw new RangeError('The height is zero or not a number.');
+      if (!Number.isFinite(w)) throw new RangeError('The width is zero or not a number.');
+      if (w === 0) throw new RangeError('The width is zero or not a number.');
+
+      if (arr.length !== w * h * 4) throw new RangeError('Source doesn\'n contain the exact number of pixels needed.');
+
+      this._width = w;
+      this._height = h;
+      this._data = arr;
+
+    } else {
+      throw new TypeError('Wrong number of arguments provided.');
+    }
   }
 }


Test patch:
diff --git a/__tests__/classes/ImageData.js b/__tests__/classes/ImageData.js
index 4a8ef77..0381468 100644
--- a/__tests__/classes/ImageData.js
+++ b/__tests__/classes/ImageData.js
@@ -1,38 +1,135 @@
 describe('ImageData', () => {
-  it('should construct an ImageData object', () => {
-    const d = new ImageData(100, 200);
-    expect(d).toBeInstanceOf(ImageData);
+  it('should throw if less than 2 arguments are provided', () => {
+    expect(() => new ImageData()).toThrow(TypeError);
   });
 
-  it('should construct a Uint8ClampedArray', () => {
-    const d = new ImageData(100, 200);
-    expect(d.data).toBeInstanceOf(Uint8ClampedArray);
+  it('should throw if more than 3 arguments are provided', () => {
+    expect(() => new ImageData(new Uint8ClampedArray([0, 0, 0, 1]), 1, 1, 1))
+      .toThrow(TypeError);
   });
 
-  it('should construct a Uint8ClampedArray of proper size', () => {
-    const d = new ImageData(100, 200);
-    expect(d.data.length).toBe(100 * 200 * 4);
-  });
+  describe('new ImageData(width, height)', () => {
+    it('should construct an ImageData object', () => {
+      const d = new ImageData(100, 200);
+      expect(d).toBeInstanceOf(ImageData);
+    });
 
-  it('should set the width and height', () => {
-    const d = new ImageData(100, 200);
-    expect(d.width).toBe(100);
-    expect(d.height).toBe(200);
-  });
+    it('should construct a Uint8ClampedArray', () => {
+      const d = new ImageData(100, 200);
+      expect(d.data).toBeInstanceOf(Uint8ClampedArray);
+    });
 
-  it('should throw if width is not finite', () => {
-    expect(() => new ImageData(Infinity, 100)).toThrow(TypeError);
-  });
+    it('should construct a Uint8ClampedArray of proper size', () => {
+      const d = new ImageData(100, 200);
+      expect(d.data.length).toBe(100 * 200 * 4);
+    });
+
+    it('should set the width and height', () => {
+      const d = new ImageData(100, 200);
+      expect(d.width).toBe(100);
+      expect(d.height).toBe(200);
+    });
+
+    it('should throw if width is not finite', () => {
+      expect(() => new ImageData(Infinity, 100)).toThrow(RangeError);
+    });
 
-  it('should throw if width is 0', () => {
-    expect(() => new ImageData(0, 100)).toThrow(TypeError);
+    it('should throw if width is 0', () => {
+      expect(() => new ImageData(0, 100)).toThrow(RangeError);
+    });
+
+    it('should throw if height is not finite', () => {
+      expect(() => new ImageData(100, Infinity)).toThrow(RangeError);
+    });
+
+    it('should throw if height is 0', () => {
+      expect(() => new ImageData(100, 0)).toThrow(RangeError);
+    });
   });
 
-  it('should throw if height is not finite', () => {
-    expect(() => new ImageData(100, Infinity)).toThrow(TypeError);
+  describe('new ImageData(array, width)', () => {
+    it('should construct an ImageData object with the right inferred height', () => {
+      const data = new Uint8ClampedArray(800);
+      const d = new ImageData(data, 200);
+      expect(d).toBeInstanceOf(ImageData);
+      expect(d.height).toBe(1);
+      expect(d.data).toBe(data);
+    });
+
+    it('should throw if width is not finite', () => {
+      expect(() => new ImageData(new Uint8ClampedArray(4), Infinity))
+        .toThrow(RangeError);
+    });
+
+    it('should throw if width is 0', () => {
+      expect(() => new ImageData(new Uint8ClampedArray(4), 0))
+        .toThrow(RangeError);
+    });
+
+    it('should throw if source length is 0', () => {
+      expect(() => new ImageData(new Uint8ClampedArray(0), 100))
+        .toThrow(RangeError);
+    })
+
+    it('should throw if source length is not a multiple of 4', () => {
+      expect(() => new ImageData(new Uint8ClampedArray(801), 200))
+        .toThrow(RangeError);
+    });
   });
 
-  it('should throw if height is 0', () => {
-    expect(() => new ImageData(100, 0)).toThrow(TypeError);
+  describe('new ImageData(array, width, height)', () => {
+    it('should construct an ImageData object', () => {
+      const d = new ImageData(new Uint8ClampedArray([0, 0, 0, 1]), 1, 1);
+      expect(d).toBeInstanceOf(ImageData);
+    });
+
+    it('should set the width and height', () => {
+      const d = new ImageData(new Uint8ClampedArray(80000), 100, 200);
+      expect(d.width).toBe(100);
+      expect(d.height).toBe(200);
+    });
+
+    it('should throw if width is not finite', () => {
+      expect(() => new ImageData(new Uint8ClampedArray(4), Infinity, 100))
+        .toThrow(RangeError);
+    });
+
+    it('should throw if width is 0', () => {
+      expect(() => new ImageData(new Uint8ClampedArray(4), 0, 100))
+        .toThrow(RangeError);
+    });
+
+    it('should throw if height is not finite', () => {
+      expect(() => new ImageData(new Uint8ClampedArray(4), 1, Infinity))
+        .toThrow(RangeError);
+    });
+
+    it('should throw if height is 0', () => {
+      expect(() => new ImageData(new Uint8ClampedArray(4), 100, 0))
+        .toThrow(RangeError);
+    });
+
+    it('should throw if first argument is not a Uint8ClampedArray', () => {
+      expect(() => new ImageData(0, 0, 100)).toThrow(TypeError);
+    });
+
+    it('should throw if source length is 0', () => {
+      expect(() => new ImageData(new Uint8ClampedArray(0), 100, 100))
+        .toThrow(RangeError);
+    });
+
+    it('should throw if source length is not a multiple of 4', () => {
+      expect(() => new ImageData(new Uint8ClampedArray(801), 200, 1))
+        .toThrow(RangeError);
+    });
+
+    it('should throw if width and height aren\'t compatible with source length ', () => {
+      expect(() => new ImageData(new Uint8ClampedArray(8), 2, 2))
+        .toThrow(RangeError);
+      expect(() => new ImageData(new Uint8ClampedArray(8), 1, 7))
+        .toThrow(RangeError);
+      expect(() => new ImageData(new Uint8ClampedArray(8), 7, 1))
+        .toThrow(RangeError);
+    });
   });
 });