mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-30 21:40:39 +03:00
bc0126c54e
fixes https://github.com/TryGhost/Team/issues/2736 Shows the actual subscription status (expires on DD MMM YYYY) in every email when show subscription details is enabled.
1697 lines
65 KiB
JavaScript
1697 lines
65 KiB
JavaScript
const {EmailRenderer} = require('../');
|
|
const assert = require('assert');
|
|
const cheerio = require('cheerio');
|
|
const {createModel, createModelClass} = require('./utils');
|
|
const linkReplacer = require('@tryghost/link-replacer');
|
|
const sinon = require('sinon');
|
|
const logging = require('@tryghost/logging');
|
|
const {HtmlValidate} = require('html-validate');
|
|
const {DateTime} = require('luxon');
|
|
|
|
function validateHtml(html) {
|
|
const htmlvalidate = new HtmlValidate({
|
|
extends: [
|
|
'html-validate:document',
|
|
'html-validate:standard'
|
|
],
|
|
rules: {
|
|
// We need deprecated attrs for legacy tables in older email clients
|
|
'no-deprecated-attr': 'off',
|
|
|
|
// Don't care that the first <hx> isn't <h1>
|
|
'heading-level': 'off'
|
|
},
|
|
elements: [
|
|
'html5',
|
|
// By default, html-validate requires the 'lang' attribute on the <html> tag. We don't really want that for now.
|
|
{
|
|
html: {
|
|
attributes: {
|
|
lang: {
|
|
required: false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
});
|
|
const report = htmlvalidate.validateString(html);
|
|
|
|
// Improve debugging and show a snippet of the invalid HTML instead of just the line number or a huge HTML-dump
|
|
const parsedErrors = [];
|
|
|
|
if (!report.valid) {
|
|
const lines = html.split('\n');
|
|
const messages = report.results[0].messages;
|
|
|
|
for (const item of messages) {
|
|
if (item.severity !== 2) {
|
|
// Ignore warnings
|
|
continue;
|
|
}
|
|
const start = Math.max(item.line - 4, 0);
|
|
const end = Math.min(item.line + 4, lines.length - 1);
|
|
|
|
const _html = lines.slice(start, end).map(l => l.trim()).join('\n');
|
|
parsedErrors.push(`${item.ruleId}: ${item.message}\n At line ${item.line}, col ${item.column}\n HTML-snippet:\n${_html}`);
|
|
}
|
|
}
|
|
|
|
// Fail if invalid HTML
|
|
assert.equal(report.valid, true, 'Expected valid HTML without warnings, got errors:\n' + parsedErrors.join('\n\n'));
|
|
}
|
|
|
|
describe('Email renderer', function () {
|
|
let logStub;
|
|
|
|
beforeEach(function () {
|
|
logStub = sinon.stub(logging, 'error');
|
|
});
|
|
|
|
afterEach(function () {
|
|
sinon.restore();
|
|
});
|
|
|
|
describe('buildReplacementDefinitions', function () {
|
|
let emailRenderer;
|
|
let newsletter;
|
|
let member;
|
|
|
|
beforeEach(function () {
|
|
emailRenderer = new EmailRenderer({
|
|
urlUtils: {
|
|
urlFor: () => 'http://example.com/subdirectory/'
|
|
},
|
|
labs: {
|
|
isSet: () => true
|
|
},
|
|
settingsCache: {
|
|
get: (key) => {
|
|
if (key === 'timezone') {
|
|
return 'UTC';
|
|
}
|
|
}
|
|
}
|
|
});
|
|
newsletter = createModel({
|
|
uuid: 'newsletteruuid'
|
|
});
|
|
member = {
|
|
id: '456',
|
|
uuid: 'myuuid',
|
|
name: 'Test User',
|
|
email: 'test@example.com',
|
|
createdAt: new Date(2023, 2, 13, 12, 0),
|
|
status: 'free'
|
|
};
|
|
});
|
|
|
|
it('returns an empty list of replacements if nothing is used', function () {
|
|
const html = 'Hello world';
|
|
const replacements = emailRenderer.buildReplacementDefinitions({html, newsletterUuid: newsletter.get('uuid')});
|
|
assert.equal(replacements.length, 0);
|
|
});
|
|
|
|
it('returns a replacement if it is used', function () {
|
|
const html = 'Hello world %%{uuid}%%';
|
|
const replacements = emailRenderer.buildReplacementDefinitions({html, newsletterUuid: newsletter.get('uuid')});
|
|
assert.equal(replacements.length, 1);
|
|
assert.equal(replacements[0].token.toString(), '/%%\\{uuid\\}%%/g');
|
|
assert.equal(replacements[0].id, 'uuid');
|
|
assert.equal(replacements[0].getValue(member), 'myuuid');
|
|
});
|
|
|
|
it('returns a replacement only once if used multiple times', function () {
|
|
const html = 'Hello world %%{uuid}%% And %%{uuid}%%';
|
|
const replacements = emailRenderer.buildReplacementDefinitions({html, newsletterUuid: newsletter.get('uuid')});
|
|
assert.equal(replacements.length, 1);
|
|
assert.equal(replacements[0].token.toString(), '/%%\\{uuid\\}%%/g');
|
|
assert.equal(replacements[0].id, 'uuid');
|
|
assert.equal(replacements[0].getValue(member), 'myuuid');
|
|
});
|
|
|
|
it('returns correct first name', function () {
|
|
const html = 'Hello %%{first_name}%%,';
|
|
const replacements = emailRenderer.buildReplacementDefinitions({html, newsletterUuid: newsletter.get('uuid')});
|
|
assert.equal(replacements.length, 1);
|
|
assert.equal(replacements[0].token.toString(), '/%%\\{first_name\\}%%/g');
|
|
assert.equal(replacements[0].id, 'first_name');
|
|
assert.equal(replacements[0].getValue(member), 'Test');
|
|
});
|
|
|
|
it('returns correct unsubscribe url', function () {
|
|
const html = 'Hello %%{unsubscribe_url}%%,';
|
|
const replacements = emailRenderer.buildReplacementDefinitions({html, newsletterUuid: newsletter.get('uuid')});
|
|
assert.equal(replacements.length, 1);
|
|
assert.equal(replacements[0].token.toString(), '/%%\\{unsubscribe_url\\}%%/g');
|
|
assert.equal(replacements[0].id, 'unsubscribe_url');
|
|
assert.equal(replacements[0].getValue(member), `http://example.com/subdirectory/unsubscribe/?uuid=myuuid&newsletter=newsletteruuid`);
|
|
});
|
|
|
|
it('returns correct name', function () {
|
|
const html = 'Hello %%{name}%%,';
|
|
const replacements = emailRenderer.buildReplacementDefinitions({html, newsletterUuid: newsletter.get('uuid')});
|
|
assert.equal(replacements.length, 1);
|
|
assert.equal(replacements[0].token.toString(), '/%%\\{name\\}%%/g');
|
|
assert.equal(replacements[0].id, 'name');
|
|
assert.equal(replacements[0].getValue(member), 'Test User');
|
|
});
|
|
|
|
it('returns correct email', function () {
|
|
const html = 'Hello %%{email}%%,';
|
|
const replacements = emailRenderer.buildReplacementDefinitions({html, newsletterUuid: newsletter.get('uuid')});
|
|
assert.equal(replacements.length, 1);
|
|
assert.equal(replacements[0].token.toString(), '/%%\\{email\\}%%/g');
|
|
assert.equal(replacements[0].id, 'email');
|
|
assert.equal(replacements[0].getValue(member), 'test@example.com');
|
|
});
|
|
|
|
it('returns correct status', function () {
|
|
const html = 'Hello %%{status}%%,';
|
|
const replacements = emailRenderer.buildReplacementDefinitions({html, newsletterUuid: newsletter.get('uuid')});
|
|
assert.equal(replacements.length, 1);
|
|
assert.equal(replacements[0].token.toString(), '/%%\\{status\\}%%/g');
|
|
assert.equal(replacements[0].id, 'status');
|
|
assert.equal(replacements[0].getValue(member), 'free');
|
|
});
|
|
|
|
it('returns mapped complimentary status', function () {
|
|
member.status = 'comped';
|
|
const html = 'Hello %%{status}%%,';
|
|
const replacements = emailRenderer.buildReplacementDefinitions({html, newsletterUuid: newsletter.get('uuid')});
|
|
assert.equal(replacements.length, 1);
|
|
assert.equal(replacements[0].token.toString(), '/%%\\{status\\}%%/g');
|
|
assert.equal(replacements[0].id, 'status');
|
|
assert.equal(replacements[0].getValue(member), 'complimentary');
|
|
});
|
|
|
|
it('returns manage_account_url', function () {
|
|
const html = 'Hello %%{manage_account_url}%%,';
|
|
const replacements = emailRenderer.buildReplacementDefinitions({html, newsletterUuid: newsletter.get('uuid')});
|
|
assert.equal(replacements.length, 1);
|
|
assert.equal(replacements[0].token.toString(), '/%%\\{manage_account_url\\}%%/g');
|
|
assert.equal(replacements[0].id, 'manage_account_url');
|
|
assert.equal(replacements[0].getValue(member), 'http://example.com/subdirectory/#/portal/account');
|
|
});
|
|
|
|
it('returns status_text', function () {
|
|
const html = 'Hello %%{status_text}%%,';
|
|
const replacements = emailRenderer.buildReplacementDefinitions({html, newsletterUuid: newsletter.get('uuid')});
|
|
assert.equal(replacements.length, 1);
|
|
assert.equal(replacements[0].token.toString(), '/%%\\{status_text\\}%%/g');
|
|
assert.equal(replacements[0].id, 'status_text');
|
|
assert.equal(replacements[0].getValue(member), 'You are currently subscribed to the free plan.');
|
|
});
|
|
|
|
it('returns correct createdAt', function () {
|
|
const html = 'Hello %%{created_at}%%,';
|
|
const replacements = emailRenderer.buildReplacementDefinitions({html, newsletterUuid: newsletter.get('uuid')});
|
|
assert.equal(replacements.length, 1);
|
|
assert.equal(replacements[0].token.toString(), '/%%\\{created_at\\}%%/g');
|
|
assert.equal(replacements[0].id, 'created_at');
|
|
assert.equal(replacements[0].getValue(member), '13 March 2023');
|
|
});
|
|
|
|
it('returns missing created at', function () {
|
|
member.createdAt = null;
|
|
const html = 'Hello %%{created_at}%%,';
|
|
const replacements = emailRenderer.buildReplacementDefinitions({html, newsletterUuid: newsletter.get('uuid')});
|
|
assert.equal(replacements.length, 1);
|
|
assert.equal(replacements[0].token.toString(), '/%%\\{created_at\\}%%/g');
|
|
assert.equal(replacements[0].id, 'created_at');
|
|
assert.equal(replacements[0].getValue(member), '');
|
|
});
|
|
|
|
it('supports fallback values', function () {
|
|
const html = 'Hey %%{first_name, "there"}%%,';
|
|
const replacements = emailRenderer.buildReplacementDefinitions({html, newsletterUuid: newsletter.get('uuid')});
|
|
assert.equal(replacements.length, 1);
|
|
assert.equal(replacements[0].token.toString(), '/%%\\{first_name, (?:"|")there(?:"|")\\}%%/g');
|
|
assert.equal(replacements[0].id, 'first_name_2');
|
|
assert.equal(replacements[0].getValue(member), 'Test');
|
|
|
|
// In case of empty name
|
|
assert.equal(replacements[0].getValue({name: ''}), 'there');
|
|
});
|
|
|
|
it('supports combination of multiple fallback values', function () {
|
|
const html = 'Hey %%{first_name, "there"}%%, %%{first_name, "member"}%% %%{first_name}%% %%{first_name, "there"}%%';
|
|
const replacements = emailRenderer.buildReplacementDefinitions({html, newsletterUuid: newsletter.get('uuid')});
|
|
assert.equal(replacements.length, 3);
|
|
assert.equal(replacements[0].token.toString(), '/%%\\{first_name, (?:"|")there(?:"|")\\}%%/g');
|
|
assert.equal(replacements[0].id, 'first_name_2');
|
|
assert.equal(replacements[0].getValue(member), 'Test');
|
|
|
|
// In case of empty name
|
|
assert.equal(replacements[0].getValue({name: ''}), 'there');
|
|
|
|
assert.equal(replacements[1].token.toString(), '/%%\\{first_name, (?:"|")member(?:"|")\\}%%/g');
|
|
assert.equal(replacements[1].id, 'first_name_3');
|
|
assert.equal(replacements[1].getValue(member), 'Test');
|
|
|
|
// In case of empty name
|
|
assert.equal(replacements[1].getValue({name: ''}), 'member');
|
|
|
|
assert.equal(replacements[2].token.toString(), '/%%\\{first_name\\}%%/g');
|
|
assert.equal(replacements[2].id, 'first_name');
|
|
assert.equal(replacements[2].getValue(member), 'Test');
|
|
|
|
// In case of empty name
|
|
assert.equal(replacements[2].getValue({name: ''}), '');
|
|
});
|
|
});
|
|
|
|
describe('getMemberStatusText', function () {
|
|
let emailRenderer;
|
|
|
|
beforeEach(function () {
|
|
emailRenderer = new EmailRenderer({
|
|
urlUtils: {
|
|
urlFor: () => 'http://example.com/subdirectory/'
|
|
},
|
|
labs: {
|
|
isSet: () => true
|
|
},
|
|
settingsCache: {
|
|
get: (key) => {
|
|
if (key === 'timezone') {
|
|
return 'UTC';
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
it('Returns for free member', function () {
|
|
const member = {
|
|
id: '456',
|
|
uuid: 'myuuid',
|
|
name: 'Test User',
|
|
email: 'test@example.com',
|
|
createdAt: new Date(2023, 2, 13, 12, 0),
|
|
status: 'free'
|
|
};
|
|
|
|
const result = emailRenderer.getMemberStatusText(member);
|
|
assert.equal(result, 'You are currently subscribed to the free plan.');
|
|
});
|
|
|
|
it('Returns for active paid member', function () {
|
|
const member = {
|
|
id: '456',
|
|
uuid: 'myuuid',
|
|
name: 'Test User',
|
|
email: 'test@example.com',
|
|
createdAt: new Date(2023, 2, 13, 12, 0),
|
|
status: 'paid',
|
|
subscriptions: [
|
|
{
|
|
status: 'active',
|
|
current_period_end: new Date(2023, 2, 13, 12, 0),
|
|
cancel_at_period_end: false
|
|
}
|
|
]
|
|
};
|
|
|
|
const result = emailRenderer.getMemberStatusText(member);
|
|
assert.equal(result, 'Your subscription will renew on 13 March 2023.');
|
|
});
|
|
|
|
it('Returns for canceled paid member', function () {
|
|
const member = {
|
|
id: '456',
|
|
uuid: 'myuuid',
|
|
name: 'Test User',
|
|
email: 'test@example.com',
|
|
createdAt: new Date(2023, 2, 13, 12, 0),
|
|
status: 'paid',
|
|
subscriptions: [
|
|
{
|
|
status: 'active',
|
|
current_period_end: new Date(2023, 2, 13, 12, 0),
|
|
cancel_at_period_end: true
|
|
}
|
|
]
|
|
};
|
|
|
|
const result = emailRenderer.getMemberStatusText(member);
|
|
assert.equal(result, 'Your subscription has been canceled and will expire on 13 March 2023. You can resume your subscription via your account settings.');
|
|
});
|
|
|
|
it('Returns for expired paid member', function () {
|
|
const member = {
|
|
id: '456',
|
|
uuid: 'myuuid',
|
|
name: 'Test User',
|
|
email: 'test@example.com',
|
|
createdAt: new Date(2023, 2, 13, 12, 0),
|
|
status: 'paid',
|
|
subscriptions: [
|
|
{
|
|
status: 'canceled',
|
|
current_period_end: new Date(2023, 2, 13, 12, 0),
|
|
cancel_at_period_end: true
|
|
}
|
|
],
|
|
tiers: []
|
|
};
|
|
|
|
const result = emailRenderer.getMemberStatusText(member);
|
|
assert.equal(result, 'Your subscription has expired.');
|
|
});
|
|
|
|
it('Returns for trialing paid member', function () {
|
|
const member = {
|
|
id: '456',
|
|
uuid: 'myuuid',
|
|
name: 'Test User',
|
|
email: 'test@example.com',
|
|
createdAt: new Date(2023, 2, 13, 12, 0),
|
|
status: 'paid',
|
|
subscriptions: [
|
|
{
|
|
status: 'trialing',
|
|
trial_end_at: new Date(2050, 2, 13, 12, 0),
|
|
current_period_end: new Date(2023, 2, 13, 12, 0),
|
|
cancel_at_period_end: false
|
|
}
|
|
],
|
|
tiers: []
|
|
};
|
|
|
|
const result = emailRenderer.getMemberStatusText(member);
|
|
assert.equal(result, 'Your free trial ends on 13 March 2050, at which time you will be charged the regular price. You can always cancel before then.');
|
|
});
|
|
|
|
it('Returns for infinite complimentary member', function () {
|
|
const member = {
|
|
id: '456',
|
|
uuid: 'myuuid',
|
|
name: 'Test User',
|
|
email: 'test@example.com',
|
|
createdAt: new Date(2023, 2, 13, 12, 0),
|
|
status: 'comped',
|
|
subscriptions: [],
|
|
tiers: [
|
|
{
|
|
name: 'Silver',
|
|
expiry_at: null
|
|
}
|
|
]
|
|
};
|
|
|
|
const result = emailRenderer.getMemberStatusText(member);
|
|
assert.equal(result, '');
|
|
});
|
|
|
|
it('Returns for expiring complimentary member', function () {
|
|
const member = {
|
|
id: '456',
|
|
uuid: 'myuuid',
|
|
name: 'Test User',
|
|
email: 'test@example.com',
|
|
createdAt: new Date(2023, 2, 13, 12, 0),
|
|
status: 'comped',
|
|
subscriptions: [],
|
|
tiers: [
|
|
{
|
|
name: 'Silver',
|
|
expiry_at: new Date(2050, 2, 13, 12, 0)
|
|
}
|
|
]
|
|
};
|
|
|
|
const result = emailRenderer.getMemberStatusText(member);
|
|
assert.equal(result, 'Your subscription will expire on 13 March 2050.');
|
|
});
|
|
|
|
it('Returns for a paid member without subscriptions', function () {
|
|
const member = {
|
|
id: '456',
|
|
uuid: 'myuuid',
|
|
name: 'Test User',
|
|
email: 'test@example.com',
|
|
createdAt: new Date(2023, 2, 13, 12, 0),
|
|
status: 'paid',
|
|
subscriptions: [],
|
|
tiers: [
|
|
{
|
|
name: 'Silver',
|
|
expiry_at: new Date(2050, 2, 13, 12, 0)
|
|
}
|
|
]
|
|
};
|
|
|
|
const result = emailRenderer.getMemberStatusText(member);
|
|
assert.equal(result, 'Your subscription has been canceled and will expire on 13 March 2050. You can resume your subscription via your account settings.');
|
|
});
|
|
|
|
it('Returns for an infinte paid member without subscriptions', function () {
|
|
const member = {
|
|
id: '456',
|
|
uuid: 'myuuid',
|
|
name: 'Test User',
|
|
email: 'test@example.com',
|
|
createdAt: new Date(2023, 2, 13, 12, 0),
|
|
status: 'paid',
|
|
subscriptions: [],
|
|
tiers: [
|
|
{
|
|
name: 'Silver',
|
|
expiry_at: null
|
|
}
|
|
]
|
|
};
|
|
|
|
const result = emailRenderer.getMemberStatusText(member);
|
|
assert.equal(result, '');
|
|
});
|
|
});
|
|
|
|
describe('getSubject', function () {
|
|
const emailRenderer = new EmailRenderer({
|
|
urlUtils: {
|
|
urlFor: () => 'http://example.com'
|
|
},
|
|
labs: {
|
|
isSet: () => true
|
|
}
|
|
});
|
|
|
|
it('returns a post with correct subject from meta', function () {
|
|
const post = createModel({
|
|
posts_meta: createModel({
|
|
email_subject: 'Test Newsletter'
|
|
}),
|
|
title: 'Sample Post',
|
|
loaded: ['posts_meta']
|
|
});
|
|
let response = emailRenderer.getSubject(post);
|
|
response.should.equal('Test Newsletter');
|
|
});
|
|
|
|
it('returns a post with correct subject from title', function () {
|
|
const post = createModel({
|
|
posts_meta: createModel({
|
|
email_subject: ''
|
|
}),
|
|
title: 'Sample Post',
|
|
loaded: ['posts_meta']
|
|
});
|
|
let response = emailRenderer.getSubject(post);
|
|
response.should.equal('Sample Post');
|
|
});
|
|
});
|
|
|
|
describe('getFromAddress', function () {
|
|
let siteTitle = 'Test Blog';
|
|
let emailRenderer = new EmailRenderer({
|
|
settingsCache: {
|
|
get: (key) => {
|
|
if (key === 'title') {
|
|
return siteTitle;
|
|
}
|
|
}
|
|
},
|
|
settingsHelpers: {
|
|
getNoReplyAddress: () => {
|
|
return 'reply@example.com';
|
|
}
|
|
},
|
|
labs: {
|
|
isSet: () => true
|
|
}
|
|
});
|
|
|
|
it('returns correct from address for newsletter', function () {
|
|
const newsletter = createModel({
|
|
sender_email: 'ghost@example.com',
|
|
sender_name: 'Ghost'
|
|
});
|
|
const response = emailRenderer.getFromAddress({}, newsletter);
|
|
response.should.equal('"Ghost" <ghost@example.com>');
|
|
});
|
|
|
|
it('defaults to site title and domain', function () {
|
|
const newsletter = createModel({
|
|
sender_email: '',
|
|
sender_name: ''
|
|
});
|
|
const response = emailRenderer.getFromAddress({}, newsletter);
|
|
response.should.equal('"Test Blog" <reply@example.com>');
|
|
});
|
|
|
|
it('changes localhost domain to proper domain in development', function () {
|
|
const newsletter = createModel({
|
|
sender_email: 'example@localhost',
|
|
sender_name: ''
|
|
});
|
|
const response = emailRenderer.getFromAddress({}, newsletter);
|
|
response.should.equal('"Test Blog" <localhost@example.com>');
|
|
});
|
|
|
|
it('ignores empty sender names', function () {
|
|
siteTitle = '';
|
|
const newsletter = createModel({
|
|
sender_email: 'example@example.com',
|
|
sender_name: ''
|
|
});
|
|
const response = emailRenderer.getFromAddress({}, newsletter);
|
|
response.should.equal('example@example.com');
|
|
});
|
|
});
|
|
|
|
describe('getReplyToAddress', function () {
|
|
let emailRenderer = new EmailRenderer({
|
|
settingsCache: {
|
|
get: (key) => {
|
|
if (key === 'title') {
|
|
return 'Test Blog';
|
|
}
|
|
}
|
|
},
|
|
settingsHelpers: {
|
|
getMembersSupportAddress: () => {
|
|
return 'support@example.com';
|
|
},
|
|
getNoReplyAddress: () => {
|
|
return 'reply@example.com';
|
|
}
|
|
},
|
|
labs: {
|
|
isSet: () => true
|
|
}
|
|
});
|
|
|
|
it('returns support address', function () {
|
|
const newsletter = createModel({
|
|
sender_email: 'ghost@example.com',
|
|
sender_name: 'Ghost',
|
|
sender_reply_to: 'support'
|
|
});
|
|
const response = emailRenderer.getReplyToAddress({}, newsletter);
|
|
response.should.equal('support@example.com');
|
|
});
|
|
|
|
it('returns correct reply to address for newsletter', function () {
|
|
const newsletter = createModel({
|
|
sender_email: 'ghost@example.com',
|
|
sender_name: 'Ghost',
|
|
sender_reply_to: 'newsletter'
|
|
});
|
|
const response = emailRenderer.getReplyToAddress({}, newsletter);
|
|
response.should.equal(`"Ghost" <ghost@example.com>`);
|
|
});
|
|
});
|
|
|
|
describe('getSegments', function () {
|
|
let emailRenderer = new EmailRenderer({
|
|
renderers: {
|
|
lexical: {
|
|
render: () => {
|
|
return '<p> Lexical Test</p>';
|
|
}
|
|
},
|
|
mobiledoc: {
|
|
render: () => {
|
|
return '<p> Mobiledoc Test</p>';
|
|
}
|
|
}
|
|
},
|
|
getPostUrl: () => {
|
|
return 'http://example.com/post-id';
|
|
},
|
|
labs: {
|
|
isSet: () => true
|
|
}
|
|
});
|
|
|
|
it('returns correct empty segment for post', function () {
|
|
let post = {
|
|
get: (key) => {
|
|
if (key === 'lexical') {
|
|
return '{}';
|
|
}
|
|
}
|
|
};
|
|
let response = emailRenderer.getSegments(post);
|
|
response.should.eql([null]);
|
|
|
|
post = {
|
|
get: (key) => {
|
|
if (key === 'mobiledoc') {
|
|
return '{}';
|
|
}
|
|
}
|
|
};
|
|
response = emailRenderer.getSegments(post);
|
|
response.should.eql([null]);
|
|
});
|
|
|
|
it('returns correct segments for post with members only card', function () {
|
|
emailRenderer = new EmailRenderer({
|
|
renderers: {
|
|
lexical: {
|
|
render: () => {
|
|
return '<p> Lexical Test <!--members-only--> members only section</p>';
|
|
}
|
|
}
|
|
},
|
|
getPostUrl: () => {
|
|
return 'http://example.com/post-id';
|
|
},
|
|
labs: {
|
|
isSet: () => true
|
|
}
|
|
});
|
|
|
|
let post = {
|
|
get: (key) => {
|
|
if (key === 'lexical') {
|
|
return '{}';
|
|
}
|
|
}
|
|
};
|
|
let response = emailRenderer.getSegments(post);
|
|
response.should.eql(['status:free', 'status:-free']);
|
|
});
|
|
|
|
it('returns correct segments for post with email card', function () {
|
|
emailRenderer = new EmailRenderer({
|
|
renderers: {
|
|
lexical: {
|
|
render: () => {
|
|
return '<html> <div> Lexical Test </div> <div data-gh-segment="status:-free"> members only section</div> </html>';
|
|
}
|
|
}
|
|
},
|
|
getPostUrl: () => {
|
|
return 'http://example.com/post-id';
|
|
},
|
|
labs: {
|
|
isSet: () => true
|
|
}
|
|
});
|
|
|
|
let post = {
|
|
get: (key) => {
|
|
if (key === 'lexical') {
|
|
return '{}';
|
|
}
|
|
}
|
|
};
|
|
let response = emailRenderer.getSegments(post);
|
|
response.should.eql(['status:free', 'status:-free']);
|
|
});
|
|
});
|
|
|
|
describe('renderBody', function () {
|
|
let renderedPost = '<p>Lexical Test</p>';
|
|
let postUrl = 'http://example.com';
|
|
let customSettings = {};
|
|
let emailRenderer = new EmailRenderer({
|
|
audienceFeedbackService: {
|
|
buildLink: (_uuid, _postId, score) => {
|
|
return new URL('http://feedback-link.com/?score=' + encodeURIComponent(score) + '&uuid=' + encodeURIComponent(_uuid));
|
|
}
|
|
},
|
|
urlUtils: {
|
|
urlFor: (type) => {
|
|
if (type === 'image') {
|
|
return 'http://icon.example.com';
|
|
}
|
|
return 'http://example.com/subdirectory';
|
|
},
|
|
isSiteUrl: (u) => {
|
|
return u.hostname === 'example.com';
|
|
}
|
|
},
|
|
settingsCache: {
|
|
get: (key) => {
|
|
if (customSettings[key]) {
|
|
return customSettings[key];
|
|
}
|
|
if (key === 'accent_color') {
|
|
return '#ffffff';
|
|
}
|
|
if (key === 'timezone') {
|
|
return 'Etc/UTC';
|
|
}
|
|
if (key === 'title') {
|
|
return 'Test Blog';
|
|
}
|
|
if (key === 'icon') {
|
|
return 'ICON';
|
|
}
|
|
}
|
|
},
|
|
getPostUrl: () => {
|
|
return postUrl;
|
|
},
|
|
renderers: {
|
|
lexical: {
|
|
render: () => {
|
|
return renderedPost;
|
|
}
|
|
},
|
|
mobiledoc: {
|
|
render: () => {
|
|
return '<p> Mobiledoc Test</p>';
|
|
}
|
|
}
|
|
},
|
|
linkReplacer,
|
|
memberAttributionService: {
|
|
addPostAttributionTracking: (u) => {
|
|
u.searchParams.append('post_tracking', 'added');
|
|
return u;
|
|
}
|
|
},
|
|
linkTracking: {
|
|
service: {
|
|
addTrackingToUrl: (u, _post, uuid) => {
|
|
return new URL('http://tracked-link.com/?m=' + encodeURIComponent(uuid) + '&url=' + encodeURIComponent(u.href));
|
|
}
|
|
}
|
|
},
|
|
outboundLinkTagger: {
|
|
addToUrl: (u, newsletter) => {
|
|
u.searchParams.append('source_tracking', newsletter?.get('name') ?? 'site');
|
|
return u;
|
|
}
|
|
},
|
|
labs: {
|
|
isSet: () => true
|
|
}
|
|
});
|
|
let basePost;
|
|
|
|
beforeEach(function () {
|
|
basePost = {
|
|
lexical: '{}',
|
|
visibility: 'public',
|
|
title: 'Test Post',
|
|
plaintext: 'Test plaintext for post',
|
|
custom_excerpt: null,
|
|
authors: [
|
|
createModel({
|
|
name: 'Test Author'
|
|
})
|
|
],
|
|
posts_meta: createModel({
|
|
feature_image_alt: null,
|
|
feature_image_caption: null
|
|
}),
|
|
loaded: ['posts_meta']
|
|
};
|
|
postUrl = 'http://example.com';
|
|
customSettings = {};
|
|
});
|
|
|
|
it('returns feedback buttons and unsubcribe links', async function () {
|
|
const post = createModel(basePost);
|
|
const newsletter = createModel({
|
|
header_image: null,
|
|
name: 'Test Newsletter',
|
|
show_badge: false,
|
|
feedback_enabled: true,
|
|
show_post_title_section: true
|
|
});
|
|
const segment = null;
|
|
const options = {};
|
|
|
|
let response = await emailRenderer.renderBody(
|
|
post,
|
|
newsletter,
|
|
segment,
|
|
options
|
|
);
|
|
|
|
const $ = cheerio.load(response.html);
|
|
|
|
response.plaintext.should.containEql('Test Post');
|
|
|
|
// Unsubscribe button included
|
|
response.plaintext.should.containEql('Unsubscribe [%%{unsubscribe_url}%%]');
|
|
response.html.should.containEql('Unsubscribe');
|
|
response.replacements.length.should.eql(2);
|
|
response.replacements.should.match([
|
|
{
|
|
id: 'uuid'
|
|
},
|
|
{
|
|
id: 'unsubscribe_url',
|
|
token: /%%\{unsubscribe_url\}%%/g
|
|
}
|
|
]);
|
|
|
|
response.plaintext.should.containEql('http://example.com');
|
|
should($('.preheader').text()).eql('Test plaintext for post');
|
|
response.html.should.containEql('Test Post');
|
|
response.html.should.containEql('http://example.com');
|
|
|
|
// Does not include Ghost badge
|
|
response.html.should.not.containEql('https://ghost.org/');
|
|
|
|
// Test feedback buttons included
|
|
response.html.should.containEql('http://feedback-link.com/?score=1');
|
|
response.html.should.containEql('http://feedback-link.com/?score=0');
|
|
});
|
|
|
|
it('uses custom excerpt as preheader', async function () {
|
|
const post = createModel({...basePost, custom_excerpt: 'Custom excerpt'});
|
|
const newsletter = createModel({
|
|
header_image: null,
|
|
name: 'Test Newsletter',
|
|
show_badge: false,
|
|
feedback_enabled: true,
|
|
show_post_title_section: true
|
|
});
|
|
const segment = null;
|
|
const options = {};
|
|
|
|
let response = await emailRenderer.renderBody(
|
|
post,
|
|
newsletter,
|
|
segment,
|
|
options
|
|
);
|
|
|
|
const $ = cheerio.load(response.html);
|
|
should($('.preheader').text()).eql('Custom excerpt');
|
|
});
|
|
|
|
it('only includes first author if more than 2', async function () {
|
|
const post = createModel({...basePost, authors: [
|
|
createModel({
|
|
name: 'A'
|
|
}),
|
|
createModel({
|
|
name: 'B'
|
|
}),
|
|
createModel({
|
|
name: 'C'
|
|
})
|
|
]});
|
|
const newsletter = createModel({
|
|
header_image: null,
|
|
name: 'Test Newsletter',
|
|
show_badge: false,
|
|
feedback_enabled: true,
|
|
show_post_title_section: true
|
|
});
|
|
const segment = null;
|
|
const options = {};
|
|
|
|
let response = await emailRenderer.renderBody(
|
|
post,
|
|
newsletter,
|
|
segment,
|
|
options
|
|
);
|
|
|
|
assert.match(response.html, /By A & 2 others/);
|
|
assert.match(response.plaintext, /By A & 2 others/);
|
|
});
|
|
|
|
it('includes header icon, title, name', async function () {
|
|
const post = createModel(basePost);
|
|
const newsletter = createModel({
|
|
header_image: null,
|
|
name: 'Test Newsletter',
|
|
show_badge: false,
|
|
feedback_enabled: true,
|
|
|
|
show_header_icon: true,
|
|
show_header_title: true,
|
|
show_header_name: true,
|
|
show_post_title_section: true
|
|
});
|
|
const segment = null;
|
|
const options = {};
|
|
|
|
let response = await emailRenderer.renderBody(
|
|
post,
|
|
newsletter,
|
|
segment,
|
|
options
|
|
);
|
|
|
|
response.html.should.containEql('http://icon.example.com');
|
|
assert.match(response.html, /class="site-title"[^>]*?>Test Blog/);
|
|
assert.match(response.html, /class="site-subtitle"[^>]*?>Test Newsletter/);
|
|
});
|
|
|
|
it('includes header icon and name', async function () {
|
|
const post = createModel(basePost);
|
|
const newsletter = createModel({
|
|
header_image: null,
|
|
name: 'Test Newsletter',
|
|
show_badge: false,
|
|
feedback_enabled: true,
|
|
|
|
show_header_icon: true,
|
|
show_header_title: false,
|
|
show_header_name: true,
|
|
show_post_title_section: true
|
|
});
|
|
const segment = null;
|
|
const options = {};
|
|
|
|
let response = await emailRenderer.renderBody(
|
|
post,
|
|
newsletter,
|
|
segment,
|
|
options
|
|
);
|
|
|
|
response.html.should.containEql('http://icon.example.com');
|
|
assert.match(response.html, /class="site-title"[^>]*?>Test Newsletter/);
|
|
});
|
|
|
|
it('includes Ghost badge if enabled', async function () {
|
|
const post = createModel(basePost);
|
|
const newsletter = createModel({
|
|
header_image: null,
|
|
name: 'Test Newsletter',
|
|
show_badge: true,
|
|
feedback_enabled: false
|
|
});
|
|
const segment = null;
|
|
const options = {};
|
|
|
|
let response = await emailRenderer.renderBody(
|
|
post,
|
|
newsletter,
|
|
segment,
|
|
options
|
|
);
|
|
|
|
// Does include include Ghost badge
|
|
assert.match(response.html, /https:\/\/ghost.org\//);
|
|
|
|
// Test feedback buttons not included
|
|
response.html.should.not.containEql('http://feedback-link.com/?score=1');
|
|
response.html.should.not.containEql('http://feedback-link.com/?score=0');
|
|
});
|
|
|
|
it('includes newsletter footer as raw html', async function () {
|
|
const post = createModel(basePost);
|
|
const newsletter = createModel({
|
|
header_image: null,
|
|
name: 'Test Newsletter',
|
|
show_badge: true,
|
|
feedback_enabled: false,
|
|
footer_content: '<p>Test footer</p>'
|
|
});
|
|
const segment = null;
|
|
const options = {};
|
|
|
|
let response = await emailRenderer.renderBody(
|
|
post,
|
|
newsletter,
|
|
segment,
|
|
options
|
|
);
|
|
|
|
// Test footer
|
|
response.html.should.containEql('Test footer</p>'); // begin tag skipped because style is inlined in that tag
|
|
response.plaintext.should.containEql('Test footer');
|
|
});
|
|
|
|
it('replaces all links except the unsubscribe and feedback links', async function () {
|
|
const post = createModel(basePost);
|
|
const newsletter = createModel({
|
|
header_image: null,
|
|
name: 'Test Newsletter',
|
|
show_badge: true,
|
|
feedback_enabled: true,
|
|
show_post_title_section: true
|
|
});
|
|
const segment = null;
|
|
const options = {
|
|
clickTrackingEnabled: true
|
|
};
|
|
|
|
renderedPost = '<p>Lexical Test</p><p><a href="https://external-domain.com/?ref=123">Hello</a><a href="https://example.com/?ref=123"><img src="example" /></a></p>';
|
|
|
|
let response = await emailRenderer.renderBody(
|
|
post,
|
|
newsletter,
|
|
segment,
|
|
options
|
|
);
|
|
|
|
// Check all links have domain tracked-link.com
|
|
const $ = cheerio.load(response.html);
|
|
const links = [];
|
|
for (const link of $('a').toArray()) {
|
|
const href = $(link).attr('href');
|
|
links.push(href);
|
|
if (href.includes('unsubscribe_url')) {
|
|
href.should.eql('%%{unsubscribe_url}%%');
|
|
} else if (href.includes('feedback-link.com')) {
|
|
href.should.containEql('%%{uuid}%%');
|
|
} else {
|
|
href.should.containEql('tracked-link.com');
|
|
href.should.containEql('m=%%{uuid}%%');
|
|
}
|
|
}
|
|
|
|
// Update the following array when you make changes to the email template, check if replacements are correct for each newly added link.
|
|
assert.deepEqual(links, [
|
|
`http://tracked-link.com/?m=%%{uuid}%%&url=http%3A%2F%2Fexample.com%2F%3Fsource_tracking%3DTest%2BNewsletter%26post_tracking%3Dadded`,
|
|
`http://tracked-link.com/?m=%%{uuid}%%&url=http%3A%2F%2Fexample.com%2F%3Fsource_tracking%3DTest%2BNewsletter%26post_tracking%3Dadded`,
|
|
`http://tracked-link.com/?m=%%{uuid}%%&url=https%3A%2F%2Fexternal-domain.com%2F%3Fref%3D123%26source_tracking%3Dsite`,
|
|
`http://tracked-link.com/?m=%%{uuid}%%&url=https%3A%2F%2Fexample.com%2F%3Fref%3D123%26source_tracking%3DTest%2BNewsletter%26post_tracking%3Dadded`,
|
|
`http://feedback-link.com/?score=1&uuid=%%{uuid}%%`,
|
|
`http://feedback-link.com/?score=0&uuid=%%{uuid}%%`,
|
|
`http://feedback-link.com/?score=1&uuid=%%{uuid}%%`,
|
|
`http://feedback-link.com/?score=0&uuid=%%{uuid}%%`,
|
|
`%%{unsubscribe_url}%%`,
|
|
`http://tracked-link.com/?m=%%{uuid}%%&url=https%3A%2F%2Fghost.org%2F%3Fsource_tracking%3Dsite`
|
|
]);
|
|
|
|
// Check uuid in replacements
|
|
response.replacements.length.should.eql(2);
|
|
response.replacements[0].id.should.eql('uuid');
|
|
response.replacements[0].token.should.eql(/%%\{uuid\}%%/g);
|
|
response.replacements[1].id.should.eql('unsubscribe_url');
|
|
response.replacements[1].token.should.eql(/%%\{unsubscribe_url\}%%/g);
|
|
});
|
|
|
|
it('removes data-gh-segment and renders paywall', async function () {
|
|
renderedPost = '<div> Lexical Test </div> <div data-gh-segment="status:-free"> members only section</div> some text for both <!--members-only--> finishing part only for members';
|
|
let post = {
|
|
related: () => {
|
|
return null;
|
|
},
|
|
get: (key) => {
|
|
if (key === 'lexical') {
|
|
return '{}';
|
|
}
|
|
|
|
if (key === 'visibility') {
|
|
return 'paid';
|
|
}
|
|
|
|
if (key === 'title') {
|
|
return 'Test Post';
|
|
}
|
|
},
|
|
getLazyRelation: () => {
|
|
return {
|
|
models: [{
|
|
get: (key) => {
|
|
if (key === 'name') {
|
|
return 'Test Author';
|
|
}
|
|
}
|
|
}]
|
|
};
|
|
}
|
|
};
|
|
let newsletter = {
|
|
get: (key) => {
|
|
if (key === 'header_image') {
|
|
return null;
|
|
}
|
|
|
|
if (key === 'name') {
|
|
return 'Test Newsletter';
|
|
}
|
|
|
|
if (key === 'badge') {
|
|
return false;
|
|
}
|
|
|
|
if (key === 'feedback_enabled') {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
let options = {};
|
|
|
|
let response = await emailRenderer.renderBody(
|
|
post,
|
|
newsletter,
|
|
'status:free',
|
|
options
|
|
);
|
|
|
|
response.plaintext.should.containEql('Test Post');
|
|
response.plaintext.should.containEql('Unsubscribe [%%{unsubscribe_url}%%]');
|
|
response.plaintext.should.containEql('http://example.com');
|
|
response.html.should.containEql('Test Post');
|
|
response.html.should.containEql('Unsubscribe');
|
|
response.html.should.containEql('http://example.com');
|
|
response.replacements.length.should.eql(2);
|
|
response.replacements.should.match([
|
|
{
|
|
id: 'uuid'
|
|
},
|
|
{
|
|
id: 'unsubscribe_url',
|
|
token: /%%\{unsubscribe_url\}%%/g
|
|
}
|
|
]);
|
|
response.html.should.not.containEql('members only section');
|
|
response.html.should.containEql('some text for both');
|
|
response.html.should.not.containEql('finishing part only for members');
|
|
response.html.should.containEql('Become a paid member of Test Blog to get access to all');
|
|
|
|
let responsePaid = await emailRenderer.renderBody(
|
|
post,
|
|
newsletter,
|
|
'status:-free',
|
|
options
|
|
);
|
|
responsePaid.html.should.containEql('members only section');
|
|
responsePaid.html.should.containEql('some text for both');
|
|
responsePaid.html.should.containEql('finishing part only for members');
|
|
responsePaid.html.should.not.containEql('Become a paid member of Test Blog to get access to all');
|
|
});
|
|
|
|
it('should output valid HTML and escape HTML characters in mobiledoc', async function () {
|
|
const post = createModel({
|
|
...basePost,
|
|
title: 'This is\' a blog po"st test <3</body>',
|
|
excerpt: 'This is a blog post test <3</body>',
|
|
authors: [
|
|
createModel({
|
|
name: 'This is a blog post test <3</body>'
|
|
})
|
|
],
|
|
posts_meta: createModel({
|
|
feature_image_alt: 'This is a blog post test <3</body>',
|
|
feature_image_caption: 'This is escaped in the frontend'
|
|
})
|
|
});
|
|
postUrl = 'https://testpost.com/t&es<3t-post"</body>/';
|
|
customSettings = {
|
|
icon: 'icon2<3</body>'
|
|
};
|
|
|
|
const newsletter = createModel({
|
|
feedback_enabled: true,
|
|
name: 'My newsletter <3</body>',
|
|
header_image: 'https://testpost.com/test-post</body>/',
|
|
show_header_icon: true,
|
|
show_header_title: true,
|
|
show_feature_image: true,
|
|
title_font_category: 'sans-serif',
|
|
title_alignment: 'center',
|
|
body_font_category: 'serif',
|
|
show_badge: true,
|
|
show_header_name: true,
|
|
// Note: we don't need to check the footer content because this should contain valid HTML (not text)
|
|
footer_content: '<span>Footer content with valid HTML</span>'
|
|
});
|
|
const segment = null;
|
|
const options = {};
|
|
|
|
const response = await emailRenderer.renderBody(
|
|
post,
|
|
newsletter,
|
|
segment,
|
|
options
|
|
);
|
|
|
|
validateHtml(response.html);
|
|
|
|
// Check footer content is not escaped
|
|
assert.equal(response.html.includes('<span>Footer content with valid HTML</span>'), true, 'Should include footer content without escaping');
|
|
|
|
// Check doesn't contain the non escaped string '<3'
|
|
assert.equal(response.html.includes('<3'), false, 'Should escape HTML characters');
|
|
});
|
|
});
|
|
|
|
describe('getTemplateData', function () {
|
|
let settings = {};
|
|
let labsEnabled = true;
|
|
let emailRenderer;
|
|
|
|
beforeEach(function () {
|
|
settings = {};
|
|
labsEnabled = true;
|
|
emailRenderer = new EmailRenderer({
|
|
audienceFeedbackService: {
|
|
buildLink: (_uuid, _postId, score) => {
|
|
return new URL('http://feedback-link.com/?score=' + encodeURIComponent(score) + '&uuid=' + encodeURIComponent(_uuid));
|
|
}
|
|
},
|
|
urlUtils: {
|
|
urlFor: (type) => {
|
|
if (type === 'image') {
|
|
return 'http://icon.example.com';
|
|
}
|
|
return 'http://example.com/subdirectory';
|
|
},
|
|
isSiteUrl: (u) => {
|
|
return u.hostname === 'example.com';
|
|
}
|
|
},
|
|
settingsCache: {
|
|
get: (key) => {
|
|
return settings[key];
|
|
}
|
|
},
|
|
getPostUrl: () => {
|
|
return 'http://example.com';
|
|
},
|
|
labs: {
|
|
isSet: () => labsEnabled
|
|
},
|
|
models: {
|
|
Post: createModelClass({
|
|
findAll: [
|
|
{
|
|
title: 'Test Post 1',
|
|
published_at: new Date('2018-01-01T00:00:00.000Z'),
|
|
feature_image: 'http://example.com/image.jpg'
|
|
},
|
|
{
|
|
title: 'Test Post 2',
|
|
published_at: new Date('2018-01-01T00:00:00.000Z'),
|
|
feature_image: null
|
|
},
|
|
{
|
|
title: 'Test Post 3',
|
|
published_at: null, // required for full test coverage
|
|
feature_image: null
|
|
}
|
|
]
|
|
})
|
|
}
|
|
});
|
|
});
|
|
|
|
it('uses default accent color', async function () {
|
|
const html = '';
|
|
const post = createModel({
|
|
posts_meta: createModel({}),
|
|
loaded: ['posts_meta']
|
|
});
|
|
const newsletter = createModel({});
|
|
const data = await emailRenderer.getTemplateData({post, newsletter, html, addPaywall: false});
|
|
assert.equal(data.accentColor, '#15212A');
|
|
});
|
|
|
|
it('handles invalid accent color', async function () {
|
|
const html = '';
|
|
settings.accent_color = '#QR';
|
|
const post = createModel({
|
|
posts_meta: createModel({}),
|
|
loaded: ['posts_meta']
|
|
});
|
|
const newsletter = createModel({});
|
|
const data = await emailRenderer.getTemplateData({post, newsletter, html, addPaywall: false});
|
|
assert.equal(data.accentColor, '#15212A');
|
|
});
|
|
|
|
it('uses post published_at', async function () {
|
|
const html = '';
|
|
const post = createModel({
|
|
posts_meta: createModel({}),
|
|
loaded: ['posts_meta'],
|
|
published_at: new Date(0)
|
|
});
|
|
const newsletter = createModel({});
|
|
const data = await emailRenderer.getTemplateData({post, newsletter, html, addPaywall: false});
|
|
assert.equal(data.post.publishedAt, '1 Jan 1970');
|
|
});
|
|
|
|
it('show feature image if post has feature image', async function () {
|
|
const html = '';
|
|
const post = createModel({
|
|
posts_meta: createModel({}),
|
|
loaded: ['posts_meta'],
|
|
published_at: new Date(0),
|
|
feature_image: 'http://example.com/image.jpg'
|
|
});
|
|
const newsletter = createModel({
|
|
show_feature_image: true
|
|
});
|
|
const data = await emailRenderer.getTemplateData({post, newsletter, html, addPaywall: false});
|
|
assert.equal(data.showFeatureImage, true);
|
|
});
|
|
|
|
it('uses newsletter font styles', async function () {
|
|
const html = '';
|
|
const post = createModel({
|
|
posts_meta: createModel({}),
|
|
loaded: ['posts_meta'],
|
|
published_at: new Date(0)
|
|
});
|
|
const newsletter = createModel({
|
|
title_font_category: 'serif',
|
|
title_alignment: 'left',
|
|
body_font_category: 'sans_serif'
|
|
});
|
|
const data = await emailRenderer.getTemplateData({post, newsletter, html, addPaywall: false});
|
|
assert.deepEqual(data.classes, {
|
|
title: 'post-title post-title-serif post-title-left',
|
|
titleLink: 'post-title-link post-title-link-left',
|
|
meta: 'post-meta post-meta-left',
|
|
body: 'post-content-sans-serif'
|
|
});
|
|
});
|
|
|
|
it('show comment CTA is enabled if labs disabled', async function () {
|
|
labsEnabled = false;
|
|
settings.comments_enabled = 'all';
|
|
const html = '';
|
|
const post = createModel({
|
|
posts_meta: createModel({}),
|
|
loaded: ['posts_meta'],
|
|
published_at: new Date(0)
|
|
});
|
|
const newsletter = createModel({
|
|
title_font_category: 'serif',
|
|
title_alignment: 'left',
|
|
body_font_category: 'sans_serif',
|
|
show_comment_cta: true
|
|
});
|
|
const data = await emailRenderer.getTemplateData({post, newsletter, html, addPaywall: false});
|
|
assert.equal(data.newsletter.showCommentCta, true);
|
|
});
|
|
|
|
it('show comment CTA is disabled if comments disabled', async function () {
|
|
labsEnabled = true;
|
|
settings.comments_enabled = 'off';
|
|
const html = '';
|
|
const post = createModel({
|
|
posts_meta: createModel({}),
|
|
loaded: ['posts_meta'],
|
|
published_at: new Date(0)
|
|
});
|
|
const newsletter = createModel({
|
|
title_font_category: 'serif',
|
|
title_alignment: 'left',
|
|
body_font_category: 'sans_serif',
|
|
show_comment_cta: true
|
|
});
|
|
const data = await emailRenderer.getTemplateData({post, newsletter, html, addPaywall: false});
|
|
assert.equal(data.newsletter.showCommentCta, false);
|
|
});
|
|
|
|
it('show comment CTA is disabled if disabled', async function () {
|
|
labsEnabled = true;
|
|
settings.comments_enabled = 'all';
|
|
const html = '';
|
|
const post = createModel({
|
|
posts_meta: createModel({}),
|
|
loaded: ['posts_meta'],
|
|
published_at: new Date(0)
|
|
});
|
|
const newsletter = createModel({
|
|
title_font_category: 'serif',
|
|
title_alignment: 'left',
|
|
body_font_category: 'sans_serif',
|
|
show_comment_cta: false
|
|
});
|
|
const data = await emailRenderer.getTemplateData({post, newsletter, html, addPaywall: false});
|
|
assert.equal(data.newsletter.showCommentCta, false);
|
|
});
|
|
|
|
it('show comment CTA is enabled if all enabled', async function () {
|
|
labsEnabled = true;
|
|
settings.comments_enabled = 'all';
|
|
const html = '';
|
|
const post = createModel({
|
|
posts_meta: createModel({}),
|
|
loaded: ['posts_meta'],
|
|
published_at: new Date(0)
|
|
});
|
|
const newsletter = createModel({
|
|
title_font_category: 'serif',
|
|
title_alignment: 'left',
|
|
body_font_category: 'sans_serif',
|
|
show_comment_cta: true
|
|
});
|
|
const data = await emailRenderer.getTemplateData({post, newsletter, html, addPaywall: false});
|
|
assert.equal(data.newsletter.showCommentCta, true);
|
|
});
|
|
|
|
it('showSubscriptionDetails is disabled if labs disabled', async function () {
|
|
labsEnabled = false;
|
|
const html = '';
|
|
const post = createModel({
|
|
posts_meta: createModel({}),
|
|
loaded: ['posts_meta'],
|
|
published_at: new Date(0)
|
|
});
|
|
const newsletter = createModel({
|
|
title_font_category: 'serif',
|
|
title_alignment: 'left',
|
|
body_font_category: 'sans_serif',
|
|
show_subscription_details: true
|
|
});
|
|
const data = await emailRenderer.getTemplateData({post, newsletter, html, addPaywall: false});
|
|
assert.equal(data.newsletter.showSubscriptionDetails, false);
|
|
});
|
|
|
|
it('showSubscriptionDetails works is enabled', async function () {
|
|
labsEnabled = true;
|
|
const html = '';
|
|
const post = createModel({
|
|
posts_meta: createModel({}),
|
|
loaded: ['posts_meta'],
|
|
published_at: new Date(0)
|
|
});
|
|
const newsletter = createModel({
|
|
title_font_category: 'serif',
|
|
title_alignment: 'left',
|
|
body_font_category: 'sans_serif',
|
|
show_subscription_details: true
|
|
});
|
|
const data = await emailRenderer.getTemplateData({post, newsletter, html, addPaywall: false});
|
|
assert.equal(data.newsletter.showSubscriptionDetails, true);
|
|
});
|
|
|
|
it('showSubscriptionDetails can be disabled', async function () {
|
|
labsEnabled = true;
|
|
const html = '';
|
|
const post = createModel({
|
|
posts_meta: createModel({}),
|
|
loaded: ['posts_meta'],
|
|
published_at: new Date(0)
|
|
});
|
|
const newsletter = createModel({
|
|
title_font_category: 'serif',
|
|
title_alignment: 'left',
|
|
body_font_category: 'sans_serif',
|
|
show_subscription_details: false
|
|
});
|
|
const data = await emailRenderer.getTemplateData({post, newsletter, html, addPaywall: false});
|
|
assert.equal(data.newsletter.showSubscriptionDetails, false);
|
|
});
|
|
|
|
it('latestPosts can be disabled', async function () {
|
|
labsEnabled = true;
|
|
const html = '';
|
|
const post = createModel({
|
|
posts_meta: createModel({}),
|
|
loaded: ['posts_meta'],
|
|
published_at: new Date(0)
|
|
});
|
|
const newsletter = createModel({
|
|
title_font_category: 'serif',
|
|
title_alignment: 'left',
|
|
body_font_category: 'sans_serif',
|
|
show_latest_posts: false
|
|
});
|
|
const data = await emailRenderer.getTemplateData({post, newsletter, html, addPaywall: false});
|
|
assert.deepEqual(data.latestPosts, []);
|
|
});
|
|
|
|
it('latestPosts can be disabled via labs', async function () {
|
|
labsEnabled = false;
|
|
const html = '';
|
|
const post = createModel({
|
|
posts_meta: createModel({}),
|
|
loaded: ['posts_meta'],
|
|
published_at: new Date(0)
|
|
});
|
|
const newsletter = createModel({
|
|
title_font_category: 'serif',
|
|
title_alignment: 'left',
|
|
body_font_category: 'sans_serif',
|
|
show_latest_posts: true
|
|
});
|
|
const data = await emailRenderer.getTemplateData({post, newsletter, html, addPaywall: false});
|
|
assert.deepEqual(data.latestPosts, []);
|
|
});
|
|
|
|
it('latestPosts can be enabled', async function () {
|
|
labsEnabled = true;
|
|
const html = '';
|
|
const post = createModel({
|
|
posts_meta: createModel({}),
|
|
loaded: ['posts_meta'],
|
|
published_at: new Date(0)
|
|
});
|
|
const newsletter = createModel({
|
|
title_font_category: 'serif',
|
|
title_alignment: 'left',
|
|
body_font_category: 'sans_serif',
|
|
show_latest_posts: true
|
|
});
|
|
const data = await emailRenderer.getTemplateData({post, newsletter, html, addPaywall: false});
|
|
assert.deepEqual(data.latestPosts,
|
|
[
|
|
{
|
|
featureImage: 'http://example.com/image.jpg',
|
|
featureImageWidth: 0,
|
|
publishedAt: '1 Jan 2018',
|
|
title: 'Test Post 1',
|
|
url: 'http://example.com'
|
|
},
|
|
{
|
|
featureImage: null,
|
|
featureImageWidth: 0,
|
|
publishedAt: '1 Jan 2018',
|
|
title: 'Test Post 2',
|
|
url: 'http://example.com'
|
|
},
|
|
{
|
|
featureImage: null,
|
|
featureImageWidth: 0,
|
|
publishedAt: DateTime.local().setZone('UTC').setLocale('en-gb').toLocaleString({
|
|
year: 'numeric',
|
|
month: 'short',
|
|
day: 'numeric'
|
|
}),
|
|
title: 'Test Post 3',
|
|
url: 'http://example.com'
|
|
}
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('createUnsubscribeUrl', function () {
|
|
it('includes member uuid and newsletter id', async function () {
|
|
const emailRenderer = new EmailRenderer({
|
|
urlUtils: {
|
|
urlFor() {
|
|
return 'http://example.com/subdirectory';
|
|
}
|
|
}
|
|
});
|
|
const response = await emailRenderer.createUnsubscribeUrl('memberuuid', {
|
|
newsletterUuid: 'newsletteruuid'
|
|
});
|
|
assert.equal(response, `http://example.com/subdirectory/unsubscribe/?uuid=memberuuid&newsletter=newsletteruuid`);
|
|
});
|
|
|
|
it('includes comments', async function () {
|
|
const emailRenderer = new EmailRenderer({
|
|
urlUtils: {
|
|
urlFor() {
|
|
return 'http://example.com/subdirectory';
|
|
}
|
|
}
|
|
});
|
|
const response = await emailRenderer.createUnsubscribeUrl('memberuuid', {
|
|
comments: true
|
|
});
|
|
assert.equal(response, `http://example.com/subdirectory/unsubscribe/?uuid=memberuuid&comments=1`);
|
|
});
|
|
|
|
it('works for previews', async function () {
|
|
const emailRenderer = new EmailRenderer({
|
|
urlUtils: {
|
|
urlFor() {
|
|
return 'http://example.com/subdirectory';
|
|
}
|
|
}
|
|
});
|
|
const response = await emailRenderer.createUnsubscribeUrl();
|
|
assert.equal(response, `http://example.com/subdirectory/unsubscribe/?preview=1`);
|
|
});
|
|
});
|
|
|
|
describe('limitImageWidth', function () {
|
|
it('Limits width of local images', async function () {
|
|
const emailRenderer = new EmailRenderer({
|
|
imageSize: {
|
|
getImageSizeFromUrl() {
|
|
return {
|
|
width: 2000
|
|
};
|
|
}
|
|
},
|
|
storageUtils: {
|
|
isLocalImage(url) {
|
|
return url === 'http://your-blog.com/content/images/2017/01/02/example.png';
|
|
}
|
|
}
|
|
});
|
|
const response = await emailRenderer.limitImageWidth('http://your-blog.com/content/images/2017/01/02/example.png');
|
|
assert.equal(response.width, 600);
|
|
assert.equal(response.href, 'http://your-blog.com/content/images/size/w1200/2017/01/02/example.png');
|
|
});
|
|
|
|
it('Ignores and logs errors', async function () {
|
|
const emailRenderer = new EmailRenderer({
|
|
imageSize: {
|
|
getImageSizeFromUrl() {
|
|
throw new Error('Oops, this is a test.');
|
|
}
|
|
},
|
|
storageUtils: {
|
|
isLocalImage(url) {
|
|
return url === 'http://your-blog.com/content/images/2017/01/02/example.png';
|
|
}
|
|
}
|
|
});
|
|
const response = await emailRenderer.limitImageWidth('http://your-blog.com/content/images/2017/01/02/example.png');
|
|
assert.equal(response.width, 0);
|
|
assert.equal(response.href, 'http://your-blog.com/content/images/2017/01/02/example.png');
|
|
sinon.assert.calledOnce(logStub);
|
|
});
|
|
|
|
it('Limits width of unsplash images', async function () {
|
|
const emailRenderer = new EmailRenderer({
|
|
imageSize: {
|
|
getImageSizeFromUrl() {
|
|
return {
|
|
width: 2000
|
|
};
|
|
}
|
|
},
|
|
storageUtils: {
|
|
isLocalImage(url) {
|
|
return url === 'http://your-blog.com/content/images/2017/01/02/example.png';
|
|
}
|
|
}
|
|
});
|
|
const response = await emailRenderer.limitImageWidth('https://images.unsplash.com/photo-1657816793628-191deb91e20f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMTc3M3wwfDF8YWxsfDJ8fHx8fHwyfHwxNjU3ODkzNjU5&ixlib=rb-1.2.1&q=80&w=2000');
|
|
assert.equal(response.width, 600);
|
|
assert.equal(response.href, 'https://images.unsplash.com/photo-1657816793628-191deb91e20f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMTc3M3wwfDF8YWxsfDJ8fHx8fHwyfHwxNjU3ODkzNjU5&ixlib=rb-1.2.1&q=80&w=1200');
|
|
});
|
|
|
|
it('Does not increase width of images', async function () {
|
|
const emailRenderer = new EmailRenderer({
|
|
imageSize: {
|
|
getImageSizeFromUrl() {
|
|
return {
|
|
width: 300
|
|
};
|
|
}
|
|
},
|
|
storageUtils: {
|
|
isLocalImage(url) {
|
|
return url === 'http://your-blog.com/content/images/2017/01/02/example.png';
|
|
}
|
|
}
|
|
});
|
|
const response = await emailRenderer.limitImageWidth('https://example.com/image.png');
|
|
assert.equal(response.width, 300);
|
|
assert.equal(response.href, 'https://example.com/image.png');
|
|
});
|
|
});
|
|
});
|