mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-09-11 20:57:21 +03:00
Added an email rendering test for all Koenig cards (#19059)
refs TryGhost/Product#4125 This PR adds two new integration tests to ensure all our Koenig cards are rendered properly after going through the EmailRenderer. Although we have thorough tests for the cards themselves in the Koenig repo, the EmailRenderer does post-processing on the rendered HTML, such as inlining CSS, which can adversely impact the rendered output of our cards in email clients (usually Outlook). Since email newsletters are a core feature of Ghost, these bugs are typically fairly urgent, and since it is email, they are also quite difficult to troubleshoot and fix. These two tests are intended to prevent bugs of this sort, which in the past have been created by seemingly harmless changes like bumping dependencies that are used in the EmailRenderer. The idea is to create a 'Golden Post' which has at least 1 of every card from Koenig, run that post through the EmailRenderer, and take a snapshot of the rendered HTML. In the future, if we make any changes to the EmailRenderer or the Koenig cards themselves, this will trigger us to carefully consider the changes, and it provides an 'expected' output to compare our changes against. Additionally, the second test simply checks that all cards from `kg-default-nodes` are included in the 'Golden Post'. This protects against any new cards that we will add in the future — as soon as we add them to Koenig and bump `kg-default-nodes` in Ghost, this test will fail, prompting us to add the new card to the Golden Post and update the snapshots. We should also run the 'Golden Post' through a test in Litmus, which allows us to visually inspect the rendered email across many different email clients. Ideally we would create a process to review the output of the 'Golden Post' in Litmus whenever we update the snapshot as well.
This commit is contained in:
parent
3346606d77
commit
c90e033fcf
12
ghost/core/test/integration/services/email-service/README.md
Normal file
12
ghost/core/test/integration/services/email-service/README.md
Normal file
@ -0,0 +1,12 @@
|
||||
# What is a golden post?
|
||||
The golden post is a single lexical post that has at least one example of every card that is available in the Koenig editor (with a few exceptions for cards that should never make it into an email). We have run into problems in the past where a small change to a particular card or to the EmailRenderer itself results in seriously mangled email rendering in one or more clients (usually Outlook).
|
||||
|
||||
# How do I update the golden post to include a new card?
|
||||
If you're seeing a failing test like `The golden post does not contain the ${card} card`, that means that you (or someone else) has added a new node to `@tryghost/kg-default-nodes` that is not currently represented in the golden post. This test is here to trigger a review of the rendered email of the new card, to make sure it doesn't break the formatting in email clients. To update this test properly, please do the following:
|
||||
|
||||
1. Create a card in the lexical editor, either at `koenig.ghost.org` or in your local Koenig repo
|
||||
2. Use the JSON Output in the bottom right of the demo to copy the lexical payload for the new card
|
||||
3. Paste the lexical payload for the card as a top level child of the `root` node in the golden post fixture at `ghost/core/test/utils/fixtures/email-service/golden-post.json`
|
||||
4. Re-run your tests with `UPDATE_SNAPSHOT=1` set to update the snapshot to include the new card
|
||||
5. Update (or recreate) the Golden Post on `main.ghost.org` using the `golden-post.json` string.
|
||||
6. Send a test email to Litmus and examine the rendered output to ensure everything looks right on different clients.
|
File diff suppressed because it is too large
Load Diff
@ -4,6 +4,10 @@ const assert = require('assert/strict');
|
||||
const configUtils = require('../../../utils/configUtils');
|
||||
const {sendEmail, matchEmailSnapshot} = require('../../../utils/batch-email-utils');
|
||||
const cheerio = require('cheerio');
|
||||
const fs = require('fs-extra');
|
||||
const {DEFAULT_NODES} = require('@tryghost/kg-default-nodes');
|
||||
|
||||
const goldenPost = fs.readJsonSync('./test/utils/fixtures/email-service/golden-post.json');
|
||||
|
||||
/**
|
||||
* Remove the preheader span from the email html and put it in a separate field called preheader
|
||||
@ -138,4 +142,49 @@ describe('Can send cards via email', function () {
|
||||
|
||||
await matchEmailSnapshot();
|
||||
});
|
||||
|
||||
it('renders the golden post correctly', async function () {
|
||||
const data = await sendEmail(agent, {
|
||||
lexical: JSON.stringify(goldenPost)
|
||||
});
|
||||
|
||||
splitPreheader(data);
|
||||
|
||||
await matchEmailSnapshot();
|
||||
});
|
||||
|
||||
it('renders all of the default nodes in the golden post', async function () {
|
||||
// This test checks that all of the default nodes from @tryghost/kg-default-nodes are present in the golden post
|
||||
// This is to ensure that if we add new cards to Koenig, they will be included in the golden post
|
||||
// This is important because the golden post is used to test the email rendering of the cards after
|
||||
// they have gone through the Email Renderer, which can change the HTML/CSS of the cards
|
||||
// See the README.md in this same directory for more information.
|
||||
|
||||
const cardsInGoldenPost = goldenPost.root.children.map((child) => {
|
||||
return child.type;
|
||||
});
|
||||
|
||||
const excludedCards = [
|
||||
'collection', // only used in pages, will never be emailed
|
||||
'extended-text', // not a card
|
||||
'extended-quote', // not a card
|
||||
'extended-heading', // not a card
|
||||
'tk' // shouldn't be present in published posts / emails
|
||||
];
|
||||
|
||||
const cardsInDefaultNodes = DEFAULT_NODES.map((node) => {
|
||||
try {
|
||||
return node.getType();
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}).filter((card) => {
|
||||
return card !== null && !excludedCards.includes(card); // don't include extended versions of regular text type nodes, we only want the cards (decorator nodes)
|
||||
});
|
||||
|
||||
// Check that every card in DEFAULT_NODES are present in the golden post (with the exception of the excludedCards above)
|
||||
for (const card of cardsInDefaultNodes) {
|
||||
assert.ok(cardsInGoldenPost.includes(card), `The golden post does not contain the ${card} card`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
391
ghost/core/test/utils/fixtures/email-service/golden-post.json
Normal file
391
ghost/core/test/utils/fixtures/email-service/golden-post.json
Normal file
@ -0,0 +1,391 @@
|
||||
{
|
||||
"root": {
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "This is just a simple paragraph, no frills.",
|
||||
"type": "extended-text",
|
||||
"version": 1
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "paragraph",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "This is block quote",
|
||||
"type": "extended-text",
|
||||
"version": 1
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "extended-quote",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "This is a...different block quote",
|
||||
"type": "extended-text",
|
||||
"version": 1
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "aside",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "This is a heading!",
|
||||
"type": "extended-text",
|
||||
"version": 1
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "extended-heading",
|
||||
"version": 1,
|
||||
"tag": "h2"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "Here's a smaller heading.",
|
||||
"type": "extended-text",
|
||||
"version": 1
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "extended-heading",
|
||||
"version": 1,
|
||||
"tag": "h3"
|
||||
},
|
||||
{
|
||||
"type": "image",
|
||||
"version": 1,
|
||||
"src": "https://upload.wikimedia.org/wikipedia/commons/thumb/8/8c/Cow_%28Fleckvieh_breed%29_Oeschinensee_Slaunger_2009-07-07.jpg/1920px-Cow_%28Fleckvieh_breed%29_Oeschinensee_Slaunger_2009-07-07.jpg",
|
||||
"width": null,
|
||||
"height": null,
|
||||
"title": "",
|
||||
"alt": "Cows eat grass.",
|
||||
"caption": "<span style=\"white-space: pre-wrap;\">A lovely cow</span>",
|
||||
"cardWidth": "regular",
|
||||
"href": ""
|
||||
},
|
||||
{
|
||||
"type": "markdown",
|
||||
"version": 1,
|
||||
"markdown": "# A heading\nand a paragraph (in markdown!)"
|
||||
},
|
||||
{
|
||||
"type": "html",
|
||||
"version": 1,
|
||||
"html": "<p>A paragraph inside an HTML card.</p>\n<p>And another one, with some <b>bold</b> text.</p>"
|
||||
},
|
||||
{
|
||||
"type": "horizontalrule",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"type": "gallery",
|
||||
"version": 1,
|
||||
"images": [
|
||||
{
|
||||
"row": 0,
|
||||
"src": "https://images.unsplash.com/photo-1702352461386-15dcf064708a?q=80&w=2128&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
|
||||
"width": null,
|
||||
"height": null,
|
||||
"alt": "",
|
||||
"caption": "",
|
||||
"fileName": "photo-1702352461386-15dcf064708a"
|
||||
},
|
||||
{
|
||||
"row": 0,
|
||||
"src": "https://images.unsplash.com/photo-1702377168432-ac8b5e387998?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
|
||||
"width": null,
|
||||
"height": null,
|
||||
"alt": "",
|
||||
"caption": "",
|
||||
"fileName": "photo-1702377168432-ac8b5e387998"
|
||||
},
|
||||
{
|
||||
"row": 0,
|
||||
"src": "https://plus.unsplash.com/premium_photo-1700558685152-81f821a40724?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
|
||||
"width": 2070,
|
||||
"height": 1380,
|
||||
"alt": "",
|
||||
"caption": "",
|
||||
"fileName": "premium_photo-1700558685152-81f821a40724"
|
||||
}
|
||||
],
|
||||
"caption": "<p dir=\"ltr\"><span style=\"white-space: pre-wrap;\">A gallery.</span></p>"
|
||||
},
|
||||
{
|
||||
"type": "bookmark",
|
||||
"version": 1,
|
||||
"url": "https://ghost.org",
|
||||
"metadata": {
|
||||
"icon": "https://ghost.org/favicon.ico",
|
||||
"title": "Ghost: Independent technology for modern publishing",
|
||||
"description": "Beautiful, modern publishing with newsletters and premium subscriptions built-in. Used by Sky, 404Media, Lever News, Tangle, The Browser, and thousands more.",
|
||||
"author": "",
|
||||
"publisher": "Ghost - The Professional Publishing Platform",
|
||||
"thumbnail": "https://ghost.org/images/meta/ghost.png"
|
||||
},
|
||||
"caption": "<p><span style=\"white-space: pre-wrap;\">My favorite website.</span></p>"
|
||||
},
|
||||
{
|
||||
"type": "email",
|
||||
"version": 1,
|
||||
"html": "<p><span style=\"white-space: pre-wrap;\">Hey </span><code spellcheck=\"false\" style=\"white-space: pre-wrap;\"><span>{first_name, \"there\"}</span></code><span style=\"white-space: pre-wrap;\">,</span></p>"
|
||||
},
|
||||
{
|
||||
"type": "email-cta",
|
||||
"version": 1,
|
||||
"alignment": "left",
|
||||
"buttonText": "",
|
||||
"buttonUrl": "",
|
||||
"html": "<p><span style=\"white-space: pre-wrap;\">Hello there, fellow users of electronic mail.</span></p>",
|
||||
"segment": "status:free",
|
||||
"showButton": false,
|
||||
"showDividers": true
|
||||
},
|
||||
{
|
||||
"type": "paywall",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"type": "button",
|
||||
"version": 1,
|
||||
"buttonText": "Click me, I'm a button!",
|
||||
"alignment": "center",
|
||||
"buttonUrl": "https://ghost.org"
|
||||
},
|
||||
{
|
||||
"type": "callout",
|
||||
"version": 1,
|
||||
"calloutText": "<p><span style=\"white-space: pre-wrap;\">I had an idea...</span></p>",
|
||||
"calloutEmoji": "💡",
|
||||
"backgroundColor": "blue"
|
||||
},
|
||||
{
|
||||
"type": "toggle",
|
||||
"version": 1,
|
||||
"heading": "<span style=\"white-space: pre-wrap;\">Spoiler alert!</span>",
|
||||
"content": "<p><span style=\"white-space: pre-wrap;\">Just kidding</span></p>"
|
||||
},
|
||||
{
|
||||
"type": "audio",
|
||||
"version": 1,
|
||||
"duration": 105.822041,
|
||||
"mimeType": "audio/mpeg",
|
||||
"src": "https://main.ghost.org/content/media/2023/12/sample.mp3",
|
||||
"title": "Sample",
|
||||
"thumbnailSrc": ""
|
||||
},
|
||||
{
|
||||
"type": "video",
|
||||
"version": 1,
|
||||
"src": "https://main.ghost.org/content/media/2023/12/sample_640x360.mp4",
|
||||
"caption": "<p dir=\"ltr\"><span style=\"white-space: pre-wrap;\">A lovely video of a woman on the beach doing nothing.</span></p>",
|
||||
"fileName": "sample_640x360.mp4",
|
||||
"mimeType": "video/mp4",
|
||||
"width": 640,
|
||||
"height": 360,
|
||||
"duration": 13.346667,
|
||||
"thumbnailSrc": "https://main.ghost.org/content/media/2023/12/sample_640x360_thumb.jpg",
|
||||
"customThumbnailSrc": "",
|
||||
"thumbnailWidth": 640,
|
||||
"thumbnailHeight": 360,
|
||||
"cardWidth": "regular",
|
||||
"loop": true
|
||||
},
|
||||
{
|
||||
"type": "product",
|
||||
"version": 1,
|
||||
"productImageSrc": "https://main.ghost.org/content/images/2023/12/ghost-logo.png",
|
||||
"productImageWidth": 800,
|
||||
"productImageHeight": 257,
|
||||
"productTitle": "<span style=\"white-space: pre-wrap;\">Make a blog!</span>",
|
||||
"productDescription": "<p dir=\"ltr\"><span style=\"white-space: pre-wrap;\">with Ghost</span></p>",
|
||||
"productRatingEnabled": true,
|
||||
"productStarRating": 5,
|
||||
"productButtonEnabled": true,
|
||||
"productButton": "Click here",
|
||||
"productUrl": "https://ghost.org"
|
||||
},
|
||||
{
|
||||
"type": "header",
|
||||
"version": 2,
|
||||
"size": "small",
|
||||
"style": "dark",
|
||||
"buttonEnabled": false,
|
||||
"buttonUrl": "",
|
||||
"buttonText": "",
|
||||
"header": "<span style=\"white-space: pre-wrap;\">Good news everyone!</span>",
|
||||
"subheader": "<span style=\"white-space: pre-wrap;\">This header renders properly in </span><i><em class=\"italic\" style=\"white-space: pre-wrap;\">all</em></i><span style=\"white-space: pre-wrap;\"> email clients!</span>",
|
||||
"backgroundImageSrc": "",
|
||||
"accentColor": "#14171b",
|
||||
"alignment": "center",
|
||||
"backgroundColor": "#000000",
|
||||
"backgroundImageWidth": null,
|
||||
"backgroundImageHeight": null,
|
||||
"backgroundSize": "cover",
|
||||
"textColor": "#FFFFFF",
|
||||
"buttonColor": "#ffffff",
|
||||
"buttonTextColor": "#000000",
|
||||
"layout": "full",
|
||||
"swapped": false
|
||||
},
|
||||
{
|
||||
"type": "embed",
|
||||
"version": 1,
|
||||
"url": "https://www.youtube.com/watch?v=jfKfPfyJRdk",
|
||||
"embedType": "video",
|
||||
"html": "<iframe width=\"200\" height=\"150\" src=\"https://www.youtube.com/embed/jfKfPfyJRdk?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" allowfullscreen title=\"lofi hip hop radio 📚 - beats to relax/study to\"></iframe>",
|
||||
"metadata": {
|
||||
"title": "lofi hip hop radio 📚 - beats to relax/study to",
|
||||
"author_name": "Lofi Girl",
|
||||
"author_url": "https://www.youtube.com/@LofiGirl",
|
||||
"type": "video",
|
||||
"height": 150,
|
||||
"width": 200,
|
||||
"version": "1.0",
|
||||
"provider_name": "YouTube",
|
||||
"provider_url": "https://www.youtube.com/",
|
||||
"thumbnail_height": 360,
|
||||
"thumbnail_width": 480,
|
||||
"thumbnail_url": "https://i.ytimg.com/vi/jfKfPfyJRdk/hqdefault.jpg",
|
||||
"html": "<iframe width=\"200\" height=\"150\" src=\"https://www.youtube.com/embed/jfKfPfyJRdk?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" allowfullscreen title=\"lofi hip hop radio 📚 - beats to relax/study to\"></iframe>"
|
||||
},
|
||||
"caption": ""
|
||||
},
|
||||
{
|
||||
"type": "signup",
|
||||
"version": 1,
|
||||
"alignment": "left",
|
||||
"backgroundColor": "#F0F0F0",
|
||||
"backgroundImageSrc": "",
|
||||
"backgroundSize": "cover",
|
||||
"textColor": "#000000",
|
||||
"buttonColor": "accent",
|
||||
"buttonTextColor": "#FFFFFF",
|
||||
"buttonText": "Subscribe",
|
||||
"disclaimer": "<span style=\"white-space: pre-wrap;\">No spam. Unsubscribe anytime.</span>",
|
||||
"header": "<span style=\"white-space: pre-wrap;\">Sign up for This Is The Main</span>",
|
||||
"labels": [],
|
||||
"layout": "wide",
|
||||
"subheader": "<span style=\"white-space: pre-wrap;\">The testing and tweaking of Ghost, now with extra long description for the same price of epic staging</span>",
|
||||
"successMessage": "Email sent! Check your inbox to complete your signup.",
|
||||
"swapped": false
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "Some text.",
|
||||
"type": "extended-text",
|
||||
"version": 1
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "paragraph",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "A blockquote",
|
||||
"type": "extended-text",
|
||||
"version": 1
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "extended-quote",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "Some more text.",
|
||||
"type": "extended-text",
|
||||
"version": 1
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "paragraph",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"type": "codeblock",
|
||||
"version": 1,
|
||||
"code": "console.log('Hello world!');",
|
||||
"language": "javascript",
|
||||
"caption": "<p><span style=\"white-space: pre-wrap;\">A tiny little script.</span></p>"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"src": "https://main.ghost.org/content/files/2023/11/test.txt",
|
||||
"fileTitle": "test",
|
||||
"fileCaption": "A tiny text file.",
|
||||
"fileName": "test.txt",
|
||||
"fileSize": 16
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "root",
|
||||
"version": 1
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user