drop use of decorator for editor context menu

This commit is contained in:
David Lechner
2021-02-01 10:26:06 -06:00
parent 6ccb77a5ad
commit 4b6817d9ad
2 changed files with 127 additions and 3 deletions
+94
View File
@@ -0,0 +1,94 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2021 The Pybricks Authors
import { I18nContext, I18nManager } from '@shopify/react-i18n';
import {
fireEvent,
render,
screen,
waitForElementToBeRemoved,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { Provider } from 'react-redux';
import { Store } from 'redux';
import Editor from './Editor';
function getTextArea(): HTMLTextAreaElement {
// the textarea in ace editor doesn't actually have any contents, but
// it gets the focus for input.
return screen.getByDisplayValue('') as HTMLTextAreaElement;
}
it('should focus the text area', () => {
const store = ({
getState: jest.fn(() => ({ settings: { darkMode: false, showDocs: false } })),
dispatch: jest.fn(),
subscribe: jest.fn(),
} as unknown) as Store;
const i18n = new I18nManager({ locale: 'en' });
render(
<Provider store={store}>
<I18nContext.Provider value={i18n}>
<Editor />
</I18nContext.Provider>
</Provider>,
);
expect(getTextArea()).toHaveFocus();
});
describe('context menu', () => {
it('should show the context menu', () => {
const store = ({
getState: jest.fn(() => ({
settings: { darkMode: false, showDocs: false },
})),
dispatch: jest.fn(),
subscribe: jest.fn(),
} as unknown) as Store;
const i18n = new I18nManager({ locale: 'en' });
render(
<Provider store={store}>
<I18nContext.Provider value={i18n}>
<Editor />
</I18nContext.Provider>
</Provider>,
);
fireEvent.contextMenu(screen.getByText('Write your program here...'));
expect(screen.getByText('Copy')).toBeInTheDocument();
});
it('should hide the context menu when Escape is pressed', async () => {
const store = ({
getState: jest.fn(() => ({
settings: { darkMode: false, showDocs: false },
})),
dispatch: jest.fn(),
subscribe: jest.fn(),
} as unknown) as Store;
const i18n = new I18nManager({ locale: 'en' });
render(
<Provider store={store}>
<I18nContext.Provider value={i18n}>
<Editor />
</I18nContext.Provider>
</Provider>,
);
fireEvent.contextMenu(screen.getByText('Write your program here...'));
expect(screen.getByText('Copy')).toBeInTheDocument();
userEvent.type(document.activeElement || document.body, '{esc}');
await waitForElementToBeRemoved(() => screen.queryByText('Copy'));
// editor should be focused after context menu closes
expect(document.activeElement).toBe(getTextArea());
});
});
+33 -3
View File
@@ -2,7 +2,7 @@
// Copyright (c) 2020-2021 The Pybricks Authors
import {
ContextMenuTarget,
ContextMenu,
Menu,
MenuDivider,
MenuItem,
@@ -47,7 +47,6 @@ type DispatchProps = {
type EditorProps = StateProps & DispatchProps & WithI18nProps;
@ContextMenuTarget
class Editor extends React.Component<EditorProps> {
private editorRef: React.RefObject<AceEditor>;
private keyBindings?: Array<{ key: string; command: string }>;
@@ -83,7 +82,34 @@ class Editor extends React.Component<EditorProps> {
render(): JSX.Element {
const { i18n, darkMode, onSessionChanged, onCheck, onToggleDocs } = this.props;
return (
<div className="h-100">
<div
className="h-100"
onContextMenu={(e) => {
// istanbul ignore if: not expected
if (e.defaultPrevented) {
return;
}
e.preventDefault();
const menu = this.renderContextMenu();
const listener = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
// istanbul ignore if: not expected
if (e.defaultPrevented) {
return;
}
e.preventDefault();
ContextMenu.hide();
}
};
window.addEventListener('keydown', listener);
ContextMenu.show(menu, { left: e.clientX, top: e.clientY }, () => {
window.removeEventListener('keydown', listener);
this.onContextMenuClose();
});
}}
>
<ResizeSensor onResize={(): void => this.editor?.resize()}>
<AceEditor
ref={this.editorRef}
@@ -213,6 +239,10 @@ class Editor extends React.Component<EditorProps> {
</Menu>
);
}
onContextMenuClose = () => {
this.editorRef.current?.editor.focus();
};
}
const mapStateToProps = (state: RootState): StateProps => ({