From 0c2cc2af8a1964d91a640502c3c51aaeae3c671f Mon Sep 17 00:00:00 2001 From: Naz Date: Wed, 4 May 2022 15:08:35 +0800 Subject: [PATCH] Refactored email utils to a class with DI params refs https://github.com/TryGhost/Toolbox/issues/292 - There's a need to reuse these utils in the version mismatch notification service. Having loads of tightly coupled dependencies makes it super hard to rip out this module for reuse - It's a groundwork for extraction of the email-utils package - Rewrote the unit tests that were written for these utils previously - they weren't testing anything useful. The goal of this util is to generate specific content based on provided data and available templates - now the tests do test those specific things, not the mailer itself! --- .../lib/email-content-generator.js | 55 ++++++++++++++ .../test/email-content-generator.test.js | 73 +++++++++---------- 2 files changed, 89 insertions(+), 39 deletions(-) create mode 100644 ghost/email-content-generator/lib/email-content-generator.js diff --git a/ghost/email-content-generator/lib/email-content-generator.js b/ghost/email-content-generator/lib/email-content-generator.js new file mode 100644 index 0000000000..a9e6218a52 --- /dev/null +++ b/ghost/email-content-generator/lib/email-content-generator.js @@ -0,0 +1,55 @@ +const _ = require('lodash').runInContext(); +const fs = require('fs-extra'); +const path = require('path'); +const htmlToText = require('html-to-text'); + +_.templateSettings.interpolate = /{{([\s\S]+?)}}/g; + +class EmailContentGenerator { + /** + * + * @param {Object} options + * @param {function} options.getSiteUrl + * @param {function} options.getSiteTitle + * @param {string} options.templatesDir - path to the directory containing email templates + */ + constructor({getSiteUrl, getSiteTitle, templatesDir}) { + this.getSiteUrl = getSiteUrl; + this.getSiteTitle = getSiteTitle; + this.templatesDir = templatesDir; + } + + /** + * + * @param {Object} options + * @param {string} options.template - HTML template name to use for generation + * @param {Object} [options.data] - variable data to use during HTML template compilation + * @returns {Promise} resolves with an object containing html and text properties + */ + async getContent(options) { + const defaults = { + siteUrl: this.getSiteUrl(), + siteTitle: this.getSiteTitle() + }; + + const data = _.defaults(defaults, options.data); + + // read the proper email body template + return fs.readFile(path.join(this.templatesDir, options.template + '.html'), 'utf8') + .then(function (content) { + // insert user-specific data into the email + const compiled = _.template(content); + const htmlContent = compiled(data); + + // generate a plain-text version of the same email + const textContent = htmlToText.fromString(htmlContent); + + return { + html: htmlContent, + text: textContent + }; + }); + } +} + +module.exports = EmailContentGenerator; diff --git a/ghost/email-content-generator/test/email-content-generator.test.js b/ghost/email-content-generator/test/email-content-generator.test.js index 4c02b56149..852dd39f0a 100644 --- a/ghost/email-content-generator/test/email-content-generator.test.js +++ b/ghost/email-content-generator/test/email-content-generator.test.js @@ -1,41 +1,38 @@ -const should = require('should'); -const sinon = require('sinon'); -const mail = require('../../../../../core/server/services/mail'); -const configUtils = require('../../../../utils/configUtils'); +const assert = require('assert'); +const path = require('path'); -describe('Mail: Utils', function () { - const scope = {ghostMailer: null}; +const EmailContentGenerator = require('../../../../../core/server/services/mail/EmailContentGenerator'); - beforeEach(function () { - configUtils.set({mail: {transport: 'stub'}}); - scope.ghostMailer = new mail.GhostMailer(); - }); +describe('Mail: EmailContentGenerator', function () { + it('generate welcome', async function () { + const emailContentGenerator = new EmailContentGenerator({ + getSiteTitle: () => 'The Ghost Blog', + getSiteUrl: () => 'http://myblog.com', + templatesDir: path.resolve(__dirname, '../../../../../core/server/services/mail/templates/') + }); - afterEach(function () { - sinon.restore(); - configUtils.restore(); - }); - - it('generate welcome', function (done) { - mail.utils.generateContent({ + const content = await emailContentGenerator.getContent({ template: 'welcome', data: { ownerEmail: 'test@example.com' } - }).then(function (result) { - return scope.ghostMailer.send({ - to: 'test@example.com', - subject: 'lol', - html: result.html, - text: result.text - }); - }).then(function () { - done(); - }).catch(done); + }); + + assert.match(content.html, /Welcome to Ghost<\/title>/); + assert.match(content.html, /This email was sent from <a href="http:\/\/myblog.com" style="color: #738A94;">http:\/\/myblog.com<\/a> to <a href="mailto:test@example.com" style="color: #738A94;">test@example.com<\/a><\/p>/); + + assert.match(content.text, /Email Address: test@example.com \[test@example.com\]/); + assert.match(content.text, /This email was sent from http:\/\/myblog.com/); }); - it('generates newsletter template', function (done) { - mail.utils.generateContent({ + it('generates newsletter template', async function () { + const emailContentGenerator = new EmailContentGenerator({ + getSiteTitle: () => 'The Ghost Blog', + getSiteUrl: () => 'http://myblog.com', + templatesDir: path.resolve(__dirname, '../../../../../core/server/services/mail/templates/') + }); + + const content = await emailContentGenerator.getContent({ template: 'newsletter', data: { blog: { @@ -93,15 +90,13 @@ describe('Mail: Utils', function () { date: 'june, 9th 2016' } } - }).then(function (result) { - return scope.ghostMailer.send({ - to: 'jbloggs@example.com', - subject: 'The Newsletter Blog', - html: result.html, - text: result.text - }); - }).then(function () { - done(); - }).catch(done); + }); + + assert.match(content.html, /<title>The Ghost Blog<\/title>/); + assert.match(content.html, /<span style="text-transform:capitalize">monthly<\/span> digest/); + assert.match(content.html, /<span style="text-transform:capitalize">june, 9th 2016<\/span><\/h3>/); + + assert.match(content.text, /MONTHLY DIGEST — JUNE, 9TH 2016/); + assert.match(content.text, /SECOND BLOG POST \[HTTP:\/\/MYBLOG.COM\/SECOND-BLOG-POST\]/); }); });