diff --git a/jest.config.js b/jest.config.js index dc4ebd9c13f..483ec27c5ae 100644 --- a/jest.config.js +++ b/jest.config.js @@ -52,5 +52,6 @@ module.exports = { setupFilesAfterEnv: [ '/web/packages/build/jest/setupTests.ts', '/web/packages/build/jest/customMatchers.ts', + '/web/packages/build/jest/canvasMock.ts', ], }; diff --git a/package.json b/package.json index d3fc756b497..ab077886d58 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,6 @@ "eslint-plugin-testing-library": "7.16.2", "eslint-plugin-unused-imports": "^4.4.1", "jest": "^30.3.0", - "jest-canvas-mock": "^2.5.2", "jsdom-testing-mocks": "^1.16.0", "msw": "2.14.2", "msw-storybook-addon": "^2.0.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 02d167df743..375aaca5b21 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,11 +6,6 @@ settings: pnpmfileChecksum: sha256-TR6zTaL3sXZMyu+QoRdd27aSUTH6sH0gp+M2XjxgTUI= -patchedDependencies: - jest-canvas-mock@2.5.2: - hash: 8329a68f6b8f930f6204c405a071859893962d4a6641b6c2c2bb33122c0441b1 - path: web/patches/jest-canvas-mock@2.5.2.patch - importers: .: @@ -184,9 +179,6 @@ importers: jest: specifier: ^30.3.0 version: 30.3.0(@types/node@24.10.9)(babel-plugin-macros@3.1.0) - jest-canvas-mock: - specifier: ^2.5.2 - version: 2.5.2(patch_hash=8329a68f6b8f930f6204c405a071859893962d4a6641b6c2c2bb33122c0441b1) jsdom-testing-mocks: specifier: ^1.16.0 version: 1.16.0 @@ -4166,9 +4158,6 @@ packages: css.escape@1.5.1: resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} - cssfontparser@1.2.1: - resolution: {integrity: sha512-6tun4LoZnj7VN6YeegOVb67KBX/7JJsqvj+pv3ZA7F878/eN33AbGa5b/S/wXxS/tcp8nc40xRUrsPlxIyNUPg==} - csso@5.0.5: resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} @@ -5088,9 +5077,6 @@ packages: engines: {node: '>=10'} hasBin: true - jest-canvas-mock@2.5.2: - resolution: {integrity: sha512-vgnpPupjOL6+L5oJXzxTxFrlGEIbHdZqFU+LFNdtLxZ3lRDCl17FlTMM7IatoRQkrcyOTMlDinjUguqmQ6bR2A==} - jest-changed-files@30.3.0: resolution: {integrity: sha512-B/7Cny6cV5At6M25EWDgf9S617lHivamL8vl6KEpJqkStauzcG4e+WPfDgMMF+H4FVH4A2PLRyvgDJan4441QA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -5600,9 +5586,6 @@ packages: module-details-from-path@1.0.4: resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} - moo-color@1.0.2: - resolution: {integrity: sha512-5iXz5n9LWQzx/C2WesGFfpE6RLamzdHwsn3KpfzShwbfIqs7stnoEpaNErf/7+3mbxwZ4s8Foq7I0tPxw7BWHg==} - ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -10814,8 +10797,6 @@ snapshots: css.escape@1.5.1: {} - cssfontparser@1.2.1: {} - csso@5.0.5: dependencies: css-tree: 2.2.1 @@ -11835,11 +11816,6 @@ snapshots: filelist: 1.0.4 minimatch: 3.1.5 - jest-canvas-mock@2.5.2(patch_hash=8329a68f6b8f930f6204c405a071859893962d4a6641b6c2c2bb33122c0441b1): - dependencies: - cssfontparser: 1.2.1 - moo-color: 1.0.2 - jest-changed-files@30.3.0: dependencies: execa: 5.1.1 @@ -12518,10 +12494,6 @@ snapshots: module-details-from-path@1.0.4: {} - moo-color@1.0.2: - dependencies: - color-name: 1.1.4 - ms@2.1.3: {} msw-storybook-addon@2.0.7(msw@2.14.2(@types/node@24.10.9)(typescript@6.0.3)): diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 64bcacdd6d2..b2ac3287b8a 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -39,8 +39,5 @@ shellEmulator: true # affect performance outside of docker. # https://github.com/gravitational/teleport/issues/46019 packageImportMethod: clone-or-copy -patchedDependencies: - # Needed for roundRect support which was merged to jest-canvas-mock but not released. - jest-canvas-mock@2.5.2: web/patches/jest-canvas-mock@2.5.2.patch # Do not install packages released less than 2 days (2880 minutes) ago minimumReleaseAge: 2880 diff --git a/web/packages/build/jest/canvasMock.ts b/web/packages/build/jest/canvasMock.ts new file mode 100644 index 00000000000..b0bc46a18ce --- /dev/null +++ b/web/packages/build/jest/canvasMock.ts @@ -0,0 +1,534 @@ +/** + * Teleport + * Copyright (C) 2026 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +// Minimal in-repo replacement for jest-canvas-mock. jsdom does not implement +// HTMLCanvasElement.getContext('2d'), and a handful of tests inspect the +// recorded canvas events to assert what was drawn (see SessionRecordings +// timeline renderers). This file mocks just enough of CanvasRenderingContext2D +// for those tests, exposing the same __getEvents/__getDrawCalls/__getPath +// helpers that jest-canvas-mock used to provide. + +type Transform = [number, number, number, number, number, number]; + +type CanvasStyle = string | CanvasGradient | CanvasPattern; + +type EventProps = { [key: string]: any }; + +export interface CanvasRenderingContext2DEvent { + type: string; + transform: Transform; + props: EventProps; +} + +const IDENTITY: Transform = [1, 0, 0, 1, 0, 0]; + +class MockTextMetrics implements TextMetrics { + readonly width: number; + readonly actualBoundingBoxLeft = 0; + readonly actualBoundingBoxRight = 0; + readonly fontBoundingBoxAscent = 0; + readonly fontBoundingBoxDescent = 0; + readonly actualBoundingBoxAscent = 0; + readonly actualBoundingBoxDescent = 0; + readonly emHeightAscent = 0; + readonly emHeightDescent = 0; + readonly hangingBaseline = 0; + readonly alphabeticBaseline = 0; + readonly ideographicBaseline = 0; + + constructor(text: string) { + this.width = text.length; + } +} + +class MockCanvasGradient implements CanvasGradient { + addColorStop(_offset: number, _color: string) {} +} + +class MockCanvasPattern implements CanvasPattern { + setTransform(_transform?: DOMMatrix2DInit) {} +} + +function createEvent( + type: string, + props: EventProps +): CanvasRenderingContext2DEvent { + return { type, transform: IDENTITY.slice() as Transform, props }; +} + +class MockCanvasRenderingContext2D { + private _events: CanvasRenderingContext2DEvent[] = []; + private _drawCalls: CanvasRenderingContext2DEvent[] = []; + private _path: CanvasRenderingContext2DEvent[] = [ + createEvent('beginPath', {}), + ]; + private _clip: CanvasRenderingContext2DEvent[] = []; + + fillStyle: CanvasStyle = '#000000'; + strokeStyle: CanvasStyle = '#000000'; + font = '10px sans-serif'; + textAlign: CanvasTextAlign = 'start'; + textBaseline: CanvasTextBaseline = 'alphabetic'; + direction: CanvasDirection = 'inherit'; + lineWidth = 1; + lineCap: CanvasLineCap = 'butt'; + lineJoin: CanvasLineJoin = 'miter'; + miterLimit = 10; + lineDashOffset = 0; + globalAlpha = 1; + globalCompositeOperation: GlobalCompositeOperation = 'source-over'; + shadowBlur = 0; + shadowColor = 'rgba(0, 0, 0, 0)'; + shadowOffsetX = 0; + shadowOffsetY = 0; + imageSmoothingEnabled = true; + imageSmoothingQuality: ImageSmoothingQuality = 'low'; + filter = 'none'; + fontKerning: CanvasFontKerning = 'auto'; + fontStretch: CanvasFontStretch = 'normal'; + fontVariantCaps: CanvasFontVariantCaps = 'normal'; + letterSpacing = '0px'; + wordSpacing = '0px'; + textRendering: CanvasTextRendering = 'auto'; + + readonly canvas: HTMLCanvasElement; + + constructor(canvas: HTMLCanvasElement) { + this.canvas = canvas; + } + + private push( + type: string, + props: EventProps = {} + ): CanvasRenderingContext2DEvent { + const event = createEvent(type, props); + this._events.push(event); + return event; + } + + private pushDraw( + type: string, + props: EventProps = {} + ): CanvasRenderingContext2DEvent { + const event = this.push(type, props); + this._drawCalls.push(event); + return event; + } + + private pushPath( + type: string, + props: EventProps = {} + ): CanvasRenderingContext2DEvent { + const event = this.push(type, props); + this._path.push(event); + return event; + } + + __getEvents(): CanvasRenderingContext2DEvent[] { + return this._events.slice(); + } + + __clearEvents() { + this._events = []; + } + + __getDrawCalls(): CanvasRenderingContext2DEvent[] { + return this._drawCalls.slice(); + } + + __clearDrawCalls() { + this._drawCalls = []; + } + + __getPath(): CanvasRenderingContext2DEvent[] { + return this._path.slice(); + } + + __clearPath() { + this._path = [createEvent('beginPath', {})]; + } + + __getClippingRegion(): CanvasRenderingContext2DEvent[] { + return this._clip.slice(); + } + + save() { + this.push('save'); + } + + restore() { + this.push('restore'); + } + + scale(x: number, y: number) { + this.push('scale', { x, y }); + } + + rotate(angle: number) { + this.push('rotate', { angle }); + } + + translate(x: number, y: number) { + this.push('translate', { x, y }); + } + + transform(a: number, b: number, c: number, d: number, e: number, f: number) { + this.push('transform', { a, b, c, d, e, f }); + } + + setTransform( + a?: number | DOMMatrix2DInit, + b?: number, + c?: number, + d?: number, + e?: number, + f?: number + ) { + this.push('setTransform', { a, b, c, d, e, f }); + } + + resetTransform() { + this.push('resetTransform'); + } + + getTransform(): DOMMatrix { + return { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 } as DOMMatrix; + } + + beginPath() { + this.__clearPath(); + this._events.push(this._path[0]); + } + + closePath() { + this.pushPath('closePath'); + } + + moveTo(x: number, y: number) { + this.pushPath('moveTo', { x, y }); + } + + lineTo(x: number, y: number) { + this.pushPath('lineTo', { x, y }); + } + + bezierCurveTo( + cp1x: number, + cp1y: number, + cp2x: number, + cp2y: number, + x: number, + y: number + ) { + this.pushPath('bezierCurveTo', { cp1x, cp1y, cp2x, cp2y, x, y }); + } + + quadraticCurveTo(cpx: number, cpy: number, x: number, y: number) { + this.pushPath('quadraticCurveTo', { cpx, cpy, x, y }); + } + + arc( + x: number, + y: number, + radius: number, + startAngle: number, + endAngle: number, + anticlockwise = false + ) { + this.pushPath('arc', { + x, + y, + radius, + startAngle, + endAngle, + anticlockwise, + }); + } + + arcTo(x1: number, y1: number, x2: number, y2: number, radius: number) { + this.pushPath('arcTo', { x1, y1, x2, y2, radius }); + } + + ellipse( + x: number, + y: number, + radiusX: number, + radiusY: number, + rotation: number, + startAngle: number, + endAngle: number, + anticlockwise = false + ) { + this.pushPath('ellipse', { + x, + y, + radiusX, + radiusY, + rotation, + startAngle, + endAngle, + anticlockwise, + }); + } + + rect(x: number, y: number, width: number, height: number) { + this.pushPath('rect', { x, y, width, height }); + } + + roundRect( + x: number, + y: number, + width: number, + height: number, + radii?: number | DOMPointInit | (number | DOMPointInit)[] + ) { + this.pushPath('roundRect', { x, y, width, height, radii }); + } + + fill() { + this.pushDraw('fill'); + } + + stroke() { + this.pushDraw('stroke'); + } + + clip() { + const event = this.push('clip'); + this._clip.push(event); + } + + fillRect(x: number, y: number, width: number, height: number) { + this.pushDraw('fillRect', { x, y, width, height }); + } + + strokeRect(x: number, y: number, width: number, height: number) { + this.pushDraw('strokeRect', { x, y, width, height }); + } + + clearRect(x: number, y: number, width: number, height: number) { + this.pushDraw('clearRect', { x, y, width, height }); + } + + fillText(text: string, x: number, y: number, maxWidth?: number) { + this.pushDraw('fillText', { text, x, y, maxWidth }); + } + + strokeText(text: string, x: number, y: number, maxWidth?: number) { + this.pushDraw('strokeText', { text, x, y, maxWidth }); + } + + drawImage( + image: CanvasImageSource, + sxOrDx: number, + syOrDy: number, + sWidthOrDWidth?: number, + sHeightOrDHeight?: number, + dx?: number, + dy?: number, + dWidth?: number, + dHeight?: number + ) { + if (dx === undefined) { + this.pushDraw('drawImage', { + img: image, + dx: sxOrDx, + dy: syOrDy, + dWidth: sWidthOrDWidth, + dHeight: sHeightOrDHeight, + }); + return; + } + this.pushDraw('drawImage', { + img: image, + sx: sxOrDx, + sy: syOrDy, + sWidth: sWidthOrDWidth, + sHeight: sHeightOrDHeight, + dx, + dy, + dWidth, + dHeight, + }); + } + + measureText(text: string): TextMetrics { + const normalized = text == null ? '' : String(text); + this.push('measureText', { text: normalized }); + return new MockTextMetrics(normalized); + } + + createLinearGradient( + x0: number, + y0: number, + x1: number, + y1: number + ): CanvasGradient { + this.push('createLinearGradient', { x0, y0, x1, y1 }); + return new MockCanvasGradient(); + } + + createRadialGradient( + x0: number, + y0: number, + r0: number, + x1: number, + y1: number, + r1: number + ): CanvasGradient { + this.push('createRadialGradient', { x0, y0, r0, x1, y1, r1 }); + return new MockCanvasGradient(); + } + + createConicGradient( + startAngle: number, + x: number, + y: number + ): CanvasGradient { + this.push('createConicGradient', { startAngle, x, y }); + return new MockCanvasGradient(); + } + + createPattern( + _image: CanvasImageSource, + _repetition: string | null + ): CanvasPattern | null { + return new MockCanvasPattern(); + } + + createImageData( + widthOrImageData: number | ImageData, + height?: number + ): ImageData { + const isImageData = typeof widthOrImageData !== 'number'; + const w = isImageData ? widthOrImageData.width : widthOrImageData; + const h = isImageData ? widthOrImageData.height : (height ?? 0); + return { + data: new Uint8ClampedArray(w * h * 4), + width: w, + height: h, + colorSpace: 'srgb', + } as ImageData; + } + + getImageData(_sx: number, _sy: number, sw: number, sh: number): ImageData { + return { + data: new Uint8ClampedArray(sw * sh * 4), + width: sw, + height: sh, + colorSpace: 'srgb', + } as ImageData; + } + + putImageData() {} + + setLineDash(_segments: number[]) {} + + getLineDash(): number[] { + return []; + } + + isPointInPath(): boolean { + return false; + } + + isPointInStroke(): boolean { + return false; + } + + drawFocusIfNeeded() {} + + scrollPathIntoView() {} + + getContextAttributes(): CanvasRenderingContext2DSettings { + return {}; + } + + isContextLost(): boolean { + return false; + } + + reset() { + this.__clearEvents(); + this.__clearDrawCalls(); + this.__clearPath(); + this._clip = []; + } +} + +const contexts = new WeakMap(); + +function installCanvasMock() { + if (typeof window === 'undefined') return; + const proto = window.HTMLCanvasElement?.prototype; + if (!proto) return; + + // The DOM `getContext` has many context-specific overloads we can't + // express in a single signature. Define it via Object.defineProperty so the + // value slot doesn't have to satisfy every overload simultaneously. + Object.defineProperty(proto, 'getContext', { + configurable: true, + writable: true, + value: function getContext( + this: HTMLCanvasElement, + type: string + ): MockCanvasRenderingContext2D | null { + if (type !== '2d') return null; + let ctx = contexts.get(this); + if (!ctx) { + ctx = new MockCanvasRenderingContext2D(this); + contexts.set(this, ctx); + } + return ctx; + }, + }); + + proto.toDataURL = function toDataURL(type?: string): string { + const mime = + type === 'image/jpeg' || type === 'image/webp' ? type : 'image/png'; + return `data:${mime};base64,00`; + }; + + proto.toBlob = function toBlob(callback: BlobCallback, type?: string) { + const mime = + type === 'image/jpeg' || type === 'image/webp' ? type : 'image/png'; + const length = (this.width || 1) * (this.height || 1) * 4; + const blob = new window.Blob([new Uint8Array(length)], { type: mime }); + setTimeout(() => callback(blob), 0); + }; +} + +installCanvasMock(); + +declare global { + interface CanvasRenderingContext2D { + __getEvents(): CanvasRenderingContext2DEvent[]; + + __clearEvents(): void; + + __getDrawCalls(): CanvasRenderingContext2DEvent[]; + + __clearDrawCalls(): void; + + __getPath(): CanvasRenderingContext2DEvent[]; + + __clearPath(): void; + + __getClippingRegion(): CanvasRenderingContext2DEvent[]; + } +} diff --git a/web/packages/shared/components/DesktopSession/DesktopSession.test.tsx b/web/packages/shared/components/DesktopSession/DesktopSession.test.tsx index 85891dd734c..233e1059d99 100644 --- a/web/packages/shared/components/DesktopSession/DesktopSession.test.tsx +++ b/web/packages/shared/components/DesktopSession/DesktopSession.test.tsx @@ -20,7 +20,6 @@ import { EventEmitter } from 'events'; import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import 'jest-canvas-mock'; import { act } from 'react'; import { render } from 'design/utils/testing'; diff --git a/web/packages/teleport/src/Console/DocumentDb/DocumentDb.test.tsx b/web/packages/teleport/src/Console/DocumentDb/DocumentDb.test.tsx index 185d2f5ad7f..e3efa857e71 100644 --- a/web/packages/teleport/src/Console/DocumentDb/DocumentDb.test.tsx +++ b/web/packages/teleport/src/Console/DocumentDb/DocumentDb.test.tsx @@ -18,7 +18,6 @@ import '@testing-library/jest-dom'; import { screen } from '@testing-library/react'; -import 'jest-canvas-mock'; import { MemoryRouter } from 'react-router'; import { act, CurrentPath, render } from 'design/utils/testing'; diff --git a/web/packages/teleport/src/Console/DocumentKubeExec/DocumentKubeExec.test.tsx b/web/packages/teleport/src/Console/DocumentKubeExec/DocumentKubeExec.test.tsx index d71f02b4850..091dbfd5ea5 100644 --- a/web/packages/teleport/src/Console/DocumentKubeExec/DocumentKubeExec.test.tsx +++ b/web/packages/teleport/src/Console/DocumentKubeExec/DocumentKubeExec.test.tsx @@ -17,7 +17,6 @@ import '@testing-library/jest-dom'; * along with this program. If not, see . */ import { screen } from '@testing-library/react'; -import 'jest-canvas-mock'; import { act, render } from 'design/utils/testing'; diff --git a/web/packages/teleport/src/Console/DocumentSsh/DocumentSsh.test.tsx b/web/packages/teleport/src/Console/DocumentSsh/DocumentSsh.test.tsx index 29e603dee36..cc1bea32158 100644 --- a/web/packages/teleport/src/Console/DocumentSsh/DocumentSsh.test.tsx +++ b/web/packages/teleport/src/Console/DocumentSsh/DocumentSsh.test.tsx @@ -16,7 +16,6 @@ * along with this program. If not, see . */ -import 'jest-canvas-mock'; import { MemoryRouter } from 'react-router'; import { render, screen } from 'design/utils/testing'; diff --git a/web/packages/teleport/src/SessionRecordings/view/Timeline/renderers/EventsRenderer.test.ts b/web/packages/teleport/src/SessionRecordings/view/Timeline/renderers/EventsRenderer.test.ts index cb988606d3d..f6df30b7981 100644 --- a/web/packages/teleport/src/SessionRecordings/view/Timeline/renderers/EventsRenderer.test.ts +++ b/web/packages/teleport/src/SessionRecordings/view/Timeline/renderers/EventsRenderer.test.ts @@ -16,7 +16,6 @@ * along with this program. If not, see . */ -import 'jest-canvas-mock'; import { darkTheme } from 'design/theme'; import { diff --git a/web/packages/teleport/src/SessionRecordings/view/Timeline/renderers/FramesRenderer.test.ts b/web/packages/teleport/src/SessionRecordings/view/Timeline/renderers/FramesRenderer.test.ts index 0338069da04..e7973444fef 100644 --- a/web/packages/teleport/src/SessionRecordings/view/Timeline/renderers/FramesRenderer.test.ts +++ b/web/packages/teleport/src/SessionRecordings/view/Timeline/renderers/FramesRenderer.test.ts @@ -16,7 +16,6 @@ * along with this program. If not, see . */ -import 'jest-canvas-mock'; import { darkTheme } from 'design/theme'; import { SessionRecordingThumbnail } from 'teleport/services/recordings'; diff --git a/web/packages/teleport/src/SessionRecordings/view/Timeline/renderers/ProgressLineRenderer.test.ts b/web/packages/teleport/src/SessionRecordings/view/Timeline/renderers/ProgressLineRenderer.test.ts index 353602292d0..d717bb5be1d 100644 --- a/web/packages/teleport/src/SessionRecordings/view/Timeline/renderers/ProgressLineRenderer.test.ts +++ b/web/packages/teleport/src/SessionRecordings/view/Timeline/renderers/ProgressLineRenderer.test.ts @@ -16,7 +16,6 @@ * along with this program. If not, see . */ -import 'jest-canvas-mock'; import { darkTheme } from 'design/theme'; import { SessionRecordingMetadata } from 'teleport/services/recordings'; diff --git a/web/packages/teleport/src/SessionRecordings/view/Timeline/renderers/TimeMarksRenderer.test.ts b/web/packages/teleport/src/SessionRecordings/view/Timeline/renderers/TimeMarksRenderer.test.ts index bab27ac0f61..89bbfc18e63 100644 --- a/web/packages/teleport/src/SessionRecordings/view/Timeline/renderers/TimeMarksRenderer.test.ts +++ b/web/packages/teleport/src/SessionRecordings/view/Timeline/renderers/TimeMarksRenderer.test.ts @@ -16,7 +16,6 @@ * along with this program. If not, see . */ -import 'jest-canvas-mock'; import { darkTheme } from 'design/theme'; import { SessionRecordingMetadata } from 'teleport/services/recordings'; diff --git a/web/packages/teleport/src/SessionRecordings/view/Timeline/renderers/TimelineRenderer.test.ts b/web/packages/teleport/src/SessionRecordings/view/Timeline/renderers/TimelineRenderer.test.ts index 1f957280386..b4cd2e88525 100644 --- a/web/packages/teleport/src/SessionRecordings/view/Timeline/renderers/TimelineRenderer.test.ts +++ b/web/packages/teleport/src/SessionRecordings/view/Timeline/renderers/TimelineRenderer.test.ts @@ -16,7 +16,6 @@ * along with this program. If not, see . */ -import 'jest-canvas-mock'; import { darkTheme } from 'design/theme'; import { diff --git a/web/packages/teleport/src/lib/term/terminal.test.ts b/web/packages/teleport/src/lib/term/terminal.test.ts index 00569de1019..79e20dd3f65 100644 --- a/web/packages/teleport/src/lib/term/terminal.test.ts +++ b/web/packages/teleport/src/lib/term/terminal.test.ts @@ -16,7 +16,6 @@ * along with this program. If not, see . */ -import 'jest-canvas-mock'; import TtyTerminal from './terminal'; import Tty from './tty'; diff --git a/web/packages/teleterm/src/ui/App.test.tsx b/web/packages/teleterm/src/ui/App.test.tsx index 152fe4cdadc..ff800337de1 100644 --- a/web/packages/teleterm/src/ui/App.test.tsx +++ b/web/packages/teleterm/src/ui/App.test.tsx @@ -18,7 +18,6 @@ import { act, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import 'jest-canvas-mock'; import { mockIntersectionObserver } from 'jsdom-testing-mocks'; import { render } from 'design/utils/testing'; diff --git a/web/packages/teleterm/src/ui/DeepLinks/deepLinks.test.tsx b/web/packages/teleterm/src/ui/DeepLinks/deepLinks.test.tsx index d3ca13382d9..3aa65d0e232 100644 --- a/web/packages/teleterm/src/ui/DeepLinks/deepLinks.test.tsx +++ b/web/packages/teleterm/src/ui/DeepLinks/deepLinks.test.tsx @@ -16,7 +16,6 @@ * along with this program. If not, see . */ -import 'jest-canvas-mock'; import { render, screen } from 'design/utils/testing'; import { makeDeepLinkWithSafeInput } from 'shared/deepLinks'; diff --git a/web/packages/teleterm/src/ui/DocumentGatewayKube/DocumentGatewayKube.test.tsx b/web/packages/teleterm/src/ui/DocumentGatewayKube/DocumentGatewayKube.test.tsx index 916c9c60285..600080ccdd5 100644 --- a/web/packages/teleterm/src/ui/DocumentGatewayKube/DocumentGatewayKube.test.tsx +++ b/web/packages/teleterm/src/ui/DocumentGatewayKube/DocumentGatewayKube.test.tsx @@ -15,7 +15,6 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -import 'jest-canvas-mock'; import userEvent from '@testing-library/user-event'; import { render, screen } from 'design/utils/testing'; diff --git a/web/packages/teleterm/src/ui/DocumentTerminal/Terminal/Terminal.test.tsx b/web/packages/teleterm/src/ui/DocumentTerminal/Terminal/Terminal.test.tsx index d8517007f13..56e20fea938 100644 --- a/web/packages/teleterm/src/ui/DocumentTerminal/Terminal/Terminal.test.tsx +++ b/web/packages/teleterm/src/ui/DocumentTerminal/Terminal/Terminal.test.tsx @@ -20,7 +20,6 @@ import { EventEmitter } from 'node:events'; import { screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import 'jest-canvas-mock'; import { render } from 'design/utils/testing'; diff --git a/web/packages/teleterm/src/ui/DocumentTerminal/Terminal/ctrl.test.ts b/web/packages/teleterm/src/ui/DocumentTerminal/Terminal/ctrl.test.ts index bc05ae08619..1ab5c0a5f70 100644 --- a/web/packages/teleterm/src/ui/DocumentTerminal/Terminal/ctrl.test.ts +++ b/web/packages/teleterm/src/ui/DocumentTerminal/Terminal/ctrl.test.ts @@ -16,7 +16,6 @@ * along with this program. If not, see . */ -import 'jest-canvas-mock'; import Logger, { NullService } from 'teleterm/logger'; import { createMockConfigService } from 'teleterm/services/config/fixtures/mocks'; import { MockPtyProcess } from 'teleterm/services/pty/fixtures/mocks'; diff --git a/web/packages/teleterm/src/ui/DocumentTerminal/useDocumentTerminal.test.tsx b/web/packages/teleterm/src/ui/DocumentTerminal/useDocumentTerminal.test.tsx index f94e7cb5402..d3098e22bd0 100644 --- a/web/packages/teleterm/src/ui/DocumentTerminal/useDocumentTerminal.test.tsx +++ b/web/packages/teleterm/src/ui/DocumentTerminal/useDocumentTerminal.test.tsx @@ -17,7 +17,6 @@ */ import { renderHook, waitFor } from '@testing-library/react'; -import 'jest-canvas-mock'; import Logger, { NullService } from 'teleterm/logger'; import { diff --git a/web/packages/teleterm/src/ui/TabHost/TabHost.test.tsx b/web/packages/teleterm/src/ui/TabHost/TabHost.test.tsx index c59abf172a9..96ea79e84be 100644 --- a/web/packages/teleterm/src/ui/TabHost/TabHost.test.tsx +++ b/web/packages/teleterm/src/ui/TabHost/TabHost.test.tsx @@ -16,7 +16,6 @@ * along with this program. If not, see . */ -import 'jest-canvas-mock'; import { createRef } from 'react'; import { act, fireEvent, render, screen } from 'design/utils/testing'; diff --git a/web/packages/teleterm/src/ui/Vnet/integration.test.tsx b/web/packages/teleterm/src/ui/Vnet/integration.test.tsx index b46a50b89d7..67fed7326f2 100644 --- a/web/packages/teleterm/src/ui/Vnet/integration.test.tsx +++ b/web/packages/teleterm/src/ui/Vnet/integration.test.tsx @@ -15,7 +15,6 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -import 'jest-canvas-mock'; import { within } from '@testing-library/react'; import { mockIntersectionObserver } from 'jsdom-testing-mocks'; diff --git a/web/packages/teleterm/src/ui/services/connectionTracker/connectionTrackerService.test.tsx b/web/packages/teleterm/src/ui/services/connectionTracker/connectionTrackerService.test.tsx index 800837f4c2d..8f326db59ce 100644 --- a/web/packages/teleterm/src/ui/services/connectionTracker/connectionTrackerService.test.tsx +++ b/web/packages/teleterm/src/ui/services/connectionTracker/connectionTrackerService.test.tsx @@ -18,7 +18,6 @@ import { within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import 'jest-canvas-mock'; import { act, ComponentType, createRef } from 'react'; import { render, screen } from 'design/utils/testing'; diff --git a/web/patches/jest-canvas-mock@2.5.2.patch b/web/patches/jest-canvas-mock@2.5.2.patch deleted file mode 100644 index f006395d8d5..00000000000 --- a/web/patches/jest-canvas-mock@2.5.2.patch +++ /dev/null @@ -1,54 +0,0 @@ -diff --git a/lib/classes/CanvasRenderingContext2D.js b/lib/classes/CanvasRenderingContext2D.js -index e1e4b8fe31826baadbdce3f48ee2a68455de8910..46dad56798ddc7fa094f911a7a7e0c97fb2a3770 100644 ---- a/lib/classes/CanvasRenderingContext2D.js -+++ b/lib/classes/CanvasRenderingContext2D.js -@@ -15,7 +15,7 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de - function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } - function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); } - function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); } --const testFuncs = ['setLineDash', 'getLineDash', 'setTransform', 'getTransform', 'getImageData', 'save', 'restore', 'createPattern', 'createRadialGradient', 'addHitRegion', 'arc', 'arcTo', 'beginPath', 'clip', 'closePath', 'scale', 'stroke', 'clearHitRegions', 'clearRect', 'fillRect', 'strokeRect', 'rect', 'resetTransform', 'translate', 'moveTo', 'lineTo', 'bezierCurveTo', 'createLinearGradient', 'ellipse', 'measureText', 'rotate', 'drawImage', 'drawFocusIfNeeded', 'isPointInPath', 'isPointInStroke', 'putImageData', 'strokeText', 'fillText', 'quadraticCurveTo', 'removeHitRegion', 'fill', 'transform', 'scrollPathIntoView', 'createImageData']; -+const testFuncs = ['setLineDash', 'getLineDash', 'setTransform', 'getTransform', 'getImageData', 'save', 'restore', 'createPattern', 'createRadialGradient', 'addHitRegion', 'arc', 'arcTo', 'beginPath', 'clip', 'closePath', 'scale', 'stroke', 'clearHitRegions', 'clearRect', 'fillRect', 'strokeRect', 'rect', 'roundRect', 'resetTransform', 'translate', 'moveTo', 'lineTo', 'bezierCurveTo', 'createLinearGradient', 'ellipse', 'measureText', 'rotate', 'drawImage', 'drawFocusIfNeeded', 'isPointInPath', 'isPointInStroke', 'putImageData', 'strokeText', 'fillText', 'quadraticCurveTo', 'removeHitRegion', 'fill', 'transform', 'scrollPathIntoView', 'createImageData']; - const compositeOperations = ['source-over', 'source-in', 'source-out', 'source-atop', 'destination-over', 'destination-in', 'destination-out', 'destination-atop', 'lighter', 'copy', 'xor', 'multiply', 'screen', 'overlay', 'darken', 'lighten', 'color-dodge', 'color-burn', 'hard-light', 'soft-light', 'difference', 'exclusion', 'hue', 'saturation', 'color', 'luminosity']; - function getTransformSlice(ctx) { - return ctx._transformStack[ctx._stackIndex].slice(); -@@ -909,6 +909,40 @@ class CanvasRenderingContext2D { - const event = (0, _createCanvasEvent.default)('restore', getTransformSlice(this), {}); - this._events.push(event); - } -+ roundRect(x, y, width, height, radii) { -+ if (arguments.length < 4) -+ throw new TypeError( -+ "Failed to execute 'roundRect' on '" + -+ this.constructor.name + -+ "': 4 arguments required, but only " + -+ arguments.length + -+ ' present.' -+ ); -+ if (radii.constructor === Array && (radii.length === 0 || radii.length > 4)) -+ throw new TypeError( -+ "Failed to execute 'roundRect' on '" + -+ this.constructor.name + -+ "': " + -+ radii.length + -+ ' radii provided. Between one and four radii are necessary.' -+ ); -+ if (!Number.isFinite(x + y + width + height)) return; -+ -+ const xResult = Number(x); -+ const yResult = Number(y); -+ const widthResult = Number(width); -+ const heightResult = Number(height); -+ const event = (0, _createCanvasEvent.default)('roundRect', getTransformSlice(this), { -+ x: xResult, -+ y: yResult, -+ width: widthResult, -+ height: heightResult, -+ radii: radii, -+ }); -+ -+ this._events.push(event); -+ this._path.push(event); -+ } - rotate(angle) { - if (arguments.length < 1) throw new TypeError("Failed to execute 'rotate' on '" + this.constructor.name + "': 1 argument required, but only 0 present."); - angle = Number(angle);