// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. 'use strict'; // Note: Don't change this to a tsx file as it loads in the unit tests. That will mess up mocha // Custom module loader so we can skip loading the 'canvas' module which won't load // inside of vscode // tslint:disable:no-var-requires no-require-imports no-any no-function-expression const Module = require('module'); (function () { const origRequire = Module.prototype.require; const _require = (context: any, filepath: any) => { return origRequire.call(context, filepath); }; Module.prototype.require = function (filepath: string) { if (filepath === 'canvas') { try { // Make sure we aren't inside of vscode. The nodejs version of Canvas won't match. At least sometimes. if (require('vscode')) { return ''; } } catch { // This should happen when not inside vscode. } } // tslint:disable-next-line:no-invalid-this return _require(this, filepath); }; })(); // tslint:disable:no-string-literal no-any object-literal-key-quotes max-func-body-length member-ordering // tslint:disable: no-require-imports no-var-requires // Monkey patch the stylesheet impl from jsdom before loading jsdom. // This is necessary to get slickgrid to work. const utils = require('jsdom/lib/jsdom/living/generated/utils'); const ssExports = require('jsdom/lib/jsdom/living/helpers/stylesheets'); if (ssExports && ssExports.createStylesheet) { const orig = ssExports.createStylesheet; ssExports.createStylesheet = (sheetText: any, elementImpl: any, baseURL: any) => { // Call the original. orig(sheetText, elementImpl, baseURL); // Then pull out the style sheet and add some properties. See the discussion here // https://github.com/jsdom/jsdom/issues/992 if (elementImpl.sheet) { elementImpl.sheet.href = baseURL; elementImpl.sheet.ownerNode = utils.wrapperForImpl(elementImpl); } }; } import { configure } from 'enzyme'; import * as Adapter from 'enzyme-adapter-react-16'; import { DOMWindow, JSDOM } from 'jsdom'; import { noop } from '../../client/common/utils/misc'; class MockCanvas implements CanvasRenderingContext2D { public canvas!: HTMLCanvasElement; public restore(): void { throw new Error('Method not implemented.'); } public save(): void { throw new Error('Method not implemented.'); } public getTransform(): DOMMatrix { throw new Error('Method not implemented.'); } public resetTransform(): void { throw new Error('Method not implemented.'); } public rotate(_angle: number): void { throw new Error('Method not implemented.'); } public scale(_x: number, _y: number): void { throw new Error('Method not implemented.'); } public setTransform(a: number, b: number, c: number, d: number, e: number, f: number): void; public setTransform(transform?: DOMMatrix2DInit | undefined): void; public setTransform(_a?: any, _b?: any, _c?: any, _d?: any, _e?: any, _f?: any) { throw new Error('Method not implemented.'); } public transform(_a: number, _b: number, _c: number, _d: number, _e: number, _f: number): void { throw new Error('Method not implemented.'); } public translate(_x: number, _y: number): void { throw new Error('Method not implemented.'); } public globalAlpha!: number; public globalCompositeOperation!: string; public imageSmoothingEnabled!: boolean; public imageSmoothingQuality!: ImageSmoothingQuality; public fillStyle!: string | CanvasGradient | CanvasPattern; public strokeStyle!: string | CanvasGradient | CanvasPattern; public createLinearGradient(_x0: number, _y0: number, _x1: number, _y1: number): CanvasGradient { throw new Error('Method not implemented.'); } public createPattern(_image: CanvasImageSource, _repetition: string): CanvasPattern | null { throw new Error('Method not implemented.'); } public createRadialGradient( _x0: number, _y0: number, _r0: number, _x1: number, _y1: number, _r1: number ): CanvasGradient { throw new Error('Method not implemented.'); } public shadowBlur!: number; public shadowColor!: string; public shadowOffsetX!: number; public shadowOffsetY!: number; public filter!: string; public clearRect(_x: number, _y: number, _w: number, _h: number): void { throw new Error('Method not implemented.'); } public fillRect(_x: number, _y: number, _w: number, _h: number): void { throw new Error('Method not implemented.'); } public strokeRect(_x: number, _y: number, _w: number, _h: number): void { throw new Error('Method not implemented.'); } public beginPath(): void { throw new Error('Method not implemented.'); } public clip(fillRule?: 'nonzero' | 'evenodd' | undefined): void; public clip(path: Path2D, fillRule?: 'nonzero' | 'evenodd' | undefined): void; public clip(_path?: any, _fillRule?: any) { throw new Error('Method not implemented.'); } public fill(fillRule?: 'nonzero' | 'evenodd' | undefined): void; public fill(path: Path2D, fillRule?: 'nonzero' | 'evenodd' | undefined): void; public fill(_path?: any, _fillRule?: any) { throw new Error('Method not implemented.'); } public isPointInPath(x: number, y: number, fillRule?: 'nonzero' | 'evenodd' | undefined): boolean; public isPointInPath(path: Path2D, x: number, y: number, fillRule?: 'nonzero' | 'evenodd' | undefined): boolean; public isPointInPath(_path: any, _x: any, _y?: any, _fillRule?: any): boolean { throw new Error('Method not implemented.'); } public isPointInStroke(x: number, y: number): boolean; public isPointInStroke(path: Path2D, x: number, y: number): boolean; public isPointInStroke(_path: any, _x: any, _y?: any): boolean { throw new Error('Method not implemented.'); } public stroke(): void; // tslint:disable-next-line: unified-signatures public stroke(path: Path2D): void; public stroke(_path?: any) { throw new Error('Method not implemented.'); } public drawFocusIfNeeded(element: Element): void; public drawFocusIfNeeded(path: Path2D, element: Element): void; public drawFocusIfNeeded(_path: any, _element?: any) { throw new Error('Method not implemented.'); } public scrollPathIntoView(): void; // tslint:disable-next-line: unified-signatures public scrollPathIntoView(path: Path2D): void; public scrollPathIntoView(_path?: any) { throw new Error('Method not implemented.'); } public fillText(_text: string, _x: number, _y: number, _maxWidth?: number | undefined): void { throw new Error('Method not implemented.'); } public measureText(_text: string): TextMetrics { throw new Error('Method not implemented.'); } public strokeText(_text: string, _x: number, _y: number, _maxWidth?: number | undefined): void { throw new Error('Method not implemented.'); } public drawImage(image: CanvasImageSource, dx: number, dy: number): void; public drawImage(image: CanvasImageSource, dx: number, dy: number, dw: number, dh: number): void; public drawImage( image: CanvasImageSource, sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw: number, dh: number ): void; public drawImage( _image: any, _sx: any, _sy: any, _sw?: any, _sh?: any, _dx?: any, _dy?: any, _dw?: any, _dh?: any ) { throw new Error('Method not implemented.'); } public createImageData(sw: number, sh: number): ImageData; public createImageData(imagedata: ImageData): ImageData; public createImageData(_sw: any, _sh?: any): ImageData { throw new Error('Method not implemented.'); } public getImageData(_sx: number, _sy: number, _sw: number, _sh: number): ImageData { throw new Error('Method not implemented.'); } public putImageData(imagedata: ImageData, dx: number, dy: number): void; public putImageData( imagedata: ImageData, dx: number, dy: number, dirtyX: number, dirtyY: number, dirtyWidth: number, dirtyHeight: number ): void; public putImageData( _imagedata: any, _dx: any, _dy: any, _dirtyX?: any, _dirtyY?: any, _dirtyWidth?: any, _dirtyHeight?: any ) { throw new Error('Method not implemented.'); } public lineCap!: CanvasLineCap; public lineDashOffset!: number; public lineJoin!: CanvasLineJoin; public lineWidth!: number; public miterLimit!: number; public getLineDash(): number[] { throw new Error('Method not implemented.'); } public setLineDash(_segments: number[]): void { throw new Error('Method not implemented.'); } public direction!: CanvasDirection; public font!: string; public textAlign!: CanvasTextAlign; public textBaseline!: CanvasTextBaseline; public arc( _x: number, _y: number, _radius: number, _startAngle: number, _endAngle: number, _anticlockwise?: boolean | undefined ): void { throw new Error('Method not implemented.'); } public arcTo(_x1: number, _y1: number, _x2: number, _y2: number, _radius: number): void { throw new Error('Method not implemented.'); } public bezierCurveTo(_cp1x: number, _cp1y: number, _cp2x: number, _cp2y: number, _x: number, _y: number): void { throw new Error('Method not implemented.'); } public closePath(): void { throw new Error('Method not implemented.'); } public ellipse( _x: number, _y: number, _radiusX: number, _radiusY: number, _rotation: number, _startAngle: number, _endAngle: number, _anticlockwise?: boolean | undefined ): void { throw new Error('Method not implemented.'); } public lineTo(_x: number, _y: number): void { throw new Error('Method not implemented.'); } public moveTo(_x: number, _y: number): void { throw new Error('Method not implemented.'); } public quadraticCurveTo(_cpx: number, _cpy: number, _x: number, _y: number): void { throw new Error('Method not implemented.'); } public rect(_x: number, _y: number, _w: number, _h: number): void { throw new Error('Method not implemented.'); } } const mockCanvas = new MockCanvas(); export function setUpDomEnvironment() { // tslint:disable-next-line:no-http-string const dom = new JSDOM('
', { pretendToBeVisual: true, url: 'http://localhost' }); const { window } = dom; // tslint:disable: no-function-expression no-empty try { // If running inside of vscode, we need to mock the canvas because the real canvas is not // returned. if (require('vscode')) { window.HTMLCanvasElement.prototype.getContext = (contextId: string, _contextAttributes?: {}): any => { if (contextId === '2d') { return mockCanvas; } return null; }; } } catch { noop(); } // tslint:disable-next-line: no-function-expression window.HTMLCanvasElement.prototype.toDataURL = function () { return ''; }; // tslist:disable-next-line:no-string-literal no-any (global as any)['Element'] = window.Element; // tslist:disable-next-line:no-string-literal no-any (global as any)['location'] = window.location; // tslint:disable-next-line:no-string-literal no-any (global as any)['window'] = window; // tslint:disable-next-line:no-string-literal no-any (global as any)['document'] = window.document; // tslint:disable-next-line:no-string-literal no-any (global as any)['navigator'] = { userAgent: 'node.js', platform: 'node' }; (global as any)['Event'] = window.Event; (global as any)['KeyboardEvent'] = window.KeyboardEvent; (global as any)['MouseEvent'] = window.MouseEvent; (global as any)['DocumentFragment'] = window.DocumentFragment; // tslint:disable-next-line:no-string-literal no-any (global as any)['getComputedStyle'] = window.getComputedStyle; // tslint:disable-next-line:no-string-literal no-any (global as any)['self'] = window; copyProps(window, global); // Special case. Monaco needs queryCommandSupported (global as any)['document'].queryCommandSupported = () => false; // Special case. Transform needs createRange (global as any)['document'].createRange = () => ({ createContextualFragment: (str: string) => JSDOM.fragment(str), setEnd: (_endNode: any, _endOffset: any) => noop(), setStart: (_startNode: any, _startOffset: any) => noop(), getBoundingClientRect: () => null, getClientRects: () => [] }); // Another special case. CodeMirror needs selection // tslint:disable-next-line:no-string-literal no-any (global as any)['document'].selection = { anchorNode: null, anchorOffset: 0, baseNode: null, baseOffset: 0, extentNode: null, extentOffset: 0, focusNode: null, focusOffset: 0, isCollapsed: false, rangeCount: 0, type: '', addRange: (_range: Range) => noop(), createRange: () => null, collapse: (_parentNode: Node, _offset: number) => noop(), collapseToEnd: noop, collapseToStart: noop, containsNode: (_node: Node, _partlyContained: boolean) => false, deleteFromDocument: noop, empty: noop, extend: (_newNode: Node, _offset: number) => noop(), getRangeAt: (_index: number) => null, removeAllRanges: noop, removeRange: (_range: Range) => noop(), selectAllChildren: (_parentNode: Node) => noop(), setBaseAndExtent: (_baseNode: Node, _baseOffset: number, _extentNode: Node, _extentOffset: number) => noop(), setPosition: (_parentNode: Node, _offset: number) => noop(), toString: () => '{Selection}' }; // For Jupyter server to load correctly. It expects the window object to not be defined // tslint:disable-next-line:no-eval no-any const fetchMod = eval('require')('node-fetch'); // tslint:disable-next-line:no-string-literal no-any (global as any)['fetch'] = fetchMod; // tslint:disable-next-line:no-string-literal no-any (global as any)['Request'] = fetchMod.Request; // tslint:disable-next-line:no-string-literal no-any (global as any)['Headers'] = fetchMod.Headers; // tslint:disable-next-line:no-string-literal no-eval no-any (global as any)['WebSocket'] = eval('require')('ws'); (global as any)['DOMParser'] = dom.window.DOMParser; (global as any)['Blob'] = dom.window.Blob; configure({ adapter: new Adapter() }); // Special case for the node_modules\monaco-editor\esm\vs\editor\browser\config\configuration.js. It doesn't // export the function we need to dispose of the timer it's set. So force it to. const configurationRegex = /.*(\\|\/)node_modules(\\|\/)monaco-editor(\\|\/)esm(\\|\/)vs(\\|\/)editor(\\|\/)browser(\\|\/)config(\\|\/)configuration\.js/g; const _oldLoader = require.extensions['.js']; // tslint:disable-next-line:no-function-expression require.extensions['.js'] = function (mod: any, filename) { if (configurationRegex.test(filename)) { let content = require('fs').readFileSync(filename, 'utf8'); content += 'export function getCSSBasedConfiguration() { return CSSBasedConfiguration.INSTANCE; };\n'; mod._compile(content, filename); } else { _oldLoader(mod, filename); } }; } export function setupTranspile() { // Some special work for getting the monaco editor to work. // We need to babel transpile some modules. Monaco-editor is not in commonJS format so imports // can't be loaded. require('@babel/register')({ plugins: ['@babel/transform-modules-commonjs'], only: [/monaco-editor/] }); // Special case for editor api. Webpack bundles editor.all.js as well. Tests don't. require('monaco-editor/esm/vs/editor/editor.api'); require('monaco-editor/esm/vs/editor/editor.all'); } function copyProps(src: any, target: any) { const props = Object.getOwnPropertyNames(src).filter((prop) => typeof target[prop] === undefined); props.forEach((p: string) => { target[p] = src[p]; }); } // map of string chars to keycodes and whether or not shift has to be hit // this is necessary to generate keypress/keydown events. // There doesn't seem to be an official way to do this (according to stack overflow) // so just hardcoding it here. const keyMap: { [key: string]: { code: number; shift: boolean } } = { A: { code: 65, shift: false }, B: { code: 66, shift: false }, C: { code: 67, shift: false }, D: { code: 68, shift: false }, E: { code: 69, shift: false }, F: { code: 70, shift: false }, G: { code: 71, shift: false }, H: { code: 72, shift: false }, I: { code: 73, shift: false }, J: { code: 74, shift: false }, K: { code: 75, shift: false }, L: { code: 76, shift: false }, M: { code: 77, shift: false }, N: { code: 78, shift: false }, O: { code: 79, shift: false }, P: { code: 80, shift: false }, Q: { code: 81, shift: false }, R: { code: 82, shift: false }, S: { code: 83, shift: false }, T: { code: 84, shift: false }, U: { code: 85, shift: false }, V: { code: 86, shift: false }, W: { code: 87, shift: false }, X: { code: 88, shift: false }, Y: { code: 89, shift: false }, Z: { code: 90, shift: false }, ESCAPE: { code: 27, shift: false }, '0': { code: 48, shift: false }, '1': { code: 49, shift: false }, '2': { code: 50, shift: false }, '3': { code: 51, shift: false }, '4': { code: 52, shift: false }, '5': { code: 53, shift: false }, '6': { code: 54, shift: false }, '7': { code: 55, shift: false }, '8': { code: 56, shift: false }, '9': { code: 57, shift: false }, ')': { code: 48, shift: true }, '!': { code: 49, shift: true }, '@': { code: 50, shift: true }, '#': { code: 51, shift: true }, $: { code: 52, shift: true }, '%': { code: 53, shift: true }, '^': { code: 54, shift: true }, '&': { code: 55, shift: true }, '*': { code: 56, shift: true }, '(': { code: 57, shift: true }, '[': { code: 219, shift: false }, '\\': { code: 209, shift: false }, ']': { code: 221, shift: false }, '{': { code: 219, shift: true }, '|': { code: 209, shift: true }, '}': { code: 221, shift: true }, ';': { code: 186, shift: false }, "'": { code: 222, shift: false }, ':': { code: 186, shift: true }, '"': { code: 222, shift: true }, ',': { code: 188, shift: false }, '.': { code: 190, shift: false }, '/': { code: 191, shift: false }, '<': { code: 188, shift: true }, '>': { code: 190, shift: true }, '?': { code: 191, shift: true }, '`': { code: 192, shift: false }, '~': { code: 192, shift: true }, ' ': { code: 32, shift: false }, '\n': { code: 13, shift: false }, '\r': { code: 0, shift: false } // remove \r from the text. }; export function createMessageEvent(data: any): MessageEvent { const domWindow = (window as any) as DOMWindow; return new domWindow.MessageEvent('message', { data }); } export function createKeyboardEvent(type: string, options: KeyboardEventInit): KeyboardEvent { const domWindow = (window as any) as DOMWindow; options.bubbles = true; options.cancelable = true; // charCodes and keyCodes are different things. Compute the keycode for cm to work. // This is the key (on an english qwerty keyboard) that would have to be hit to generate the key // This site was a great help with the mapping: // https://www.cambiaresearch.com/articles/15/javascript-char-codes-key-codes const upper = options.key!.toUpperCase(); const keyCode = keyMap.hasOwnProperty(upper) ? keyMap[upper].code : options.key!.charCodeAt(0); const shift = keyMap.hasOwnProperty(upper) ? keyMap[upper].shift || options.shiftKey : options.shiftKey; // JSDOM doesn't support typescript so well. The options are supposed to be flexible to support just about anything, but // the type KeyboardEventInit only supports the minimum. Stick in extras with some typecasting hacks return new domWindow.KeyboardEvent(type, ({ ...options, keyCode, shiftKey: shift } as any) as KeyboardEventInit); } export function createInputEvent(): Event { const domWindow = (window as any) as DOMWindow; return new domWindow.Event('input', { bubbles: true, cancelable: false }); } export function blurWindow() { // blur isn't implemented. We just need to dispatch the blur event const domWindow = (window as any) as DOMWindow; const blurEvent = new domWindow.Event('blur', { bubbles: true }); domWindow.dispatchEvent(blurEvent); }