diff --git a/__mocks__/worker.js b/__mocks__/worker.js index 7f01031..a657dff 100644 --- a/__mocks__/worker.js +++ b/__mocks__/worker.js @@ -1,3 +1,7 @@ +const { TextEncoder, TextDecoder } = require('util'); +global.TextEncoder = TextEncoder; +global.TextDecoder = TextDecoder; + global.window.Worker = class { constructor(stringUrl) { this.url = stringUrl; diff --git a/__tests__/classes/CanvasGradient.js b/__tests__/classes/CanvasGradient.js index 79d7676..84a5d25 100644 --- a/__tests__/classes/CanvasGradient.js +++ b/__tests__/classes/CanvasGradient.js @@ -24,10 +24,12 @@ describe('CanvasGradient', () => { [Infinity, NaN, -Infinity].forEach((value) => { test('CanvasGradient should throw if offset is ' + value, () => { - expect(() => { + const fn = () => { var grd = ctx.createLinearGradient(1, 2, 3, 4); grd.addColorStop(value, 'blue'); - }).toThrow(DOMException); + }; + expect(fn).toThrow(DOMException); + expect(fn).toThrow('is outside the range'); }); }); diff --git a/__tests__/classes/CanvasRenderingContext2D.addHitRegion.js b/__tests__/classes/CanvasRenderingContext2D.addHitRegion.js index a95b7e5..cbc6589 100644 --- a/__tests__/classes/CanvasRenderingContext2D.addHitRegion.js +++ b/__tests__/classes/CanvasRenderingContext2D.addHitRegion.js @@ -19,13 +19,15 @@ describe('addHitRegion', () => { }); it('should throw if called with no parameters', () => { - expect(() => ctx.addHitRegion()).toThrow(DOMException); + const fn = () => ctx.addHitRegion(); + expect(fn).toThrow(DOMException); + expect(fn).toThrow('Both id and control are null'); }); it("should throw if fillRule is set and isn't 'evenodd' or 'nonzero'", () => { - expect(() => - ctx.addHitRegion({ id: 'test', fillRule: 'wrong!' }) - ).toThrow(); + const fn = () => ctx.addHitRegion({ id: 'test', fillRule: 'wrong!' }); + expect(fn).toThrow(TypeError); + expect(fn).toThrow('is not a valid enum value of type CanvasFillRule'); }); it('should not throw if fillRule is valid', () => { diff --git a/__tests__/classes/CanvasRenderingContext2D.arc.js b/__tests__/classes/CanvasRenderingContext2D.arc.js index 548fac1..50a0451 100644 --- a/__tests__/classes/CanvasRenderingContext2D.arc.js +++ b/__tests__/classes/CanvasRenderingContext2D.arc.js @@ -18,7 +18,7 @@ describe('arc', () => { expect(ctx.arc).toBeCalled(); }); - it("shouldn't accept parameters less than 7", () => { + it("shouldn't accept parameters less than 5", () => { expect(() => ctx.arc()).toThrow(TypeError); expect(() => ctx.arc(1)).toThrow(TypeError); expect(() => ctx.arc(1, 2)).toThrow(TypeError); @@ -27,7 +27,9 @@ describe('arc', () => { }); it('should throw when radius is negative', () => { - expect(() => ctx.arc(1, 2, -1, 4, 5)).toThrow(DOMException); + const fn = () => ctx.arc(1, 2, -1, 4, 5); + expect(fn).toThrow(DOMException); + expect(fn).toThrow('The radius provided (-1) is negative.'); }); it('should not throw if any value is `NaN`', () => { diff --git a/__tests__/classes/CanvasRenderingContext2D.arcTo.js b/__tests__/classes/CanvasRenderingContext2D.arcTo.js index 3214414..e68c315 100644 --- a/__tests__/classes/CanvasRenderingContext2D.arcTo.js +++ b/__tests__/classes/CanvasRenderingContext2D.arcTo.js @@ -19,11 +19,17 @@ describe('arcTo', () => { }); it("shouldn't accept parameters less than 5", () => { - expect(() => ctx.arcTo(1, 2, 3)).toThrow(DOMException); + expect(() => ctx.arcTo()).toThrow(TypeError); + expect(() => ctx.arcTo(1)).toThrow(TypeError); + expect(() => ctx.arcTo(1, 2)).toThrow(TypeError); + expect(() => ctx.arcTo(1, 2, 3)).toThrow(TypeError); + expect(() => ctx.arcTo(1, 2, 3, 4)).toThrow(TypeError); }); it('should throw when radius is negative', () => { - expect(() => ctx.arcTo(1, 2, -1, 3, -1)).toThrow(TypeError); + const fn = () => ctx.arcTo(1, 2, 3, 4, -1); + expect(fn).toThrow(DOMException); + expect(fn).toThrow('The radius provided (-1) is negative.'); }); it('should accept 5 parameters regardless of type', () => { diff --git a/__tests__/classes/CanvasRenderingContext2D.createImagePattern.js b/__tests__/classes/CanvasRenderingContext2D.createImagePattern.js index ae0a180..1e5daa3 100644 --- a/__tests__/classes/CanvasRenderingContext2D.createImagePattern.js +++ b/__tests__/classes/CanvasRenderingContext2D.createImagePattern.js @@ -63,7 +63,9 @@ describe('createImagePattern', () => { it('should not create a pattern if the image bitmap is closed', () => { const bmp = new ImageBitmap(400, 300); bmp.close(); - expect(() => ctx.createPattern(bmp, 'repeat')).toThrow(DOMException); + const fn = () => ctx.createPattern(bmp, 'repeat'); + expect(fn).toThrow(DOMException); + expect(fn).toThrow('The image source is detached.'); }); it('should create a valid pattern for all repeat types', () => { diff --git a/__tests__/classes/CanvasRenderingContext2D.createRadialGradient.js b/__tests__/classes/CanvasRenderingContext2D.createRadialGradient.js index 8410468..46ef160 100644 --- a/__tests__/classes/CanvasRenderingContext2D.createRadialGradient.js +++ b/__tests__/classes/CanvasRenderingContext2D.createRadialGradient.js @@ -43,13 +43,16 @@ describe('createRadialGradient', () => { ); }); - it('should not create a radial gradient if any of the radius values are < 0', () => { - expect(() => ctx.createRadialGradient(0, 0, -1, 0, 0, 0)).toThrow( - DOMException - ); - expect(() => ctx.createRadialGradient(0, 0, 0, 0, 0, -1)).toThrow( - DOMException - ); + it('should not create a radial gradient if r0 value is < 0', () => { + const fn = () => ctx.createRadialGradient(0, 0, -1, 0, 0, 0); + expect(fn).toThrow(DOMException); + expect(fn).toThrow('The r0 provided is less than 0.'); + }); + + it('should not create a radial gradient if r1 value is < 0', () => { + const fn = () => ctx.createRadialGradient(0, 0, 0, 0, 0, -1); + expect(fn).toThrow(DOMException); + expect(fn).toThrow('The r1 provided is less than 0.'); }); it('should create a radial gradient with string values', () => { diff --git a/__tests__/classes/CanvasRenderingContext2D.drawImage.js b/__tests__/classes/CanvasRenderingContext2D.drawImage.js index 40daaaf..6a96023 100644 --- a/__tests__/classes/CanvasRenderingContext2D.drawImage.js +++ b/__tests__/classes/CanvasRenderingContext2D.drawImage.js @@ -36,7 +36,9 @@ describe('drawImage', () => { it('should not draw if the image bitmap is closed', () => { const bmp = new ImageBitmap(100, 100); bmp.close(); - expect(() => ctx.drawImage(bmp, 1, 2)).toThrow(DOMException); + const fn = () => ctx.drawImage(bmp, 1, 2); + expect(fn).toThrow(DOMException); + expect(fn).toThrow('The image source is detached.'); }); it('should accept 3, 5, and 9 parameters', () => { diff --git a/__tests__/classes/CanvasRenderingContext2D.ellipse.js b/__tests__/classes/CanvasRenderingContext2D.ellipse.js index 5d5aee0..be2d080 100644 --- a/__tests__/classes/CanvasRenderingContext2D.ellipse.js +++ b/__tests__/classes/CanvasRenderingContext2D.ellipse.js @@ -28,9 +28,16 @@ describe('ellipse', () => { expect(() => ctx.ellipse(1, 2, 3, 4, 5, 6)).toThrow(TypeError); }); - it('should throw when radius is negative', () => { - expect(() => ctx.ellipse(1, 2, -1, 4, 5, 6, 7)).toThrow(DOMException); - expect(() => ctx.ellipse(1, 2, 3, -1, 5, 6, 7)).toThrow(DOMException); + it('should throw when major axis radius is negative', () => { + const fn = () => ctx.ellipse(1, 2, -1, 4, 5, 6, 7); + expect(fn).toThrow(DOMException); + expect(fn).toThrow('The major-axis radius provided (-1) is negative.'); + }); + + it('should throw when minor axis radius is negative', () => { + const fn = () => ctx.ellipse(1, 2, 3, -1, 5, 6, 7); + expect(fn).toThrow(DOMException); + expect(fn).toThrow('The minor-axis radius provided (-1) is negative.'); }); it('should not throw if any value is `NaN`', () => { diff --git a/__tests__/classes/CanvasRenderingContext2D.getImageData.js b/__tests__/classes/CanvasRenderingContext2D.getImageData.js index 39a530b..e58996f 100644 --- a/__tests__/classes/CanvasRenderingContext2D.getImageData.js +++ b/__tests__/classes/CanvasRenderingContext2D.getImageData.js @@ -14,18 +14,39 @@ describe('getImageData', () => { }); it('should be callable', () => { - ctx.getImageData(); + ctx.getImageData(0, 0, 10, 20); expect(ctx.getImageData).toBeCalled(); }); + it('should throw if less than 4 parameters are given', () => { + expect(() => ctx.getImageData()).toThrow(TypeError); + expect(() => ctx.getImageData(0)).toThrow(TypeError); + expect(() => ctx.getImageData(0, 0)).toThrow(TypeError); + expect(() => ctx.getImageData(0, 0, 0)).toThrow(TypeError); + }); + it('should return a image data from getImageData', () => { - expect(ctx.getImageData()).toBeInstanceOf(ImageData); + expect(ctx.getImageData(0, 0, 10, 20)).toBeInstanceOf(ImageData); }); it('should return a image data from getImageData of proper size', () => { - const data = ctx.getImageData(); - expect(data.width).toBe(400); - expect(data.height).toBe(300); - expect(data.data.length).toBe(400 * 300 * 4); + const data = ctx.getImageData(0, 0, 10, 20); + expect(data.width).toBe(10); + expect(data.height).toBe(20); + expect(data.data.length).toBe(10 * 20 * 4); + }); + + it('should return image data from getImageData if size is negative', () => { + const data = ctx.getImageData(0, 0, -10, -20); + expect(data.width).toBe(10); + expect(data.height).toBe(20); + expect(data.data.length).toBe(10 * 20 * 4); + }); + + it('should throw if width or height are zero', () => { + expect(() => ctx.getImageData(0, 0, 0, 1)).toThrow(DOMException); + expect(() => ctx.getImageData(0, 0, 0, 1)).toThrow('source width is 0'); + expect(() => ctx.getImageData(0, 0, 1, 0)).toThrow('source height is 0'); + expect(() => ctx.getImageData(0, 0, 0, 0)).toThrow('source width is 0'); }); }); diff --git a/__tests__/classes/CanvasRenderingContext2D.js b/__tests__/classes/CanvasRenderingContext2D.js index 8b05aa4..773e8a7 100644 --- a/__tests__/classes/CanvasRenderingContext2D.js +++ b/__tests__/classes/CanvasRenderingContext2D.js @@ -18,10 +18,10 @@ describe('CanvasRenderingContext2D prototype', () => { }); it('should have a getContext function', () => { - expect(canvas.getContext).toBeInstanceOf(Function); + expect(canvas.getContext).toBeCalled(); }); it('should be defined on the prototype', () => { - expect(HTMLCanvasElement.prototype.getContext).toBeInstanceOf(Function); + expect(HTMLCanvasElement.prototype.getContext).toBeCalled(); }); }); diff --git a/__tests__/index.js b/__tests__/index.js index 948adec..300c85d 100644 --- a/__tests__/index.js +++ b/__tests__/index.js @@ -16,3 +16,14 @@ describe('canvas', () => { expect(ver).toBe(pkg.version); }); }); + +describe('setupJestCanvasMock', () => { + it('should setup after resetAllMocks', () => { + jest.resetAllMocks(); + expect(document.createElement('canvas').getContext('2d')).toBe(undefined); + setupJestCanvasMock(); + expect(document.createElement('canvas').getContext('2d')).toHaveProperty( + 'createImageData' + ); + }); +}); diff --git a/__tests__/mock/prototype.js b/__tests__/mock/prototype.js index 3cae425..6618743 100644 --- a/__tests__/mock/prototype.js +++ b/__tests__/mock/prototype.js @@ -1,3 +1,5 @@ +import { JSDOM } from 'jsdom'; +import mockPrototype from '../../src/mock/prototype'; import WebGLRenderingContext from '../../src/classes/WebGLRenderingContext'; let canvas; @@ -14,6 +16,11 @@ describe('mock', () => { expect(ctx).toBeInstanceOf(CanvasRenderingContext2D); }); + it('should expect getContext to be called', () => { + canvas.getContext('2d'); + expect(canvas.getContext).toBeCalled(); + }); + it('context creation of type webgl returns WebGLRenderingContext', () => { const ctx = canvas.getContext('webgl'); expect(ctx).toBeInstanceOf(WebGLRenderingContext); @@ -113,7 +120,7 @@ describe('mock', () => { const error = console.error; console.error = () => void 0; canvas.dataset.internalRequireTest = true; - canvas.getContext('random'); + canvas.getContext('incorrect-value'); console.error = error; }); @@ -123,6 +130,35 @@ describe('mock', () => { expect(first).toBe(second); }); + it('should work in both cases where passed in window object has and does not have .HTMLCanvasElement.prototype', () => { + // arrange/act + const mockWin = new JSDOM().window; + mockPrototype(); + // assert (should not have effected the mockWindow since we did not pass it in ) + expect( + jest.isMockFunction(mockWin.HTMLCanvasElement.prototype.getContext) + ).toBe(false); + expect( + jest.isMockFunction(mockWin.HTMLCanvasElement.prototype.toBlob) + ).toBe(false); + expect( + jest.isMockFunction(mockWin.HTMLCanvasElement.prototype.toDataURL) + ).toBe(false); + + // act + mockPrototype(mockWin); + // assert ( should mock out expected fields in passed in window object ) + expect( + jest.isMockFunction(mockWin.HTMLCanvasElement.prototype.getContext) + ).toBe(true); + expect( + jest.isMockFunction(mockWin.HTMLCanvasElement.prototype.toBlob) + ).toBe(true); + expect( + jest.isMockFunction(mockWin.HTMLCanvasElement.prototype.toDataURL) + ).toBe(true); + }); + it('should return the same context if getContext("webgl") is called twice', () => { const first = canvas.getContext('webgl'); const second = canvas.getContext('webgl'); diff --git a/__tests__/mock/window.js b/__tests__/mock/window.js new file mode 100644 index 0000000..39cdd3a --- /dev/null +++ b/__tests__/mock/window.js @@ -0,0 +1,47 @@ +import { JSDOM } from 'jsdom'; +import mockWindow from '../../src/window'; + +describe('mockWindow', () => { + it('mocks the passed object', () => { + const win = new JSDOM().window; + + mockWindow(win); + + expect(win.Path2D).not.toBeNull(); + expect(win.CanvasGradient).not.toBeNull(); + expect(win.CanvasPattern).not.toBeNull(); + expect(win.CanvasRenderingContext2D).not.toBeNull(); + expect(win.DOMMatrix).not.toBeNull(); + expect(win.ImageData).not.toBeNull(); + expect(win.TextMetrics).not.toBeNull(); + expect(win.ImageBitmap).not.toBeNull(); + expect(win.createImageBitmap).not.toBeNull(); + + expect( + jest.isMockFunction(win.HTMLCanvasElement.prototype.getContext) + ).toBe(true); + expect(jest.isMockFunction(win.HTMLCanvasElement.prototype.toBlob)).toBe( + true + ); + expect(jest.isMockFunction(win.HTMLCanvasElement.prototype.toDataURL)).toBe( + true + ); + }); + + it('mocks without a fully formed passed in window object', () => { + const win = mockWindow({ document: {} }); + + expect(win.Path2D).not.toBeNull(); + expect(win.CanvasGradient).not.toBeNull(); + expect(win.CanvasPattern).not.toBeNull(); + expect(win.CanvasRenderingContext2D).not.toBeNull(); + expect(win.DOMMatrix).not.toBeNull(); + expect(win.ImageData).not.toBeNull(); + expect(win.TextMetrics).not.toBeNull(); + expect(win.ImageBitmap).not.toBeNull(); + expect(win.createImageBitmap).not.toBeNull(); + + expect(jest.isMockFunction(win.HTMLCanvasElement)).toBe(false); + expect(win.HTMLCanvasElement).toBeUndefined(); + }); +}); diff --git a/package.json b/package.json index c154d11..8e23ab3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jest-webgl-canvas-mock", - "version": "2.5.0", + "version": "2.5.3", "description": "Mock both 2D and WebGL contexts in Jest.", "main": "lib/index.js", "types": "types/index.d.ts", @@ -25,11 +25,14 @@ "@babel/preset-env": "^7.9.5", "@commitlint/cli": "^17.6.1", "@commitlint/config-angular": "^17.6.1", + "@inrupt/jest-jsdom-polyfills": "^2.5.0", "babel-jest": "^25.3.0", "babel-plugin-version": "^0.2.3", + "coveralls": "^3.0.11", "husky": "^4.2.5", "jest": "^29.5.0", "jest-environment-jsdom": "^29.5.0", + "jsdom": "^22.1.0", "prettier": "^2.0.4" }, "commitlint": { @@ -51,6 +54,7 @@ "src/mock/**/*.js" ], "setupFiles": [ + "@inrupt/jest-jsdom-polyfills", "./src/index.js", "./__mocks__/worker.js" ] diff --git a/src/classes/CanvasGradient.js b/src/classes/CanvasGradient.js index 6685e88..b6edb97 100644 --- a/src/classes/CanvasGradient.js +++ b/src/classes/CanvasGradient.js @@ -9,10 +9,10 @@ export default class CanvasGradient { const numoffset = Number(offset); 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)" + "') is outside the range (0.0, 1.0)", + 'IndexSizeError' ); } try { diff --git a/src/classes/CanvasRenderingContext2D.js b/src/classes/CanvasRenderingContext2D.js index b7e063a..3f2a42c 100644 --- a/src/classes/CanvasRenderingContext2D.js +++ b/src/classes/CanvasRenderingContext2D.js @@ -189,10 +189,10 @@ export default class CanvasRenderingContext2D { options; if (!path && !id) throw new DOMException( - 'ConstraintError', "Failed to execute 'addHitRegion' on '" + this.constructor.name + - "': Both id and control are null." + "': Both id and control are null.", + 'ConstraintError' ); if (fillRule && fillRule !== 'evenodd' && fillRule !== 'nonzero') throw new TypeError( @@ -247,12 +247,12 @@ export default class CanvasRenderingContext2D { if (Number(radius) < 0) throw new DOMException( - 'IndexSizeError', "Failed to execute 'arc' on '" + this.constructor.name + "': The radius provided (" + radius + - ') is negative.' + ') is negative.', + 'IndexSizeError' ); const event = createCanvasEvent('arc', getTransformSlice(this), { @@ -269,8 +269,7 @@ export default class CanvasRenderingContext2D { arcTo(cpx1, cpy1, cpx2, cpy2, radius) { if (arguments.length < 5) - throw new DOMException( - 'IndexSizeError', + throw new TypeError( "Failed to execute 'arcTo' on '" + this.constructor.name + "': 5 arguments required, but only " + @@ -290,12 +289,13 @@ export default class CanvasRenderingContext2D { ) return; if (radiusResult < 0) - throw new TypeError( + throw new DOMException( "Failed to execute 'arcTo' on '" + this.constructor.name + "': The radius provided (" + radius + - ') is negative.' + ') is negative.', + 'IndexSizeError' ); const event = createCanvasEvent('arcTo', getTransformSlice(this), { @@ -558,8 +558,8 @@ export default class CanvasRenderingContext2D { if (image instanceof ImageBitmap) { if (image._closed) throw new DOMException( - 'InvalidStateError', - "Failed to execute 'createPattern' on 'CanvasRenderingContext2D': The image source is detached." + "Failed to execute 'createPattern' on 'CanvasRenderingContext2D': The image source is detached.", + 'InvalidStateError' ); this._events.push(event); return new CanvasPattern(); @@ -621,17 +621,17 @@ export default class CanvasRenderingContext2D { ); if (r0Result < 0) throw new DOMException( - 'IndexSizeError', "Failed to execute 'createRadialGradient' on '" + this.constructor.name + - "': The r0 provided is less than 0." + "': The r0 provided is less than 0.", + 'IndexSizeError' ); if (r1Result < 0) throw new DOMException( - 'IndexSizeError', "Failed to execute 'createRadialGradient' on '" + this.constructor.name + - "': The r1 provided is less than 0." + "': The r1 provided is less than 0.", + 'IndexSizeError' ); const event = createCanvasEvent( @@ -751,8 +751,8 @@ export default class CanvasRenderingContext2D { if (img instanceof ImageBitmap) { if (img._closed) throw new DOMException( - 'InvalidStateError', - "DOMException: Failed to execute 'drawImage' on 'CanvasRenderingContext2D': The image source is detached." + "DOMException: Failed to execute 'drawImage' on 'CanvasRenderingContext2D': The image source is detached.", + 'InvalidStateError' ); valid = true; } @@ -869,21 +869,21 @@ export default class CanvasRenderingContext2D { if (Number(radiusX) < 0) throw new DOMException( - 'IndexSizeError', "Failed to execute 'ellipse' on '" + this.constructor.name + "': The major-axis radius provided (" + radiusX + - ') is negative.' + ') is negative.', + 'IndexSizeError' ); if (Number(radiusY) < 0) throw new DOMException( - 'IndexSizeError', "Failed to execute 'ellipse' on '" + this.constructor.name + "': The minor-axis radius provided (" + radiusY + - ') is negative.' + ') is negative.', + 'IndexSizeError' ); const event = createCanvasEvent('ellipse', getTransformSlice(this), { @@ -1058,8 +1058,25 @@ export default class CanvasRenderingContext2D { return this._fontStack[this._stackIndex]; } - getImageData() { - return new ImageData(this._canvas.width, this._canvas.height); + getImageData(x, y, w, h) { + if (arguments.length < 4) + throw new TypeError( + "Failed to execute 'getImageData' on '" + + this.constructor.name + + "': 4 arguments required, but only " + + arguments.length + + ' present.' + ); + if (w == 0 || h == 0) + throw new DOMException( + "Failed to execute 'getImageData' on '" + + this.constructor.name + + "': The source " + + (w == 0 ? 'width' : 'height') + + ' is 0.', + 'IndexSizeError' + ); + return new ImageData(Math.abs(w), Math.abs(h)); } getLineDash() { diff --git a/src/mock/prototype.js b/src/mock/prototype.js index 217d28d..4c6a4a0 100644 --- a/src/mock/prototype.js +++ b/src/mock/prototype.js @@ -1,7 +1,7 @@ import CanvasRenderingContext2D from '../classes/CanvasRenderingContext2D'; import WebGLRenderingContext from '../classes/WebGLRenderingContext'; -export default function mockPrototype() { +export default function mockPrototype(win) { /** * 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 @@ -13,7 +13,7 @@ export default function mockPrototype() { * value of getContext. It attempts to preserve the original getContext function by storing it on * the callback as a property. */ - function getContext(type, options) { + const getContextMock = jest.fn(function getContext(type, options) { if (type === '2d') { /** * Contexts must be idempotent. Once they are generated, they should be returned when @@ -29,12 +29,28 @@ export default function mockPrototype() { generatedContexts.set(this, ctx); return ctx; } - return getContext.internal.call(this, type, options); - } - getContext.internal = HTMLCanvasElement.prototype.getContext; + // return getContext.internal.call(this, type, options); + + try { + if (!this.dataset.internalRequireTest) require('canvas'); + } catch { + return null; + } + return getContextMock.internal.call(this, type, options); + }); + + let htmlCanvasElementPrototype = HTMLCanvasElement.prototype; + if (win?.HTMLCanvasElement?.prototype) { + htmlCanvasElementPrototype = win?.HTMLCanvasElement?.prototype; + } - HTMLCanvasElement.prototype.getContext = getContext; + if (!jest.isMockFunction(htmlCanvasElementPrototype.getContext)) { + getContextMock.internal = htmlCanvasElementPrototype.getContext; + } else { + getContextMock.internal = htmlCanvasElementPrototype.getContext.internal; + } + htmlCanvasElementPrototype.getContext = getContextMock; /** * This function technically throws SecurityError at runtime, but it cannot be mocked, because @@ -73,12 +89,12 @@ export default function mockPrototype() { setTimeout(() => callback(blob), 0); }); - if (!jest.isMockFunction(HTMLCanvasElement.prototype.toBlob)) { - toBlobOverride.internal = HTMLCanvasElement.prototype.toBlob; + if (!jest.isMockFunction(htmlCanvasElementPrototype.toBlob)) { + toBlobOverride.internal = htmlCanvasElementPrototype.toBlob; } else { - toBlobOverride.internal = HTMLCanvasElement.prototype.toBlob.internal; + toBlobOverride.internal = htmlCanvasElementPrototype.toBlob.internal; } - HTMLCanvasElement.prototype.toBlob = toBlobOverride; + htmlCanvasElementPrototype.toBlob = toBlobOverride; /** * This section creates a dataurl with a validated mime type. This is not actually valid, because @@ -103,10 +119,10 @@ export default function mockPrototype() { return 'data:' + type + ';base64,00'; }); - if (!jest.isMockFunction(HTMLCanvasElement.prototype.toDataURL)) { - toDataURLOverride.internal = HTMLCanvasElement.prototype.toDataURL; + if (!jest.isMockFunction(htmlCanvasElementPrototype.toDataURL)) { + toDataURLOverride.internal = htmlCanvasElementPrototype.toDataURL; } else { - toDataURLOverride.internal = HTMLCanvasElement.prototype.toDataURL.internal; + toDataURLOverride.internal = htmlCanvasElementPrototype.toDataURL.internal; } - HTMLCanvasElement.prototype.toDataURL = toDataURLOverride; + htmlCanvasElementPrototype.toDataURL = toDataURLOverride; } diff --git a/src/window.js b/src/window.js index 9c190da..8bf4391 100644 --- a/src/window.js +++ b/src/window.js @@ -77,7 +77,7 @@ export default (win) => { if (!win.WebGLTexture) win.WebGLTexture = function () {}; if (!win.WebGLUniformLocation) win.WebGLUniformLocation = function () {}; - mockPrototype(); + mockPrototype(win); return win; }; diff --git a/types/index.d.ts b/types/index.d.ts index ff2d50c..640e55c 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1,4 +1,4 @@ -export function setupJestCanvasMock(window?: Window) {} +export function setupJestCanvasMock(window?: Window): void; export interface CanvasRenderingContext2DEvent { /**