From 0f0ee3ea11a3fa9014d08ef52d266869c1c7824c Mon Sep 17 00:00:00 2001 From: Dimitri Mitropoulos Date: Thu, 6 Feb 2020 22:50:49 -0500 Subject: [PATCH 1/5] fix for setMaxListeners Mutation Notifier --- scripts/jest/polyfills/mutation_observer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/jest/polyfills/mutation_observer.js b/scripts/jest/polyfills/mutation_observer.js index 7bf55f00a90..efd725e7d8d 100644 --- a/scripts/jest/polyfills/mutation_observer.js +++ b/scripts/jest/polyfills/mutation_observer.js @@ -524,7 +524,7 @@ var MutationNotifier = /** @class */ (function (_super) { __extends(MutationNotifier, _super); function MutationNotifier() { var _this = _super.call(this) || this; - _this.setMaxListeners(150); // bump this as needed - some tests do not perform the unmounting lifecycle + _this.setMaxListeners(294); // bump this as needed - some tests do not perform the unmounting lifecycle return _this; } MutationNotifier.getInstance = function () { From 47b619bf26c91918c58d2a313a653b01e233dd65 Mon Sep 17 00:00:00 2001 From: Dimitri Mitropoulos Date: Fri, 7 Feb 2020 00:24:00 -0500 Subject: [PATCH 2/5] converts CodeEditor to TypeScript cleanup --- package.json | 3 +- ...test.js.snap => code_editor.test.tsx.snap} | 0 ...de_editor.test.js => code_editor.test.tsx} | 7 +- .../{code_editor.js => code_editor.tsx} | 116 +++++++++++------- .../code_editor/{index.js => index.ts} | 0 yarn.lock | 5 + 6 files changed, 83 insertions(+), 48 deletions(-) rename src/components/code_editor/__snapshots__/{code_editor.test.js.snap => code_editor.test.tsx.snap} (100%) rename src/components/code_editor/{code_editor.test.js => code_editor.test.tsx} (95%) rename src/components/code_editor/{code_editor.js => code_editor.tsx} (67%) rename src/components/code_editor/{index.js => index.ts} (100%) diff --git a/package.json b/package.json index c9affa3ddd2..e448d556c8a 100644 --- a/package.json +++ b/package.json @@ -48,8 +48,8 @@ "@types/enzyme": "^3.1.13", "@types/lodash": "^4.14.116", "@types/numeral": "^0.0.25", - "@types/react-virtualized": "^9.18.7", "@types/react-beautiful-dnd": "^10.1.0", + "@types/react-virtualized": "^9.18.7", "chroma-js": "^2.0.4", "classnames": "^2.2.5", "highlight.js": "^9.12.0", @@ -90,6 +90,7 @@ "@types/react-dom": "^16.9.4", "@types/react-is": "^16.7.1", "@types/resize-observer-browser": "^0.1.1", + "@types/sinon": "^7.5.1", "@types/tabbable": "^3.1.0", "@types/uuid": "^3.4.4", "@typescript-eslint/eslint-plugin": "^1.13.0", diff --git a/src/components/code_editor/__snapshots__/code_editor.test.js.snap b/src/components/code_editor/__snapshots__/code_editor.test.tsx.snap similarity index 100% rename from src/components/code_editor/__snapshots__/code_editor.test.js.snap rename to src/components/code_editor/__snapshots__/code_editor.test.tsx.snap diff --git a/src/components/code_editor/code_editor.test.js b/src/components/code_editor/code_editor.test.tsx similarity index 95% rename from src/components/code_editor/code_editor.test.js rename to src/components/code_editor/code_editor.test.tsx index 7a3c8a75319..b5ba2becc53 100644 --- a/src/components/code_editor/code_editor.test.js +++ b/src/components/code_editor/code_editor.test.tsx @@ -1,6 +1,6 @@ import React from 'react'; import sinon from 'sinon'; -import { mount } from 'enzyme'; +import { mount, ReactWrapper } from 'enzyme'; import { EuiCodeEditor } from './code_editor'; import { keyCodes } from '../../services'; import { @@ -46,7 +46,7 @@ describe('EuiCodeEditor', () => { }); describe('behavior', () => { - let component; + let component: ReactWrapper; beforeEach(() => { component = mount(); @@ -69,6 +69,7 @@ describe('EuiCodeEditor', () => { test('should be enabled when the ui ace box loses focus', () => { const hint = findTestSubject(component, 'codeEditorHint'); hint.simulate('keyup', { keyCode: keyCodes.ENTER }); + // @ts-ignore component.instance().onBlurAce(); expect( findTestSubject(component, 'codeEditorHint').getDOMNode() @@ -80,11 +81,13 @@ describe('EuiCodeEditor', () => { test('bluring the ace textbox should call a passed onBlur prop', () => { const blurSpy = sinon.spy(); const el = mount(); + // @ts-ignore el.instance().onBlurAce(); expect(blurSpy.called).toBe(true); }); test('pressing escape in ace textbox will enable overlay', () => { + // @ts-ignore component.instance().onKeydownAce({ preventDefault: () => {}, stopPropagation: () => {}, diff --git a/src/components/code_editor/code_editor.js b/src/components/code_editor/code_editor.tsx similarity index 67% rename from src/components/code_editor/code_editor.js rename to src/components/code_editor/code_editor.tsx index faa13e98a78..68a91dd2db9 100644 --- a/src/components/code_editor/code_editor.js +++ b/src/components/code_editor/code_editor.tsx @@ -1,14 +1,17 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; +import React, { Component, AriaAttributes, KeyboardEventHandler } from 'react'; import classNames from 'classnames'; -import AceEditor from 'react-ace'; +import AceEditor, { IAceEditorProps } from 'react-ace'; import { htmlIdGenerator, keyCodes } from '../../services'; import { EuiI18n } from '../i18n'; const DEFAULT_MODE = 'text'; -function setOrRemoveAttribute(element, attributeName, value) { +function setOrRemoveAttribute( + element: HTMLTextAreaElement, + attributeName: SupportedAriaAttribute, + value: SupportedAriaAttributes[SupportedAriaAttribute] +) { if (value === null || value === undefined) { element.removeAttribute(attributeName); } else { @@ -16,18 +19,54 @@ function setOrRemoveAttribute(element, attributeName, value) { } } -export class EuiCodeEditor extends Component { - state = { +type SupportedAriaAttribute = + | 'aria-label' + | 'aria-labelledby' + | 'aria-describedby'; +type SupportedAriaAttributes = Pick; + +export interface EuiCodeEditorProps extends SupportedAriaAttributes { + width?: string; + height?: string; + onBlur?: IAceEditorProps['onBlur']; + onFocus?: IAceEditorProps['onFocus']; + isReadOnly?: boolean; + setOptions: IAceEditorProps['setOptions']; + cursorStart?: number; + 'data-test-subj'?: string; + + /** + * Use string for a built-in mode or object for a custom mode + */ + mode?: IAceEditorProps['mode'] | object; +} + +export interface EuiCodeEditorState { + isHintActive: boolean; + isEditing: boolean; +} + +export class EuiCodeEditor extends Component< + EuiCodeEditorProps, + EuiCodeEditorState +> { + static defaultProps = { + setOptions: {}, + }; + + state: EuiCodeEditorState = { isHintActive: true, isEditing: false, }; idGenerator = htmlIdGenerator(); + aceEditor: AceEditor | null = null; + editorHint: HTMLDivElement | null = null; - aceEditorRef = aceEditor => { + aceEditorRef = (aceEditor: AceEditor | null) => { if (aceEditor) { this.aceEditor = aceEditor; - const textbox = aceEditor.editor.textInput.getElement(); + const textbox = aceEditor.editor.textInput.getElement() as HTMLTextAreaElement; textbox.tabIndex = -1; textbox.addEventListener('keydown', this.onKeydownAce); setOrRemoveAttribute(textbox, 'aria-label', this.props['aria-label']); @@ -44,38 +83,40 @@ export class EuiCodeEditor extends Component { } }; - onKeydownAce = ev => { - if (ev.keyCode === keyCodes.ESCAPE) { + onKeydownAce = (event: KeyboardEvent) => { + if (event.keyCode === keyCodes.ESCAPE) { // If the autocompletion context menu is open then we want to let ESCAPE close it but // **not** exit out of editing mode. - if (!this.aceEditor.editor.completer) { - ev.preventDefault(); - ev.stopPropagation(); + if (this.aceEditor !== null && !this.aceEditor.editor.completer) { + event.preventDefault(); + event.stopPropagation(); this.stopEditing(); - this.editorHint.focus(); + if (this.editorHint) { + this.editorHint.focus(); + } } } }; - onFocusAce = (...args) => { + onFocusAce: IAceEditorProps['onFocus'] = (event, editor) => { this.setState({ isEditing: true, }); if (this.props.onFocus) { - this.props.onFocus(...args); + this.props.onFocus(event, editor); } }; - onBlurAce = (...args) => { + onBlurAce: IAceEditorProps['onBlur'] = (event, editor) => { this.stopEditing(); if (this.props.onBlur) { - this.props.onBlur(...args); + this.props.onBlur(event, editor); } }; - onKeyDownHint = ev => { - if (ev.keyCode === keyCodes.ENTER) { - ev.preventDefault(); + onKeyDownHint: KeyboardEventHandler = event => { + if (event.keyCode === keyCodes.ENTER) { + event.preventDefault(); this.startEditing(); } }; @@ -84,7 +125,9 @@ export class EuiCodeEditor extends Component { this.setState({ isHintActive: false, }); - this.aceEditor.editor.textInput.focus(); + if (this.aceEditor !== null) { + this.aceEditor.editor.textInput.focus(); + } }; stopEditing() { @@ -99,7 +142,9 @@ export class EuiCodeEditor extends Component { } setCustomMode() { - this.aceEditor.editor.getSession().setMode(this.props.mode); + if (this.aceEditor !== null) { + this.aceEditor.editor.getSession().setMode(this.props.mode); + } } componentDidMount() { @@ -108,7 +153,7 @@ export class EuiCodeEditor extends Component { } } - componentDidUpdate(prevProps) { + componentDidUpdate(prevProps: EuiCodeEditorProps) { if (this.props.mode !== prevProps.mode && this.isCustomMode()) { this.setCustomMode(); } @@ -161,7 +206,7 @@ export class EuiCodeEditor extends Component { ref={hint => { this.editorHint = hint; }} - tabIndex="0" + tabIndex={0} role="button" onClick={this.startEditing} onKeyDown={this.onKeyDownHint} @@ -206,7 +251,7 @@ export class EuiCodeEditor extends Component { Date: Thu, 6 Feb 2020 23:50:35 -0500 Subject: [PATCH 3/5] updates changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c61f5471f14..e0f46c23ef2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## [`master`](https://github.com/elastic/eui/tree/master) +- Converted `EuiCodeEditor` to Typescript ([#2836](https://github.com/elastic/eui/pull/2836)) - Converted `EuiFilePicker` to TypeScript ([#2832](https://github.com/elastic/eui/issues/2832)) - Exported `EuiSelectOptionProps` type ([#2830](https://github.com/elastic/eui/pull/2830)) - Added `paperClip` glyph to `EuiIcon` ([#2845](https://github.com/elastic/eui/pull/2845)) From ab06cd008390b8d212fac530c24e63a53fee8240 Mon Sep 17 00:00:00 2001 From: Dimitri Mitropoulos Date: Tue, 18 Feb 2020 14:05:45 -0500 Subject: [PATCH 4/5] removes sinon usage per review feedback --- package.json | 1 - src/components/code_editor/code_editor.test.tsx | 5 ++--- yarn.lock | 5 ----- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index e448d556c8a..dfc6b2ecaa6 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,6 @@ "@types/react-dom": "^16.9.4", "@types/react-is": "^16.7.1", "@types/resize-observer-browser": "^0.1.1", - "@types/sinon": "^7.5.1", "@types/tabbable": "^3.1.0", "@types/uuid": "^3.4.4", "@typescript-eslint/eslint-plugin": "^1.13.0", diff --git a/src/components/code_editor/code_editor.test.tsx b/src/components/code_editor/code_editor.test.tsx index b5ba2becc53..ac4eb6d4de0 100644 --- a/src/components/code_editor/code_editor.test.tsx +++ b/src/components/code_editor/code_editor.test.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import sinon from 'sinon'; import { mount, ReactWrapper } from 'enzyme'; import { EuiCodeEditor } from './code_editor'; import { keyCodes } from '../../services'; @@ -79,11 +78,11 @@ describe('EuiCodeEditor', () => { describe('interaction', () => { test('bluring the ace textbox should call a passed onBlur prop', () => { - const blurSpy = sinon.spy(); + const blurSpy = jest.fn().mockName('blurSpy'); const el = mount(); // @ts-ignore el.instance().onBlurAce(); - expect(blurSpy.called).toBe(true); + expect(blurSpy).toHaveBeenCalled(); }); test('pressing escape in ace textbox will enable overlay', () => { diff --git a/yarn.lock b/yarn.lock index 2488412eae4..69accd29c94 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1330,11 +1330,6 @@ resolved "https://registry.yarnpkg.com/@types/resize-observer-browser/-/resize-observer-browser-0.1.1.tgz#9b7cdae9cdc8b1a7020ca7588018dac64c770866" integrity sha512-5/bJS/uGB5kmpRrrAWXQnmyKlv+4TlPn4f+A2NBa93p+mt6Ht+YcNGkQKf8HMx28a9hox49ZXShtbGqZkk41Sw== -"@types/sinon@^7.5.1": - version "7.5.1" - resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-7.5.1.tgz#d27b81af0d1cfe1f9b24eebe7a24f74ae40f5b7c" - integrity sha512-EZQUP3hSZQyTQRfiLqelC9NMWd1kqLcmQE0dMiklxBkgi84T+cHOhnKpgk4NnOWpGX863yE6+IaGnOXUNFqDnQ== - "@types/tabbable@^3.1.0": version "3.1.0" resolved "https://registry.yarnpkg.com/@types/tabbable/-/tabbable-3.1.0.tgz#540d4c2729872560badcc220e73c9412c1d2bffe" From 640719688ff8c13f67cf814c2be5bacb8d44966d Mon Sep 17 00:00:00 2001 From: Dimitri Mitropoulos Date: Wed, 19 Feb 2020 14:44:26 -0500 Subject: [PATCH 5/5] exports EuiCodeEditorProps --- src/components/code_editor/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/code_editor/index.ts b/src/components/code_editor/index.ts index bdf16bd6792..11a27dc3266 100644 --- a/src/components/code_editor/index.ts +++ b/src/components/code_editor/index.ts @@ -1 +1 @@ -export { EuiCodeEditor } from './code_editor'; +export { EuiCodeEditor, EuiCodeEditorProps } from './code_editor';