External link marker: new tab + icon on links leaving the blog
External links in post bodies now open in a new tab and show a box-with-arrow marker icon, so readers can tell at a glance which links leave the site. - blogex: new Blogex.ExternalLinks.rewrite/1 marks up anchors with http(s) hrefs at compile time with target="_blank", rel="noopener" and class="blogex-external" (idempotent, merges into existing class/rel values, leaves relative, mailto: and #anchor links alone) - blogex: Post.build/3 applies the rewrite, so every blog gets it - app: CSS in .blogex-post-body draws the marker via a mask so it inherits the link colour; empty ::after content stays invisible to screen readers - make test now also runs the blogex suite; it is currently red with 3 pre-existing blog_integration_test failures caused by the vacuous link validator (parked yak link-validator-never-sees-compiled-html-...)
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
defmodule Blogex.ExternalLinksTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias Blogex.ExternalLinks
|
||||
|
||||
describe "rewrite/1 on anchors with http(s) URLs" do
|
||||
test "adds target, rel and marker class to an external https link" do
|
||||
html = ~s(<p>See <a href="https://en.wikipedia.org/wiki/Blog">Wikipedia</a>.</p>)
|
||||
|
||||
rewritten = ExternalLinks.rewrite(html)
|
||||
|
||||
assert rewritten ==
|
||||
~s(<p>See <a href="https://en.wikipedia.org/wiki/Blog" target="_blank" rel="noopener" class="blogex-external">Wikipedia</a>.</p>)
|
||||
end
|
||||
|
||||
test "also rewrites external http links" do
|
||||
rewritten = ExternalLinks.rewrite(~s(<a href="http://www.exampler.com/about/">about</a>))
|
||||
|
||||
assert rewritten =~ ~s(target="_blank" rel="noopener" class="blogex-external")
|
||||
end
|
||||
|
||||
test "rewrites each external link in a document with many links" do
|
||||
html =
|
||||
~s(<p><a href="https://a.example/">a</a> and <a href="https://b.example/">b</a></p>)
|
||||
|
||||
rewritten = ExternalLinks.rewrite(html)
|
||||
|
||||
assert rewritten ==
|
||||
~s(<p><a href="https://a.example/" target="_blank" rel="noopener" class="blogex-external">a</a> and <a href="https://b.example/" target="_blank" rel="noopener" class="blogex-external">b</a></p>)
|
||||
end
|
||||
|
||||
test "preserves other attributes on the anchor" do
|
||||
html = ~s(<a title="Docs" href="https://example.com/docs">docs</a>)
|
||||
|
||||
rewritten = ExternalLinks.rewrite(html)
|
||||
|
||||
assert rewritten =~ ~s(title="Docs")
|
||||
assert rewritten =~ ~s(target="_blank" rel="noopener" class="blogex-external")
|
||||
end
|
||||
|
||||
test "merges the marker class into an existing class attribute" do
|
||||
html = ~s(<a class="fancy" href="https://example.com/">fancy</a>)
|
||||
|
||||
rewritten = ExternalLinks.rewrite(html)
|
||||
|
||||
assert rewritten =~ ~s(class="fancy blogex-external")
|
||||
end
|
||||
|
||||
test "is idempotent — no duplicated attributes when applied twice" do
|
||||
html = ~s(<a href="https://example.com/">once</a>)
|
||||
|
||||
once = ExternalLinks.rewrite(html)
|
||||
twice = ExternalLinks.rewrite(once)
|
||||
|
||||
assert once == twice
|
||||
end
|
||||
end
|
||||
|
||||
describe "rewrite/1 leaves other links untouched" do
|
||||
test "internal relative links are unchanged" do
|
||||
html = ~s(<a href="/blog/engineering/swim-the-test">Swim the Test</a>)
|
||||
|
||||
assert ExternalLinks.rewrite(html) == html
|
||||
end
|
||||
|
||||
test "mailto links are unchanged" do
|
||||
html = ~s(<a href="mailto:hi@example.com">mail</a>)
|
||||
|
||||
assert ExternalLinks.rewrite(html) == html
|
||||
end
|
||||
|
||||
test "anchor-only links are unchanged" do
|
||||
html = ~s(<a href="#section">jump</a>)
|
||||
|
||||
assert ExternalLinks.rewrite(html) == html
|
||||
end
|
||||
|
||||
test "plain text without anchors is unchanged" do
|
||||
assert ExternalLinks.rewrite("<p>no links here</p>") == "<p>no links here</p>"
|
||||
end
|
||||
|
||||
test "URLs appearing in text but not in an href are unchanged" do
|
||||
html = ~s(<p>read https://example.com/inline today</p>)
|
||||
|
||||
assert ExternalLinks.rewrite(html) == html
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -45,6 +45,23 @@ defmodule Blogex.PostTest do
|
||||
assert post.body == html
|
||||
end
|
||||
|
||||
test "adds target, rel and marker class to external links in stored body" do
|
||||
html = ~s(<p>See <a href="https://en.wikipedia.org/wiki/Blog">Wikipedia</a></p>)
|
||||
|
||||
post = Post.build("x/2026/01-01-x.md", valid_attrs(), html)
|
||||
|
||||
assert post.body =~
|
||||
~s(<a href="https://en.wikipedia.org/wiki/Blog" target="_blank" rel="noopener" class="blogex-external">Wikipedia</a>)
|
||||
end
|
||||
|
||||
test "leaves internal links in stored body untouched" do
|
||||
html = ~s(<p>See <a href="/blog/engineering/swim-the-test">Swim the Test</a></p>)
|
||||
|
||||
post = Post.build("x/2026/01-01-x.md", valid_attrs(), html)
|
||||
|
||||
assert post.body == html
|
||||
end
|
||||
|
||||
test "defaults published to true" do
|
||||
post = Post.build("x/2026/01-01-x.md", valid_attrs(), "<p>x</p>")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user