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

Issue:
getContext() should be idempotent
I have some code that uses an old jQuery plugin and it ends up calling `getContext()` more than once one the same Canvas element.  This works fine in the browser with the real Canvas element.  This causes `_stackIndex` along with all of the other internal instance variables in CanvasRenderingContext2D.js to be reset when they shouldn't be.  This should be pretty easy to fix by adding a Symbol to the Canvas element containing the context from `getContext()` and then checking if that Symbol exists (and returning it) whenever `getContext()` is called.  Would you be interested in a pull request for this issue?

Pull request description:
fix(getcontext): repeated calls to getContext('2d') return same context
This pull request maintains 100% code coverage, fixes standard `HTMLCanvasElement#getContext` behavior.

closes #34 

Patch:
diff --git a/src/mock/prototype.js b/src/mock/prototype.js
index 1300b3d..4120251 100644
--- a/src/mock/prototype.js
+++ b/src/mock/prototype.js
@@ -1,11 +1,26 @@
 export default function mockPrototype() {
+  /**
+   * This weakmap is designed to contain all of the generated canvas contexts. It's keys are the
+   * jsdom canvases obtained by using the `this` keyword inside the `#getContext('2d')` function
+   * call. It's values are the generated `CanvasRenderingContext2D` objects.
+   */
+  const generatedContexts = new WeakMap();
   /**
    * Overrides getContext. Every test run will create a new function that overrides the current
    * value of getContext. It attempts to preserve the original getContext function by storing it on
    * the callback as a property.
    */
   const getContext2D = jest.fn(function getContext2d(type) {
-    if (type === '2d') return new CanvasRenderingContext2D(this);
+    if (type === '2d') {
+      /**
+       * Contexts must be indempotent. Once they are generated, they should be returned when
+       * getContext() is called on the same canvas object multiple times.
+       */
+      if (generatedContexts.has(this)) return generatedContexts.get(this);
+      const ctx = new CanvasRenderingContext2D(this);
+      generatedContexts.set(this, ctx);
+      return ctx;
+    }
     try {
       if (!this.dataset.internalRequireTest) require('canvas');
     } catch {


Test patch:
diff --git a/__tests__/mock/prototype.js b/__tests__/mock/prototype.js
index 7ed2fe5..b3aef79 100644
--- a/__tests__/mock/prototype.js
+++ b/__tests__/mock/prototype.js
@@ -111,4 +111,10 @@ describe('mock', () => {
     canvas.getContext('webgl');
     console.error = error;
   });
+
+  it('should return the same context if getContext("2d") is called twice', () => {
+    const first = canvas.getContext("2d");
+    const second = canvas.getContext("2d");
+    expect(first).toBe(second);
+  });
 });