mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2026-09-03 20:52:57 -05:00
Upgrade CodeMirror from version 5 to 6. #7097
This commit is contained in:
@@ -172,8 +172,8 @@ class QueryToolAutoCompleteFeatureTest(BaseFeatureTest):
|
||||
ActionChains(self.page.driver).key_down(
|
||||
Keys.CONTROL).send_keys(Keys.SPACE).key_up(
|
||||
Keys.CONTROL).perform()
|
||||
if self.page.check_if_element_exist_by_xpath(
|
||||
QueryToolLocators.code_mirror_hint_box_xpath, 15):
|
||||
if self.page.check_if_element_exist_by_css_selector(
|
||||
QueryToolLocators.code_mirror_hint_box, 15):
|
||||
hint_displayed = True
|
||||
break
|
||||
else:
|
||||
@@ -186,12 +186,11 @@ class QueryToolAutoCompleteFeatureTest(BaseFeatureTest):
|
||||
else:
|
||||
# if no IntelliSense is present it means there is only one option
|
||||
# so check if required string is present in codeMirror
|
||||
code_mirror = self.driver.find_elements(
|
||||
By.XPATH, QueryToolLocators.code_mirror_data_xpath)
|
||||
for data in code_mirror:
|
||||
code_mirror_text = data.text
|
||||
print("Single entry..........")
|
||||
if expected_string not in code_mirror_text:
|
||||
print("single entry exception.........")
|
||||
raise RuntimeError("Required String %s is not "
|
||||
"present" % expected_string)
|
||||
code_mirror_text = self.driver.find_element(
|
||||
By.CSS_SELECTOR, QueryToolLocators.code_mirror_content
|
||||
.format('#id-query')).text
|
||||
|
||||
if expected_string not in code_mirror_text:
|
||||
print("single entry exception.........")
|
||||
raise RuntimeError("Required String %s is not "
|
||||
"present" % expected_string)
|
||||
|
||||
@@ -46,7 +46,7 @@ class TableDdlFeatureTest(BaseFeatureTest):
|
||||
|
||||
# Wait till data is displayed in SQL Tab
|
||||
self.assertTrue(self.page.check_if_element_exist_by_xpath(
|
||||
"//*[contains(@class,'CodeMirror-lines') and "
|
||||
"//*[contains(@class,'cm-line') and "
|
||||
"contains(.,'CREATE TABLE IF NOT EXISTS public.%s')]"
|
||||
% self.test_table_name, 10), "No data displayed in SQL tab")
|
||||
|
||||
|
||||
@@ -51,18 +51,14 @@ class CopySQLFeatureTest(BaseFeatureTest):
|
||||
self.page.click_tab("SQL")
|
||||
# Wait till data is displayed in SQL Tab
|
||||
self.assertTrue(self.page.check_if_element_exist_by_xpath(
|
||||
"//*[contains(@class,'CodeMirror-lines') and "
|
||||
"//*[contains(@class,'cm-line') and "
|
||||
"contains(.,'CREATE TABLE IF NOT EXISTS public.%s')]"
|
||||
% self.test_table_name, 10), "No data displayed in SQL tab")
|
||||
|
||||
# Fetch the inner html & check for escaped characters
|
||||
source_code = self.driver.find_elements(
|
||||
By.XPATH, QueryToolLocators.code_mirror_data_xpath)
|
||||
|
||||
sql_query = ''
|
||||
for data in source_code:
|
||||
sql_query += data.text
|
||||
sql_query += '\n'
|
||||
sql_query = self.driver.find_element(
|
||||
By.CSS_SELECTOR, QueryToolLocators.code_mirror_content
|
||||
.format('#id-sql')).text
|
||||
|
||||
return sql_query
|
||||
|
||||
@@ -76,12 +72,9 @@ class CopySQLFeatureTest(BaseFeatureTest):
|
||||
self.driver.switch_to.frame(
|
||||
self.driver.find_element(By.TAG_NAME, "iframe"))
|
||||
|
||||
code_mirror = self.driver.find_elements(
|
||||
By.XPATH, QueryToolLocators.code_mirror_data_xpath)
|
||||
query_tool_result = ''
|
||||
for data in code_mirror:
|
||||
query_tool_result += data.text
|
||||
query_tool_result += '\n'
|
||||
query_tool_result = self.driver.find_element(
|
||||
By.CSS_SELECTOR, QueryToolLocators.code_mirror_content
|
||||
.format('#id-query')).text
|
||||
|
||||
return query_tool_result
|
||||
|
||||
|
||||
@@ -128,12 +128,12 @@ class CheckForXssFeatureTest(BaseFeatureTest):
|
||||
|
||||
# Wait till data is displayed in SQL Tab
|
||||
self.assertTrue(self.page.check_if_element_exist_by_xpath(
|
||||
"//*[contains(@class,'CodeMirror-lines') and "
|
||||
"//*[contains(@class,'cm-line') and "
|
||||
"contains(.,'CREATE TABLE')]", 10), "No data displayed in SQL tab")
|
||||
|
||||
# Fetch the inner html & check for escaped characters
|
||||
source_code = self.page.find_by_xpath(
|
||||
"//*[contains(@class,'CodeMirror-lines') and "
|
||||
"//*[contains(@class,'cm-line') and "
|
||||
"contains(.,'CREATE TABLE')]"
|
||||
).get_attribute('innerHTML')
|
||||
|
||||
|
||||
@@ -235,12 +235,13 @@ class QueryToolLocators:
|
||||
|
||||
sql_editor_message = "//div[@id='id-messages'][contains(string(), '{}')]"
|
||||
|
||||
code_mirror_hint_box_xpath = "//ul[@class='CodeMirror-hints default']"
|
||||
code_mirror_hint_box = ".cm-editor .cm-tooltip-autocomplete"
|
||||
|
||||
code_mirror_hint_item_xpath = \
|
||||
"//ul[contains(@class, 'CodeMirror-hints') and contains(., '{}')]"
|
||||
("//div[contains(@class, 'cm-tooltip-autocomplete') "
|
||||
"and contains(., '{}')]")
|
||||
|
||||
code_mirror_data_xpath = "//pre[@class=' CodeMirror-line ']/span"
|
||||
code_mirror_content = "{0} .cm-content"
|
||||
|
||||
btn_commit = "button[data-label='Commit']"
|
||||
|
||||
|
||||
@@ -892,7 +892,8 @@ class PgadminPage:
|
||||
driver.switch_to.frame(
|
||||
driver.find_element(By.TAG_NAME, "iframe"))
|
||||
element = driver.find_element(
|
||||
By.CSS_SELECTOR, "#sqleditor-container .CodeMirror")
|
||||
By.CSS_SELECTOR,
|
||||
"#sqleditor-container #id-query .cm-content")
|
||||
if element.is_displayed() and element.is_enabled():
|
||||
return element
|
||||
except (NoSuchElementException, WebDriverException):
|
||||
@@ -933,9 +934,9 @@ class PgadminPage:
|
||||
action.perform()
|
||||
else:
|
||||
self.driver.execute_script(
|
||||
"arguments[0].CodeMirror.setValue(arguments[1]);"
|
||||
"arguments[0].CodeMirror.setCursor("
|
||||
"arguments[0].CodeMirror.lineCount(),0);",
|
||||
"arguments[0].cmView.view.setValue(arguments[1]);"
|
||||
"arguments[0].cmView.view.setCursor("
|
||||
"arguments[0].cmView.view.lineCount(),0);",
|
||||
codemirror_ele, field_content)
|
||||
|
||||
def click_tab(self, tab_name):
|
||||
|
||||
@@ -168,20 +168,20 @@ describe('SchemaView', ()=>{
|
||||
|
||||
it('no changes', async ()=>{
|
||||
await user.click(ctrl.container.querySelector('button[data-test="SQL"]'));
|
||||
expect(ctrl.container.querySelector('[data-testid="SQL"] textarea')).toHaveValue('-- No updates.');
|
||||
expect(ctrl.container.querySelector('[data-testid="SQL"] .cm-content')).toHaveTextContent('-- No updates.');
|
||||
});
|
||||
|
||||
it('data invalid', async ()=>{
|
||||
await user.clear(ctrl.container.querySelector('[name="field2"]'));
|
||||
await user.type(ctrl.container.querySelector('[name="field2"]'), '2');
|
||||
await user.click(ctrl.container.querySelector('button[data-test="SQL"]'));
|
||||
expect(ctrl.container.querySelector('[data-testid="SQL"] textarea')).toHaveValue('-- Definition incomplete.');
|
||||
expect(ctrl.container.querySelector('[data-testid="SQL"] .cm-content')).toHaveTextContent('-- Definition incomplete.');
|
||||
});
|
||||
|
||||
it('valid data', async ()=>{
|
||||
await simulateValidData();
|
||||
await user.click(ctrl.container.querySelector('button[data-test="SQL"]'));
|
||||
expect(ctrl.container.querySelector('[data-testid="SQL"] textarea')).toHaveValue('select 1;');
|
||||
expect(ctrl.container.querySelector('[data-testid="SQL"] .cm-content')).toHaveTextContent('select 1;');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
const getSearchCursorRet = {
|
||||
_from: 3,
|
||||
_to: 14,
|
||||
find: function(_rev) {
|
||||
if(_rev){
|
||||
this._from = 1;
|
||||
this._to = 10;
|
||||
} else {
|
||||
this._from = 3;
|
||||
this._to = 14;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
from: function() {return this._from;},
|
||||
to: function() {return this._to;},
|
||||
replace: jest.fn(),
|
||||
};
|
||||
const fromTextAreaRet = {
|
||||
'getValue':()=>'',
|
||||
'setValue': jest.fn(),
|
||||
'refresh': jest.fn(),
|
||||
'setOption': jest.fn(),
|
||||
'removeKeyMap': jest.fn(),
|
||||
'addKeyMap': jest.fn(),
|
||||
'getSelection': () => '',
|
||||
'getSearchCursor': jest.fn(()=>getSearchCursorRet),
|
||||
'getCursor': jest.fn(),
|
||||
'removeOverlay': jest.fn(),
|
||||
'addOverlay': jest.fn(),
|
||||
'setSelection': jest.fn(),
|
||||
'scrollIntoView': jest.fn(),
|
||||
'getWrapperElement': ()=>document.createElement('div'),
|
||||
'on': jest.fn(),
|
||||
'off': jest.fn(),
|
||||
'toTextArea': jest.fn(),
|
||||
};
|
||||
module.exports = {
|
||||
fromTextArea: jest.fn(()=>fromTextAreaRet)
|
||||
};
|
||||
@@ -9,41 +9,54 @@
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {default as OrigCodeMirror} from 'bundled_codemirror';
|
||||
import { withTheme } from '../fake_theme';
|
||||
|
||||
import pgWindow from 'sources/window';
|
||||
import CodeMirror from 'sources/components/CodeMirror';
|
||||
import { FindDialog } from '../../../pgadmin/static/js/components/CodeMirror';
|
||||
import CodeMirror from 'sources/components/ReactCodeMirror';
|
||||
import FindDialog from 'sources/components/ReactCodeMirror/FindDialog';
|
||||
import CustomEditorView from 'sources/components/ReactCodeMirror/CustomEditorView';
|
||||
import fakePgAdmin from '../fake_pgadmin';
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import * as CMSearch from '@codemirror/search';
|
||||
|
||||
jest.mock('sources/components/ReactCodeMirror/CustomEditorView');
|
||||
jest.mock('@codemirror/search', () => ({
|
||||
...(jest.requireActual('@codemirror/search')),
|
||||
SearchQuery: jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
eq: jest.fn(),
|
||||
};
|
||||
}),
|
||||
openSearchPanel: jest.fn(),
|
||||
closeSearchPanel: jest.fn(),
|
||||
replaceNext: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('CodeMirror', ()=>{
|
||||
const ThemedCM = withTheme(CodeMirror);
|
||||
let cmInstance, options={
|
||||
lineNumbers: true,
|
||||
mode: 'text/x-pgsql',
|
||||
},
|
||||
cmObj = OrigCodeMirror.fromTextArea();
|
||||
let cmInstance, editor;
|
||||
|
||||
const cmRerender = (props)=>{
|
||||
cmInstance.rerender(
|
||||
<ThemedCM
|
||||
value={'Init text'}
|
||||
options={options}
|
||||
className="testClass"
|
||||
currEditor={(obj) => {
|
||||
editor = obj;
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
beforeEach(()=>{
|
||||
pgWindow.pgAdmin = fakePgAdmin;
|
||||
// jest.spyOn(OrigCodeMirror, 'fromTextArea').mockReturnValue(cmObj);
|
||||
cmInstance = render(
|
||||
<ThemedCM
|
||||
value={'Init text'}
|
||||
options={options}
|
||||
className="testClass"
|
||||
currEditor={(obj) => {
|
||||
editor = obj;
|
||||
}}
|
||||
/>);
|
||||
});
|
||||
|
||||
@@ -52,17 +65,23 @@ describe('CodeMirror', ()=>{
|
||||
});
|
||||
|
||||
it('init', async ()=>{
|
||||
/* textarea ref passed to fromTextArea */
|
||||
expect(OrigCodeMirror.fromTextArea).toHaveBeenCalledWith(cmInstance.container.querySelector('textarea'), expect.objectContaining(options));
|
||||
await waitFor(() => expect(cmObj.setValue).toHaveBeenCalledWith('Init text'));
|
||||
expect(CustomEditorView).toHaveBeenCalledTimes(1);
|
||||
expect(editor.setValue).toHaveBeenCalledWith('Init text');
|
||||
});
|
||||
|
||||
it('change value', ()=>{
|
||||
editor.state = {
|
||||
doc: [],
|
||||
};
|
||||
editor.setValue.mockClear();
|
||||
jest.spyOn(editor, 'getValue').mockReturnValue('Init text');
|
||||
cmRerender({value: 'the new text'});
|
||||
expect(cmObj.setValue).toHaveBeenCalledWith('the new text');
|
||||
expect(editor.setValue).toHaveBeenCalledWith('the new text');
|
||||
|
||||
editor.setValue.mockClear();
|
||||
jest.spyOn(editor, 'getValue').mockReturnValue('the new text');
|
||||
cmRerender({value: null});
|
||||
expect(cmObj.setValue).toHaveBeenCalledWith('');
|
||||
expect(editor.setValue).toHaveBeenCalledWith('');
|
||||
});
|
||||
|
||||
|
||||
@@ -73,7 +92,7 @@ describe('CodeMirror', ()=>{
|
||||
const ctrlMount = (props)=>{
|
||||
ctrl = render(
|
||||
<ThemedFindDialog
|
||||
editor={cmObj}
|
||||
editor={editor}
|
||||
show={true}
|
||||
onClose={onClose}
|
||||
{...props}
|
||||
@@ -84,29 +103,27 @@ describe('CodeMirror', ()=>{
|
||||
it('init', ()=>{
|
||||
ctrlMount({});
|
||||
|
||||
cmObj.removeOverlay.mockClear();
|
||||
cmObj.addOverlay.mockClear();
|
||||
CMSearch.SearchQuery.mockClear();
|
||||
const input = ctrl.container.querySelector('input');
|
||||
|
||||
fireEvent.change(input, {
|
||||
target: {value: '\n\r\tA'},
|
||||
});
|
||||
|
||||
expect(cmObj.removeOverlay).toHaveBeenCalled();
|
||||
expect(cmObj.addOverlay).toHaveBeenCalled();
|
||||
expect(cmObj.setSelection).toHaveBeenCalledWith(3, 14);
|
||||
expect(cmObj.scrollIntoView).toHaveBeenCalled();
|
||||
expect(CMSearch.SearchQuery).toHaveBeenCalledWith(expect.objectContaining({
|
||||
search: expect.stringContaining('A')
|
||||
}));
|
||||
});
|
||||
|
||||
it('escape', ()=>{
|
||||
ctrlMount({});
|
||||
cmObj.removeOverlay.mockClear();
|
||||
CMSearch.closeSearchPanel.mockClear();
|
||||
|
||||
fireEvent.keyDown(ctrl.container.querySelector('input'), {
|
||||
key: 'Escape',
|
||||
});
|
||||
|
||||
expect(cmObj.removeOverlay).toHaveBeenCalled();
|
||||
expect(CMSearch.closeSearchPanel).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('toggle match case', ()=>{
|
||||
@@ -132,7 +149,8 @@ describe('CodeMirror', ()=>{
|
||||
|
||||
it('replace', async ()=>{
|
||||
ctrlMount({replace: true});
|
||||
cmObj.getSearchCursor().replace.mockClear();
|
||||
CMSearch.SearchQuery.mockClear();
|
||||
|
||||
fireEvent.change(ctrl.container.querySelectorAll('input')[0], {
|
||||
target: {value: 'A'},
|
||||
});
|
||||
@@ -142,9 +160,13 @@ describe('CodeMirror', ()=>{
|
||||
fireEvent.keyPress(ctrl.container.querySelectorAll('input')[1], {
|
||||
key: 'Enter', shiftKey: true, code: 13, charCode: 13
|
||||
});
|
||||
await waitFor(()=>{
|
||||
expect(cmObj.getSearchCursor().replace).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
expect(CMSearch.SearchQuery).toHaveBeenCalledWith(expect.objectContaining({
|
||||
search: 'A',
|
||||
replace: 'B'
|
||||
}));
|
||||
|
||||
expect(CMSearch.replaceNext).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -50,6 +50,20 @@ global.afterEach(() => {
|
||||
|
||||
window.HTMLElement.prototype.scrollIntoView = function() {};
|
||||
|
||||
// required for Codemirror 6 to run in jsdom
|
||||
document.createRange = () => {
|
||||
const range = new Range();
|
||||
|
||||
range.getBoundingClientRect = jest.fn();
|
||||
|
||||
range.getClientRects = jest.fn(() => ({
|
||||
item: () => null,
|
||||
length: 0,
|
||||
}));
|
||||
|
||||
return range;
|
||||
};
|
||||
|
||||
|
||||
jest.setTimeout(15000); // 1 second
|
||||
|
||||
|
||||
Reference in New Issue
Block a user