diff --git a/examples/existing_canvas.html b/examples/existing_canvas.html
index ac7be53..baa743e 100644
--- a/examples/existing_canvas.html
+++ b/examples/existing_canvas.html
@@ -42,7 +42,7 @@
ctx.stroke();
document.querySelector("button").addEventListener("click", function() {
- html2canvas(document.querySelector("#content"), {canvas: canvas}).then(function(canvas) {
+ html2canvas(document.querySelector("#content"), {canvas: canvas, scale: 1}).then(function(canvas) {
console.log('Drew on the existing canvas');
});
}, false);
diff --git a/src/__tests__/index.ts b/src/__tests__/index.ts
new file mode 100644
index 0000000..1cc7708
--- /dev/null
+++ b/src/__tests__/index.ts
@@ -0,0 +1,91 @@
+import html2canvas from '../index';
+
+import {CanvasRenderer} from '../render/canvas/canvas-renderer';
+import {DocumentCloner} from '../dom/document-cloner';
+import {COLORS} from '../css/types/color';
+
+jest.mock('../core/logger');
+jest.mock('../css/layout/bounds');
+jest.mock('../dom/document-cloner');
+jest.mock('../dom/node-parser', () => {
+ return {
+ isBodyElement: () => false,
+ isHTMLElement: () => false,
+ parseTree: jest.fn().mockImplementation(() => {
+ return {styles: {}};
+ })
+ };
+});
+
+jest.mock('../render/stacking-context');
+jest.mock('../render/canvas/canvas-renderer');
+
+describe('html2canvas', () => {
+ // eslint-disable-next-line @typescript-eslint/no-object-literal-type-assertion
+ const element = {
+ ownerDocument: {
+ defaultView: {
+ pageXOffset: 12,
+ pageYOffset: 34
+ }
+ }
+ } as HTMLElement;
+
+ it('should render with an element', async () => {
+ DocumentCloner.destroy = jest.fn().mockReturnValue(true);
+ await html2canvas(element);
+ expect(CanvasRenderer).toHaveBeenLastCalledWith(
+ expect.objectContaining({
+ backgroundColor: 0xffffffff,
+ scale: 1,
+ height: 50,
+ width: 200,
+ x: 0,
+ y: 0,
+ scrollX: 12,
+ scrollY: 34,
+ canvas: undefined
+ })
+ );
+ expect(DocumentCloner.destroy).toBeCalled();
+ });
+
+ it('should have transparent background with backgroundColor: null', async () => {
+ await html2canvas(element, {backgroundColor: null});
+ expect(CanvasRenderer).toHaveBeenLastCalledWith(
+ expect.objectContaining({
+ backgroundColor: COLORS.TRANSPARENT
+ })
+ );
+ });
+
+ it('should use existing canvas when given as option', async () => {
+ // eslint-disable-next-line @typescript-eslint/no-object-literal-type-assertion
+ const canvas = {} as HTMLCanvasElement;
+ await html2canvas(element, {canvas});
+ expect(CanvasRenderer).toHaveBeenLastCalledWith(
+ expect.objectContaining({
+ canvas
+ })
+ );
+ });
+
+ it('should not remove cloned window when removeContainer: false', async () => {
+ DocumentCloner.destroy = jest.fn();
+ await html2canvas(element, {removeContainer: false});
+ expect(CanvasRenderer).toHaveBeenLastCalledWith(
+ expect.objectContaining({
+ backgroundColor: 0xffffffff,
+ scale: 1,
+ height: 50,
+ width: 200,
+ x: 0,
+ y: 0,
+ scrollX: 12,
+ scrollY: 34,
+ canvas: undefined
+ })
+ );
+ expect(DocumentCloner.destroy).not.toBeCalled();
+ });
+});
diff --git a/src/core/__mocks__/logger.ts b/src/core/__mocks__/logger.ts
new file mode 100644
index 0000000..8819bcd
--- /dev/null
+++ b/src/core/__mocks__/logger.ts
@@ -0,0 +1,17 @@
+export class Logger {
+ debug() {}
+
+ static create() {}
+
+ static destroy() {}
+
+ static getInstance(): Logger {
+ return logger;
+ }
+
+ info() {}
+
+ error() {}
+}
+
+const logger = new Logger();
diff --git a/src/css/layout/__mocks__/bounds.ts b/src/css/layout/__mocks__/bounds.ts
new file mode 100644
index 0000000..34daef7
--- /dev/null
+++ b/src/css/layout/__mocks__/bounds.ts
@@ -0,0 +1,4 @@
+export const {Bounds} = jest.requireActual('../bounds');
+export const parseBounds = () => {
+ return new Bounds(0, 0, 200, 50);
+};
diff --git a/src/dom/__mocks__/document-cloner.ts b/src/dom/__mocks__/document-cloner.ts
new file mode 100644
index 0000000..366ceec
--- /dev/null
+++ b/src/dom/__mocks__/document-cloner.ts
@@ -0,0 +1,16 @@
+export class DocumentCloner {
+ clonedReferenceElement?: HTMLElement;
+
+ constructor() {
+ // eslint-disable-next-line @typescript-eslint/no-object-literal-type-assertion
+ this.clonedReferenceElement = {} as HTMLElement;
+ }
+
+ toIFrame() {
+ return Promise.resolve({});
+ }
+
+ static destroy() {
+ return true;
+ }
+}
diff --git a/src/dom/document-cloner.ts b/src/dom/document-cloner.ts
index 360084a..54e7ba4 100644
--- a/src/dom/document-cloner.ts
+++ b/src/dom/document-cloner.ts
@@ -410,6 +410,14 @@ export class DocumentCloner {
: ` ${PSEUDO_HIDE_ELEMENT_CLASS_AFTER}`;
return anonymousReplacedElement;
}
+
+ static destroy(container: HTMLIFrameElement): boolean {
+ if (container.parentNode) {
+ container.parentNode.removeChild(container);
+ return true;
+ }
+ return false;
+ }
}
enum PseudoElementType {
diff --git a/src/index.ts b/src/index.ts
index 7e47073..ee5e9a1 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -116,6 +116,7 @@ const renderElement = async (element: HTMLElement, opts: Partial): Prom
const renderOptions = {
id: instanceName,
cache: options.cache,
+ canvas: options.canvas,
backgroundColor,
scale: options.scale,
x: options.x,
@@ -153,7 +154,7 @@ const renderElement = async (element: HTMLElement, opts: Partial): Prom
}
if (options.removeContainer === true) {
- if (!cleanContainer(container)) {
+ if (!DocumentCloner.destroy(container)) {
Logger.getInstance(instanceName).error(`Cannot detach cloned iframe as it is not in the DOM anymore`);
}
}
@@ -163,11 +164,3 @@ const renderElement = async (element: HTMLElement, opts: Partial): Prom
CacheStorage.destroy(instanceName);
return canvas;
};
-
-const cleanContainer = (container: HTMLIFrameElement): boolean => {
- if (container.parentNode) {
- container.parentNode.removeChild(container);
- return true;
- }
- return false;
-};
diff --git a/src/render/canvas/canvas-renderer.ts b/src/render/canvas/canvas-renderer.ts
index 87303c1..93ca923 100644
--- a/src/render/canvas/canvas-renderer.ts
+++ b/src/render/canvas/canvas-renderer.ts
@@ -71,10 +71,12 @@ export class CanvasRenderer {
this.canvas = options.canvas ? options.canvas : document.createElement('canvas');
this.ctx = this.canvas.getContext('2d') as CanvasRenderingContext2D;
this.options = options;
- this.canvas.width = Math.floor(options.width * options.scale);
- this.canvas.height = Math.floor(options.height * options.scale);
- this.canvas.style.width = `${options.width}px`;
- this.canvas.style.height = `${options.height}px`;
+ if (!options.canvas) {
+ this.canvas.width = Math.floor(options.width * options.scale);
+ this.canvas.height = Math.floor(options.height * options.scale);
+ this.canvas.style.width = `${options.width}px`;
+ this.canvas.style.height = `${options.height}px`;
+ }
this.fontMetrics = new FontMetrics(document);
this.ctx.scale(this.options.scale, this.options.scale);
this.ctx.translate(-options.x + options.scrollX, -options.y + options.scrollY);
diff --git a/www/src/preview.ts b/www/src/preview.ts
index 6d21d3b..d780a19 100644
--- a/www/src/preview.ts
+++ b/www/src/preview.ts
@@ -42,8 +42,10 @@ function onBrowserChange(browserTest: Test) {
previewImage.src = `/results/${browserTest.screenshot}.png`;
if (browserTest.devicePixelRatio > 1) {
previewImage.style.transform = `scale(${1 / browserTest.devicePixelRatio})`;
+ previewImage.style.transformOrigin = 'top left';
} else {
previewImage.style.transform = '';
+ previewImage.style.transformOrigin = '';
}
}
}