Refactor email template rendering to support preview mode and improve CSS handling
All checks were successful
farmcontrol/farmcontrol-api/pipeline/head This commit looks good

This commit updates the email template rendering logic to conditionally apply a preview wrapper and associated CSS when the preview flag is set. It modifies the `renderEmailTemplate` and `renderTemplate` methods to ensure that the email content is correctly wrapped in a preview structure, while also ensuring that scaling is consistently applied. Additionally, tests are added to verify the correct behavior of the rendering process in both preview and non-preview scenarios, enhancing the overall functionality of the TemplateManager.
This commit is contained in:
Tom Butcher 2026-09-12 23:48:03 +01:00
parent c10281064e
commit 58cb444bf2
2 changed files with 92 additions and 14 deletions

View File

@ -203,11 +203,44 @@ describe('TemplateManager', () => {
); );
expect(result.subject).toBe('rendered: Hello'); expect(result.subject).toBe('rendered: Hello');
expect(result.html).toContain('max-width:640px'); expect(result.html).toContain('width: 640px');
expect(result.html).toContain('transformed: rendered: <Text>Body</Text>'); expect(result.html).toContain('class="preview"');
expect(ejs.render).toHaveBeenCalledWith(
'<div class="preview"><%- content %></div>',
expect.objectContaining({
content: 'transformed: rendered: <Text>Body</Text>',
}),
expect.objectContaining({ async: true })
);
expect(result).not.toHaveProperty('height'); expect(result).not.toHaveProperty('height');
expect(result).not.toHaveProperty('intellisense'); expect(result).not.toHaveProperty('intellisense');
}); });
it('does not wrap sent email HTML in the preview template', async () => {
getObject.mockResolvedValue({
_reference: 'EMAIL1',
active: true,
global: true,
subject: 'Hello',
content: '<Text>Body</Text>',
});
const result = await templateManager.renderEmailTemplate(
'email-template-id',
undefined,
undefined,
{ name: 'Test' },
{},
false
);
expect(result.html).toContain('transformed: rendered: <Text>Body</Text>');
expect(ejs.render).not.toHaveBeenCalledWith(
'<div class="preview"><%- content %></div>',
expect.anything(),
expect.anything()
);
});
}); });
describe('renderTemplate', () => { describe('renderTemplate', () => {
@ -230,6 +263,40 @@ describe('TemplateManager', () => {
expect(result.width).toBe(100); expect(result.width).toBe(100);
expect(result.height).toBe(100); expect(result.height).toBe(100);
expect(result).not.toHaveProperty('intellisense'); expect(result).not.toHaveProperty('intellisense');
expect(ejs.render).toHaveBeenCalledWith(
'<html><%- content %></html>',
expect.objectContaining({
scale: '1',
scaledWidth: '100mm',
scaledPageWidth: '100mm',
}),
expect.anything()
);
});
it('does not apply zoom in preview HTML even when a scale is requested', async () => {
getObject.mockResolvedValue({
documentSize: { width: 100, height: 100, infiniteHeight: false },
global: false,
objectType: 'printer',
});
await templateManager.renderTemplate(
'temp-id',
'some content',
{ name: 'Test' },
2.5
);
expect(ejs.render).toHaveBeenCalledWith(
'<html><%- content %></html>',
expect.objectContaining({
scale: '1',
scaledWidth: '100mm',
scaledHeight: '100mm',
}),
expect.anything()
);
}); });
it('does not return intellisense when preview render throws', async () => { it('does not return intellisense when preview render throws', async () => {

View File

@ -786,18 +786,29 @@ export class TemplateManager {
pixelId != null pixelId != null
? `<img src="${config.app.urlApi}/emailmessages/${pixelId}/pixel" width="1" height="1" style="display:none" />` ? `<img src="${config.app.urlApi}/emailmessages/${pixelId}/pixel" width="1" height="1" style="display:none" />`
: ''; : '';
const emailContent = let emailContent = transformed;
preview === true let previewCss = '';
? `<div style="max-width:${width}px;margin:0 auto">${transformed}</div>` if (preview === true) {
: transformed; emailContent = await ejs.render(previewTemplate, { content: transformed }, renderOptions);
previewCss = `
.previewWrapper {
width: ${width}px;
height: auto;
}
.previewDocument {
width: ${width}px;
height: auto;
overflow: visible;
}`;
}
const outerHtml = (css, content, pixel = undefined) => { const outerHtml = (css, content, pixel = undefined) => {
return `<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><style>${css}</style></head><body>${pixel != undefined ? pixel : ''}${content}</body></html>`; return `<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><style>${css}</style></head><body>${pixel != undefined ? pixel : ''}${content}</body></html>`;
}; };
return { return {
subject: renderedSubject, subject: renderedSubject,
html: outerHtml(baseCSS, emailContent), html: outerHtml(`${baseCSS}${previewCss}`, emailContent),
htmlWithPixel: outerHtml(baseCSS, emailContent, renderedPixel), htmlWithPixel: outerHtml(`${baseCSS}${previewCss}`, emailContent, renderedPixel),
}; };
} catch (error) { } catch (error) {
logger.warn('Error whilst rendering email template:', error.message); logger.warn('Error whilst rendering email template:', error.message);
@ -841,7 +852,7 @@ export class TemplateManager {
return object; return object;
} }
async renderTemplate(id, content, data = {}, scale = 1, options = {}, preview = true) { async renderTemplate(id, content, data = {}, _scale = 1, options = {}, preview = true) {
try { try {
const { const {
padding: paddingRequested = false, padding: paddingRequested = false,
@ -994,15 +1005,15 @@ export class TemplateManager {
content: innerHtml, content: innerHtml,
width: `${documentSize.width}mm`, width: `${documentSize.width}mm`,
height: infiniteHeight ? 'fit-content' : `${documentSize.height}mm`, height: infiniteHeight ? 'fit-content' : `${documentSize.height}mm`,
scaledWidth: `${documentSize.width * scale}mm`, scaledWidth: `${documentSize.width}mm`,
scaledHeight: infiniteHeight ? 'auto' : `${documentSize.height * scale}mm`, scaledHeight: infiniteHeight ? 'auto' : `${documentSize.height}mm`,
scaledPageWidth: `${pageWidth * scale}mm`, scaledPageWidth: `${pageWidth}mm`,
scaledPageHeight: infiniteHeight ? 'auto' : `${pageHeight * scale}mm`, scaledPageHeight: infiniteHeight ? 'auto' : `${pageHeight}mm`,
paddingTop: `${padding.paddingTop}mm`, paddingTop: `${padding.paddingTop}mm`,
paddingRight: `${padding.paddingRight}mm`, paddingRight: `${padding.paddingRight}mm`,
paddingBottom: `${padding.paddingBottom}mm`, paddingBottom: `${padding.paddingBottom}mm`,
paddingLeft: `${padding.paddingLeft}mm`, paddingLeft: `${padding.paddingLeft}mm`,
scale: `${scale}`, scale: '1',
baseCSS: baseCSS, baseCSS: baseCSS,
infiniteHeight: infiniteHeight, infiniteHeight: infiniteHeight,
pageWidth: `${pageWidth}mm`, pageWidth: `${pageWidth}mm`,