diff --git a/packages/nodes-base/nodes/Form/Form.node.ts b/packages/nodes-base/nodes/Form/Form.node.ts index f060e3f81500a..5e77b01ca1eb0 100644 --- a/packages/nodes-base/nodes/Form/Form.node.ts +++ b/packages/nodes-base/nodes/Form/Form.node.ts @@ -336,15 +336,7 @@ export class Form extends Node { }); } } else { - fields = (context.getNodeParameter('formFields.values', []) as FormFieldsParameter).map( - (field) => { - if (field.fieldType === 'hiddenField') { - field.fieldLabel = field.fieldName as string; - } - - return field; - }, - ); + fields = context.getNodeParameter('formFields.values', []) as FormFieldsParameter; } const method = context.getRequestObject().method; diff --git a/packages/nodes-base/nodes/Form/formNodeUtils.ts b/packages/nodes-base/nodes/Form/formNodeUtils.ts index ac8e1d5655caa..4d33b1365d56a 100644 --- a/packages/nodes-base/nodes/Form/formNodeUtils.ts +++ b/packages/nodes-base/nodes/Form/formNodeUtils.ts @@ -6,7 +6,7 @@ import { type IWebhookResponseData, } from 'n8n-workflow'; -import { renderForm, sanitizeHtml } from './utils'; +import { renderForm } from './utils'; export const renderFormNode = async ( context: IWebhookFunctions, @@ -42,12 +42,6 @@ export const renderFormNode = async ( ) as string) || 'Submit'; } - for (const field of fields) { - if (field.fieldType === 'html') { - field.html = sanitizeHtml(field.html as string); - } - } - const appendAttribution = context.evaluateExpression( `{{ $('${trigger?.name}').params.options?.appendAttribution === false ? false : true }}`, ) as boolean; diff --git a/packages/nodes-base/nodes/Form/test/formNodeUtils.test.ts b/packages/nodes-base/nodes/Form/test/formNodeUtils.test.ts index a9bbf936f1750..a21426d1b5eff 100644 --- a/packages/nodes-base/nodes/Form/test/formNodeUtils.test.ts +++ b/packages/nodes-base/nodes/Form/test/formNodeUtils.test.ts @@ -1,4 +1,5 @@ import { type Response } from 'express'; +import type { MockProxy } from 'jest-mock-extended'; import { mock } from 'jest-mock-extended'; import { type FormFieldsParameter, @@ -9,10 +10,19 @@ import { import { renderFormNode } from '../formNodeUtils'; describe('formNodeUtils', () => { + let webhookFunctions: MockProxy; + + beforeEach(() => { + webhookFunctions = mock(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + it('should sanitize custom html', async () => { - const executeFunctions = mock(); - executeFunctions.getNode.mockReturnValue({ typeVersion: 2.1 } as any); - executeFunctions.getNodeParameter.calledWith('options').mockReturnValue({ + webhookFunctions.getNode.mockReturnValue({ typeVersion: 2.1 } as any); + webhookFunctions.getNodeParameter.calledWith('options').mockReturnValue({ formTitle: 'Test Title', formDescription: 'Test Description', buttonLabel: 'Test Button Label', @@ -47,12 +57,12 @@ describe('formNodeUtils', () => { }, ]; - executeFunctions.getNodeParameter.calledWith('formFields.values').mockReturnValue(formFields); + webhookFunctions.getNodeParameter.calledWith('formFields.values').mockReturnValue(formFields); const responseMock = mock({ render: mockRender } as any); const triggerMock = mock({ name: 'triggerName' } as any); - await renderFormNode(executeFunctions, responseMock, triggerMock, formFields, 'test'); + await renderFormNode(webhookFunctions, responseMock, triggerMock, formFields, 'test'); expect(mockRender).toHaveBeenCalledWith('form-trigger', { appendAttribution: true, diff --git a/packages/nodes-base/nodes/Form/test/utils.test.ts b/packages/nodes-base/nodes/Form/test/utils.test.ts index 432bce51215e9..66b584e2c7e35 100644 --- a/packages/nodes-base/nodes/Form/test/utils.test.ts +++ b/packages/nodes-base/nodes/Form/test/utils.test.ts @@ -17,6 +17,7 @@ import { isFormConnected, sanitizeHtml, validateResponseModeConfiguration, + prepareFormFields, } from '../utils'; describe('FormTrigger, parseFormDescription', () => { @@ -994,4 +995,40 @@ describe('validateResponseModeConfiguration', () => { expect(() => validateResponseModeConfiguration(webhookFunctions)).not.toThrow(); }); + + describe('prepareFormFields', () => { + it('should resolve expressions in html fields', async () => { + webhookFunctions.evaluateExpression.mockImplementation((expression) => { + if (expression === '{{ $json.formMode }}') { + return 'Title'; + } + }); + + const result = prepareFormFields(webhookFunctions, [ + { + fieldLabel: 'Custom HTML', + fieldType: 'html', + elementName: 'test', + html: '

{{ $json.formMode }}

', + }, + ]); + + expect(result[0].html).toBe('

Title

'); + }); + it('should prepare hiddenField', async () => { + const result = prepareFormFields(webhookFunctions, [ + { + fieldLabel: '', + fieldName: 'test', + fieldType: 'hiddenField', + }, + ]); + + expect(result[0]).toEqual({ + fieldLabel: 'test', + fieldName: 'test', + fieldType: 'hiddenField', + }); + }); + }); }); diff --git a/packages/nodes-base/nodes/Form/utils.ts b/packages/nodes-base/nodes/Form/utils.ts index c8b750e6d1e0d..054da42e83e4e 100644 --- a/packages/nodes-base/nodes/Form/utils.ts +++ b/packages/nodes-base/nodes/Form/utils.ts @@ -72,6 +72,28 @@ export function sanitizeHtml(text: string) { }); } +export const prepareFormFields = (context: IWebhookFunctions, fields: FormFieldsParameter) => { + return fields.map((field) => { + if (field.fieldType === 'html') { + let { html } = field; + + if (!html) return field; + + for (const resolvable of getResolvables(html)) { + html = html.replace(resolvable, context.evaluateExpression(resolvable) as string); + } + + field.html = sanitizeHtml(html as string); + } + + if (field.fieldType === 'hiddenField') { + field.fieldLabel = field.fieldName as string; + } + + return field; + }); +}; + export function sanitizeCustomCss(css: string | undefined): string | undefined { if (!css) return undefined; @@ -411,6 +433,8 @@ export function renderForm({ } catch (error) {} } + formFields = prepareFormFields(context, formFields); + const data = prepareFormData({ formTitle, formDescription, @@ -476,17 +500,8 @@ export async function formWebhook( } const mode = context.getMode() === 'manual' ? 'test' : 'production'; - const formFields = (context.getNodeParameter('formFields.values', []) as FormFieldsParameter).map( - (field) => { - if (field.fieldType === 'html') { - field.html = sanitizeHtml(field.html as string); - } - if (field.fieldType === 'hiddenField') { - field.fieldLabel = field.fieldName as string; - } - return field; - }, - ); + const formFields = context.getNodeParameter('formFields.values', []) as FormFieldsParameter; + const method = context.getRequestObject().method; validateResponseModeConfiguration(context);