External links: screen-reader hint on links that leave the blog
External links in post bodies already opened in a new tab and showed an icon, but the icon is invisible to screen readers, so nothing announced that the link leaves the site. - blogex: ExternalLinks.rewrite/1 appends <span class="blogex-external-hint"> (opens in a new tab)</span> as the last child of every external anchor. No aria-label, so the accessible name still contains the visible link text (WCAG 2.5.3); the icon colour and 2.5.3 both hold. Idempotent: a second pass adds no second hint. - blogex: anchors are now matched together with their closing tag (the hint goes inside), so a stray unclosed <a> is left alone. - app: sr-only rule for .blogex-external-hint in .blogex-post-body. The split stays as before: blogex marks up, the host app styles. - tests: hint cases in blogex (appended, per link, after inner markup, idempotent, not on internal/mailto/anchor links) + anchors without href (Blogex.ExternalLinks now at 100% line coverage), and an app test that the hint reaches the rendered post page. Two exact-string expectations updated for the intended markup change. make test: app 179, blogex 162. make verify: green.
This commit is contained in:
@@ -10,7 +10,7 @@ defmodule Blogex.ExternalLinksTest do
|
||||
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>)
|
||||
~s|<p>See <a href="https://en.wikipedia.org/wiki/Blog" target="_blank" rel="noopener" class="blogex-external">Wikipedia<span class="blogex-external-hint"> (opens in a new tab)</span></a>.</p>|
|
||||
end
|
||||
|
||||
test "also rewrites external http links" do
|
||||
@@ -26,7 +26,7 @@ defmodule Blogex.ExternalLinksTest do
|
||||
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>)
|
||||
~s|<p><a href="https://a.example/" target="_blank" rel="noopener" class="blogex-external">a<span class="blogex-external-hint"> (opens in a new tab)</span></a> and <a href="https://b.example/" target="_blank" rel="noopener" class="blogex-external">b<span class="blogex-external-hint"> (opens in a new tab)</span></a></p>|
|
||||
end
|
||||
|
||||
test "preserves other attributes on the anchor" do
|
||||
@@ -56,6 +56,48 @@ defmodule Blogex.ExternalLinksTest do
|
||||
end
|
||||
end
|
||||
|
||||
describe "rewrite/1 accessible hint" do
|
||||
test "appends a visually hidden hint inside the external anchor" do
|
||||
html = ~s(<p>See <a href="https://en.wikipedia.org/wiki/Blog">Wikipedia</a>.</p>)
|
||||
|
||||
assert ExternalLinks.rewrite(html) ==
|
||||
~s|<p>See <a href="https://en.wikipedia.org/wiki/Blog" target="_blank" rel="noopener" class="blogex-external">Wikipedia<span class="blogex-external-hint"> (opens in a new tab)</span></a>.</p>|
|
||||
end
|
||||
|
||||
test "adds one hint per external link" do
|
||||
html = ~s(<a href="https://a.example/">a</a> and <a href="https://b.example/">b</a>)
|
||||
|
||||
assert html |> ExternalLinks.rewrite() |> hints() == 2
|
||||
end
|
||||
|
||||
test "places the hint after inner markup, keeping it untouched" do
|
||||
html = ~s(<a href="https://example.com/"><code>docs</code> and more</a>)
|
||||
|
||||
assert ExternalLinks.rewrite(html) =~
|
||||
~s|<code>docs</code> and more<span class="blogex-external-hint"> (opens in a new tab)</span></a>|
|
||||
end
|
||||
|
||||
test "does not add a second hint when applied twice" do
|
||||
once = ExternalLinks.rewrite(~s(<a href="https://example.com/">once</a>))
|
||||
twice = ExternalLinks.rewrite(once)
|
||||
|
||||
assert twice == once
|
||||
assert hints(twice) == 1
|
||||
end
|
||||
|
||||
test "adds no hint to links that are not external" do
|
||||
for html <- [
|
||||
~s(<a href="/blog/engineering/swim-the-test">internal</a>),
|
||||
~s(<a href="mailto:hi@example.com">mail</a>),
|
||||
~s(<a href="#section">jump</a>)
|
||||
] do
|
||||
assert ExternalLinks.rewrite(html) == html
|
||||
end
|
||||
end
|
||||
|
||||
defp hints(html), do: length(Regex.scan(~r/class="blogex-external-hint"/, html))
|
||||
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>)
|
||||
@@ -75,6 +117,12 @@ defmodule Blogex.ExternalLinksTest do
|
||||
assert ExternalLinks.rewrite(html) == html
|
||||
end
|
||||
|
||||
test "anchors without an href are unchanged" do
|
||||
html = ~s(<a name="top">top</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
|
||||
|
||||
@@ -51,7 +51,7 @@ defmodule Blogex.PostTest do
|
||||
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>)
|
||||
~s|<a href="https://en.wikipedia.org/wiki/Blog" target="_blank" rel="noopener" class="blogex-external">Wikipedia<span class="blogex-external-hint"> (opens in a new tab)</span></a>|
|
||||
end
|
||||
|
||||
test "leaves internal links in stored body untouched" do
|
||||
|
||||
Reference in New Issue
Block a user