Skip to content

Commit

Permalink
fix(n8n Form Node): Resolve expressions in HTML fields (#13755)
Browse files Browse the repository at this point in the history
  • Loading branch information
michael-radency authored Mar 11, 2025
1 parent 4fe2495 commit de23ae5
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 32 deletions.
10 changes: 1 addition & 9 deletions packages/nodes-base/nodes/Form/Form.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
8 changes: 1 addition & 7 deletions packages/nodes-base/nodes/Form/formNodeUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down
20 changes: 15 additions & 5 deletions packages/nodes-base/nodes/Form/test/formNodeUtils.test.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -9,10 +10,19 @@ import {
import { renderFormNode } from '../formNodeUtils';

describe('formNodeUtils', () => {
let webhookFunctions: MockProxy<IWebhookFunctions>;

beforeEach(() => {
webhookFunctions = mock<IWebhookFunctions>();
});

afterEach(() => {
jest.clearAllMocks();
});

it('should sanitize custom html', async () => {
const executeFunctions = mock<IWebhookFunctions>();
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',
Expand Down Expand Up @@ -47,12 +57,12 @@ describe('formNodeUtils', () => {
},
];

executeFunctions.getNodeParameter.calledWith('formFields.values').mockReturnValue(formFields);
webhookFunctions.getNodeParameter.calledWith('formFields.values').mockReturnValue(formFields);

const responseMock = mock<Response>({ render: mockRender } as any);
const triggerMock = mock<NodeTypeAndVersion>({ 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,
Expand Down
37 changes: 37 additions & 0 deletions packages/nodes-base/nodes/Form/test/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
isFormConnected,
sanitizeHtml,
validateResponseModeConfiguration,
prepareFormFields,
} from '../utils';

describe('FormTrigger, parseFormDescription', () => {
Expand Down Expand Up @@ -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: '<h1>{{ $json.formMode }}</h1>',
},
]);

expect(result[0].html).toBe('<h1>Title</h1>');
});
it('should prepare hiddenField', async () => {
const result = prepareFormFields(webhookFunctions, [
{
fieldLabel: '',
fieldName: 'test',
fieldType: 'hiddenField',
},
]);

expect(result[0]).toEqual({
fieldLabel: 'test',
fieldName: 'test',
fieldType: 'hiddenField',
});
});
});
});
37 changes: 26 additions & 11 deletions packages/nodes-base/nodes/Form/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -411,6 +433,8 @@ export function renderForm({
} catch (error) {}
}

formFields = prepareFormFields(context, formFields);

const data = prepareFormData({
formTitle,
formDescription,
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit de23ae5

Please sign in to comment.