diff --git a/.betterer.results b/.betterer.results index 80d88469839..c7fe29a6e66 100644 --- a/.betterer.results +++ b/.betterer.results @@ -8,18 +8,6 @@ exports[`no enzyme tests`] = { "packages/grafana-ui/src/components/QueryField/QueryField.test.tsx:2976628669": [ [0, 26, 13, "RegExp match", "2409514259"] ], - "packages/grafana-ui/src/slate-plugins/braces.test.tsx:823049948": [ - [0, 19, 13, "RegExp match", "2409514259"] - ], - "packages/grafana-ui/src/slate-plugins/clear.test.tsx:3927593033": [ - [0, 19, 13, "RegExp match", "2409514259"] - ], - "packages/grafana-ui/src/slate-plugins/runner.test.tsx:1123710822": [ - [0, 19, 13, "RegExp match", "2409514259"] - ], - "packages/grafana-ui/src/slate-plugins/suggestions.test.tsx:2682912140": [ - [0, 18, 13, "RegExp match", "2409514259"] - ], "packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/ViewingLayer.test.js:1676554632": [ [14, 19, 13, "RegExp match", "2409514259"] ], @@ -1688,27 +1676,10 @@ exports[`better eslint`] = { "packages/grafana-ui/src/options/builder/stacking.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], - "packages/grafana-ui/src/slate-plugins/braces.test.tsx:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"], - [0, 0, 0, "Unexpected any. Specify a different type.", "2"], - [0, 0, 0, "Unexpected any. Specify a different type.", "3"], - [0, 0, 0, "Unexpected any. Specify a different type.", "4"], - [0, 0, 0, "Unexpected any. Specify a different type.", "5"], - [0, 0, 0, "Unexpected any. Specify a different type.", "6"] - ], "packages/grafana-ui/src/slate-plugins/braces.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"] ], - "packages/grafana-ui/src/slate-plugins/clear.test.tsx:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"], - [0, 0, 0, "Unexpected any. Specify a different type.", "2"] - ], - "packages/grafana-ui/src/slate-plugins/runner.test.tsx:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"] - ], "packages/grafana-ui/src/slate-plugins/slate-prism/index.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"], @@ -1719,14 +1690,6 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"] ], - "packages/grafana-ui/src/slate-plugins/suggestions.test.tsx:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"], - [0, 0, 0, "Unexpected any. Specify a different type.", "2"], - [0, 0, 0, "Unexpected any. Specify a different type.", "3"], - [0, 0, 0, "Unexpected any. Specify a different type.", "4"], - [0, 0, 0, "Unexpected any. Specify a different type.", "5"] - ], "packages/grafana-ui/src/slate-plugins/suggestions.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"], diff --git a/e2e/various-suite/slate.spec.ts b/e2e/various-suite/slate.spec.ts new file mode 100644 index 00000000000..ae13d14385f --- /dev/null +++ b/e2e/various-suite/slate.spec.ts @@ -0,0 +1,106 @@ +import { e2e } from '@grafana/e2e'; + +const dataSourceName = 'LokiSlate'; +const addDataSource = () => { + e2e.flows.addDataSource({ + type: 'Loki', + expectedAlertMessage: + 'Unable to fetch labels from Loki (Failed to call resource), please check the server logs for more details', + name: dataSourceName, + form: () => { + e2e.components.DataSource.DataSourceHttpSettings.urlInput().type('http://loki-url:3100'); + }, + }); +}; + +describe('Loki slate editor', () => { + beforeEach(() => { + e2e.flows.login('admin', 'admin'); + + e2e() + .request({ url: `${e2e.env('BASE_URL')}/api/datasources/name/${dataSourceName}`, failOnStatusCode: false }) + .then((response) => { + if (response.isOkStatusCode) { + return; + } + addDataSource(); + }); + }); + + it('Braces plugin should insert closing brace', () => { + e2e().intercept(/labels?/, (req) => { + req.reply({ status: 'success', data: ['instance', 'job', 'source'] }); + }); + + e2e().intercept(/series?/, (req) => { + req.reply({ status: 'success', data: [{ instance: 'instance1' }] }); + }); + + // Go to Explore and choose Loki data source + e2e.pages.Explore.visit(); + e2e.components.DataSourcePicker.container().should('be.visible').click(); + e2e().contains(dataSourceName).scrollIntoView().should('be.visible').click(); + + // adds closing braces around empty value + e2e().contains('Code').click(); + const queryField = e2e().get('.slate-query-field'); + queryField.type('time('); + queryField.then(($el) => { + expect($el.text().replace(/\uFEFF/g, '')).to.eq('time()'); + }); + + // removes closing brace when opening brace is removed + queryField.type('{backspace}'); + queryField.then(($el) => { + expect($el.text().replace(/\uFEFF/g, '')).to.eq('time'); + }); + + // keeps closing brace when opening brace is removed and inner values exist + queryField.type(`{selectall}{backspace}time(test{leftArrow}{leftArrow}{leftArrow}{leftArrow}{backspace}`); + queryField.then(($el) => { + expect($el.text().replace(/\uFEFF/g, '')).to.eq('timetest)'); + }); + + // overrides an automatically inserted brace + queryField.type(`{selectall}{backspace}time()`); + queryField.then(($el) => { + expect($el.text().replace(/\uFEFF/g, '')).to.eq('time()'); + }); + + // does not override manually inserted braces + queryField.type(`{selectall}{backspace}))`); + queryField.then(($el) => { + expect($el.text().replace(/\uFEFF/g, '')).to.eq('))'); + }); + + /** Clear Plugin */ + + //does not change the empty value + queryField.type(`{selectall}{backspace}{ctrl+k}`); + queryField.then(($el) => { + expect($el.text().replace(/\uFEFF/g, '')).to.match(/Enter a Loki query/); + }); + + // clears to the end of the line + queryField.type(`{selectall}{backspace}foo{leftArrow}{leftArrow}{leftArrow}{ctrl+k}`); + queryField.then(($el) => { + expect($el.text().replace(/\uFEFF/g, '')).to.match(/Enter a Loki query/); + }); + + // clears from the middle to the end of the line + queryField.type(`{selectall}{backspace}foo bar{leftArrow}{leftArrow}{leftArrow}{leftArrow}{ctrl+k}`); + queryField.then(($el) => { + expect($el.text().replace(/\uFEFF/g, '')).to.eq('foo'); + }); + + /** Runner plugin */ + + //should execute query when enter with shift is pressed + queryField.type(`{selectall}{backspace}{shift+enter}`); + e2e().get('[data-testid="explore-no-data"]').should('be.visible'); + + /** Suggestions plugin */ + e2e().get('.slate-query-field').type(`{selectall}av`); + e2e().get('.slate-typeahead').should('be.visible').contains('avg_over_time'); + }); +}); diff --git a/packages/grafana-ui/src/slate-plugins/braces.test.tsx b/packages/grafana-ui/src/slate-plugins/braces.test.tsx deleted file mode 100644 index 566761cad5b..00000000000 --- a/packages/grafana-ui/src/slate-plugins/braces.test.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { shallow } from 'enzyme'; -import React from 'react'; -import Plain from 'slate-plain-serializer'; -import { Editor } from 'slate-react'; - -import { BracesPlugin } from './braces'; - -describe('braces', () => { - const handler = BracesPlugin().onKeyDown!; - const nextMock = () => {}; - - it('adds closing braces around empty value', () => { - const value = Plain.deserialize(''); - const editor = shallow(); - const event = new window.KeyboardEvent('keydown', { key: '(' }); - expect(handler(event as any, editor.instance(), nextMock)).toBeTruthy(); - expect(Plain.serialize(editor.instance().value)).toEqual('()'); - }); - - it('removes closing brace when opening brace is removed', () => { - const value = Plain.deserialize('time()'); - const editor = shallow(); - const event = new window.KeyboardEvent('keydown', { key: 'Backspace' }); - editor.instance().moveForward(5); - handler(event as any, editor.instance(), nextMock); - expect(Plain.serialize(editor.instance().value)).toEqual('time'); - }); - - it('keeps closing brace when opening brace is removed and inner values exist', () => { - const value = Plain.deserialize('time(value)'); - const editor = shallow(); - const event = new window.KeyboardEvent('keydown', { key: 'Backspace' }); - editor.instance().moveForward(5); - const handled = handler(event as any, editor.instance(), nextMock); - expect(handled).toBeFalsy(); - }); - - it('overrides an automatically inserted brace', () => { - const value = Plain.deserialize(''); - const editor = shallow(); - const opening = new window.KeyboardEvent('keydown', { key: '(' }); - expect(handler(opening as any, editor.instance(), nextMock)).toBeTruthy(); - const closing = new window.KeyboardEvent('keydown', { key: ')' }); - expect(handler(closing as any, editor.instance(), nextMock)).toBeTruthy(); - expect(Plain.serialize(editor.instance().value)).toEqual('()'); - }); - - it.skip('does not override manually inserted braces', () => { - const value = Plain.deserialize(''); - const editor = shallow(); - const event1 = new window.KeyboardEvent('keydown', { key: ')' }); - expect(handler(event1 as any, editor.instance(), nextMock)).toBeFalsy(); - const event2 = new window.KeyboardEvent('keydown', { key: ')' }); - editor.instance().moveBackward(1); - expect(handler(event2 as any, editor.instance(), nextMock)).toBeFalsy(); - expect(Plain.serialize(editor.instance().value)).toEqual('))'); - }); -}); diff --git a/packages/grafana-ui/src/slate-plugins/clear.test.tsx b/packages/grafana-ui/src/slate-plugins/clear.test.tsx deleted file mode 100644 index 3934dbea246..00000000000 --- a/packages/grafana-ui/src/slate-plugins/clear.test.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { shallow } from 'enzyme'; -import React from 'react'; -import Plain from 'slate-plain-serializer'; -import { Editor } from 'slate-react'; - -import { ClearPlugin } from './clear'; - -describe('clear', () => { - const handler = ClearPlugin().onKeyDown!; - - it('does not change the empty value', () => { - const value = Plain.deserialize(''); - const editor = shallow(); - const event = new window.KeyboardEvent('keydown', { - key: 'k', - ctrlKey: true, - }); - handler(event as any, editor.instance(), () => {}); - expect(Plain.serialize(editor.instance().value)).toEqual(''); - }); - - it('clears to the end of the line', () => { - const value = Plain.deserialize('foo'); - const editor = shallow(); - const event = new window.KeyboardEvent('keydown', { - key: 'k', - ctrlKey: true, - }); - handler(event as any, editor.instance(), () => {}); - expect(Plain.serialize(editor.instance().value)).toEqual(''); - }); - - it('clears from the middle to the end of the line', () => { - const value = Plain.deserialize('foo bar'); - const editor = shallow(); - const event = new window.KeyboardEvent('keydown', { - key: 'k', - ctrlKey: true, - }); - editor.instance().moveForward(4); - handler(event as any, editor.instance(), () => {}); - expect(Plain.serialize(editor.instance().value)).toEqual('foo '); - }); -}); diff --git a/packages/grafana-ui/src/slate-plugins/runner.test.tsx b/packages/grafana-ui/src/slate-plugins/runner.test.tsx deleted file mode 100644 index 59c47c86b4b..00000000000 --- a/packages/grafana-ui/src/slate-plugins/runner.test.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { shallow } from 'enzyme'; -import React from 'react'; -import Plain from 'slate-plain-serializer'; -import { Editor } from 'slate-react'; - -import { RunnerPlugin } from './runner'; - -describe('runner', () => { - const mockHandler = jest.fn(); - const handler = RunnerPlugin({ handler: mockHandler }).onKeyDown!; - - it('should execute query when enter with shift is pressed', () => { - const value = Plain.deserialize(''); - const editor = shallow(); - handler( - new window.KeyboardEvent('keydown', { - key: 'Enter', - shiftKey: true, - }) as any, - editor.instance() as Editor, - () => {} - ); - expect(mockHandler).toBeCalled(); - }); -}); diff --git a/packages/grafana-ui/src/slate-plugins/suggestions.test.tsx b/packages/grafana-ui/src/slate-plugins/suggestions.test.tsx deleted file mode 100644 index 1cbc164950c..00000000000 --- a/packages/grafana-ui/src/slate-plugins/suggestions.test.tsx +++ /dev/null @@ -1,159 +0,0 @@ -import { render } from 'enzyme'; -import _ from 'lodash'; // eslint-disable-line lodash/import-scope -import { Plugin as SlatePlugin } from 'slate-react'; - -import { CompletionItemGroup, SuggestionsState } from '../types'; -import { SearchFunctionType } from '../utils'; -import { SearchFunctionMap } from '../utils/searchFunctions'; - -import { SuggestionsPlugin } from './suggestions'; - -jest.spyOn(_, 'debounce').mockImplementation((func: (...args: any) => any) => { - return Object.assign(func, { cancel: jest.fn(), flush: jest.fn() }); -}); - -jest.mock('../utils/searchFunctions', () => ({ - // @ts-ignore - ...jest.requireActual('../utils/searchFunctions'), - SearchFunctionMap: { - Prefix: jest.fn((items) => items), - Word: jest.fn((items) => items), - Fuzzy: jest.fn((items) => items), - }, -})); - -const TypeaheadMock = jest.fn(() => ''); -jest.mock('../components/Typeahead/Typeahead', () => { - return { - Typeahead: (state: Partial) => { - // @ts-ignore - TypeaheadMock(state); - return ''; - }, - }; -}); - -describe('SuggestionsPlugin', () => { - let plugin: SlatePlugin, nextMock: any, suggestions: CompletionItemGroup[], editorMock: any, eventMock: any; - - beforeEach(() => { - let onTypeahead = async () => { - return { - suggestions: suggestions, - }; - }; - - (SearchFunctionMap.Prefix as jest.Mock).mockClear(); - (SearchFunctionMap.Word as jest.Mock).mockClear(); - (SearchFunctionMap.Fuzzy as jest.Mock).mockClear(); - - plugin = SuggestionsPlugin({ portalOrigin: '', onTypeahead }); - nextMock = () => {}; - editorMock = createEditorMock('foo'); - eventMock = new window.KeyboardEvent('keydown', { key: 'a' }); - }); - - async function triggerAutocomplete() { - await plugin.onKeyDown!(eventMock, editorMock, nextMock); - render(plugin.renderEditor!({} as any, editorMock, nextMock)); - } - - it('is backward compatible with prefixMatch and sortText', async () => { - suggestions = [ - { - label: 'group', - prefixMatch: true, - items: [ - { label: 'foobar', sortText: '3' }, - { label: 'foobar', sortText: '1' }, - { label: 'foobar', sortText: '2' }, - ], - }, - ]; - - await triggerAutocomplete(); - - expect(SearchFunctionMap.Word).not.toBeCalled(); - expect(SearchFunctionMap.Fuzzy).not.toBeCalled(); - expect(SearchFunctionMap.Prefix).toBeCalled(); - - expect(TypeaheadMock).toBeCalledWith( - expect.objectContaining({ - groupedItems: [ - { - label: 'group', - prefixMatch: true, - items: [ - { label: 'foobar', sortText: '1' }, - { label: 'foobar', sortText: '2' }, - { label: 'foobar', sortText: '3' }, - ], - }, - ], - }) - ); - }); - - it('uses searchFunction to create autocomplete list and sortValue if defined', async () => { - suggestions = [ - { - label: 'group', - searchFunctionType: SearchFunctionType.Fuzzy, - items: [ - { label: 'foobar', sortValue: 3 }, - { label: 'foobar', sortValue: 1 }, - { label: 'foobar', sortValue: 2 }, - ], - }, - ]; - - await triggerAutocomplete(); - - expect(SearchFunctionMap.Word).not.toBeCalled(); - expect(SearchFunctionMap.Prefix).not.toBeCalled(); - expect(SearchFunctionMap.Fuzzy).toBeCalled(); - - expect(TypeaheadMock).toBeCalledWith( - expect.objectContaining({ - groupedItems: [ - { - label: 'group', - searchFunctionType: SearchFunctionType.Fuzzy, - items: [ - { label: 'foobar', sortValue: 1 }, - { label: 'foobar', sortValue: 2 }, - { label: 'foobar', sortValue: 3 }, - ], - }, - ], - }) - ); - }); -}); - -function createEditorMock(currentText: string) { - return { - blur: jest.fn().mockReturnThis(), - focus: jest.fn().mockReturnThis(), - value: { - selection: { - start: { - offset: 0, - }, - end: { - offset: 0, - }, - focus: { - offset: currentText.length, - }, - }, - document: { - getClosestBlock: () => {}, - }, - focusText: { - text: currentText, - }, - focusBlock: {}, - }, - }; -}