mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
fix(editor): Prevent updating node parameter value if it hasn't changed (#9535)
This commit is contained in:
parent
f914c97d11
commit
63990f14e3
|
@ -1,71 +0,0 @@
|
||||||
import { WorkflowPage, NDV } from '../pages';
|
|
||||||
|
|
||||||
const workflowPage = new WorkflowPage();
|
|
||||||
const ndv = new NDV();
|
|
||||||
|
|
||||||
describe('SQL editors', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
workflowPage.actions.visit();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should preserve changes when opening-closing Postgres node', () => {
|
|
||||||
workflowPage.actions.addInitialNodeToCanvas('Postgres', {
|
|
||||||
action: 'Execute a SQL query',
|
|
||||||
keepNdvOpen: true,
|
|
||||||
});
|
|
||||||
ndv.getters
|
|
||||||
.sqlEditorContainer()
|
|
||||||
.click()
|
|
||||||
.find('.cm-content')
|
|
||||||
.type('SELECT * FROM `testTable`')
|
|
||||||
.type('{esc}');
|
|
||||||
ndv.actions.close();
|
|
||||||
workflowPage.actions.openNode('Postgres');
|
|
||||||
ndv.getters.sqlEditorContainer().find('.cm-content').type('{end} LIMIT 10').type('{esc}');
|
|
||||||
ndv.actions.close();
|
|
||||||
workflowPage.actions.openNode('Postgres');
|
|
||||||
ndv.getters.sqlEditorContainer().should('contain', 'SELECT * FROM `testTable` LIMIT 10');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should update expression output dropdown as the query is edited', () => {
|
|
||||||
workflowPage.actions.addInitialNodeToCanvas('MySQL', {
|
|
||||||
action: 'Execute a SQL query',
|
|
||||||
});
|
|
||||||
ndv.actions.close();
|
|
||||||
|
|
||||||
workflowPage.actions.openNode('When clicking "Test workflow"');
|
|
||||||
ndv.actions.setPinnedData([{ table: 'test_table' }]);
|
|
||||||
ndv.actions.close();
|
|
||||||
|
|
||||||
workflowPage.actions.openNode('MySQL');
|
|
||||||
ndv.getters
|
|
||||||
.sqlEditorContainer()
|
|
||||||
.find('.cm-content')
|
|
||||||
.type('SELECT * FROM {{ $json.table }}', { parseSpecialCharSequences: false });
|
|
||||||
workflowPage.getters
|
|
||||||
.inlineExpressionEditorOutput()
|
|
||||||
.should('have.text', 'SELECT * FROM test_table');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not push NDV header out with a lot of code in Postgres editor', () => {
|
|
||||||
workflowPage.actions.addInitialNodeToCanvas('Postgres', {
|
|
||||||
action: 'Execute a SQL query',
|
|
||||||
keepNdvOpen: true,
|
|
||||||
});
|
|
||||||
cy.fixture('Dummy_javascript.txt').then((code) => {
|
|
||||||
ndv.getters.sqlEditorContainer().find('.cm-content').paste(code);
|
|
||||||
});
|
|
||||||
ndv.getters.nodeExecuteButton().should('be.visible');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not push NDV header out with a lot of code in MySQL editor', () => {
|
|
||||||
workflowPage.actions.addInitialNodeToCanvas('MySQL', {
|
|
||||||
action: 'Execute a SQL query',
|
|
||||||
keepNdvOpen: true,
|
|
||||||
});
|
|
||||||
cy.fixture('Dummy_javascript.txt').then((code) => {
|
|
||||||
ndv.getters.sqlEditorContainer().find('.cm-content').paste(code);
|
|
||||||
});
|
|
||||||
ndv.getters.nodeExecuteButton().should('be.visible');
|
|
||||||
});
|
|
||||||
});
|
|
169
cypress/e2e/41-editors.cy.ts
Normal file
169
cypress/e2e/41-editors.cy.ts
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
import { WorkflowPage, NDV } from '../pages';
|
||||||
|
|
||||||
|
const workflowPage = new WorkflowPage();
|
||||||
|
const ndv = new NDV();
|
||||||
|
|
||||||
|
// Update is debounced in editors, so adding typing delay to catch up
|
||||||
|
const TYPING_DELAY = 100;
|
||||||
|
|
||||||
|
describe('Editors', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
workflowPage.actions.visit();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('SQL Editor', () => {
|
||||||
|
|
||||||
|
it('should preserve changes when opening-closing Postgres node', () => {
|
||||||
|
workflowPage.actions.addInitialNodeToCanvas('Postgres', {
|
||||||
|
action: 'Execute a SQL query',
|
||||||
|
keepNdvOpen: true,
|
||||||
|
});
|
||||||
|
ndv.getters
|
||||||
|
.sqlEditorContainer()
|
||||||
|
.click()
|
||||||
|
.find('.cm-content')
|
||||||
|
.type('SELECT * FROM `testTable`', { delay: TYPING_DELAY })
|
||||||
|
.type('{esc}');
|
||||||
|
ndv.actions.close();
|
||||||
|
workflowPage.actions.openNode('Postgres');
|
||||||
|
ndv.getters.sqlEditorContainer().find('.cm-content').type('{end} LIMIT 10', { delay: TYPING_DELAY }).type('{esc}');
|
||||||
|
ndv.actions.close();
|
||||||
|
workflowPage.actions.openNode('Postgres');
|
||||||
|
ndv.getters.sqlEditorContainer().should('contain', 'SELECT * FROM `testTable` LIMIT 10');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update expression output dropdown as the query is edited', () => {
|
||||||
|
workflowPage.actions.addInitialNodeToCanvas('MySQL', {
|
||||||
|
action: 'Execute a SQL query',
|
||||||
|
});
|
||||||
|
ndv.actions.close();
|
||||||
|
|
||||||
|
workflowPage.actions.openNode('When clicking "Test workflow"');
|
||||||
|
ndv.actions.setPinnedData([{ table: 'test_table' }]);
|
||||||
|
ndv.actions.close();
|
||||||
|
|
||||||
|
workflowPage.actions.openNode('MySQL');
|
||||||
|
ndv.getters
|
||||||
|
.sqlEditorContainer()
|
||||||
|
.find('.cm-content')
|
||||||
|
.type('SELECT * FROM {{ $json.table }}', { parseSpecialCharSequences: false });
|
||||||
|
workflowPage.getters
|
||||||
|
.inlineExpressionEditorOutput()
|
||||||
|
.should('have.text', 'SELECT * FROM test_table');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not push NDV header out with a lot of code in Postgres editor', () => {
|
||||||
|
workflowPage.actions.addInitialNodeToCanvas('Postgres', {
|
||||||
|
action: 'Execute a SQL query',
|
||||||
|
keepNdvOpen: true,
|
||||||
|
});
|
||||||
|
cy.fixture('Dummy_javascript.txt').then((code) => {
|
||||||
|
ndv.getters.sqlEditorContainer().find('.cm-content').paste(code);
|
||||||
|
});
|
||||||
|
ndv.getters.nodeExecuteButton().should('be.visible');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not push NDV header out with a lot of code in MySQL editor', () => {
|
||||||
|
workflowPage.actions.addInitialNodeToCanvas('MySQL', {
|
||||||
|
action: 'Execute a SQL query',
|
||||||
|
keepNdvOpen: true,
|
||||||
|
});
|
||||||
|
cy.fixture('Dummy_javascript.txt').then((code) => {
|
||||||
|
ndv.getters.sqlEditorContainer().find('.cm-content').paste(code);
|
||||||
|
});
|
||||||
|
ndv.getters.nodeExecuteButton().should('be.visible');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not trigger dirty flag if nothing is changed', () => {
|
||||||
|
workflowPage.actions.addInitialNodeToCanvas('Postgres', {
|
||||||
|
action: 'Execute a SQL query',
|
||||||
|
keepNdvOpen: true,
|
||||||
|
});
|
||||||
|
ndv.actions.close();
|
||||||
|
workflowPage.actions.saveWorkflowOnButtonClick();
|
||||||
|
workflowPage.getters.isWorkflowSaved();
|
||||||
|
workflowPage.actions.openNode('Postgres');
|
||||||
|
ndv.actions.close();
|
||||||
|
// Workflow should still be saved
|
||||||
|
workflowPage.getters.isWorkflowSaved();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should trigger dirty flag if query is updated', () => {
|
||||||
|
workflowPage.actions.addInitialNodeToCanvas('Postgres', {
|
||||||
|
action: 'Execute a SQL query',
|
||||||
|
keepNdvOpen: true,
|
||||||
|
});
|
||||||
|
ndv.actions.close();
|
||||||
|
workflowPage.actions.saveWorkflowOnButtonClick();
|
||||||
|
workflowPage.getters.isWorkflowSaved();
|
||||||
|
workflowPage.actions.openNode('Postgres');
|
||||||
|
ndv.getters
|
||||||
|
.sqlEditorContainer()
|
||||||
|
.click()
|
||||||
|
.find('.cm-content')
|
||||||
|
.type('SELECT * FROM `testTable`', { delay: TYPING_DELAY })
|
||||||
|
.type('{esc}');
|
||||||
|
ndv.actions.close();
|
||||||
|
workflowPage.getters.isWorkflowSaved().should('not.be.true');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('HTML Editor', () => {
|
||||||
|
// Closing tags will be added by the editor
|
||||||
|
const TEST_ELEMENT_H1 = '<h1>Test';
|
||||||
|
const TEST_ELEMENT_P = '<p>Test';
|
||||||
|
|
||||||
|
it('should preserve changes when opening-closing HTML node', () => {
|
||||||
|
workflowPage.actions.addInitialNodeToCanvas('HTML', {
|
||||||
|
action: 'Generate HTML template',
|
||||||
|
keepNdvOpen: true,
|
||||||
|
});
|
||||||
|
ndv.getters
|
||||||
|
.htmlEditorContainer()
|
||||||
|
.click()
|
||||||
|
.find('.cm-content')
|
||||||
|
.type(`{selectall}${TEST_ELEMENT_H1}`, { delay: TYPING_DELAY, force: true })
|
||||||
|
.type('{esc}');
|
||||||
|
ndv.actions.close();
|
||||||
|
workflowPage.actions.openNode('HTML');
|
||||||
|
ndv.getters.htmlEditorContainer().find('.cm-content').type(`{end}${TEST_ELEMENT_P}`, { delay: TYPING_DELAY, force: true }).type('{esc}');
|
||||||
|
ndv.actions.close();
|
||||||
|
workflowPage.actions.openNode('HTML');
|
||||||
|
ndv.getters.htmlEditorContainer().should('contain', TEST_ELEMENT_H1);
|
||||||
|
ndv.getters.htmlEditorContainer().should('contain', TEST_ELEMENT_P);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not trigger dirty flag if nothing is changed', () => {
|
||||||
|
workflowPage.actions.addInitialNodeToCanvas('HTML', {
|
||||||
|
action: 'Generate HTML template',
|
||||||
|
keepNdvOpen: true,
|
||||||
|
});
|
||||||
|
ndv.actions.close();
|
||||||
|
workflowPage.actions.saveWorkflowOnButtonClick();
|
||||||
|
workflowPage.getters.isWorkflowSaved();
|
||||||
|
workflowPage.actions.openNode('HTML');
|
||||||
|
ndv.actions.close();
|
||||||
|
// Workflow should still be saved
|
||||||
|
workflowPage.getters.isWorkflowSaved();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should trigger dirty flag if query is updated', () => {
|
||||||
|
workflowPage.actions.addInitialNodeToCanvas('HTML', {
|
||||||
|
action: 'Generate HTML template',
|
||||||
|
keepNdvOpen: true,
|
||||||
|
});
|
||||||
|
ndv.actions.close();
|
||||||
|
workflowPage.actions.saveWorkflowOnButtonClick();
|
||||||
|
workflowPage.getters.isWorkflowSaved();
|
||||||
|
workflowPage.actions.openNode('HTML');
|
||||||
|
ndv.getters
|
||||||
|
.htmlEditorContainer()
|
||||||
|
.click()
|
||||||
|
.find('.cm-content')
|
||||||
|
.type(`{selectall}${TEST_ELEMENT_H1}`, { delay: TYPING_DELAY, force: true })
|
||||||
|
.type('{esc}');
|
||||||
|
ndv.actions.close();
|
||||||
|
workflowPage.getters.isWorkflowSaved().should('not.be.true');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -84,6 +84,7 @@ export class NDV extends BasePage {
|
||||||
cy.getByTestId('columns-parameter-input-options-container'),
|
cy.getByTestId('columns-parameter-input-options-container'),
|
||||||
resourceMapperRemoveAllFieldsOption: () => cy.getByTestId('action-removeAllFields'),
|
resourceMapperRemoveAllFieldsOption: () => cy.getByTestId('action-removeAllFields'),
|
||||||
sqlEditorContainer: () => cy.getByTestId('sql-editor-container'),
|
sqlEditorContainer: () => cy.getByTestId('sql-editor-container'),
|
||||||
|
htmlEditorContainer: () => cy.getByTestId('html-editor-container'),
|
||||||
filterComponent: (paramName: string) => cy.getByTestId(`filter-${paramName}`),
|
filterComponent: (paramName: string) => cy.getByTestId(`filter-${paramName}`),
|
||||||
filterCombinator: (paramName: string, index = 0) =>
|
filterCombinator: (paramName: string, index = 0) =>
|
||||||
this.getters.filterComponent(paramName).getByTestId('filter-combinator-select').eq(index),
|
this.getters.filterComponent(paramName).getByTestId('filter-combinator-select').eq(index),
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div :class="$style.editor">
|
<div :class="$style.editor">
|
||||||
<div ref="htmlEditor"></div>
|
<div ref="htmlEditor" data-test-id="html-editor-container"></div>
|
||||||
<slot name="suffix" />
|
<slot name="suffix" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -244,7 +244,6 @@ onMounted(() => {
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
htmlEditorEventBus.off('format-html', formatHtml);
|
htmlEditorEventBus.off('format-html', formatHtml);
|
||||||
emit('update:model-value', readEditorValue());
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1189,6 +1189,13 @@ function valueChanged(value: NodeParameterValueType | {} | Date) {
|
||||||
if (remoteParameterOptionsLoading.value) {
|
if (remoteParameterOptionsLoading.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Only update the value if it has changed
|
||||||
|
const oldValue = node.value?.parameters
|
||||||
|
? nodeHelpers.getParameterValue(node.value?.parameters, props.parameter.name, '')
|
||||||
|
: undefined;
|
||||||
|
if (oldValue !== undefined && oldValue === value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (props.parameter.name === 'nodeCredentialType') {
|
if (props.parameter.name === 'nodeCredentialType') {
|
||||||
activeCredentialType.value = value as string;
|
activeCredentialType.value = value as string;
|
||||||
|
|
|
@ -163,7 +163,6 @@ onMounted(() => {
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
codeNodeEditorEventBus.off('error-line-number', highlightLine);
|
codeNodeEditorEventBus.off('error-line-number', highlightLine);
|
||||||
emit('update:model-value', readEditorValue());
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function line(lineNumber: number): Line | null {
|
function line(lineNumber: number): Line | null {
|
||||||
|
|
Loading…
Reference in a new issue