mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
feat(editor): Add fullscreen view to code editor (#8084)
## Summary <img width="1240" alt="image" src="https://github.com/n8n-io/n8n/assets/8850410/2819f4ce-c343-431a-8a88-a1bc9c4b572a"> <img width="2649" alt="image" src="https://github.com/n8n-io/n8n/assets/8850410/36862aaf-cc4c-4668-bdc8-cf5a6f00babe"> 1. Add code node and open it 3. Click the fullscreen button in the bottom right 4. A fullscreen dialog should appear and allow editing the code 5. Changes made in the fullscreen dialog should be applied to the original code editor when closed It should work the same way for HTML/SQL/JSON editors ⚠️ Modal layout was updated so that modals/dialogs are centered, try to test some modals ## Related tickets and issues https://linear.app/n8n/issue/NODE-1009/add-fullscreen-view-to-code-node ## Review / Merge checklist - [ ] PR title and summary are descriptive. **Remember, the title automatically goes into the changelog. Use `(no-changelog)` otherwise.** ([conventions](https://github.com/n8n-io/n8n/blob/master/.github/pull_request_title_conventions.md)) - [ ] [Docs updated](https://github.com/n8n-io/n8n-docs) or follow-up ticket created. - [ ] Tests included. > A bug is not considered fixed, unless a test is added to prevent it from happening again. > A feature is not complete without tests. --------- Co-authored-by: Giulio Andreini <andreini@netseven.it>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { WorkflowPage, NDV, CredentialsModal } from '../pages';
|
||||
import { getPopper, getVisiblePopper, getVisibleSelect } from '../utils';
|
||||
import { getVisiblePopper, getVisibleSelect } from '../utils';
|
||||
|
||||
const workflowPage = new WorkflowPage();
|
||||
const ndv = new NDV();
|
||||
@@ -66,6 +66,8 @@ describe('Resource Locator', () => {
|
||||
workflowPage.actions.addInitialNodeToCanvas('E2e Test', { action: 'Resource Locator' });
|
||||
|
||||
ndv.getters.resourceLocatorInput('rlc').click();
|
||||
|
||||
cy.getByTestId('rlc-item').should('exist');
|
||||
getVisiblePopper()
|
||||
.should('have.length', 1)
|
||||
.findChildByTestId('rlc-item')
|
||||
@@ -73,9 +75,11 @@ describe('Resource Locator', () => {
|
||||
|
||||
ndv.actions.setInvalidExpression({ fieldName: 'fieldId' });
|
||||
|
||||
ndv.getters.container().click(); // remove focus from input, hide expression preview
|
||||
ndv.getters.nodeParameters().click(); // remove focus from input, hide expression preview
|
||||
|
||||
ndv.getters.resourceLocatorInput('rlc').click();
|
||||
|
||||
cy.getByTestId('rlc-item').should('exist');
|
||||
getVisiblePopper()
|
||||
.should('have.length', 1)
|
||||
.findChildByTestId('rlc-item')
|
||||
|
||||
@@ -302,7 +302,7 @@ describe('NDV', () => {
|
||||
|
||||
ndv.actions.setInvalidExpression({ fieldName: 'fieldId', delay: 200 });
|
||||
|
||||
ndv.getters.container().click(); // remove focus from input, hide expression preview
|
||||
ndv.getters.nodeParameters().click(); // remove focus from input, hide expression preview
|
||||
|
||||
ndv.getters.parameterInput('remoteOptions').click();
|
||||
|
||||
@@ -320,7 +320,7 @@ describe('NDV', () => {
|
||||
|
||||
ndv.actions.setInvalidExpression({ fieldName: 'otherField', delay: 50 });
|
||||
|
||||
ndv.getters.container().click(); // remove focus from input, hide expression preview
|
||||
ndv.getters.nodeParameters().click(); // remove focus from input, hide expression preview
|
||||
|
||||
ndv.getters.parameterInput('remoteOptions').click();
|
||||
getVisibleSelect().find('.el-select-dropdown__item').should('have.length', 3);
|
||||
@@ -360,6 +360,15 @@ describe('NDV', () => {
|
||||
ndv.getters.nodeExecuteButton().should('be.visible');
|
||||
});
|
||||
|
||||
it('should allow editing code in fullscreen in the Code node', () => {
|
||||
workflowPage.actions.addInitialNodeToCanvas('Code', { keepNdvOpen: true });
|
||||
ndv.actions.openCodeEditorFullscreen();
|
||||
|
||||
ndv.getters.codeEditorFullscreen().type('{selectall}').type('{backspace}').type('foo()');
|
||||
ndv.getters.codeEditorDialog().find('.el-dialog__close').click();
|
||||
ndv.getters.parameterInput('jsCode').get('.cm-content').should('contain.text', 'foo()');
|
||||
});
|
||||
|
||||
it('should not retrieve remote options when a parameter value changes', () => {
|
||||
cy.intercept('/rest/dynamic-node-parameters/options?**', cy.spy().as('fetchParameterOptions'));
|
||||
workflowPage.actions.addInitialNodeToCanvas('E2e Test', { action: 'Remote Options' });
|
||||
@@ -370,106 +379,126 @@ describe('NDV', () => {
|
||||
});
|
||||
|
||||
describe('floating nodes', () => {
|
||||
function getFloatingNodeByPosition(position: 'inputMain' | 'outputMain' | 'outputSub'| 'inputSub') {
|
||||
function getFloatingNodeByPosition(
|
||||
position: 'inputMain' | 'outputMain' | 'outputSub' | 'inputSub',
|
||||
) {
|
||||
return cy.get(`[data-node-placement=${position}]`);
|
||||
}
|
||||
beforeEach(() => {
|
||||
cy.createFixtureWorkflow('Floating_Nodes.json', `Floating Nodes`);
|
||||
workflowPage.getters.canvasNodes().first().dblclick()
|
||||
getFloatingNodeByPosition("inputMain").should('not.exist');
|
||||
getFloatingNodeByPosition("outputMain").should('exist');
|
||||
workflowPage.getters.canvasNodes().first().dblclick();
|
||||
getFloatingNodeByPosition('inputMain').should('not.exist');
|
||||
getFloatingNodeByPosition('outputMain').should('exist');
|
||||
});
|
||||
|
||||
it('should traverse floating nodes with mouse', () => {
|
||||
// Traverse 4 connected node forwards
|
||||
Array.from(Array(4).keys()).forEach(i => {
|
||||
getFloatingNodeByPosition("outputMain").click({ force: true});
|
||||
Array.from(Array(4).keys()).forEach((i) => {
|
||||
getFloatingNodeByPosition('outputMain').click({ force: true });
|
||||
ndv.getters.nodeNameContainer().should('contain', `Node ${i + 1}`);
|
||||
getFloatingNodeByPosition("inputMain").should('exist');
|
||||
getFloatingNodeByPosition("outputMain").should('exist');
|
||||
getFloatingNodeByPosition('inputMain').should('exist');
|
||||
getFloatingNodeByPosition('outputMain').should('exist');
|
||||
ndv.actions.close();
|
||||
workflowPage.getters.selectedNodes().should('have.length', 1);
|
||||
workflowPage.getters.selectedNodes().first().should('contain', `Node ${i + 1}`);
|
||||
workflowPage.getters
|
||||
.selectedNodes()
|
||||
.first()
|
||||
.should('contain', `Node ${i + 1}`);
|
||||
workflowPage.getters.selectedNodes().first().dblclick();
|
||||
})
|
||||
});
|
||||
|
||||
getFloatingNodeByPosition("outputMain").click({ force: true});
|
||||
getFloatingNodeByPosition('outputMain').click({ force: true });
|
||||
ndv.getters.nodeNameContainer().should('contain', 'Chain');
|
||||
getFloatingNodeByPosition("inputSub").should('exist');
|
||||
getFloatingNodeByPosition("inputSub").click({ force: true});
|
||||
getFloatingNodeByPosition('inputSub').should('exist');
|
||||
getFloatingNodeByPosition('inputSub').click({ force: true });
|
||||
ndv.getters.nodeNameContainer().should('contain', 'Model');
|
||||
getFloatingNodeByPosition("inputSub").should('not.exist');
|
||||
getFloatingNodeByPosition("inputMain").should('not.exist');
|
||||
getFloatingNodeByPosition("outputMain").should('not.exist');
|
||||
getFloatingNodeByPosition("outputSub").should('exist');
|
||||
getFloatingNodeByPosition('inputSub').should('not.exist');
|
||||
getFloatingNodeByPosition('inputMain').should('not.exist');
|
||||
getFloatingNodeByPosition('outputMain').should('not.exist');
|
||||
getFloatingNodeByPosition('outputSub').should('exist');
|
||||
ndv.actions.close();
|
||||
workflowPage.getters.selectedNodes().should('have.length', 1);
|
||||
workflowPage.getters.selectedNodes().first().should('contain', 'Model');
|
||||
workflowPage.getters.selectedNodes().first().dblclick();
|
||||
getFloatingNodeByPosition("outputSub").click({ force: true});
|
||||
getFloatingNodeByPosition('outputSub').click({ force: true });
|
||||
ndv.getters.nodeNameContainer().should('contain', 'Chain');
|
||||
|
||||
// Traverse 4 connected node backwards
|
||||
Array.from(Array(4).keys()).forEach(i => {
|
||||
getFloatingNodeByPosition("inputMain").click({ force: true});
|
||||
ndv.getters.nodeNameContainer().should('contain', `Node ${4 - (i)}`);
|
||||
getFloatingNodeByPosition("outputMain").should('exist');
|
||||
getFloatingNodeByPosition("inputMain").should('exist');
|
||||
})
|
||||
getFloatingNodeByPosition("inputMain").click({ force: true});
|
||||
workflowPage.getters.selectedNodes().first().should('contain', MANUAL_TRIGGER_NODE_DISPLAY_NAME);
|
||||
getFloatingNodeByPosition("inputMain").should('not.exist');
|
||||
getFloatingNodeByPosition("inputSub").should('not.exist');
|
||||
getFloatingNodeByPosition("outputSub").should('not.exist');
|
||||
Array.from(Array(4).keys()).forEach((i) => {
|
||||
getFloatingNodeByPosition('inputMain').click({ force: true });
|
||||
ndv.getters.nodeNameContainer().should('contain', `Node ${4 - i}`);
|
||||
getFloatingNodeByPosition('outputMain').should('exist');
|
||||
getFloatingNodeByPosition('inputMain').should('exist');
|
||||
});
|
||||
getFloatingNodeByPosition('inputMain').click({ force: true });
|
||||
workflowPage.getters
|
||||
.selectedNodes()
|
||||
.first()
|
||||
.should('contain', MANUAL_TRIGGER_NODE_DISPLAY_NAME);
|
||||
getFloatingNodeByPosition('inputMain').should('not.exist');
|
||||
getFloatingNodeByPosition('inputSub').should('not.exist');
|
||||
getFloatingNodeByPosition('outputSub').should('not.exist');
|
||||
ndv.actions.close();
|
||||
workflowPage.getters.selectedNodes().should('have.length', 1);
|
||||
workflowPage.getters.selectedNodes().first().should('contain', MANUAL_TRIGGER_NODE_DISPLAY_NAME);
|
||||
workflowPage.getters
|
||||
.selectedNodes()
|
||||
.first()
|
||||
.should('contain', MANUAL_TRIGGER_NODE_DISPLAY_NAME);
|
||||
});
|
||||
|
||||
it('should traverse floating nodes with mouse', () => {
|
||||
// Traverse 4 connected node forwards
|
||||
Array.from(Array(4).keys()).forEach(i => {
|
||||
cy.realPress(['ShiftLeft', 'Meta', 'AltLeft', 'ArrowRight'])
|
||||
Array.from(Array(4).keys()).forEach((i) => {
|
||||
cy.realPress(['ShiftLeft', 'Meta', 'AltLeft', 'ArrowRight']);
|
||||
ndv.getters.nodeNameContainer().should('contain', `Node ${i + 1}`);
|
||||
getFloatingNodeByPosition("inputMain").should('exist');
|
||||
getFloatingNodeByPosition("outputMain").should('exist');
|
||||
getFloatingNodeByPosition('inputMain').should('exist');
|
||||
getFloatingNodeByPosition('outputMain').should('exist');
|
||||
ndv.actions.close();
|
||||
workflowPage.getters.selectedNodes().should('have.length', 1);
|
||||
workflowPage.getters.selectedNodes().first().should('contain', `Node ${i + 1}`);
|
||||
workflowPage.getters
|
||||
.selectedNodes()
|
||||
.first()
|
||||
.should('contain', `Node ${i + 1}`);
|
||||
workflowPage.getters.selectedNodes().first().dblclick();
|
||||
})
|
||||
});
|
||||
|
||||
cy.realPress(['ShiftLeft', 'Meta', 'AltLeft', 'ArrowRight'])
|
||||
cy.realPress(['ShiftLeft', 'Meta', 'AltLeft', 'ArrowRight']);
|
||||
ndv.getters.nodeNameContainer().should('contain', 'Chain');
|
||||
getFloatingNodeByPosition("inputSub").should('exist');
|
||||
cy.realPress(['ShiftLeft', 'Meta', 'AltLeft', 'ArrowDown'])
|
||||
getFloatingNodeByPosition('inputSub').should('exist');
|
||||
cy.realPress(['ShiftLeft', 'Meta', 'AltLeft', 'ArrowDown']);
|
||||
ndv.getters.nodeNameContainer().should('contain', 'Model');
|
||||
getFloatingNodeByPosition("inputSub").should('not.exist');
|
||||
getFloatingNodeByPosition("inputMain").should('not.exist');
|
||||
getFloatingNodeByPosition("outputMain").should('not.exist');
|
||||
getFloatingNodeByPosition("outputSub").should('exist');
|
||||
getFloatingNodeByPosition('inputSub').should('not.exist');
|
||||
getFloatingNodeByPosition('inputMain').should('not.exist');
|
||||
getFloatingNodeByPosition('outputMain').should('not.exist');
|
||||
getFloatingNodeByPosition('outputSub').should('exist');
|
||||
ndv.actions.close();
|
||||
workflowPage.getters.selectedNodes().should('have.length', 1);
|
||||
workflowPage.getters.selectedNodes().first().should('contain', 'Model');
|
||||
workflowPage.getters.selectedNodes().first().dblclick();
|
||||
cy.realPress(['ShiftLeft', 'Meta', 'AltLeft', 'ArrowUp'])
|
||||
cy.realPress(['ShiftLeft', 'Meta', 'AltLeft', 'ArrowUp']);
|
||||
ndv.getters.nodeNameContainer().should('contain', 'Chain');
|
||||
|
||||
// Traverse 4 connected node backwards
|
||||
Array.from(Array(4).keys()).forEach(i => {
|
||||
cy.realPress(['ShiftLeft', 'Meta', 'AltLeft', 'ArrowLeft'])
|
||||
ndv.getters.nodeNameContainer().should('contain', `Node ${4 - (i)}`);
|
||||
getFloatingNodeByPosition("outputMain").should('exist');
|
||||
getFloatingNodeByPosition("inputMain").should('exist');
|
||||
})
|
||||
cy.realPress(['ShiftLeft', 'Meta', 'AltLeft', 'ArrowLeft'])
|
||||
workflowPage.getters.selectedNodes().first().should('contain', MANUAL_TRIGGER_NODE_DISPLAY_NAME);
|
||||
getFloatingNodeByPosition("inputMain").should('not.exist');
|
||||
getFloatingNodeByPosition("inputSub").should('not.exist');
|
||||
getFloatingNodeByPosition("outputSub").should('not.exist');
|
||||
Array.from(Array(4).keys()).forEach((i) => {
|
||||
cy.realPress(['ShiftLeft', 'Meta', 'AltLeft', 'ArrowLeft']);
|
||||
ndv.getters.nodeNameContainer().should('contain', `Node ${4 - i}`);
|
||||
getFloatingNodeByPosition('outputMain').should('exist');
|
||||
getFloatingNodeByPosition('inputMain').should('exist');
|
||||
});
|
||||
cy.realPress(['ShiftLeft', 'Meta', 'AltLeft', 'ArrowLeft']);
|
||||
workflowPage.getters
|
||||
.selectedNodes()
|
||||
.first()
|
||||
.should('contain', MANUAL_TRIGGER_NODE_DISPLAY_NAME);
|
||||
getFloatingNodeByPosition('inputMain').should('not.exist');
|
||||
getFloatingNodeByPosition('inputSub').should('not.exist');
|
||||
getFloatingNodeByPosition('outputSub').should('not.exist');
|
||||
ndv.actions.close();
|
||||
workflowPage.getters.selectedNodes().should('have.length', 1);
|
||||
workflowPage.getters.selectedNodes().first().should('contain', MANUAL_TRIGGER_NODE_DISPLAY_NAME);
|
||||
workflowPage.getters
|
||||
.selectedNodes()
|
||||
.first()
|
||||
.should('contain', MANUAL_TRIGGER_NODE_DISPLAY_NAME);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -501,23 +530,34 @@ describe('NDV', () => {
|
||||
|
||||
ndv.getters.outputDisplayMode().find('[class*=active]').should('contain', 'Table');
|
||||
|
||||
ndv.getters.outputTableRow(1).should('include.text', '<?xml version="1.0" encoding="UTF-8"?> <library>');
|
||||
ndv.getters
|
||||
.outputTableRow(1)
|
||||
.should('include.text', '<?xml version="1.0" encoding="UTF-8"?> <library>');
|
||||
|
||||
cy.document().trigger('keyup', { key: '/' });
|
||||
ndv.getters.searchInput().filter(':focus').type('<lib');
|
||||
|
||||
ndv.getters.outputTableRow(1).find('mark').should('have.text', '<lib')
|
||||
ndv.getters.outputTableRow(1).find('mark').should('have.text', '<lib');
|
||||
|
||||
ndv.getters.outputDisplayMode().find('label').eq(1).should('include.text', 'JSON');
|
||||
ndv.getters.outputDisplayMode().find('label').eq(1).click();
|
||||
|
||||
ndv.getters.outputDataContainer().find('.json-data').should('be.visible');
|
||||
ndv.getters.outputDataContainer().should('have.text', '[{"body": "<?xml version="1.0" encoding="UTF-8"?> <library> <book> <title>Introduction to XML</title> <author>John Doe</author> <publication_year>2020</publication_year> <isbn>1234567890</isbn> </book> <book> <title>Data Science Basics</title> <author>Jane Smith</author> <publication_year>2019</publication_year> <isbn>0987654321</isbn> </book> <book> <title>Programming in Python</title> <author>Bob Johnson</author> <publication_year>2021</publication_year> <isbn>5432109876</isbn> </book> </library>"}]');
|
||||
ndv.getters.outputDataContainer().find('mark').should('have.text', '<lib')
|
||||
ndv.getters.outputDataContainer().find('.json-data').should('exist');
|
||||
ndv.getters
|
||||
.outputDataContainer()
|
||||
.should(
|
||||
'have.text',
|
||||
'[{"body": "<?xml version="1.0" encoding="UTF-8"?> <library> <book> <title>Introduction to XML</title> <author>John Doe</author> <publication_year>2020</publication_year> <isbn>1234567890</isbn> </book> <book> <title>Data Science Basics</title> <author>Jane Smith</author> <publication_year>2019</publication_year> <isbn>0987654321</isbn> </book> <book> <title>Programming in Python</title> <author>Bob Johnson</author> <publication_year>2021</publication_year> <isbn>5432109876</isbn> </book> </library>"}]',
|
||||
);
|
||||
ndv.getters.outputDataContainer().find('mark').should('have.text', '<lib');
|
||||
|
||||
ndv.getters.outputDisplayMode().find('label').eq(2).should('include.text', 'Schema');
|
||||
ndv.getters.outputDisplayMode().find('label').eq(2).click({force: true});
|
||||
ndv.getters.outputDataContainer().findChildByTestId('run-data-schema-item').find('> span').should('include.text', '<?xml version="1.0" encoding="UTF-8"?>');
|
||||
ndv.getters.outputDisplayMode().find('label').eq(2).click({ force: true });
|
||||
ndv.getters
|
||||
.outputDataContainer()
|
||||
.findChildByTestId('run-data-schema-item')
|
||||
.find('> span')
|
||||
.should('include.text', '<?xml version="1.0" encoding="UTF-8"?>');
|
||||
});
|
||||
|
||||
it('should properly show node execution indicator', () => {
|
||||
@@ -546,7 +586,10 @@ describe('NDV', () => {
|
||||
});
|
||||
|
||||
it('Should handle mismatched option attributes', () => {
|
||||
workflowPage.actions.addInitialNodeToCanvas('LDAP', { keepNdvOpen: true, action: 'Create a new entry' });
|
||||
workflowPage.actions.addInitialNodeToCanvas('LDAP', {
|
||||
keepNdvOpen: true,
|
||||
action: 'Create a new entry',
|
||||
});
|
||||
// Add some attributes in Create operation
|
||||
cy.getByTestId('parameter-item').contains('Add Attributes').click();
|
||||
ndv.actions.changeNodeOperation('Update');
|
||||
@@ -556,7 +599,10 @@ describe('NDV', () => {
|
||||
|
||||
it('Should keep RLC values after operation change', () => {
|
||||
const TEST_DOC_ID = '1111';
|
||||
workflowPage.actions.addInitialNodeToCanvas('Google Sheets', { keepNdvOpen: true, action: 'Append row in sheet' });
|
||||
workflowPage.actions.addInitialNodeToCanvas('Google Sheets', {
|
||||
keepNdvOpen: true,
|
||||
action: 'Append row in sheet',
|
||||
});
|
||||
ndv.actions.setRLCValue('documentId', TEST_DOC_ID);
|
||||
ndv.actions.changeNodeOperation('Update Row');
|
||||
ndv.getters.resourceLocatorInput('documentId').find('input').should('have.value', TEST_DOC_ID);
|
||||
|
||||
@@ -98,6 +98,9 @@ export class NDV extends BasePage {
|
||||
pagination: () => cy.getByTestId('ndv-data-pagination'),
|
||||
nodeVersion: () => cy.getByTestId('node-version'),
|
||||
nodeSettingsTab: () => cy.getByTestId('tab-settings'),
|
||||
codeEditorFullscreenButton: () => cy.getByTestId('code-editor-fullscreen-button'),
|
||||
codeEditorDialog: () => cy.getByTestId('code-editor-fullscreen'),
|
||||
codeEditorFullscreen: () => this.getters.codeEditorDialog().find('.cm-content'),
|
||||
nodeRunSuccessIndicator: () => cy.getByTestId('node-run-info-success'),
|
||||
nodeRunErrorIndicator: () => cy.getByTestId('node-run-info-danger'),
|
||||
};
|
||||
@@ -251,9 +254,15 @@ export class NDV extends BasePage {
|
||||
openSettings: () => {
|
||||
this.getters.nodeSettingsTab().click();
|
||||
},
|
||||
|
||||
openCodeEditorFullscreen: () => {
|
||||
this.getters.codeEditorFullscreenButton().click({ force: true });
|
||||
},
|
||||
changeNodeOperation: (operation: string) => {
|
||||
this.getters.parameterInput('operation').click();
|
||||
cy.get('.el-select-dropdown__item').contains(new RegExp(`^${operation}$`)).click({ force: true });
|
||||
cy.get('.el-select-dropdown__item')
|
||||
.contains(new RegExp(`^${operation}$`))
|
||||
.click({ force: true });
|
||||
this.getters.parameterInput('operation').find('input').should('have.value', operation);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -160,6 +160,7 @@ Cypress.Commands.add('draganddrop', (draggableSelector, droppableSelector) => {
|
||||
cy.get(draggableSelector).trigger('mousedown');
|
||||
}
|
||||
// We don't chain these commands to make sure cy.get is re-trying correctly
|
||||
cy.get(droppableSelector).realMouseMove(0, 0);
|
||||
cy.get(droppableSelector).realMouseMove(pageX, pageY);
|
||||
cy.get(droppableSelector).realHover();
|
||||
cy.get(droppableSelector).realMouseUp();
|
||||
|
||||
Reference in New Issue
Block a user