From d8865aa917c7b09f0734d95d978fe3db909e85ea Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= <ivov.src@gmail.com>
Date: Wed, 8 Feb 2023 10:41:36 +0100
Subject: [PATCH] test: Add e2e for data transformation expressions (#5375)

* :test_tube: Add e2e for data transormation expressions

* :recycle: Apply feedback
---
 .../14-data-transformation-expressions.cy.ts  | 120 ++++++++++++++++++
 cypress/pages/ndv.ts                          |   6 +
 cypress/pages/workflow.ts                     |   3 +-
 3 files changed, 128 insertions(+), 1 deletion(-)
 create mode 100644 cypress/e2e/14-data-transformation-expressions.cy.ts

diff --git a/cypress/e2e/14-data-transformation-expressions.cy.ts b/cypress/e2e/14-data-transformation-expressions.cy.ts
new file mode 100644
index 0000000000000..ab59eef826ece
--- /dev/null
+++ b/cypress/e2e/14-data-transformation-expressions.cy.ts
@@ -0,0 +1,120 @@
+import { WorkflowPage, NDV } from '../pages';
+
+const wf = new WorkflowPage();
+const ndv = new NDV();
+
+describe('Data transformation expressions', () => {
+	before(() => {
+		cy.resetAll();
+		cy.skipSetup();
+		cy.waitForLoad();
+	});
+
+	it('$json + native string methods', () => {
+		wf.actions.visit();
+
+		wf.actions.addInitialNodeToCanvas('Schedule Trigger', { keepNdvOpen: true });
+		ndv.actions.setPinnedData([{ myStr: 'Monday' }]);
+		ndv.actions.close();
+		addSet();
+
+		const input = '{{$json.myStr.toLowerCase() + " is " + "today".toUpperCase()';
+		const output = 'monday is TODAY';
+
+		ndv.getters.inlineExpressionEditorInput().clear().type(input);
+		ndv.actions.execute();
+		ndv.getters.outputDataContainer().contains(output).should('be.visible');
+	});
+
+	it('$json + n8n string methods', () => {
+		wf.actions.visit();
+
+		wf.actions.addInitialNodeToCanvas('Schedule Trigger', { keepNdvOpen: true });
+		ndv.actions.setPinnedData([{ myStr: 'hello@n8n.io is an email' }]);
+		ndv.actions.close();
+		addSet();
+
+		const input = '{{$json.myStr.extractEmail() + " " + $json.myStr.isEmpty()';
+		const output = 'hello@n8n.io false';
+
+		ndv.getters.inlineExpressionEditorInput().clear().type(input);
+		ndv.actions.execute();
+		ndv.getters.outputDataContainer().contains(output).should('be.visible');
+	});
+
+	it('$json + native numeric methods', () => {
+		wf.actions.visit();
+
+		wf.actions.addInitialNodeToCanvas('Schedule Trigger', { keepNdvOpen: true });
+		ndv.actions.setPinnedData([{ myNum: 9.123 }]);
+		ndv.actions.close();
+		addSet();
+
+		const input = '{{$json.myNum.toPrecision(3)';
+		const output = '9.12';
+
+		ndv.getters.inlineExpressionEditorInput().clear().type(input);
+		ndv.actions.execute();
+		ndv.getters.outputDataContainer().contains(output).should('be.visible');
+	});
+
+	it('$json + n8n numeric methods', () => {
+		wf.actions.visit();
+
+		wf.actions.addInitialNodeToCanvas('Schedule Trigger', { keepNdvOpen: true });
+		ndv.actions.setPinnedData([{ myStr: 'hello@n8n.io is an email' }]);
+		ndv.actions.close();
+		addSet();
+
+		const input = '{{$json.myStr.extractEmail() + " " + $json.myStr.isEmpty()';
+		const output = 'hello@n8n.io false';
+
+		ndv.getters.inlineExpressionEditorInput().clear().type(input);
+		ndv.actions.execute();
+		ndv.getters.outputDataContainer().contains(output).should('be.visible');
+	});
+
+	it('$json + native array methods', () => {
+		wf.actions.visit();
+
+		wf.actions.addInitialNodeToCanvas('Schedule Trigger', { keepNdvOpen: true });
+		ndv.actions.setPinnedData([{ myArr: [1, 2, 3] }]);
+		ndv.actions.close();
+		addSet();
+
+		const input = '{{$json.myArr.includes(1) + " " + $json.myArr.at(2)';
+		const output = 'true 3';
+
+		ndv.getters.inlineExpressionEditorInput().clear().type(input);
+		ndv.actions.execute();
+		ndv.getters.outputDataContainer().contains(output).should('be.visible');
+	});
+
+	it('$json + n8n array methods', () => {
+		wf.actions.visit();
+
+		wf.actions.addInitialNodeToCanvas('Schedule Trigger', { keepNdvOpen: true });
+		ndv.actions.setPinnedData([{ myArr: [1, 2, 3] }]);
+		ndv.actions.close();
+		addSet();
+
+		const input = '{{$json.myArr.first() + " " + $json.myArr.last()';
+		const output = '1 3';
+
+		ndv.getters.inlineExpressionEditorInput().clear().type(input);
+		ndv.actions.execute();
+		ndv.getters.outputDataContainer().contains(output).should('be.visible');
+	});
+});
+
+// ----------------------------------
+//             utils
+// ----------------------------------
+
+const addSet = () => {
+	wf.actions.addNodeToCanvas('Set', true, true);
+	ndv.getters.parameterInput('keepOnlySet').find('div[role=switch]').click(); // shorten output
+	cy.get('input[placeholder="Add Value"]').click();
+	cy.get('span').contains('String').click();
+	ndv.getters.nthParam(3).contains('Expression').invoke('show').click(); // Values to Set > String > Value
+};
diff --git a/cypress/pages/ndv.ts b/cypress/pages/ndv.ts
index 9842c724d07cf..74febf8c00175 100644
--- a/cypress/pages/ndv.ts
+++ b/cypress/pages/ndv.ts
@@ -35,6 +35,8 @@ export class NDV extends BasePage {
 		nodeRenameInput: () => cy.getByTestId('node-rename-input'),
 		executePrevious: () => cy.getByTestId('execute-previous-node'),
 		httpRequestNotice: () => cy.getByTestId('node-parameters-http-notice'),
+		inlineExpressionEditorInput: () => cy.getByTestId('inline-expression-editor-input'),
+		nthParam: (n: number) => cy.getByTestId('node-parameters').find('.parameter-item').eq(n),
 	};
 
 	actions = {
@@ -50,6 +52,10 @@ export class NDV extends BasePage {
 		close: () => {
 			this.getters.backToCanvas().click();
 		},
+		openInlineExpressionEditor: () => {
+			cy.contains('Expression').invoke('show').click();
+			this.getters.inlineExpressionEditorInput().click();
+		},
 		setPinnedData: (data: object) => {
 			this.getters.editPinnedDataButton().click();
 
diff --git a/cypress/pages/workflow.ts b/cypress/pages/workflow.ts
index d1e90e815cf41..e8e8a99489067 100644
--- a/cypress/pages/workflow.ts
+++ b/cypress/pages/workflow.ts
@@ -90,10 +90,11 @@ export class WorkflowPage extends BasePage {
 			cy.visit(this.url);
 			cy.waitForLoad();
 		},
-		addInitialNodeToCanvas: (nodeDisplayName: string) => {
+		addInitialNodeToCanvas: (nodeDisplayName: string, { keepNdvOpen } = { keepNdvOpen: false }) => {
 			this.getters.canvasPlusButton().click();
 			this.getters.nodeCreatorSearchBar().type(nodeDisplayName);
 			this.getters.nodeCreatorSearchBar().type('{enter}');
+			if (keepNdvOpen) return;
 			cy.get('body').type('{esc}');
 		},
 		addNodeToCanvas: (nodeDisplayName: string, plusButtonClick = true, preventNdvClose?: boolean) => {