mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-14 14:13:43 +01:00
[web] update email base templates (#33791)
* update email template design * gate email template on split test GitOrigin-RevId: 2e0a1d9abf11a0c3f16e103511191d43d542b8a4
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
import _ from 'lodash'
|
||||
import { fontFamily, colors } from '../emailStyles.mjs'
|
||||
|
||||
export default _.template(`\
|
||||
<% if (title) { %>
|
||||
<h2 style="margin: 0 0 16px 0; font-family: ${fontFamily}; font-size: 30px; font-weight: 600; color: ${colors.textDark}; line-height: 40px;">
|
||||
<%= title %>
|
||||
</h2>
|
||||
<% } %>
|
||||
|
||||
<% if (greeting) { %>
|
||||
<p style="margin: 0 0 16px 0;">
|
||||
<%= greeting %>
|
||||
</p>
|
||||
<% } %>
|
||||
|
||||
<% (message).forEach(function(paragraph) { %>
|
||||
<p class="force-overleaf-style" style="margin: 0 0 16px 0;">
|
||||
<%= paragraph %>
|
||||
</p>
|
||||
<% }) %>
|
||||
|
||||
<% if (highlightedText) { %>
|
||||
<div style="text-align: center; color: ${colors.textDark}; font-size: 20px; margin: 16px 0; padding: 16px 8px; border-radius: 8px; background-color: ${colors.background};">
|
||||
<b><%= highlightedText %></b>
|
||||
</div>
|
||||
<% } %>\
|
||||
`)
|
||||
@@ -0,0 +1,65 @@
|
||||
import _ from 'lodash'
|
||||
import { fontFamily, colors } from '../emailStyles.mjs'
|
||||
|
||||
export default _.template(`\
|
||||
<% if (title) { %>
|
||||
<h2 style="margin: 0 0 16px 0; font-family: ${fontFamily}; font-size: 30px; font-weight: 600; color: ${colors.textDark}; line-height: 40px;">
|
||||
<%= title %>
|
||||
</h2>
|
||||
<% } %>
|
||||
|
||||
<% if (greeting) { %>
|
||||
<p style="margin: 0 0 16px 0;">
|
||||
<%= greeting %>
|
||||
</p>
|
||||
<% } %>
|
||||
|
||||
<% (message).forEach(function(paragraph) { %>
|
||||
<p class="force-overleaf-style" style="margin: 0 0 16px 0;">
|
||||
<%= paragraph %>
|
||||
</p>
|
||||
<% }) %>
|
||||
|
||||
<table role="presentation" border="0" cellpadding="0" cellspacing="0" style="margin: 32px 0;">
|
||||
<tr>
|
||||
<td bgcolor="${colors.ctaGreen}" style="background-color: ${colors.ctaGreen}; border-radius: 9999px; padding: 6px 16px; text-align: center;">
|
||||
<a href="<%= ctaURL %>" style="color: ${colors.ctaText}; display: block; font-family: ${fontFamily}; font-size: 16px; font-weight: 600; line-height: 24px; text-align: center; text-decoration: none; -webkit-hyphens: none; -ms-hyphens: none; hyphens: none;">
|
||||
<%= ctaText %>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<% if (secondaryMessage && secondaryMessage.length > 0) { %>
|
||||
<% (secondaryMessage).forEach(function(paragraph) { %>
|
||||
<p class="force-overleaf-style" style="margin: 0 0 16px 0;">
|
||||
<%= paragraph %>
|
||||
</p>
|
||||
<% }) %>
|
||||
<% } %>
|
||||
|
||||
<p style="margin: 0 0 4px 0; font-size: 14px; line-height: 20px; color: ${colors.textMuted};">
|
||||
If the button above does not appear, please copy and paste this link into your browser's address bar:
|
||||
</p>
|
||||
<p style="margin: 0; font-size: 14px; line-height: 20px; word-break: break-all;">
|
||||
<a href="<%= ctaURL %>" style="color: ${colors.linkGreen}; text-decoration: none;"><%= ctaURL %></a>
|
||||
</p>
|
||||
|
||||
<% if (gmailGoToAction) { %>
|
||||
<script type="application/ld+json">
|
||||
<%=
|
||||
StringHelper.stringifyJsonForScript({
|
||||
"@context": "http://schema.org",
|
||||
"@type": "EmailMessage",
|
||||
"potentialAction": {
|
||||
"@type": "ViewAction",
|
||||
"target": gmailGoToAction.target,
|
||||
"url": gmailGoToAction.target,
|
||||
"name": gmailGoToAction.name
|
||||
},
|
||||
"description": gmailGoToAction.description
|
||||
})
|
||||
%>
|
||||
</script>
|
||||
<% } %>\
|
||||
`)
|
||||
@@ -4,9 +4,12 @@ import moment from 'moment'
|
||||
import EmailMessageHelper from './EmailMessageHelper.mjs'
|
||||
import StringHelper from '../Helpers/StringHelper.mjs'
|
||||
import BaseEmailLayout from './Layouts/BaseEmailLayout.mjs'
|
||||
import UpdatedBaseEmailLayout from './Layouts/UpdatedBaseEmailLayout.mjs'
|
||||
import SpamSafe from './SpamSafe.mjs'
|
||||
import ctaEmailBody from './Bodies/cta-email.mjs'
|
||||
import updatedCtaEmailBody from './Bodies/updated-cta-email.mjs'
|
||||
import NoCTAEmailBody from './Bodies/NoCTAEmailBody.mjs'
|
||||
import UpdatedNoCTAEmailBody from './Bodies/UpdatedNoCTAEmailBody.mjs'
|
||||
|
||||
function _emailBodyPlainText(content, opts, ctaEmail) {
|
||||
let emailBody = `${content.greeting(opts, true)}`
|
||||
@@ -75,7 +78,12 @@ function ctaTemplate(content) {
|
||||
subject(opts) {
|
||||
return content.subject(opts)
|
||||
},
|
||||
layout: BaseEmailLayout,
|
||||
layout(opts) {
|
||||
const layoutFn = opts.useNewEmailDesign
|
||||
? UpdatedBaseEmailLayout
|
||||
: BaseEmailLayout
|
||||
return layoutFn(opts)
|
||||
},
|
||||
footerMessage(opts) {
|
||||
return content.footerMessage(opts)
|
||||
},
|
||||
@@ -83,7 +91,8 @@ function ctaTemplate(content) {
|
||||
return _emailBodyPlainText(content, opts, true)
|
||||
},
|
||||
compiledTemplate(opts) {
|
||||
return ctaEmailBody({
|
||||
const bodyFn = opts.useNewEmailDesign ? updatedCtaEmailBody : ctaEmailBody
|
||||
return bodyFn({
|
||||
title: content.title(opts),
|
||||
greeting: content.greeting(opts),
|
||||
message: content.message(opts),
|
||||
@@ -108,7 +117,12 @@ function NoCTAEmailTemplate(content) {
|
||||
subject(opts) {
|
||||
return content.subject(opts)
|
||||
},
|
||||
layout: BaseEmailLayout,
|
||||
layout(opts) {
|
||||
const layoutFn = opts.useNewEmailDesign
|
||||
? UpdatedBaseEmailLayout
|
||||
: BaseEmailLayout
|
||||
return layoutFn(opts)
|
||||
},
|
||||
plainTextTemplate(opts) {
|
||||
return `\
|
||||
${content.greeting(opts)}
|
||||
@@ -120,7 +134,10 @@ The ${settings.appName} Team - ${settings.siteUrl}\
|
||||
`
|
||||
},
|
||||
compiledTemplate(opts) {
|
||||
return NoCTAEmailBody({
|
||||
const bodyFn = opts.useNewEmailDesign
|
||||
? UpdatedNoCTAEmailBody
|
||||
: NoCTAEmailBody
|
||||
return bodyFn({
|
||||
title:
|
||||
typeof content.title === 'function' ? content.title(opts) : undefined,
|
||||
greeting: content.greeting(opts),
|
||||
@@ -137,7 +154,6 @@ The ${settings.appName} Team - ${settings.siteUrl}\
|
||||
|
||||
function buildEmail(templateName, opts) {
|
||||
const template = templates[templateName]
|
||||
opts.siteUrl = settings.siteUrl
|
||||
opts.body = template.compiledTemplate(opts)
|
||||
opts.footerMessage = template.footerMessage
|
||||
? template.footerMessage(opts)
|
||||
@@ -189,7 +205,7 @@ templates.canceledSubscription = ctaTemplate({
|
||||
return ['Thank you in advance!']
|
||||
},
|
||||
ctaText() {
|
||||
return 'Leave Feedback'
|
||||
return 'Leave feedback'
|
||||
},
|
||||
ctaURL(opts) {
|
||||
return 'https://docs.google.com/forms/d/e/1FAIpQLSfa7z_s-cucRRXm70N4jEcSbFsZeb0yuKThHGQL8ySEaQzF0Q/viewform?usp=sf_link'
|
||||
@@ -394,7 +410,7 @@ templates.reconfirmEmail = ctaTemplate({
|
||||
]
|
||||
},
|
||||
ctaText() {
|
||||
return 'Reconfirm Email'
|
||||
return 'Reconfirm email'
|
||||
},
|
||||
ctaURL(opts) {
|
||||
return opts.confirmEmailUrl
|
||||
@@ -894,7 +910,7 @@ templates.SAMLDataCleared = ctaTemplate({
|
||||
]
|
||||
},
|
||||
ctaText(opts) {
|
||||
return 'Update my Emails and affiliations'
|
||||
return 'Update my emails and affiliations'
|
||||
},
|
||||
ctaURL(opts) {
|
||||
return `${settings.siteUrl}/user/settings`
|
||||
|
||||
@@ -4,14 +4,37 @@ import logger from '@overleaf/logger'
|
||||
import EmailBuilder from './EmailBuilder.mjs'
|
||||
import EmailSender from './EmailSender.mjs'
|
||||
import Queues from '../../infrastructure/Queues.mjs'
|
||||
import SplitTestHandler from '../SplitTests/SplitTestHandler.mjs'
|
||||
import UserGetter from '../User/UserGetter.mjs'
|
||||
|
||||
const EMAIL_SETTINGS = Settings.email || {}
|
||||
|
||||
/**
|
||||
* @param {string} emailType
|
||||
* @param {opts} any
|
||||
* @param {object} opts
|
||||
*/
|
||||
async function sendEmail(emailType, opts) {
|
||||
if (opts.to) {
|
||||
try {
|
||||
const user = await UserGetter.promises.getUserByAnyEmail(opts.to, {
|
||||
_id: 1,
|
||||
})
|
||||
if (user) {
|
||||
const { variant } =
|
||||
await SplitTestHandler.promises.getAssignmentForUser(
|
||||
user._id.toString(),
|
||||
'new-email-design'
|
||||
)
|
||||
opts.useNewEmailDesign = variant === 'enabled'
|
||||
}
|
||||
} catch (err) {
|
||||
logger.warn(
|
||||
{ err },
|
||||
'failed to get new-email-design split test assignment'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const email = EmailBuilder.buildEmail(emailType, opts)
|
||||
if (email.type === 'lifecycle' && !EMAIL_SETTINGS.lifecycle) {
|
||||
return
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
import _ from 'lodash'
|
||||
import settings from '@overleaf/settings'
|
||||
import { fontFamily, colors } from '../emailStyles.mjs'
|
||||
|
||||
const _isSaas = settings.env === 'saas'
|
||||
const _imageBase = settings.cdn?.web?.host || settings.siteUrl || null
|
||||
|
||||
let _logoHtml = `<span style="font-family: ${fontFamily}; font-size: 30px; font-weight: bold; color: ${colors.logoGreen};">${settings.appName}</span>`
|
||||
let _taglineHtml = ''
|
||||
if (_isSaas && _imageBase) {
|
||||
_logoHtml = `
|
||||
<a href="${settings.siteUrl}" style="text-decoration: none; display: inline-block; border: 0;">
|
||||
<img
|
||||
src="${_imageBase}/img/ol-brand/email-logo@2x.png"
|
||||
alt="${settings.appName}"
|
||||
width="139"
|
||||
height="40"
|
||||
style="display: block; border: 0; width: 139px; height: 40px;"
|
||||
>
|
||||
</a>
|
||||
`
|
||||
|
||||
_taglineHtml = `
|
||||
<!--[if mso]>
|
||||
<img
|
||||
src="${_imageBase}/img/ol-brand/email-footer-tagline@2x.png"
|
||||
alt="The home of scientific and technical writing"
|
||||
width="452"
|
||||
height="25"
|
||||
style="display: block; border: 0;"
|
||||
/>
|
||||
<![endif]-->
|
||||
<!--[if !mso]><!-->
|
||||
<img
|
||||
src="${_imageBase}/img/ol-brand/email-footer-tagline@2x.png"
|
||||
alt="The home of scientific and technical writing"
|
||||
style="display: block; border: 0; width: 100%; max-width: 452px; height: auto;"
|
||||
>
|
||||
<!--<![endif]-->
|
||||
`
|
||||
}
|
||||
|
||||
const _customFooter = settings.email?.template?.customFooter || ''
|
||||
|
||||
export default _.template(`\
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="x-apple-disable-message-reformatting">
|
||||
<!--[if mso]>
|
||||
<style type="text/css">
|
||||
body, table, td, p, a, li { font-family: Arial, Helvetica, sans-serif !important; }
|
||||
</style>
|
||||
<![endif]-->
|
||||
<style>
|
||||
body { margin: 0; padding: 0; background-color: ${colors.background}; }
|
||||
img { border: 0; display: block; outline: none; text-decoration: none; }
|
||||
.email-layout-table { border-collapse: collapse !important; }
|
||||
a[x-apple-data-detectors] { color: inherit !important; text-decoration: none !important; }
|
||||
|
||||
.force-overleaf-style a,
|
||||
.force-overleaf-style a[href] {
|
||||
color: ${colors.linkGreen} !important;
|
||||
text-decoration: underline !important;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
-webkit-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
.force-overleaf-style a:visited,
|
||||
.force-overleaf-style a[href]:visited { color: ${colors.linkGreen}; }
|
||||
.force-overleaf-style a:hover,
|
||||
.force-overleaf-style a[href]:hover { color: ${colors.linkHover}; }
|
||||
|
||||
@media only screen and (min-width: 621px) {
|
||||
.email-card-inner { padding: 56px !important; }
|
||||
}
|
||||
@media only screen and (max-width: 620px) {
|
||||
.email-container { width: 100% !important; max-width: 100% !important; }
|
||||
.email-outer-cell { padding: 0 10px !important; }
|
||||
.email-header-cell { padding: 16px 0 !important; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body bgcolor="${colors.background}" style="margin: 0; padding: 0; background-color: ${colors.background}; font-family: ${fontFamily};">
|
||||
|
||||
<!-- Prevent Gmail iOS font size manipulation -->
|
||||
<div style="display: none; white-space: nowrap; font: 15px courier; line-height: 0;">
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<table class="email-layout-table" role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="${colors.background}" style="background-color: ${colors.background}; min-width: 100%;">
|
||||
<tr>
|
||||
<td class="email-outer-cell" align="center" valign="top" style="padding: 40px 10px;">
|
||||
|
||||
<!--[if mso]>
|
||||
<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="600" align="center"><tr><td>
|
||||
<![endif]-->
|
||||
<table class="email-container email-layout-table" role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px;">
|
||||
|
||||
<!-- Logo header -->
|
||||
<tr>
|
||||
<td class="email-header-cell" style="padding: 0 0 24px 0;">
|
||||
${_logoHtml}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- White content card -->
|
||||
<tr>
|
||||
<td bgcolor="#ffffff" style="background-color: #ffffff; border-radius: 8px; overflow: hidden;">
|
||||
<table class="email-layout-table" role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%">
|
||||
<tr>
|
||||
<td
|
||||
class="email-card-inner"
|
||||
style="padding: 32px 24px; font-family: ${fontFamily}; font-size: 16px; line-height: 1.5; color: ${colors.textDark};"
|
||||
>
|
||||
<%= body %>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer spacer -->
|
||||
<tr>
|
||||
<td style="height: 24px; font-size: 24px; line-height: 24px; mso-line-height-rule: exactly;"> </td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer tagline -->
|
||||
<tr>
|
||||
<td align="center" style="padding: 0 0 48px 0;">
|
||||
${_taglineHtml}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer site info -->
|
||||
${
|
||||
_customFooter
|
||||
? `<tr>
|
||||
<td align="center" style="padding: 0 0 32px 0; font-family: ${fontFamily}; font-size: 13px; color: ${colors.textMuted}; line-height: 1.5;">
|
||||
${_customFooter}
|
||||
</td>
|
||||
</tr>`
|
||||
: ''
|
||||
}
|
||||
|
||||
<% if (footerMessage) { %>
|
||||
<tr>
|
||||
<td align="center" class="force-overleaf-style" style="padding: 0 0 32px 0; font-family: ${fontFamily}; font-size: 14px; color: ${colors.textMuted}; line-height: 20px;">
|
||||
<%= footerMessage %>
|
||||
</td>
|
||||
</tr>
|
||||
<% } %>
|
||||
|
||||
</table>
|
||||
<!--[if mso]>
|
||||
</td></tr></table>
|
||||
<![endif]-->
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</body>
|
||||
</html>\
|
||||
`)
|
||||
@@ -0,0 +1,12 @@
|
||||
export const fontFamily = 'Arial, Helvetica, sans-serif'
|
||||
|
||||
export const colors = {
|
||||
textDark: '#1b222c',
|
||||
textMuted: '#495365',
|
||||
background: '#f4f5f6',
|
||||
ctaGreen: '#098842',
|
||||
ctaText: '#ffffff',
|
||||
linkGreen: '#1e6b41',
|
||||
linkHover: '#155a30',
|
||||
logoGreen: '#04652f',
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.7 KiB |
@@ -275,13 +275,11 @@ describe('EmailBuilder', function () {
|
||||
describe('HTML email', function () {
|
||||
it('should include a CTA button and a fallback CTA link', function (ctx) {
|
||||
const dom = cheerio.load(ctx.email.html)
|
||||
const buttonLink = dom('a:contains("Leave Feedback")')
|
||||
const buttonLink = dom('a:contains("Leave feedback")')
|
||||
expect(buttonLink.length).to.equal(1)
|
||||
expect(buttonLink.attr('href')).to.equal(ctx.expectedUrl)
|
||||
const fallback = dom('.force-overleaf-style').last()
|
||||
expect(fallback.length).to.equal(1)
|
||||
const fallbackLink = fallback.html()
|
||||
expect(fallbackLink).to.contain(ctx.expectedUrl)
|
||||
expect(ctx.email.html).to.contain('copy and paste this link')
|
||||
expect(ctx.email.html).to.contain(ctx.expectedUrl)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -317,10 +315,8 @@ describe('EmailBuilder', function () {
|
||||
const buttonLink = dom('a:contains("Leave feedback")')
|
||||
expect(buttonLink.length).to.equal(1)
|
||||
expect(buttonLink.attr('href')).to.equal(ctx.expectedUrl)
|
||||
const fallback = dom('.force-overleaf-style').last()
|
||||
expect(fallback.length).to.equal(1)
|
||||
const fallbackLink = fallback.html()
|
||||
expect(fallbackLink).to.contain(ctx.expectedUrl)
|
||||
expect(ctx.email.html).to.contain('copy and paste this link')
|
||||
expect(ctx.email.html).to.contain(ctx.expectedUrl)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -354,10 +350,8 @@ describe('EmailBuilder', function () {
|
||||
const buttonLink = dom('a:contains("Confirm email")')
|
||||
expect(buttonLink.length).to.equal(1)
|
||||
expect(buttonLink.attr('href')).to.equal(ctx.opts.confirmEmailUrl)
|
||||
const fallback = dom('.force-overleaf-style').last()
|
||||
expect(fallback.length).to.equal(1)
|
||||
const fallbackLink = fallback.html()
|
||||
expect(fallbackLink).to.contain(ctx.opts.confirmEmailUrl)
|
||||
expect(ctx.email.html).to.contain('copy and paste this link')
|
||||
expect(ctx.email.html).to.contain(ctx.opts.confirmEmailUrl)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -396,13 +390,11 @@ describe('EmailBuilder', function () {
|
||||
describe('HTML email', function () {
|
||||
it('should include a CTA button and a fallback CTA link', function (ctx) {
|
||||
const dom = cheerio.load(ctx.email.html)
|
||||
const buttonLink = dom('td a')
|
||||
expect(buttonLink).to.exist
|
||||
const buttonLink = dom('a:contains("View project")')
|
||||
expect(buttonLink.length).to.equal(1)
|
||||
expect(buttonLink.attr('href')).to.equal(ctx.expectedUrl)
|
||||
const fallback = dom('.force-overleaf-style').last()
|
||||
expect(fallback).to.exist
|
||||
const fallbackLink = fallback.html().replace(/&/g, '&')
|
||||
expect(fallbackLink).to.contain(ctx.expectedUrl)
|
||||
expect(ctx.email.html).to.contain('copy and paste this link')
|
||||
expect(ctx.email.html).to.contain(ctx.expectedUrl)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -438,13 +430,11 @@ describe('EmailBuilder', function () {
|
||||
describe('HTML email', function () {
|
||||
it('should include a CTA button and a fallback CTA link', function (ctx) {
|
||||
const dom = cheerio.load(ctx.email.html)
|
||||
const buttonLink = dom('td a')
|
||||
expect(buttonLink).to.exist
|
||||
const buttonLink = dom('a:contains("Reset password")')
|
||||
expect(buttonLink.length).to.equal(1)
|
||||
expect(buttonLink.attr('href')).to.equal(ctx.opts.setNewPasswordUrl)
|
||||
const fallback = dom('.force-overleaf-style').last()
|
||||
expect(fallback).to.exist
|
||||
const fallbackLink = fallback.html().replace(/&/g, '&')
|
||||
expect(fallbackLink).to.contain(ctx.opts.setNewPasswordUrl)
|
||||
expect(ctx.email.html).to.contain('copy and paste this link')
|
||||
expect(ctx.email.html).to.contain(ctx.opts.setNewPasswordUrl)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -475,13 +465,11 @@ describe('EmailBuilder', function () {
|
||||
describe('HTML email', function () {
|
||||
it('should include a CTA button and a fallback CTA link', function (ctx) {
|
||||
const dom = cheerio.load(ctx.email.html)
|
||||
const buttonLink = dom('a:contains("Reconfirm Email")')
|
||||
const buttonLink = dom('a:contains("Reconfirm email")')
|
||||
expect(buttonLink.length).to.equal(1)
|
||||
expect(buttonLink.attr('href')).to.equal(ctx.opts.confirmEmailUrl)
|
||||
const fallback = dom('.force-overleaf-style').last()
|
||||
expect(fallback.length).to.equal(1)
|
||||
const fallbackLink = fallback.html()
|
||||
expect(fallbackLink).to.contain(ctx.opts.confirmEmailUrl)
|
||||
expect(ctx.email.html).to.contain('copy and paste this link')
|
||||
expect(ctx.email.html).to.contain(ctx.opts.confirmEmailUrl)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -521,10 +509,8 @@ describe('EmailBuilder', function () {
|
||||
const buttonLink = dom('a:contains("Join now")')
|
||||
expect(buttonLink.length).to.equal(1)
|
||||
expect(buttonLink.attr('href')).to.equal(ctx.opts.acceptInviteUrl)
|
||||
const fallback = dom('.force-overleaf-style').last()
|
||||
expect(fallback.length).to.equal(1)
|
||||
const fallbackLink = fallback.html()
|
||||
expect(fallbackLink).to.contain(ctx.opts.acceptInviteUrl)
|
||||
expect(ctx.email.html).to.contain('copy and paste this link')
|
||||
expect(ctx.email.html).to.contain(ctx.opts.acceptInviteUrl)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -559,10 +545,8 @@ describe('EmailBuilder', function () {
|
||||
const buttonLink = dom('a:contains("View Subscription Dashboard")')
|
||||
expect(buttonLink.length).to.equal(1)
|
||||
expect(buttonLink.attr('href')).to.equal(ctx.expectedUrl)
|
||||
const fallback = dom('.force-overleaf-style').last()
|
||||
expect(fallback.length).to.equal(1)
|
||||
const fallbackLink = fallback.html()
|
||||
expect(fallbackLink).to.contain(ctx.expectedUrl)
|
||||
expect(ctx.email.html).to.contain('copy and paste this link')
|
||||
expect(ctx.email.html).to.contain(ctx.expectedUrl)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -593,10 +577,8 @@ describe('EmailBuilder', function () {
|
||||
const buttonLink = dom(`a:contains("Open ${ctx.settings.appName}")`)
|
||||
expect(buttonLink.length).to.equal(1)
|
||||
expect(buttonLink.attr('href')).to.equal(ctx.settings.siteUrl)
|
||||
const fallback = dom('.force-overleaf-style').last()
|
||||
expect(fallback.length).to.equal(1)
|
||||
const fallbackLink = fallback.html()
|
||||
expect(fallbackLink).to.contain(ctx.settings.siteUrl)
|
||||
expect(ctx.email.html).to.contain('copy and paste this link')
|
||||
expect(ctx.email.html).to.contain(ctx.settings.siteUrl)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -630,10 +612,8 @@ describe('EmailBuilder', function () {
|
||||
const buttonLink = dom('a:contains("Set password")')
|
||||
expect(buttonLink.length).to.equal(1)
|
||||
expect(buttonLink.attr('href')).to.equal(ctx.opts.setNewPasswordUrl)
|
||||
const fallback = dom('.force-overleaf-style').last()
|
||||
expect(fallback.length).to.equal(1)
|
||||
const fallbackLink = fallback.html().replace(/&/, '&')
|
||||
expect(fallbackLink).to.contain(ctx.opts.setNewPasswordUrl)
|
||||
expect(ctx.email.html).to.contain('copy and paste this link')
|
||||
expect(ctx.email.html).to.contain(ctx.opts.setNewPasswordUrl)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -676,10 +656,8 @@ describe('EmailBuilder', function () {
|
||||
const buttonLink = dom('a:contains("View project")')
|
||||
expect(buttonLink.length).to.equal(1)
|
||||
expect(buttonLink.attr('href')).to.equal(ctx.opts.inviteUrl)
|
||||
const fallback = dom('.force-overleaf-style').last()
|
||||
expect(fallback.length).to.equal(1)
|
||||
const fallbackLink = fallback.html().replace(/&/g, '&')
|
||||
expect(fallbackLink).to.contain(ctx.opts.inviteUrl)
|
||||
expect(ctx.email.html).to.contain('copy and paste this link')
|
||||
expect(ctx.email.html).to.contain(ctx.opts.inviteUrl)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -711,9 +689,8 @@ describe('EmailBuilder', function () {
|
||||
const buttonLink = ctx.dom('a:contains("Confirm email")')
|
||||
expect(buttonLink.length).to.equal(1)
|
||||
expect(buttonLink.attr('href')).to.equal(ctx.opts.confirmEmailUrl)
|
||||
const fallback = ctx.dom('.force-overleaf-style').last()
|
||||
expect(fallback.length).to.equal(1)
|
||||
expect(fallback.html()).to.contain(ctx.opts.confirmEmailUrl)
|
||||
expect(ctx.email.html).to.contain('copy and paste this link')
|
||||
expect(ctx.email.html).to.contain(ctx.opts.confirmEmailUrl)
|
||||
})
|
||||
it('should include help links', function (ctx) {
|
||||
const helpGuidesLink = ctx.dom('a:contains("Help Guides")')
|
||||
@@ -761,11 +738,12 @@ describe('EmailBuilder', function () {
|
||||
expect(email.html).to.contain(
|
||||
'You can still log in to Overleaf using one of our other'
|
||||
)
|
||||
const links = dom('a')
|
||||
expect(links[0].attribs.href).to.equal(
|
||||
const loginLink = dom('a:contains("login options")')
|
||||
expect(loginLink.attr('href')).to.equal(
|
||||
`${ctx.settings.siteUrl}/login`
|
||||
)
|
||||
expect(links[1].attribs.href).to.equal(setNewPasswordUrl)
|
||||
const passwordLink = dom('a:contains("Set your new password")')
|
||||
expect(passwordLink.attr('href')).to.equal(setNewPasswordUrl)
|
||||
expect(email.html).to.contain(
|
||||
"If you don't have a password, you can set one now."
|
||||
)
|
||||
@@ -813,8 +791,8 @@ describe('EmailBuilder', function () {
|
||||
expect(email.html).to.contain(
|
||||
'You now need an email address and password to sign in to your Overleaf account.'
|
||||
)
|
||||
const links = dom('a')
|
||||
expect(links[0].attribs.href).to.equal(
|
||||
const ctaLink = dom('a:contains("Set your new password")')
|
||||
expect(ctaLink.attr('href')).to.equal(
|
||||
`${ctx.settings.siteUrl}/user/password/reset`
|
||||
)
|
||||
|
||||
@@ -1067,13 +1045,11 @@ describe('EmailBuilder', function () {
|
||||
expect(ctx.email.html).to.contain(
|
||||
'Remove inactive users to free up licenses'
|
||||
)
|
||||
const buttonLink = dom('td a')
|
||||
const buttonLink = dom('a:contains("Add licenses")')
|
||||
expect(buttonLink).to.exist
|
||||
expect(buttonLink.attr('href')).to.equal(ctx.expectedUrl)
|
||||
const fallback = dom('.force-overleaf-style').last()
|
||||
expect(fallback).to.exist
|
||||
const fallbackLink = fallback.html().replace(/&/g, '&')
|
||||
expect(fallbackLink).to.contain(ctx.expectedUrl)
|
||||
expect(ctx.email.html).to.contain('copy and paste this link')
|
||||
expect(ctx.email.html).to.contain(ctx.expectedUrl)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -39,6 +39,27 @@ describe('EmailHandler', function () {
|
||||
default: ctx.Queues,
|
||||
}))
|
||||
|
||||
ctx.SplitTestHandler = {
|
||||
promises: {
|
||||
getAssignmentForUser: sinon.stub().resolves({ variant: 'default' }),
|
||||
},
|
||||
}
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/SplitTests/SplitTestHandler',
|
||||
() => ({
|
||||
default: ctx.SplitTestHandler,
|
||||
})
|
||||
)
|
||||
|
||||
ctx.UserGetter = {
|
||||
promises: {
|
||||
getUserByAnyEmail: sinon.stub().resolves(null),
|
||||
},
|
||||
}
|
||||
vi.doMock('../../../../app/src/Features/User/UserGetter', () => ({
|
||||
default: ctx.UserGetter,
|
||||
}))
|
||||
|
||||
ctx.EmailHandler = (await import(MODULE_PATH)).default
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import { vi, expect } from 'vitest'
|
||||
import { load } from 'cheerio'
|
||||
import path from 'node:path'
|
||||
|
||||
const LAYOUT_PATH = path.join(
|
||||
import.meta.dirname,
|
||||
'../../../../app/src/Features/Email/Layouts/UpdatedBaseEmailLayout'
|
||||
)
|
||||
|
||||
async function buildLayoutWithEnv(env) {
|
||||
vi.resetModules()
|
||||
vi.doMock('@overleaf/settings', () => ({
|
||||
default: {
|
||||
appName: 'TestApp',
|
||||
siteUrl: 'https://www.example.com',
|
||||
env,
|
||||
},
|
||||
}))
|
||||
const { default: layout } = await import(LAYOUT_PATH)
|
||||
return layout({ body: 'Test body', footerMessage: '' })
|
||||
}
|
||||
|
||||
describe('UpdatedBaseEmailLayout branding', function () {
|
||||
afterEach(function () {
|
||||
vi.restoreAllMocks()
|
||||
vi.resetModules()
|
||||
})
|
||||
|
||||
describe('on SaaS', function () {
|
||||
it('should show the logo image', async function () {
|
||||
const html = await buildLayoutWithEnv('saas')
|
||||
const dom = load(html)
|
||||
const logoImg = dom('img[alt="TestApp"]')
|
||||
expect(logoImg.length).to.equal(1)
|
||||
expect(logoImg.attr('src')).to.contain('email-logo@2x.png')
|
||||
})
|
||||
|
||||
it('should show the tagline image', async function () {
|
||||
const html = await buildLayoutWithEnv('saas')
|
||||
expect(html).to.contain('email-footer-tagline@2x.png')
|
||||
})
|
||||
})
|
||||
|
||||
describe('on server-ce', function () {
|
||||
it('should show the app name as text instead of logo', async function () {
|
||||
const html = await buildLayoutWithEnv('server-ce')
|
||||
const dom = load(html)
|
||||
const logoImg = dom('img[alt="TestApp"]')
|
||||
expect(logoImg.length).to.equal(0)
|
||||
expect(html).to.contain('TestApp')
|
||||
expect(html).to.not.contain('email-logo@2x.png')
|
||||
})
|
||||
|
||||
it('should not show the tagline image', async function () {
|
||||
const html = await buildLayoutWithEnv('server-ce')
|
||||
expect(html).to.not.contain('email-footer-tagline@2x.png')
|
||||
})
|
||||
})
|
||||
|
||||
describe('on server-pro', function () {
|
||||
it('should show the app name as text instead of logo', async function () {
|
||||
const html = await buildLayoutWithEnv('server-pro')
|
||||
const dom = load(html)
|
||||
const logoImg = dom('img[alt="TestApp"]')
|
||||
expect(logoImg.length).to.equal(0)
|
||||
expect(html).to.contain('TestApp')
|
||||
expect(html).to.not.contain('email-logo@2x.png')
|
||||
})
|
||||
|
||||
it('should not show the tagline image', async function () {
|
||||
const html = await buildLayoutWithEnv('server-pro')
|
||||
expect(html).to.not.contain('email-footer-tagline@2x.png')
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user