add HTML email templates for newsletter
All checks were successful
deploy / deploy (push) Successful in 1m2s
All checks were successful
deploy / deploy (push) Successful in 1m2s
Multipart emails (HTML + plain text fallback) with a branded wrapper: shop name header, content area with auto-linked URLs and paragraph formatting, and unsubscribe footer. Applied to both confirmation and campaign emails. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
136
test/berrypod/newsletter/notifier_test.exs
Normal file
136
test/berrypod/newsletter/notifier_test.exs
Normal file
@@ -0,0 +1,136 @@
|
||||
defmodule Berrypod.Newsletter.NotifierTest do
|
||||
use Berrypod.DataCase, async: true
|
||||
|
||||
import Swoosh.TestAssertions
|
||||
import Berrypod.NewsletterFixtures
|
||||
|
||||
alias Berrypod.Newsletter.Notifier
|
||||
|
||||
describe "deliver_confirmation/2" do
|
||||
test "sends multipart email with confirmation link" do
|
||||
sub = confirmed_subscriber_fixture(email: "confirm@example.com")
|
||||
token = "test-token-123"
|
||||
|
||||
assert {:ok, _} = Notifier.deliver_confirmation(sub, token)
|
||||
|
||||
assert_email_sent(fn email ->
|
||||
assert email.to == [{"", "confirm@example.com"}]
|
||||
assert email.subject == "Confirm your subscription"
|
||||
|
||||
# text fallback
|
||||
assert email.text_body =~ "confirm@example.com" || email.text_body =~ token
|
||||
assert email.text_body =~ "/newsletter/confirm/test-token-123"
|
||||
|
||||
# html version
|
||||
assert email.html_body =~ "Confirm subscription"
|
||||
assert email.html_body =~ "/newsletter/confirm/test-token-123"
|
||||
assert email.html_body =~ "<!DOCTYPE html>"
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
describe "deliver_campaign/2" do
|
||||
test "sends multipart email with HTML wrapper" do
|
||||
sub = confirmed_subscriber_fixture(email: "reader@example.com")
|
||||
campaign = campaign_fixture(subject: "Big news", body: "Hello world!\n\nSecond paragraph.")
|
||||
|
||||
assert {:ok, _} = Notifier.deliver_campaign(campaign, sub)
|
||||
|
||||
assert_email_sent(fn email ->
|
||||
assert email.to == [{"", "reader@example.com"}]
|
||||
assert email.subject == "Big news"
|
||||
|
||||
# text fallback has body and unsubscribe
|
||||
assert email.text_body =~ "Hello world!"
|
||||
assert email.text_body =~ "Unsubscribe:"
|
||||
|
||||
# html version wraps in template
|
||||
assert email.html_body =~ "<!DOCTYPE html>"
|
||||
assert email.html_body =~ "<p>Hello world!</p>"
|
||||
assert email.html_body =~ "<p>Second paragraph.</p>"
|
||||
assert email.html_body =~ "Unsubscribe"
|
||||
end)
|
||||
end
|
||||
|
||||
test "replaces {{unsubscribe_url}} in both text and html" do
|
||||
sub = confirmed_subscriber_fixture(email: "unsub@example.com")
|
||||
|
||||
campaign =
|
||||
campaign_fixture(body: "Click here to unsubscribe: {{unsubscribe_url}}")
|
||||
|
||||
assert {:ok, _} = Notifier.deliver_campaign(campaign, sub)
|
||||
|
||||
assert_email_sent(fn email ->
|
||||
refute email.text_body =~ "{{unsubscribe_url}}"
|
||||
refute email.html_body =~ "{{unsubscribe_url}}"
|
||||
assert email.text_body =~ "/unsubscribe/"
|
||||
assert email.html_body =~ "/unsubscribe/"
|
||||
end)
|
||||
end
|
||||
|
||||
test "includes List-Unsubscribe headers" do
|
||||
sub = confirmed_subscriber_fixture()
|
||||
campaign = campaign_fixture()
|
||||
|
||||
assert {:ok, _} = Notifier.deliver_campaign(campaign, sub)
|
||||
|
||||
assert_email_sent(fn email ->
|
||||
headers = Map.new(email.headers)
|
||||
assert headers["List-Unsubscribe"] =~ "/unsubscribe/"
|
||||
assert headers["List-Unsubscribe-Post"] == "List-Unsubscribe=One-Click"
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
describe "text_to_html/1" do
|
||||
test "wraps paragraphs split by blank lines" do
|
||||
html = Notifier.text_to_html("First paragraph.\n\nSecond paragraph.")
|
||||
assert html =~ "<p>First paragraph.</p>"
|
||||
assert html =~ "<p>Second paragraph.</p>"
|
||||
end
|
||||
|
||||
test "preserves single line breaks within paragraphs" do
|
||||
html = Notifier.text_to_html("Line one.\nLine two.")
|
||||
assert html =~ "Line one.<br>Line two."
|
||||
end
|
||||
|
||||
test "escapes HTML in content" do
|
||||
html = Notifier.text_to_html("Use <b>bold</b> & \"quotes\"")
|
||||
assert html =~ "<b>bold</b>"
|
||||
assert html =~ "&"
|
||||
assert html =~ ""quotes""
|
||||
end
|
||||
|
||||
test "turns URLs into links" do
|
||||
html = Notifier.text_to_html("Visit https://example.com/shop for more")
|
||||
assert html =~ ~s(<a href="https://example.com/shop")
|
||||
assert html =~ "https://example.com/shop</a>"
|
||||
end
|
||||
end
|
||||
|
||||
describe "wrap_html/2" do
|
||||
test "includes shop name in header" do
|
||||
html = Notifier.wrap_html("Test Shop", "<p>Hello</p>")
|
||||
assert html =~ "Test Shop"
|
||||
assert html =~ "<!DOCTYPE html>"
|
||||
assert html =~ "<p>Hello</p>"
|
||||
end
|
||||
|
||||
test "includes unsubscribe link when provided" do
|
||||
html = Notifier.wrap_html("Shop", "<p>Hi</p>", "https://example.com/unsub")
|
||||
assert html =~ "https://example.com/unsub"
|
||||
assert html =~ "Unsubscribe"
|
||||
end
|
||||
|
||||
test "omits footer when no unsubscribe url" do
|
||||
html = Notifier.wrap_html("Shop", "<p>Hi</p>")
|
||||
refute html =~ "Unsubscribe"
|
||||
end
|
||||
|
||||
test "escapes shop name in HTML" do
|
||||
html = Notifier.wrap_html("Bob's <Shop>", "<p>Hi</p>")
|
||||
assert html =~ "<Shop>"
|
||||
refute html =~ "<Shop>"
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user