Upgrade CodeMirror from version 5 to 6. #7097

This commit is contained in:
Aditya Toshniwal
2024-02-21 11:15:25 +05:30
committed by GitHub
parent 721290b1e9
commit d3ede3151a
53 changed files with 1565 additions and 1611 deletions
@@ -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')
+4 -3
View File
@@ -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']"
+5 -4
View File
@@ -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();
});
});
});
+14
View File
@@ -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