// Licensed to Cloudera, Inc. under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. Cloudera, Inc. licenses this file // to you under the Apache License, Version 2.0 (the // 'License'); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an 'AS IS' BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import { shallowMount } from '@vue/test-utils'; import { CancellablePromise } from '../../../../api/cancellablePromise'; import Executor from '../../../../apps/editor/execution/executor'; import dataCatalog from '../../../../catalog/dataCatalog'; import { Ace } from '../../../../ext/ace'; import { INSERT_AT_CURSOR_EVENT } from '../../../../ko/bindings/ace/ko.aceEditor'; import { AutocompleteParser, SqlParserProvider, SyntaxError, SyntaxParser } from '../../../../parse/types'; import { SetOptions, SqlReferenceProvider, UdfCategory } from '../../../../sql/reference/types'; import huePubSub from '../../../../utils/huePubSub'; import AceEditor from './AceEditor.vue'; import impalaSyntaxParser from '../../../../parse/sql/impala/impalaSyntaxParser'; import impalaAutocompleteParser from '../../../../parse/sql/impala/impalaAutocompleteParser'; const sqlParserProvider: SqlParserProvider = { async getAutocompleteParser(): Promise { return impalaAutocompleteParser as unknown as AutocompleteParser; }, async getSyntaxParser(): Promise { return impalaSyntaxParser as unknown as SyntaxParser; } }; const sqlReferenceProvider: SqlReferenceProvider = { getReservedKeywords(): Promise> { return Promise.resolve(new Set()); }, getSetOptions(): Promise { return Promise.resolve({}); }, getUdfCategories(): Promise { return Promise.resolve([]); }, hasUdfCategories(): boolean { return false; } }; describe('AceEditor.vue', () => { const mockExecutor = { connector: ko.observable({ dialect: 'impala', id: 'impala' }), namespace: ko.observable({ id: 'foo' }), compute: ko.observable({ id: 'foo' }), database: ko.observable('default') } as unknown as Executor; let mountedTwice = false; const shallowMountForEditor = async ( initialValue?: string ): Promise<{ element: Element; editor: Ace.Editor }> => new Promise(resolve => { jest.spyOn(dataCatalog, 'getChildren').mockReturnValue(CancellablePromise.resolve([])); const props = { initialValue, id: 'some-id', executor: mockExecutor, sqlParserProvider, sqlReferenceProvider }; let wrapper = shallowMount(AceEditor, { props }); if (!mountedTwice) { // There seems to be a bug in vue-test-utils where the first mount won't trigger the onMounted hook. mountedTwice = true; wrapper = shallowMount(AceEditor, { props }); } wrapper.vm.$nextTick(() => { expect(wrapper.emitted()['ace-created']).toBeTruthy(); const editor = (wrapper.emitted()['ace-created'][0] as Ace.Editor[])[0]; resolve({ element: wrapper.element, editor }); }); }); it('should render', async () => { const { element } = await shallowMountForEditor('some query'); expect(element).toMatchSnapshot(); }); it('should handle drag and drop pubsub event targeting this editor', async () => { const { editor } = await shallowMountForEditor(); const draggedText = 'Some dropped text'; huePubSub.publish(INSERT_AT_CURSOR_EVENT, { text: draggedText, targetEditor: editor, cursorEndAdjust: 0 }); expect(editor.getValue()).toEqual(draggedText); }); it('should not handle drag and drop pubsub event targeting another editor', async () => { const { editor } = await shallowMountForEditor(); const draggedText = 'Some dropped text'; huePubSub.publish(INSERT_AT_CURSOR_EVENT, { text: draggedText, targetEditor: {}, // Other instance cursorEndAdjust: 0 }); expect(editor.getValue()).not.toEqual(draggedText); }); it('should adjust parser 1-based parser location to 0-based ace range for syntax errors', async () => { const invalidTokenValue = 'slelect'; const { editor } = await shallowMountForEditor(invalidTokenValue); expect(editor.getValue()).toEqual(invalidTokenValue); // Add syntax error manually (normally through web socket) const token = editor.session.getTokenAt(0, 0); expect(token).toBeDefined(); expect(token!.value).toEqual(invalidTokenValue); const syntaxError: SyntaxError = { expected: [{ text: 'select', distance: 0 }], loc: { first_line: 1, last_line: 1, first_column: 0, last_column: 7 }, text: invalidTokenValue }; token!.syntaxError = syntaxError; // Listen for the dropdown show event let syntaxDropdownCalled = false; const subscription = huePubSub.subscribe('sql.syntax.dropdown.show', details => { expect(details.range.start.row).toEqual(syntaxError.loc.first_line - 1); expect(details.range.end.row).toEqual(syntaxError.loc.last_line - 1); expect(details.data).toEqual(syntaxError); syntaxDropdownCalled = true; }); // Trigger right click in the editor editor.container.dispatchEvent(new MouseEvent('contextmenu', { clientX: 1, clientY: 1 })); subscription.remove(); expect(syntaxDropdownCalled).toBeTruthy(); }); });