Repository: hustcc/jest-canvas-mock (js)

Issue:
Add ability to clear event lists
Hello! First of all, thank you so much for writing and releasing this library. I am trying to build a Photoshop like editor that uses `<canvas>` for rendering and I have no idea how I would have tested it without the help of `jest-canvas-mock`!

In the interface I am building the user can add layers to the canvas, and the canvas automatically updates for each layer that is added.

What I would like to do is compare a snapshot of the final canvas state, but not the interim states where the user hasn't finished adding layers.

The way I was thinking about doing it was to clear the set of events before the final layers is added in the test, and then take a snapshot of the result. At the moment it is not possible to clear the recorded events on the context, as those are stored internally.

What would be handy would be a matching set of `clear` methods for the current API:

* `__clearEvents`
* `__clearPath`
* `__clearDrawCalls`

These would clear the corresponding internal arrays.

Does this sound like it would be a useful addition to the library? If so, I could try and scrabble together a PR.

Alternatively, do you think there is another way of approaching my requirement?



Pull request:
feat(mock): support clearing draw calls and events
* Adds __clearDrawCalls for clearing draw calls
* Adds __clearEvents for clearing events
* Updates README to mention the new functions
* Adds TypeScript definitions for __clearDrawCalls and __clearEvents

Closes #48.

I decided not to add `__clearPath` as that is cleared for every `beginPath` call already.

I have never used TypeScript, so the definitions are a bit of a guess.

Patch:
diff --git a/README.md b/README.md
index 630a778..9cb9c93 100644
--- a/README.md
+++ b/README.md
@@ -144,6 +144,16 @@ const calls = ctx.__getDrawCalls();
 expect(calls).toMatchSnapshot();
 ```
 
+In some cases it may be useful to clear the events or draw calls that have already been logged.
+
+```ts
+// Clear events
+ctx.__clearEvents();
+
+// Clear draw calls
+ctx.__clearDrawCalls();
+```
+
 ## Override default mock return value
 
 You can override the default mock return value in your test to suit your need. For example, to override return value of `toDataURL`:
diff --git a/package.json b/package.json
index 8cc5cad..0152c64 100644
--- a/package.json
+++ b/package.json
@@ -15,17 +15,17 @@
     "parse-color": "^1.0.0"
   },
   "devDependencies": {
-    "@babel/cli": "^7.4.4",
-    "@babel/core": "^7.4.4",
-    "@babel/plugin-proposal-class-properties": "^7.4.4",
-    "@babel/preset-env": "^7.4.4",
-    "@commitlint/cli": "^7.5.2",
-    "@commitlint/config-angular": "^7.5.0",
-    "babel-jest": "^24.8.0",
+    "@babel/cli": "^7.6.4",
+    "@babel/core": "^7.6.4",
+    "@babel/plugin-proposal-class-properties": "^7.5.5",
+    "@babel/preset-env": "^7.6.3",
+    "@commitlint/cli": "^8.2.0",
+    "@commitlint/config-angular": "^8.2.0",
+    "babel-jest": "^24.9.0",
     "babel-plugin-version": "^0.2.3",
-    "coveralls": "^3.0.3",
-    "husky": "^2.2.0",
-    "jest": "^24.8.0"
+    "coveralls": "^3.0.7",
+    "husky": "^3.0.9",
+    "jest": "^24.9.0"
   },
   "commitlint": {
     "extends": [
diff --git a/src/classes/CanvasRenderingContext2D.js b/src/classes/CanvasRenderingContext2D.js
index cb4d0fb..2d331c1 100644
--- a/src/classes/CanvasRenderingContext2D.js
+++ b/src/classes/CanvasRenderingContext2D.js
@@ -42,6 +42,13 @@ export default class CanvasRenderingContext2D {
     return this._drawCalls.slice();
   }
 
+  /**
+   * Clear the list of draw calls
+   */
+  __clearDrawCalls() {
+    this._drawCalls = []
+  }
+
   /**
    * Every time a function call results in something that would have modified the state of the context,
    * an event is added to this array. This goes for every property set, and draw call.
@@ -51,6 +58,13 @@ export default class CanvasRenderingContext2D {
     return this._events.slice();
   }
 
+  /**
+   * Clear the list of events
+   */
+  __clearEvents() {
+    this._events = [];
+  }
+
   /**
    * This array keeps track of the current path, so that fill and stroke operations can store the
    * path.
@@ -60,6 +74,18 @@ export default class CanvasRenderingContext2D {
     return this._path.slice();
   }
 
+  /**
+   * Clear the path and reset it to a single beginPath event.
+   */
+  __clearPath() {
+    const event = createCanvasEvent(
+      'beginPath',
+      getTransformSlice(this),
+      { },
+    );
+    this._path = [event];
+  }
+
   _directionStack = ['inherit'];
   _fillStyleStack = ['#000'];
   _filterStack = ['none'];
@@ -159,13 +185,9 @@ export default class CanvasRenderingContext2D {
   }
 
   beginPath() {
-    const event = createCanvasEvent(
-      'beginPath',
-      getTransformSlice(this),
-      { },
-    );
-    this._path = [event];
-    this._events.push(event);
+    this.__clearPath();
+    // push the generated beginPath to the event list
+    this._events.push(this._path[0]);
   }
 
   bezierCurveTo(cpx1, cpy1, cpx2, cpy2, x, y) {
diff --git a/types/index.d.ts b/types/index.d.ts
index 3646ad8..1acca41 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -29,6 +29,18 @@ declare global {
      */
     __getEvents(): CanvasRenderingContext2DEvent[];
 
+    /**
+     * Clear all the events associated with this CanvasRenderingContext2D object.
+     *
+     * This method cannot be used in a production environment, only with `jest` using
+     * `jest-canvas-mock` and should only be used for testing.
+     *
+     * @example
+     * ctx.__clearEvents());
+     * expect(ctx.__getEvents()).toBe([]);
+     */
+    __clearEvents(): void;
+
     /**
      * Get all the successful draw calls associated with this CanvasRenderingContext2D object.
      *
@@ -40,6 +52,18 @@ declare global {
      */
     __getDrawCalls(): CanvasRenderingContext2DEvent[];
 
+    /**
+     * Clear all the successful draw calls associated with this CanvasRenderingContext2D object.
+     *
+     * This method cannot be used in a production environment, only with `jest` using
+     * `jest-canvas-mock` and should only be used for testing.
+     *
+     * @example
+     * ctx.__clearDrawCalls());
+     * expect(ctx.__getDrawCalls()).toBe([]);
+     */
+    __clearDrawCalls(): void;
+
     /**
      * Get the current path associated with this CanvasRenderingContext2D object.
      *
@@ -50,5 +74,13 @@ declare global {
      * expect(ctx.__getPath()).toMatchSnapshot();
      */
     __getPath(): CanvasRenderingContext2DEvent[];
+
+    /**
+     * Clears the current path associated with this CanvasRenderingContext2D object.
+     *
+     * This method cannot be used in a production environment, only with `jest` using
+     * `jest-canvas-mock` and should be only used for testing.
+     */
+    __clearPath(): void;
   }
 }


Test patch:
diff --git a/__tests__/classes/CanvasRenderingContext2D.__clearDrawCalls.js b/__tests__/classes/CanvasRenderingContext2D.__clearDrawCalls.js
new file mode 100644
index 0000000..fa25184
--- /dev/null
+++ b/__tests__/classes/CanvasRenderingContext2D.__clearDrawCalls.js
@@ -0,0 +1,24 @@
+let ctx;
+beforeEach(() => {
+  // get a new context each test
+  ctx = document.createElement('canvas')
+    .getContext('2d');
+});
+
+afterEach(() => {
+  const drawCalls = ctx.__getDrawCalls();
+  expect(drawCalls).toMatchSnapshot();
+});
+
+describe('__clearDrawCalls', () => {
+  it("should clear the list of draw calls", () => {
+    ctx.fillRect(1, 2, 3, 4);
+    ctx.__clearDrawCalls();
+  });
+
+  it("should not prevent additional draw calls from being collected", () => {
+    ctx.fillRect(1, 2, 3, 4);
+    ctx.__clearDrawCalls();
+    ctx.fillRect(1, 2, 3, 4);
+  });
+});
diff --git a/__tests__/classes/CanvasRenderingContext2D.__clearEvents.js b/__tests__/classes/CanvasRenderingContext2D.__clearEvents.js
new file mode 100644
index 0000000..6af3841
--- /dev/null
+++ b/__tests__/classes/CanvasRenderingContext2D.__clearEvents.js
@@ -0,0 +1,24 @@
+let ctx;
+beforeEach(() => {
+  // get a new context each test
+  ctx = document.createElement('canvas')
+    .getContext('2d');
+});
+
+afterEach(() => {
+  const events = ctx.__getEvents();
+  expect(events).toMatchSnapshot();
+});
+
+describe('__clearEvents', () => {
+  it("should clear the list of events", () => {
+    ctx.fillRect(1, 2, 3, 4);
+    ctx.__clearEvents();
+  });
+
+  it("should not prevent additional events from being collected", () => {
+    ctx.fillRect(1, 2, 3, 4);
+    ctx.__clearEvents();
+    ctx.fillRect(1, 2, 3, 4);
+  });
+});
diff --git a/__tests__/classes/CanvasRenderingContext2D.__clearPath.js b/__tests__/classes/CanvasRenderingContext2D.__clearPath.js
new file mode 100644
index 0000000..c63ac0e
--- /dev/null
+++ b/__tests__/classes/CanvasRenderingContext2D.__clearPath.js
@@ -0,0 +1,24 @@
+let ctx;
+beforeEach(() => {
+  // get a new context each test
+  ctx = document.createElement('canvas')
+    .getContext('2d');
+});
+
+afterEach(() => {
+  const events = ctx.__getPath();
+  expect(events).toMatchSnapshot();
+});
+
+describe('__clearEvents', () => {
+  it("should clear the list of events", () => {
+    ctx.arc(1, 2, 3, 4, 5);
+    ctx.__clearPath();
+  });
+
+  it("should not prevent additional events from being collected", () => {
+    ctx.arc(1, 2, 3, 4, 5);
+    ctx.__clearPath();
+    ctx.arc(1, 2, 3, 4, 5);
+  });
+});
diff --git a/__tests__/classes/__snapshots__/CanvasRenderingContext2D.__clearDrawCalls.js.snap b/__tests__/classes/__snapshots__/CanvasRenderingContext2D.__clearDrawCalls.js.snap
new file mode 100644
index 0000000..81c3392
--- /dev/null
+++ b/__tests__/classes/__snapshots__/CanvasRenderingContext2D.__clearDrawCalls.js.snap
@@ -0,0 +1,25 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`__clearDrawCalls should clear the list of draw calls 1`] = `Array []`;
+
+exports[`__clearDrawCalls should not prevent additional draw calls from being collected 1`] = `
+Array [
+  Object {
+    "props": Object {
+      "height": 4,
+      "width": 3,
+      "x": 1,
+      "y": 2,
+    },
+    "transform": Array [
+      1,
+      0,
+      0,
+      1,
+      0,
+      0,
+    ],
+    "type": "fillRect",
+  },
+]
+`;
diff --git a/__tests__/classes/__snapshots__/CanvasRenderingContext2D.__clearEvents.js.snap b/__tests__/classes/__snapshots__/CanvasRenderingContext2D.__clearEvents.js.snap
new file mode 100644
index 0000000..b68b457
--- /dev/null
+++ b/__tests__/classes/__snapshots__/CanvasRenderingContext2D.__clearEvents.js.snap
@@ -0,0 +1,25 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`__clearEvents should clear the list of events 1`] = `Array []`;
+
+exports[`__clearEvents should not prevent additional events from being collected 1`] = `
+Array [
+  Object {
+    "props": Object {
+      "height": 4,
+      "width": 3,
+      "x": 1,
+      "y": 2,
+    },
+    "transform": Array [
+      1,
+      0,
+      0,
+      1,
+      0,
+      0,
+    ],
+    "type": "fillRect",
+  },
+]
+`;
diff --git a/__tests__/classes/__snapshots__/CanvasRenderingContext2D.__clearPath.js.snap b/__tests__/classes/__snapshots__/CanvasRenderingContext2D.__clearPath.js.snap
new file mode 100644
index 0000000..00e067f
--- /dev/null
+++ b/__tests__/classes/__snapshots__/CanvasRenderingContext2D.__clearPath.js.snap
@@ -0,0 +1,54 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`__clearEvents should clear the list of events 1`] = `
+Array [
+  Object {
+    "props": Object {},
+    "transform": Array [
+      1,
+      0,
+      0,
+      1,
+      0,
+      0,
+    ],
+    "type": "beginPath",
+  },
+]
+`;
+
+exports[`__clearEvents should not prevent additional events from being collected 1`] = `
+Array [
+  Object {
+    "props": Object {},
+    "transform": Array [
+      1,
+      0,
+      0,
+      1,
+      0,
+      0,
+    ],
+    "type": "beginPath",
+  },
+  Object {
+    "props": Object {
+      "anticlockwise": false,
+      "endAngle": 5,
+      "radius": 3,
+      "startAngle": 4,
+      "x": 1,
+      "y": 2,
+    },
+    "transform": Array [
+      1,
+      0,
+      0,
+      1,
+      0,
+      0,
+    ],
+    "type": "arc",
+  },
+]
+`;