vi-canvas-mock
Version:
Mock a canvas in your vitest tests.
108 lines (102 loc) • 4.05 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = mockPrototype;
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 = vi.fn(function getContext2d(type) {
if (type === '2d') {
/**
* Contexts must be idempotent. 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 (_unused) {
return null;
}
return getContext2D.internal.call(this, type);
});
if (!vi.isMockFunction(HTMLCanvasElement.prototype.getContext)) {
getContext2D.internal = HTMLCanvasElement.prototype.getContext;
} else {
getContext2D.internal = HTMLCanvasElement.prototype.getContext.internal;
}
HTMLCanvasElement.prototype.getContext = getContext2D;
/**
* This function technically throws SecurityError at runtime, but it cannot be mocked, because
* we don't know if the canvas is tainted. These kinds of errors will be silent.
*/
const toBlobOverride = vi.fn(function toBlobOverride(callback, mimetype) {
if (arguments.length < 1) throw new TypeError("Failed to execute 'toBlob' on 'HTMLCanvasElement': 1 argument required, but only 0 present.");
if (typeof callback !== 'function') throw new TypeError("Failed to execute 'toBlob' on 'HTMLCanvasElement': The callback provided as parameter 1 is not a function.");
/**
* Mime type must be image/jpeg or image/webp exactly for the browser to accept it, otherwise
* it's image/png.
*/
switch (mimetype) {
case 'image/webp':
break;
case 'image/jpeg':
break;
default:
mimetype = 'image/png';
}
/**
* This section creates a blob of size width * height * 4. This is not actually valid, because
* jpeg size is variable, and so is png. TODO: Is there a better way to do this?
*/
const length = this.width * this.height * 4;
const data = new Uint8Array(length);
const blob = new window.Blob([data], {
type: mimetype
});
setTimeout(() => callback(blob), 0);
});
if (!vi.isMockFunction(HTMLCanvasElement.prototype.toBlob)) {
toBlobOverride.internal = HTMLCanvasElement.prototype.toBlob;
} else {
toBlobOverride.internal = HTMLCanvasElement.prototype.toBlob.internal;
}
HTMLCanvasElement.prototype.toBlob = toBlobOverride;
/**
* This section creates a dataurl with a validated mime type. This is not actually valid, because
* jpeg size is variable, and so is png. TODO: Is there a better way to do this?
*/
const toDataURLOverride = vi.fn(function toDataURLOverride(type, encoderOptions) {
switch (type) {
case 'image/jpeg':
break;
case 'image/webp':
break;
default:
type = 'image/png';
}
/**
* This is the smallest valid data url I could generate.
*/
return 'data:' + type + ';base64,00';
});
if (!vi.isMockFunction(HTMLCanvasElement.prototype.toDataURL)) {
toDataURLOverride.internal = HTMLCanvasElement.prototype.toDataURL;
} else {
toDataURLOverride.internal = HTMLCanvasElement.prototype.toDataURL.internal;
}
HTMLCanvasElement.prototype.toDataURL = toDataURLOverride;
}