···3333 />
3434 <meta name="twitter:image" content={~p"/images/og_light.png"} />
35353636- <!-- Just a little fun with preloading -->
3736 <link
3837 rel="preload"
3938 href={~p"/fonts/plus-jakarta-sans-latin-wght-normal.woff2"}
+79
test/annot_at/feeds/rss_test.exs
···11+defmodule AnnotAt.Feeds.RSSTest do
22+ use ExUnit.Case, async: true
33+44+ alias AnnotAt.Feeds.Entry
55+ alias AnnotAt.Feeds.Feed
66+ alias AnnotAt.Feeds.RSS
77+88+ @fixture "../../support/fixtures/feeds/rss_sample.xml"
99+ |> Path.expand(__DIR__)
1010+ |> File.read!()
1111+1212+ test "parses channel-level feed metadata" do
1313+ assert {:ok, %Feed{} = feed} = RSS.parse(@fixture)
1414+ assert "Sample Blog" == feed.title
1515+ assert "https://example.com" == feed.url
1616+ assert "Thoughts about things." == feed.description
1717+ end
1818+1919+ test "parses entries with all fields" do
2020+ assert {:ok, %{entries: [first, _second]}} = RSS.parse(@fixture)
2121+2222+ assert %Entry{} = first
2323+ assert "First Post" == first.title
2424+ assert "https://example.com/posts/first" == first.url
2525+ assert "abc" == first.id
2626+ assert "A short summary of the first post." == first.summary
2727+ assert "<p>The full <strong>content</strong> of the first post.</p>" == first.content
2828+ assert %DateTime{} = first.published_at
2929+ assert ~U[2024-10-02 13:00:00Z] == first.published_at
3030+ end
3131+3232+ test "falls back to URL as ID when guid is missing" do
3333+ assert {:ok, %{entries: [_first, second]}} = RSS.parse(@fixture)
3434+ assert second.id == second.url
3535+ end
3636+3737+ test "leaves published_at nil when pubDate is missing" do
3838+ assert {:ok, %{entries: [_first, second]}} = RSS.parse(@fixture)
3939+ refute second.published_at
4040+ end
4141+4242+ test "leaves content nil when missing" do
4343+ assert {:ok, %{entries: [_first, second]}} = RSS.parse(@fixture)
4444+ refute second.content
4545+ end
4646+4747+ test "returns an error on malformed" do
4848+ assert {:error, :invalid_feed} = RSS.parse("<rss><channel><title>oops")
4949+ end
5050+5151+ test "accepts a valid but empty blog" do
5252+ body = """
5353+ <?xml version="1.0" encoding="UTF-8"?>
5454+ <rss version="2.0">
5555+ <channel>
5656+ <title>Brand New Blog</title>
5757+ <link>https://example.com</link>
5858+ <description>Nothing here yet.</description>
5959+ </channel>
6060+ </rss>
6161+ """
6262+6363+ assert {:ok, %Feed{title: "Brand New Blog", entries: []}} = RSS.parse(body)
6464+ end
6565+6666+ test "rejects a feed with no channel title as invalid" do
6767+ body = """
6868+ <?xml version="1.0" encoding="UTF-8"?>
6969+ <rss version="2.0">
7070+ <channel>
7171+ <link>https://example.com</link>
7272+ <description>No title here.</description>
7373+ </channel>
7474+ </rss>
7575+ """
7676+7777+ assert {:error, :invalid_feed} = RSS.parse(body)
7878+ end
7979+end
+49
test/annot_at/feeds_test.exs
···11+defmodule AnnotAt.FeedsTest do
22+ use ExUnit.Case, async: true
33+44+ alias AnnotAt.Feeds
55+ alias AnnotAt.Feeds.Feed
66+77+ @fixture "../support/fixtures/feeds/rss_sample.xml"
88+ |> Path.expand(__DIR__)
99+ |> File.read!()
1010+1111+ describe "Feeds.parse/2" do
1212+ test "detects and dispatches RSS" do
1313+ assert {:ok, %Feed{title: "Sample Blog"}} = Feeds.parse(@fixture, "application/rss+xml")
1414+ end
1515+1616+ test "rejects an unrecognized body" do
1717+ assert {:error, :unrecognized_feed} = Feeds.parse("not a feed at all", nil)
1818+ end
1919+ end
2020+2121+ describe "Feeds.discover/2" do
2222+ test "finds and resolves a relative feed url" do
2323+ html = """
2424+ <html>
2525+ <head>
2626+ <link rel="alternate" type="application/rss+xml" href="/feed.xml">
2727+ </head>
2828+ </html>
2929+ """
3030+3131+ assert {:ok, "https://blog.example.com/feed.xml"} =
3232+ Feeds.discover(
3333+ html,
3434+ "https://blog.example.com"
3535+ )
3636+ end
3737+3838+ test "returns :error when there's no feed link" do
3939+ html = """
4040+ <html>
4141+ <head>
4242+ </head>
4343+ </html>
4444+ """
4545+4646+ assert :error = Feeds.discover(html, "https://blog.example.com")
4747+ end
4848+ end
4949+end
+27
test/support/fixtures/feeds/rss_sample.xml
···11+<?xml version="1.0" encoding="UTF-8" ?>
22+<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
33+ <channel>
44+ <title>Sample Blog</title>
55+ <link>https://example.com</link>
66+ <description>Thoughts about things.</description>
77+ <image>
88+ <url>https://example.com/icon.png</url>
99+ <title>Sample Blog Logo</title>
1010+ <link>https://example.com</link>
1111+ </image>
1212+ <item>
1313+ <title>First Post</title>
1414+ <link>https://example.com/posts/first</link>
1515+ <guid>abc</guid>
1616+ <description>A short summary of the first post.</description>
1717+ <content:encoded
1818+ ><![CDATA[<p>The full <strong>content</strong> of the first post.</p>]]></content:encoded>
1919+ <pubDate>Wed, 02 Oct 2024 13:00:00 GMT</pubDate>
2020+ </item>
2121+ <item>
2222+ <title>Second Post</title>
2323+ <link>https://example.com/posts/second</link>
2424+ <description>Another summary.</description>
2525+ </item>
2626+ </channel>
2727+</rss>