UI: Add focus styles to QueryField component (#45933)

* Add focus styles to QueryField component
This commit is contained in:
Connor Lindsey 2022-03-02 08:33:14 -07:00 committed by GitHub
parent 5c05a3deb9
commit 9067715d1d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 74 additions and 21 deletions

View File

@ -44,7 +44,7 @@ exports[`no enzyme tests`] = {
"packages/grafana-ui/src/components/Logs/LogRows.test.tsx:2288254498": [
[3, 17, 13, "RegExp match", "2409514259"]
],
"packages/grafana-ui/src/components/QueryField/QueryField.test.tsx:1906163280": [
"packages/grafana-ui/src/components/QueryField/QueryField.test.tsx:1297745712": [
[1, 19, 13, "RegExp match", "2409514259"]
],
"packages/grafana-ui/src/components/Slider/Slider.test.tsx:2110443485": [

View File

@ -1,30 +1,43 @@
import React from 'react';
import { shallow } from 'enzyme';
import { QueryField } from './QueryField';
import { UnThemedQueryField } from './QueryField';
import { Editor } from 'slate';
import { createTheme } from '@grafana/data';
describe('<QueryField />', () => {
it('should render with null initial value', () => {
const wrapper = shallow(<QueryField query={null} onTypeahead={jest.fn()} portalOrigin="mock-origin" />);
const wrapper = shallow(
<UnThemedQueryField theme={createTheme()} query={null} onTypeahead={jest.fn()} portalOrigin="mock-origin" />
);
expect(wrapper.find('div').exists()).toBeTruthy();
});
it('should render with empty initial value', () => {
const wrapper = shallow(<QueryField query="" onTypeahead={jest.fn()} portalOrigin="mock-origin" />);
const wrapper = shallow(
<UnThemedQueryField theme={createTheme()} query="" onTypeahead={jest.fn()} portalOrigin="mock-origin" />
);
expect(wrapper.find('div').exists()).toBeTruthy();
});
it('should render with initial value', () => {
const wrapper = shallow(<QueryField query="my query" onTypeahead={jest.fn()} portalOrigin="mock-origin" />);
const wrapper = shallow(
<UnThemedQueryField theme={createTheme()} query="my query" onTypeahead={jest.fn()} portalOrigin="mock-origin" />
);
expect(wrapper.find('div').exists()).toBeTruthy();
});
it('should execute query on blur', () => {
const onRun = jest.fn();
const wrapper = shallow(
<QueryField query="my query" onTypeahead={jest.fn()} onRunQuery={onRun} portalOrigin="mock-origin" />
<UnThemedQueryField
theme={createTheme()}
query="my query"
onTypeahead={jest.fn()}
onRunQuery={onRun}
portalOrigin="mock-origin"
/>
);
const field = wrapper.instance() as QueryField;
const field = wrapper.instance() as UnThemedQueryField;
expect(onRun.mock.calls.length).toBe(0);
field.handleBlur(new Event('bogus'), new Editor({}), () => {});
expect(onRun.mock.calls.length).toBe(1);
@ -33,9 +46,15 @@ describe('<QueryField />', () => {
it('should run onChange with clean text', () => {
const onChange = jest.fn();
const wrapper = shallow(
<QueryField query={`my\r clean query `} onTypeahead={jest.fn()} onChange={onChange} portalOrigin="mock-origin" />
<UnThemedQueryField
theme={createTheme()}
query={`my\r clean query `}
onTypeahead={jest.fn()}
onChange={onChange}
portalOrigin="mock-origin"
/>
);
const field = wrapper.instance() as QueryField;
const field = wrapper.instance() as UnThemedQueryField;
field.runOnChange();
expect(onChange.mock.calls.length).toBe(1);
expect(onChange.mock.calls[0][0]).toBe('my clean query ');
@ -45,7 +64,8 @@ describe('<QueryField />', () => {
const onBlur = jest.fn();
const onRun = jest.fn();
const wrapper = shallow(
<QueryField
<UnThemedQueryField
theme={createTheme()}
query="my query"
onTypeahead={jest.fn()}
onBlur={onBlur}
@ -53,7 +73,7 @@ describe('<QueryField />', () => {
portalOrigin="mock-origin"
/>
);
const field = wrapper.instance() as QueryField;
const field = wrapper.instance() as UnThemedQueryField;
expect(onBlur.mock.calls.length).toBe(0);
expect(onRun.mock.calls.length).toBe(0);
field.handleBlur(new Event('bogus'), new Editor({}), () => {});
@ -62,14 +82,18 @@ describe('<QueryField />', () => {
});
describe('syntaxLoaded', () => {
it('should re-render the editor after syntax has fully loaded', () => {
const wrapper: any = shallow(<QueryField query="my query" portalOrigin="mock-origin" />);
const wrapper: any = shallow(
<UnThemedQueryField theme={createTheme()} query="my query" portalOrigin="mock-origin" />
);
const spyOnChange = jest.spyOn(wrapper.instance(), 'onChange').mockImplementation(jest.fn());
wrapper.instance().editor = { insertText: () => ({ deleteBackward: () => ({ value: 'fooo' }) }) };
wrapper.setProps({ syntaxLoaded: true });
expect(spyOnChange).toHaveBeenCalledWith('fooo', true);
});
it('should not re-render the editor if syntax is already loaded', () => {
const wrapper: any = shallow(<QueryField query="my query" portalOrigin="mock-origin" />);
const wrapper: any = shallow(
<UnThemedQueryField theme={createTheme()} query="my query" portalOrigin="mock-origin" />
);
const spyOnChange = jest.spyOn(wrapper.instance(), 'onChange').mockImplementation(jest.fn());
wrapper.setProps({ syntaxLoaded: true });
wrapper.instance().editor = {};
@ -77,14 +101,18 @@ describe('<QueryField />', () => {
expect(spyOnChange).not.toBeCalled();
});
it('should not re-render the editor if editor itself is not defined', () => {
const wrapper: any = shallow(<QueryField query="my query" portalOrigin="mock-origin" />);
const wrapper: any = shallow(
<UnThemedQueryField theme={createTheme()} query="my query" portalOrigin="mock-origin" />
);
const spyOnChange = jest.spyOn(wrapper.instance(), 'onChange').mockImplementation(jest.fn());
wrapper.setProps({ syntaxLoaded: true });
expect(wrapper.instance().editor).toBeFalsy();
expect(spyOnChange).not.toBeCalled();
});
it('should not re-render the editor twice once syntax is fully loaded', () => {
const wrapper: any = shallow(<QueryField query="my query" portalOrigin="mock-origin" />);
const wrapper: any = shallow(
<UnThemedQueryField theme={createTheme()} query="my query" portalOrigin="mock-origin" />
);
const spyOnChange = jest.spyOn(wrapper.instance(), 'onChange').mockImplementation(jest.fn());
wrapper.instance().editor = { insertText: () => ({ deleteBackward: () => ({ value: 'fooo' }) }) };
wrapper.setProps({ syntaxLoaded: true });

View File

@ -16,10 +16,22 @@ import {
SuggestionsPlugin,
} from '../../slate-plugins';
import { makeValue, SCHEMA, CompletionItemGroup, TypeaheadOutput, TypeaheadInput, SuggestionsState } from '../..';
import {
makeValue,
SCHEMA,
CompletionItemGroup,
TypeaheadOutput,
TypeaheadInput,
SuggestionsState,
Themeable2,
} from '../..';
import { selectors } from '@grafana/e2e-selectors';
import { css, cx } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { withTheme2 } from '../../themes';
import { getFocusStyles } from '../../themes/mixins';
export interface QueryFieldProps {
export interface QueryFieldProps extends Themeable2 {
additionalPlugins?: Plugin[];
cleanText?: (text: string) => string;
disabled?: boolean;
@ -38,6 +50,7 @@ export interface QueryFieldProps {
portalOrigin: string;
syntax?: string;
syntaxLoaded?: boolean;
theme: GrafanaTheme2;
}
export interface QueryFieldState {
@ -54,7 +67,7 @@ export interface QueryFieldState {
* This component can only process strings. Internally it uses Slate Value.
* Implement props.onTypeahead to use suggestions, see PromQueryField.tsx as an example.
*/
export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldState> {
export class UnThemedQueryField extends React.PureComponent<QueryFieldProps, QueryFieldState> {
plugins: Plugin[];
runOnChangeDebounced: Function;
lastExecutedValue: Value | null = null;
@ -197,13 +210,14 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS
}
render() {
const { disabled } = this.props;
const { disabled, theme } = this.props;
const wrapperClassName = classnames('slate-query-field__wrapper', {
'slate-query-field__wrapper--disabled': disabled,
});
const styles = getStyles(theme);
return (
<div className={wrapperClassName}>
<div className={cx(wrapperClassName, styles.wrapper)}>
<div className="slate-query-field" aria-label={selectors.components.QueryField.container}>
<Editor
ref={(editor) => (this.editor = editor!)}
@ -227,4 +241,15 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS
}
}
export default QueryField;
export const QueryField = withTheme2(UnThemedQueryField);
const getStyles = (theme: GrafanaTheme2) => {
const focusStyles = getFocusStyles(theme);
return {
wrapper: css`
&:focus-within {
${focusStyles}
}
`,
};
};