diff --git a/package-lock.json b/package-lock.json index b24a79ce..9afff043 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,14 +25,14 @@ "ws": "^8.11.0" }, "devDependencies": { - "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^16.0.0", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.4.3", "@types/jest": "^29.2.3", "@types/md5": "^2.3.2", "@types/node": "^22.0.0", - "@types/react": "^18.0.25", - "@types/react-dom": "^18.0.9", + "@types/react": "^19.1.2", + "@types/react-dom": "^19.1.2", "@types/ws": "^8.2.0", "@typescript-eslint/eslint-plugin": "^6.11.0", "@typescript-eslint/parser": "^6.11.0", @@ -50,8 +50,8 @@ "jest-environment-jsdom": "^29.3.1", "jest-junit": "^16.0.0", "jest-transform-stub": "^2.0.0", - "react": "^19.0.0", - "react-dom": "^19.0.0", + "react": "^19.1.0", + "react-dom": "^19.1.0", "sass-loader": "^16.0.0", "source-map-loader": "^5.0.0", "style-loader": "^4.0.0", @@ -64,8 +64,8 @@ "webpack-node-externals": "^3.0.0" }, "peerDependencies": { - "react": "^18.2.0", - "react-dom": "^18.2.0" + "react": "^18.2.0 || ^19.0.0", + "react-dom": "^18.2.0 || ^19.0.0" } }, "node_modules/@adobe/css-tools": { @@ -1765,6 +1765,7 @@ "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz", "integrity": "sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==", "dev": true, + "license": "MIT", "dependencies": { "@adobe/css-tools": "^4.4.0", "aria-query": "^5.0.0", @@ -2136,12 +2137,6 @@ "@types/node": "*" } }, - "node_modules/@types/prop-types": { - "version": "15.7.12", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", - "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==", - "dev": true - }, "node_modules/@types/qs": { "version": "6.9.15", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", @@ -2155,22 +2150,23 @@ "dev": true }, "node_modules/@types/react": { - "version": "18.3.12", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz", - "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==", + "version": "19.1.2", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.2.tgz", + "integrity": "sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==", "dev": true, + "license": "MIT", "dependencies": { - "@types/prop-types": "*", "csstype": "^3.0.2" } }, "node_modules/@types/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==", + "version": "19.1.2", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.2.tgz", + "integrity": "sha512-XGJkWF41Qq305SKWEILa1O8vzhb3aOo3ogBlSmiqNko/WmRb6QIaweuZCXjKygVDXpzXb5wyxKTSOsmkuqj+Qw==", "dev": true, - "dependencies": { - "@types/react": "*" + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" } }, "node_modules/@types/retry": { @@ -9712,24 +9708,24 @@ } }, "node_modules/react": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", - "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", + "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", - "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", + "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", "license": "MIT", "dependencies": { - "scheduler": "^0.25.0" + "scheduler": "^0.26.0" }, "peerDependencies": { - "react": "^19.0.0" + "react": "^19.1.0" } }, "node_modules/react-i18next": { @@ -10196,9 +10192,9 @@ } }, "node_modules/scheduler": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", - "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", "license": "MIT" }, "node_modules/schema-utils": { diff --git a/package.json b/package.json index 85f59b0a..476e47da 100644 --- a/package.json +++ b/package.json @@ -22,8 +22,8 @@ "homepage": "https://github.com/device-management-toolkit/ui-toolkit-react#readme", "types": "index.d.ts", "peerDependencies": { - "react": "^18.2.0", - "react-dom": "^18.2.0" + "react": "^18.2.0 || ^19.0.0", + "react-dom": "^18.2.0 || ^19.0.0" }, "dependencies": { "@fortawesome/fontawesome-svg-core": "^6.2.1", @@ -42,14 +42,14 @@ "ws": "^8.11.0" }, "devDependencies": { - "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^16.0.0", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.4.3", "@types/jest": "^29.2.3", "@types/md5": "^2.3.2", "@types/node": "^22.0.0", - "@types/react": "^18.0.25", - "@types/react-dom": "^18.0.9", + "@types/react": "^19.1.2", + "@types/react-dom": "^19.1.2", "@types/ws": "^8.2.0", "@typescript-eslint/eslint-plugin": "^6.11.0", "@typescript-eslint/parser": "^6.11.0", @@ -67,8 +67,8 @@ "jest-environment-jsdom": "^29.3.1", "jest-junit": "^16.0.0", "jest-transform-stub": "^2.0.0", - "react": "^19.0.0", - "react-dom": "^19.0.0", + "react": "^19.1.0", + "react-dom": "^19.1.0", "sass-loader": "^16.0.0", "source-map-loader": "^5.0.0", "style-loader": "^4.0.0", diff --git a/src/reactjs/KVM/Header.tsx b/src/reactjs/KVM/Header.tsx index eaac9cd9..6b8e4de9 100644 --- a/src/reactjs/KVM/Header.tsx +++ b/src/reactjs/KVM/Header.tsx @@ -18,7 +18,7 @@ export interface IHeaderProps { } export class Header extends React.Component { - render (): JSX.Element { + render (): React.ReactNode { return (
diff --git a/src/reactjs/KVM/PureCanvas.tsx b/src/reactjs/KVM/PureCanvas.tsx index 08c6245c..433d711c 100644 --- a/src/reactjs/KVM/PureCanvas.tsx +++ b/src/reactjs/KVM/PureCanvas.tsx @@ -32,7 +32,18 @@ export class PureCanvas extends React.Component { onMouseMove: this.props.mouseMove } return ( - isFalsy(c) ? this.props.contextRef(c.getContext('2d')) : null}/> + { + if (isFalsy(c)) { + try { + const context = c.getContext('2d') + if (context) { + this.props.contextRef(context) + } + } catch (error) { + console.warn('Canvas context could not be accessed') + } + } + }}/> ) } } diff --git a/src/test/ui.spec.tsx b/src/test/ui.spec.tsx index c06f7be5..1245e4b7 100644 --- a/src/test/ui.spec.tsx +++ b/src/test/ui.spec.tsx @@ -5,142 +5,337 @@ import React from 'react' import { KVM, KVMProps } from '../reactjs/KVM/UI' -import { render } from '@testing-library/react' -import resetAllMocks = jest.resetAllMocks -import { act } from 'react' - -let props: KVMProps -beforeEach(() => { - props = { - deviceId: '1234', - mpsServer: 'wss://localhost/mps', - mouseDebounceTime: 10, - canvasHeight: '600', - canvasWidth: '400', - autoConnect: false, - authToken: 'authToken' +import { render, screen } from '@testing-library/react' + +class MockFileReader { + onload: jest.Mock + onerror: jest.Mock + readAsArrayBuffer: jest.Mock + readAsBinaryString: jest.Mock + readAsDataURL: jest.Mock + readAsText: jest.Mock + abort: jest.Mock + + constructor() { + this.onload = jest.fn() + this.onerror = jest.fn() + this.readAsArrayBuffer = jest.fn() + this.readAsBinaryString = jest.fn() + this.readAsDataURL = jest.fn() + this.readAsText = jest.fn() + this.abort = jest.fn() } -}) +} -describe('Testing purely KVM UI', () => { - it('should render successfully', () => { - const container = render() - expect(container).not.toBeNull() - }) +(global as any).FileReader = MockFileReader + +// Mock WebSocket +class MockWebSocket { + onopen: jest.Mock + onclose: jest.Mock + onmessage: jest.Mock + onerror: jest.Mock + close: jest.Mock + send: jest.Mock + + constructor() { + this.onopen = jest.fn() + this.onclose = jest.fn() + this.onmessage = jest.fn() + this.onerror = jest.fn() + this.close = jest.fn() + this.send = jest.fn() + } +} + +(global as any).WebSocket = MockWebSocket; + +jest.mock('../reactjs/KVM/PureCanvas', () => { + return { + PureCanvas: ({ contextRef, mouseMove, mouseDown, mouseUp }: any) => { + const mockCtx = { + canvas: { + width: 800, + height: 600 + }, + clearRect: jest.fn(), + fillRect: jest.fn(), + drawImage: jest.fn(), + putImageData: jest.fn(), + getImageData: jest.fn(() => ({ + data: new Uint8ClampedArray(10), + })), + createImageData: jest.fn() + }; + + React.useEffect(() => { + if (contextRef) { + contextRef(mockCtx) + } + }, [contextRef]) + + return + } + } }) -describe('Testing KVM component methods', () => { - let kvm: KVM - beforeEach(() => { - resetAllMocks() - jest.useFakeTimers() - kvm = new KVM(props) - kvm.setState = jest.fn((newState: any) => { - Object.assign(kvm.state, kvm.state, newState) - }) - kvm.redirector = { +// Mock AMT Desktop and other dependencies +jest.mock('@open-amt-cloud-toolkit/ui-toolkit/core', () => { + return { + AMTDesktop: jest.fn().mockImplementation(() => ({ + processData: jest.fn(), + start: jest.fn(), + onStateChange: jest.fn(), + onSendKvmData: jest.fn(), + state: 0, + bpp: 1 + })), + AMTKvmDataRedirector: jest.fn().mockImplementation(() => ({ + send: jest.fn(), start: jest.fn(), stop: jest.fn() - } - kvm.keyboard = { + })), + DataProcessor: jest.fn().mockImplementation(() => ({ + processData: jest.fn() + })), + MouseHelper: jest.fn().mockImplementation(() => ({ + mousemove: jest.fn(), + mousedown: jest.fn(), + mouseup: jest.fn() + })), + KeyBoardHelper: jest.fn().mockImplementation(() => ({ GrabKeyInput: jest.fn(), UnGrabKeyInput: jest.fn() - } - kvm.cleanUp = jest.fn() - kvm.init = jest.fn() + })), + Protocol: { KVM: 1 } + } +}) + +describe('KVM Component', () => { + let props: KVMProps; + + beforeEach(() => { + props = { + deviceId: '1234', + mpsServer: 'wss://localhost/mps', + mouseDebounceTime: 200, + canvasHeight: '600', + canvasWidth: '400', + autoConnect: false, + authToken: 'authToken' + }; + jest.useFakeTimers(); }) afterEach(() => { jest.useRealTimers() + jest.clearAllMocks() + }) + + it('should render successfully', () => { + const { container } = render() + expect(container).not.toBeNull() + expect(screen.getByTestId('pure-canvas-testid')).toBeInTheDocument(); }) - it('should handle state changes', async () => { - expect(kvm.state.kvmstate).toEqual(0) - kvm.handleConnectClick({ persist: jest.fn() }) - expect(kvm.redirector.start).toHaveBeenCalledTimes(1) - expect(kvm.redirector.stop).toHaveBeenCalledTimes(0) - expect(kvm.keyboard.GrabKeyInput).toHaveBeenCalledTimes(1) - expect(kvm.keyboard.UnGrabKeyInput).toHaveBeenCalledTimes(0) - expect(kvm.cleanUp).toHaveBeenCalledTimes(0) - expect(kvm.init).toHaveBeenCalledTimes(0) - - // nothing really happens on 1 - kvm.OnConnectionStateChange({}, 1) - expect(kvm.state.kvmstate).toEqual(1) - kvm.handleConnectClick({ persist: jest.fn() }) - expect(kvm.redirector.start).toHaveBeenCalledTimes(1) - expect(kvm.redirector.stop).toHaveBeenCalledTimes(0) - expect(kvm.keyboard.GrabKeyInput).toHaveBeenCalledTimes(1) - expect(kvm.keyboard.UnGrabKeyInput).toHaveBeenCalledTimes(0) - expect(kvm.cleanUp).toHaveBeenCalledTimes(0) - expect(kvm.init).toHaveBeenCalledTimes(0) - - kvm.OnConnectionStateChange({}, 2) - expect(kvm.state.kvmstate).toEqual(2) - kvm.handleConnectClick({ persist: jest.fn() }) - expect(kvm.redirector.start).toHaveBeenCalledTimes(1) - expect(kvm.redirector.stop).toHaveBeenCalledTimes(1) - expect(kvm.keyboard.GrabKeyInput).toHaveBeenCalledTimes(1) - expect(kvm.keyboard.UnGrabKeyInput).toHaveBeenCalledTimes(1) - expect(kvm.cleanUp).toHaveBeenCalledTimes(1) - expect(kvm.init).toHaveBeenCalledTimes(1) - - expect(kvm.desktopSettingsChange).toBeFalsy() - kvm.desktopSettingsChange = true - kvm.OnConnectionStateChange({}, 0) - expect(kvm.state.kvmstate).toEqual(0) - await act(() => { - jest.advanceTimersByTime(100) + it('should render with autoConnect enabled', () => { + render(); + expect(screen.getByTestId('pure-canvas-testid')).toBeInTheDocument() + }) + + describe('KVM instance methods', () => { + let kvm: KVM + let mockStart: jest.Mock + let mockStop: jest.Mock + let mockGrabKeyInput: jest.Mock + let mockUnGrabKeyInput: jest.Mock + + beforeEach(() => { + mockStart = jest.fn() + mockStop = jest.fn() + mockGrabKeyInput = jest.fn() + mockUnGrabKeyInput = jest.fn() + + kvm = new KVM(props) + + kvm.setState = jest.fn((newState: any) => { + Object.assign(kvm.state, newState) + }) + + kvm.redirector = { + start: mockStart, + stop: mockStop, + onProcessData: jest.fn(), + onStart: jest.fn(), + onNewState: jest.fn(), + onSendKvmData: jest.fn(), + onStateChanged: jest.fn(), + onError: jest.fn(), + send: jest.fn() + }; + + kvm.keyboard = { + GrabKeyInput: mockGrabKeyInput, + UnGrabKeyInput: mockUnGrabKeyInput + } + + kvm.module = { + processData: jest.fn(), + start: jest.fn(), + onStateChange: jest.fn(), + onSendKvmData: jest.fn(), + state: 0, + bpp: 1, + onSend: jest.fn(), + onProcessData: jest.fn() + } + + // Set up ctx mock to avoid null reference errors + kvm.ctx = { + canvas: { + width: 800, + height: 600 + }, + clearRect: jest.fn(), + fillRect: jest.fn(), + drawImage: jest.fn(), + putImageData: jest.fn(), + getImageData: jest.fn(), + createImageData: jest.fn() + } as unknown as CanvasRenderingContext2D; + + kvm.cleanUp = jest.fn() + kvm.init = jest.fn() + }) + + it('should start KVM when handleConnectClick is called in disconnected state', () => { + kvm.state = { kvmstate: 0, encodingOption: 1 } + + kvm.handleConnectClick({ persist: jest.fn() }) + + expect(mockStart).toHaveBeenCalledTimes(1) + expect(mockStart).toHaveBeenCalledWith(MockWebSocket) + expect(mockGrabKeyInput).toHaveBeenCalledTimes(1) + expect(mockStop).not.toHaveBeenCalled() + expect(mockUnGrabKeyInput).not.toHaveBeenCalled() + }) + + it('should stop KVM when handleConnectClick is called in connected state', () => { + kvm.state = { kvmstate: 2, encodingOption: 1 } + + kvm.handleConnectClick({ persist: jest.fn() }) + + expect(mockStop).toHaveBeenCalledTimes(1) + expect(mockUnGrabKeyInput).toHaveBeenCalledTimes(1) + expect(kvm.cleanUp).toHaveBeenCalledTimes(1) + expect(kvm.init).toHaveBeenCalledTimes(1) + }) + + it('should change desktop settings correctly in disconnected state', () => { + kvm.state = { kvmstate: 0, encodingOption: 1 } + + kvm.changeDesktopSettings({ encoding: 5 }) + + expect(kvm.state.encodingOption).toBe(5) + expect(kvm.module.bpp).toBe(5); + expect(kvm.desktopSettingsChange).toBe(false) + }) + + it('should change desktop settings and restart KVM in connected state', () => { + kvm.state = { kvmstate: 2, encodingOption: 1 } + + kvm.changeDesktopSettings({ encoding: 7 }) + + expect(kvm.desktopSettingsChange).toBe(true) + expect(kvm.module.bpp).toBe(7) + expect(mockStop).toHaveBeenCalledTimes(1) + }) + + it('should handle connection state changes', () => { + kvm.OnConnectionStateChange({}, 3) + + expect(kvm.state.kvmstate).toBe(3) }) - expect(kvm.redirector.start).toHaveBeenCalledTimes(1) - await act(() => { + + it('should restart KVM after desktop settings change and connection state changes to 0', () => { + kvm.desktopSettingsChange = true + + kvm.OnConnectionStateChange({}, 0) + + expect(kvm.state.kvmstate).toBe(0) + expect(kvm.desktopSettingsChange).toBe(false) + jest.advanceTimersByTime(2000) + + expect(mockStart).toHaveBeenCalledTimes(1) }) - expect(kvm.redirector.start).toHaveBeenCalledTimes(2) - expect(kvm.redirector.stop).toHaveBeenCalledTimes(1) - expect(kvm.keyboard.GrabKeyInput).toHaveBeenCalledTimes(2) - expect(kvm.keyboard.UnGrabKeyInput).toHaveBeenCalledTimes(1) - }) - it('should stop on redirection error', () => { - kvm.onRedirectorError() - expect(kvm.cleanUp).toHaveBeenCalledTimes(1) - expect(kvm.init).toHaveBeenCalledTimes(1) - }) + it('should reset when redirector error occurs', () => { + kvm.onRedirectorError() + + expect(kvm.cleanUp).toHaveBeenCalledTimes(1) + expect(kvm.init).toHaveBeenCalledTimes(1) + }) - it('should stop on component update with different device id', () => { - kvm.componentDidUpdate({ deviceId: 5678 }) - expect(kvm.cleanUp).toHaveBeenCalledTimes(1) - expect(kvm.init).toHaveBeenCalledTimes(1) - }) + it('should clean up and reinitialize when device ID changes', () => { + kvm.componentDidUpdate({ deviceId: 'different-id' }) + + expect(mockStop).toHaveBeenCalledTimes(1) + expect(mockUnGrabKeyInput).toHaveBeenCalledTimes(1) + expect(kvm.cleanUp).toHaveBeenCalledTimes(1) + expect(kvm.init).toHaveBeenCalledTimes(1) + }) - it('should handle changing desktop settings', () => { - kvm.module = { - bpp: 1 - } + it('should return the correct render status', () => { + kvm.module.state = 2 + + expect(kvm.getRenderStatus()).toBe(2) + }) - // default state - expect(kvm.state.kvmstate).toEqual(0) - expect(kvm.state.encodingOption).toEqual(1) - - kvm.changeDesktopSettings({ encoding: 5 }) - expect(kvm.desktopSettingsChange).toBeFalsy() - expect(kvm.state.encodingOption).toEqual(5) - expect(kvm.module.bpp).toEqual(5) - - kvm.setState({ kvmstate: 2 }) - kvm.changeDesktopSettings({ encoding: 7 }) - expect(kvm.desktopSettingsChange).toBeTruthy() - expect(kvm.state.encodingOption).toEqual(5) - expect(kvm.module.bpp).toEqual(7) - expect(kvm.cleanUp).toHaveBeenCalledTimes(1) - expect(kvm.init).toHaveBeenCalledTimes(1) - }) + it('should handle saveContext and initialize', () => { + const freshKvm = new KVM(props) + + const initSpy = jest.spyOn(freshKvm, 'init').mockImplementation(() => {}) + + const mockContext = {} as CanvasRenderingContext2D + + freshKvm.saveContext(mockContext) + + expect(freshKvm.ctx).toBe(mockContext) + expect(initSpy).toHaveBeenCalledTimes(1) + }) - it('should return module state when queried', () => { - kvm.module = { - state: 2 - } - expect(kvm.getRenderStatus()).toEqual(2) + it('should properly clean up resources', () => { + const cleanKvm = new KVM(props) + + cleanKvm.module = { + bpp: 1, + state: 0 + } as any + + cleanKvm.redirector = {} as any + cleanKvm.dataProcessor = {} as any + cleanKvm.mouseHelper = {} as any + cleanKvm.keyboard = {} as any + + // Set up the ctx mock + const clearRectMock = jest.fn() + cleanKvm.ctx = { + canvas: { + width: 800, + height: 600 + }, + clearRect: clearRectMock + } as unknown as CanvasRenderingContext2D + + cleanKvm.cleanUp() + + expect(cleanKvm.module).toBeNull() + expect(cleanKvm.redirector).toBeNull() + expect(cleanKvm.dataProcessor).toBeNull() + expect(cleanKvm.mouseHelper).toBeNull() + expect(cleanKvm.keyboard).toBeNull() + expect(clearRectMock).toHaveBeenCalledWith(0, 0, 600, 800) + }) }) -}) +}) \ No newline at end of file