From cfc846b24e4a90527f86d711912d5453616fb4cb Mon Sep 17 00:00:00 2001 From: Vadim Kazakov Date: Mon, 6 Mar 2023 13:05:31 -0700 Subject: [PATCH] major code rewrite * rewrite using modern ES syntax * update tests to handle more test cases * add peerDependencies for nodemailer and express-handlebars --- .prettierrc.js | 14 ++ README.md | 18 ++- lib/generator.js | 60 ++++---- lib/index.js | 21 ++- package.json | 9 +- test/tests/index.js | 202 +++++++++++++++----------- test/views/partials/header.handlebars | 1 + test/views/text.handlebars | 2 + test/views/with_partial.handlebars | 2 + yarn.lock | 32 ++-- 10 files changed, 216 insertions(+), 145 deletions(-) create mode 100644 .prettierrc.js create mode 100644 test/views/partials/header.handlebars create mode 100644 test/views/text.handlebars create mode 100644 test/views/with_partial.handlebars diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 0000000..0e1e731 --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,14 @@ +'use strict'; + +module.exports = { + singleQuote: true, + overrides: [ + { + files: '*.hbs', + options: { + singleQuote: false, + parser: 'glimmer', + }, + }, + ], +}; diff --git a/README.md b/README.md index 5000e2a..60b96d9 100644 --- a/README.md +++ b/README.md @@ -9,11 +9,11 @@ npm install nodemailer-express-handlebars # Usage ```javascript //reference the plugin -var hbs = require('nodemailer-express-handlebars'); +const hbs = require('nodemailer-express-handlebars'); //attach the plugin to the nodemailer transporter transporter.use('compile', hbs(options)); //send mail with options -var mail = { +const mail = { from: 'from@domain.com', to: 'to@domain.com', subject: 'Test', @@ -24,6 +24,20 @@ var mail = { } transporter.sendMail(mail); ``` + +You can send a multipart html and text email by setting the `text_template` option on a mail message. +```javascript +const mail = { + from: 'from@domain.com', + to: 'to@domain.com', + subject: 'Test', + template: 'email', + text_template: 'text', + context: { + name: 'Name' + } +} +``` ## Plugin Options The plugin expects the following options: * __viewEngine (required)__ either the express-handlebars view engine instance or [options for the view engine](https://github.com/express-handlebars/express-handlebars#configuration-and-defaults) diff --git a/lib/generator.js b/lib/generator.js index c55c4c4..023f588 100644 --- a/lib/generator.js +++ b/lib/generator.js @@ -1,39 +1,47 @@ 'use strict'; -var path = require('path'), - handlebars = require('express-handlebars'); +const path = require('path'); +const handlebars = require('express-handlebars'); -var TemplateGenerator = function(opts) { - var viewEngine = opts.viewEngine || {}; +class TemplateGenerator { + constructor(opts) { + let viewEngine = opts.viewEngine || {}; if (!viewEngine.renderView) { - viewEngine = handlebars.create(viewEngine); + viewEngine = handlebars.create(viewEngine); } this.viewEngine = viewEngine; this.viewPath = opts.viewPath; this.extName = opts.extName || '.handlebars'; -}; + } -TemplateGenerator.prototype.render = function render(mail, cb) { - if (mail.data.html) return cb(); + async render(mail) { + if (mail.data.html) { + return; + } + + let templatePath = path.join( + this.viewPath, + mail.data.template + this.extName + ); + let textTemplatePath = ''; + if (mail.data.text_template) { + textTemplatePath = path.join( + this.viewPath, + mail.data.text_template + this.extName + ); + } - var templatePath = path.join(this.viewPath, mail.data.template + this.extName); - var textTemplatePath=''; - if (!!mail.data.text_template) { - textTemplatePath=path.join(this.viewPath, mail.data.text_template + this.extName); + mail.data.html = await this.viewEngine.renderView( + templatePath, + mail.data.context + ); + if (mail.data.text_template) { + mail.data.text = await this.viewEngine.renderView( + textTemplatePath, + mail.data.context + ); } - var viewEngine=this.viewEngine; - this.viewEngine.renderView(templatePath, mail.data.context, function(err, body) { - if (err) return cb(err); - mail.data.html = body; - if(!mail.data.text_template)cb(); - else{ - viewEngine.renderView(textTemplatePath, mail.data.context, function(err, body) { - if (err) return cb(err); - mail.data.text = body; - cb(); - }); - } - }); -}; + } +} module.exports = TemplateGenerator; diff --git a/lib/index.js b/lib/index.js index 7610840..7da6000 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,11 +1,16 @@ -'use strict'; +const TemplateGenerator = require('./generator'); -var TemplateGenerator = require('./generator'); +module.exports = function (options) { + const generator = new TemplateGenerator(options); -module.exports = function(options) { - var generator = new TemplateGenerator(options); + return async (mail, cb) => { + try { + await generator.render(mail); + } catch (err) { + return cb(err); + } + + cb(); + }; +}; - return function(mail, cb) { - generator.render(mail, cb); - }; -}; \ No newline at end of file diff --git a/package.json b/package.json index 9d688e1..a9ba60b 100644 --- a/package.json +++ b/package.json @@ -20,14 +20,15 @@ "url": "https://github.com/yads/nodemailer-express-handlebars" }, "license": "MIT", - "dependencies": { - "express-handlebars": "^6.0.0" + "peerDependencies": { + "express-handlebars": ">= 6.0.0", + "nodemailer": ">= 6.0.0" }, "devDependencies": { "chai": "^4.1.2", + "express-handlebars": "^6.0.0", "mocha": "^10.0.0", - "nodemailer": "^6.4.6", - "nodemailer-stub-transport": "^1.1.0" + "nodemailer": "^6.4.6" }, "engines": { "node": "14.* || 16.* || >= 18" diff --git a/test/tests/index.js b/test/tests/index.js index bcaf94b..2565d60 100644 --- a/test/tests/index.js +++ b/test/tests/index.js @@ -1,106 +1,134 @@ 'use strict'; -var should = require('chai').should(); -var nodemailerExpressHandlebars = require('../..'), - nodemailer = require('nodemailer'), - transport = require('nodemailer-stub-transport'), - handlebars = require('express-handlebars'), - path = require('path'); - -describe('when view engine passed', function() { - - var sut, - mail, - transporter, - viewEngine; - - beforeEach(function() { - transporter = nodemailer.createTransport(transport()); - viewEngine = handlebars.create({ - partialsDir: 'partials/', - defaultLayout: false - }); - sut = nodemailerExpressHandlebars({ - viewEngine: viewEngine, - viewPath: path.resolve(__dirname, '../views') - }); - transporter.use('compile', sut); - mail = { - from: 'from@domain.com', - to: 'to@domain.com', - subject: 'Test', - template: 'email', - context: { - name: 'Name' - } - }; +const { expect } = require('chai'); +const nodemailerExpressHandlebars = require('../..'), + nodemailer = require('nodemailer'), + handlebars = require('express-handlebars'), + path = require('path'); + +describe('when view engine passed', function () { + let sut, mail, transporter, viewEngine; + + beforeEach(function () { + transporter = nodemailer.createTransport({ + streamTransport: true, + buffer: true, }); + viewEngine = handlebars.create({ + partialsDir: path.resolve(__dirname, '../views/partials/'), + defaultLayout: false, + }); + sut = nodemailerExpressHandlebars({ + viewEngine: viewEngine, + viewPath: path.resolve(__dirname, '../views'), + }); + transporter.use('compile', sut); + mail = { + from: 'from@domain.com', + to: 'to@domain.com', + subject: 'Test', + template: 'email', + context: { + name: 'Name', + }, + }; + }); + + it('should handle errors', function (done) { + viewEngine.renderView = () => { + throw 'Rendering Error'; + }; + + transporter.sendMail(mail, (err, info) => { + expect(err).to.eq('Rendering Error'); + done(); + }); + }); - it('should set html on email', function(done) { + it('should set html on email', function (done) { + transporter.sendMail(mail, (err, info) => { + if (err) return done(err); - transporter.sendMail(mail, function(err, info) { - if (err) return done(err); + const body = info.message.toString(); + expect(body).to.contain('

This is a test

'); + expect(body).to.contain('Name'); + done(); + }); + }); - var body = info.response.toString(); - body.should.contain('

This is a test

'); - body.should.contain('Name'); - done(); - }); + it('should not overwrite existing html entry', function (done) { + const html = (mail.html = '

hardcoded

'); + transporter.sendMail(mail, (err, info) => { + if (err) return done(err); + const body = info.message.toString(); + expect(body).to.contain(html); + done(); }); + }); - it('should not overwrite existing html entry', function(done) { - - var html = mail.html = '

hardcoded

'; - transporter.sendMail(mail, function(err, info) { - if (err) return done(err); + it('should handle text_template', function (done) { + mail.text_template = 'text'; + transporter.sendMail(mail, (err, info) => { + if (err) return done(err); - var body = info.response.toString(); - body.should.contain(html); - done(); - }); + const body = info.message.toString(); + expect(body).to.contain('

This is a test

'); + expect(body).to.contain('Name'); + expect(body).to.contain('Text email'); + done(); }); -}); + }); + it('should handle view and partials', function (done) { + mail.template = 'with_partial'; + transporter.sendMail(mail, (err, info) => { + if (err) return done(err); -describe('when options passed', function() { - - var sut, - mail, - transporter; - - beforeEach(function() { - transporter = nodemailer.createTransport(transport()); - sut = nodemailerExpressHandlebars({ - viewEngine: { - partialsDir: 'partials/', - defaultLayout: false - }, - viewPath: path.resolve(__dirname, '../views') - }); - transporter.use('compile', sut); - mail = { - from: 'from@domain.com', - to: 'to@domain.com', - subject: 'Test', - template: 'email', - context: { - name: 'Name' - } - }; + const body = info.message.toString(); + expect(body).to.contain('

Header

'); + expect(body).to.contain('Email content'); + done(); }); + }); +}); - it('should set html on email', function(done) { - - transporter.sendMail(mail, function(err, info) { - if (err) return done(err); - - var body = info.response.toString(); - body.should.contain('

This is a test

'); - body.should.contain('Name'); - done(); - }); +describe('when options passed', function () { + var sut, mail, transporter; + beforeEach(function () { + transporter = nodemailer.createTransport({ + streamTransport: true, + buffer: true, + }); + sut = nodemailerExpressHandlebars({ + viewEngine: { + partialsDir: path.resolve(__dirname, '../views/partials/'), + defaultLayout: false, + }, + viewPath: path.resolve(__dirname, '../views'), + }); + transporter.use('compile', sut); + mail = { + from: 'from@domain.com', + to: 'to@domain.com', + subject: 'Test', + template: 'email', + context: { + name: 'Name', + }, + }; + }); + + it('should set html on email', function (done) { + transporter.sendMail(mail, (err, info) => { + if (err) return done(err); + + const body = info.message.toString(); + expect(body).to.contain('

This is a test

'); + expect(body).to.contain('Name'); + done(); }); + }); }); diff --git a/test/views/partials/header.handlebars b/test/views/partials/header.handlebars new file mode 100644 index 0000000..78e5a6a --- /dev/null +++ b/test/views/partials/header.handlebars @@ -0,0 +1 @@ +

Header

diff --git a/test/views/text.handlebars b/test/views/text.handlebars new file mode 100644 index 0000000..32647a0 --- /dev/null +++ b/test/views/text.handlebars @@ -0,0 +1,2 @@ +Text email +{{name}} diff --git a/test/views/with_partial.handlebars b/test/views/with_partial.handlebars new file mode 100644 index 0000000..9833039 --- /dev/null +++ b/test/views/with_partial.handlebars @@ -0,0 +1,2 @@ +{{> header}} +Email content diff --git a/yarn.lock b/yarn.lock index 31396d9..1760523 100644 --- a/yarn.lock +++ b/yarn.lock @@ -186,11 +186,11 @@ escape-string-regexp@4.0.0: integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== express-handlebars@^6.0.0: - version "6.0.6" - resolved "https://registry.yarnpkg.com/express-handlebars/-/express-handlebars-6.0.6.tgz#2589bcc4cf9545918047c767e66fa625f5ace85b" - integrity sha512-E4QHYCh+9fyfdBEb8uKJ8p6HD4qq/sUSHBq83lRNlLJp2TQKEg2nFJYbVdC+M3QzaV19dODe43lgjQWVaIpbyQ== + version "6.0.7" + resolved "https://registry.yarnpkg.com/express-handlebars/-/express-handlebars-6.0.7.tgz#f779254664eff0e250362ef1c2b30587059c212a" + integrity sha512-iYeMFpc/hMD+E6FNAZA5fgWeXnXr4rslOSPkeEV6TwdmpJ5lEXuWX0u9vFYs31P2MURctQq2batR09oeNj0LIg== dependencies: - glob "^8.0.2" + glob "^8.1.0" graceful-fs "^4.2.10" handlebars "^4.7.7" @@ -253,10 +253,10 @@ glob@7.2.0: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^8.0.2: - version "8.0.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-8.0.3.tgz#415c6eb2deed9e502c68fa44a272e6da6eeca42e" - integrity sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ== +glob@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" + integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" @@ -387,16 +387,16 @@ minimatch@^3.0.4: brace-expansion "^1.1.7" minimatch@^5.0.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.1.tgz#6c9dffcf9927ff2a31e74b5af11adf8b9604b022" - integrity sha512-362NP+zlprccbEt/SkxKfRMHnNY85V74mVnpUpNyr3F35covl09Kec7/sEFLt3RA4oXmewtoaanoIf67SE5Y5g== + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== dependencies: brace-expansion "^2.0.1" minimist@^1.2.5: - version "1.2.7" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" - integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== mocha@^10.0.0: version "10.2.0" @@ -445,10 +445,6 @@ neo-async@^2.6.0: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== -nodemailer-stub-transport@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/nodemailer-stub-transport/-/nodemailer-stub-transport-1.1.0.tgz#11421d2d66b4ee6f405354f914c1f4641eb24b0d" - nodemailer@^6.4.6: version "6.8.0" resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.8.0.tgz#804bcc5256ee5523bc914506ee59f8de8f0b1cd5"