diff --git a/src/__tests__/__snapshots__/atom1.spec.ts.snap b/src/__tests__/__snapshots__/atom1.spec.ts.snap
index a5a080a..7d8775e 100644
--- a/src/__tests__/__snapshots__/atom1.spec.ts.snap
+++ b/src/__tests__/__snapshots__/atom1.spec.ts.snap
@@ -29,7 +29,7 @@ exports[`atom 1.0 should generate a valid feed 1`] = `
https://example.com/hello-world?id=this&that=true
-
+
2013-07-13T23:00:00.000Z
diff --git a/src/__tests__/atom1.enclosure.test.ts b/src/__tests__/atom1.enclosure.test.ts
new file mode 100644
index 0000000..560acba
--- /dev/null
+++ b/src/__tests__/atom1.enclosure.test.ts
@@ -0,0 +1,114 @@
+import { Feed } from "../feed";
+
+test("Atom uses explicit enclosure.type when provided", () => {
+ const feed = new Feed({
+ title: "t",
+ id: "https://example.com",
+ link: "https://example.com",
+ copyright: "c",
+ });
+
+ // Proxy URL with no file extension -> auto-detection would fail
+ const proxied = "https://wsrv.nl/?url=https%3A%2F%2Fexample.com%2Fimg.jpg&w=1000";
+
+ feed.addItem({
+ title: "post",
+ id: "https://example.com/p",
+ link: "https://example.com/p",
+ date: new Date("2025-01-01"),
+ image: { url: proxied, type: "image/jpeg" }, // <— explicit type
+ });
+
+ const xml = feed.atom1();
+ expect(xml).toContain(`rel="enclosure"`);
+ expect(xml).toContain(`href="${proxied}"`);
+ expect(xml).toContain(`type="image/jpeg"`);
+});
+
+test("Atom falls back to auto-detection when no type provided", () => {
+ const feed = new Feed({
+ title: "t",
+ id: "https://example.com",
+ link: "https://example.com",
+ copyright: "c",
+ });
+
+ feed.addItem({
+ title: "post",
+ id: "https://example.com/p",
+ link: "https://example.com/p",
+ date: new Date("2025-01-01"),
+ image: { url: "https://example.com/image.png", length: 12345 }, // no explicit type
+ });
+
+ const xml = feed.atom1();
+ expect(xml).toContain(`rel="enclosure"`);
+ expect(xml).toContain(`href="https://example.com/image.png"`);
+ expect(xml).toContain(`type="image/png"`); // auto-detected from URL
+});
+
+test("Atom handles empty string type by falling back to auto-detection", () => {
+ const feed = new Feed({
+ title: "t",
+ id: "https://example.com",
+ link: "https://example.com",
+ copyright: "c",
+ });
+
+ feed.addItem({
+ title: "post",
+ id: "https://example.com/p",
+ link: "https://example.com/p",
+ date: new Date("2025-01-01"),
+ image: { url: "https://example.com/image.webp", type: "" }, // empty type
+ });
+
+ const xml = feed.atom1();
+ expect(xml).toContain(`rel="enclosure"`);
+ expect(xml).toContain(`href="https://example.com/image.webp"`);
+ expect(xml).toContain(`type="image/webp"`); // auto-detected from URL
+});
+
+test("Atom handles video enclosures with explicit type", () => {
+ const feed = new Feed({
+ title: "t",
+ id: "https://example.com",
+ link: "https://example.com",
+ copyright: "c",
+ });
+
+ feed.addItem({
+ title: "post",
+ id: "https://example.com/p",
+ link: "https://example.com/p",
+ date: new Date("2025-01-01"),
+ video: { url: "https://example.com/video.mov", type: "video/quicktime" },
+ });
+
+ const xml = feed.atom1();
+ expect(xml).toContain(`rel="enclosure"`);
+ expect(xml).toContain(`href="https://example.com/video.mov"`);
+ expect(xml).toContain(`type="video/quicktime"`);
+});
+
+test("Atom handles audio enclosures with explicit type", () => {
+ const feed = new Feed({
+ title: "t",
+ id: "https://example.com",
+ link: "https://example.com",
+ copyright: "c",
+ });
+
+ feed.addItem({
+ title: "post",
+ id: "https://example.com/p",
+ link: "https://example.com/p",
+ date: new Date("2025-01-01"),
+ audio: { url: "https://example.com/audio.ogg", type: "audio/ogg" },
+ });
+
+ const xml = feed.atom1();
+ expect(xml).toContain(`rel="enclosure"`);
+ expect(xml).toContain(`href="https://example.com/audio.ogg"`);
+ expect(xml).toContain(`type="audio/ogg"`);
+});
diff --git a/src/__tests__/rss2.enclosure.test.ts b/src/__tests__/rss2.enclosure.test.ts
new file mode 100644
index 0000000..cd56c7d
--- /dev/null
+++ b/src/__tests__/rss2.enclosure.test.ts
@@ -0,0 +1,114 @@
+import { Feed } from "../feed";
+
+test("RSS2 uses explicit enclosure.type when provided", () => {
+ const feed = new Feed({
+ title: "t",
+ id: "https://example.com",
+ link: "https://example.com",
+ copyright: "c",
+ });
+
+ // Proxy URL with no file extension -> auto-detection would fail
+ const proxied = "https://wsrv.nl/?url=https%3A%2F%2Fexample.com%2Fimg.jpg&w=1000";
+
+ feed.addItem({
+ title: "post",
+ id: "https://example.com/p",
+ link: "https://example.com/p",
+ date: new Date("2025-01-01"),
+ image: { url: proxied, type: "image/jpeg" }, // <— explicit type
+ });
+
+ const xml = feed.rss2();
+ expect(xml).toContain(` {
+ const feed = new Feed({
+ title: "t",
+ id: "https://example.com",
+ link: "https://example.com",
+ copyright: "c",
+ });
+
+ feed.addItem({
+ title: "post",
+ id: "https://example.com/p",
+ link: "https://example.com/p",
+ date: new Date("2025-01-01"),
+ image: { url: "https://example.com/image.png", length: 12345 }, // no explicit type
+ });
+
+ const xml = feed.rss2();
+ expect(xml).toContain(` {
+ const feed = new Feed({
+ title: "t",
+ id: "https://example.com",
+ link: "https://example.com",
+ copyright: "c",
+ });
+
+ feed.addItem({
+ title: "post",
+ id: "https://example.com/p",
+ link: "https://example.com/p",
+ date: new Date("2025-01-01"),
+ image: { url: "https://example.com/image.webp", type: "" }, // empty type
+ });
+
+ const xml = feed.rss2();
+ expect(xml).toContain(` {
+ const feed = new Feed({
+ title: "t",
+ id: "https://example.com",
+ link: "https://example.com",
+ copyright: "c",
+ });
+
+ feed.addItem({
+ title: "post",
+ id: "https://example.com/p",
+ link: "https://example.com/p",
+ date: new Date("2025-01-01"),
+ video: { url: "https://example.com/video.mov", type: "video/quicktime" },
+ });
+
+ const xml = feed.rss2();
+ expect(xml).toContain(` {
+ const feed = new Feed({
+ title: "t",
+ id: "https://example.com",
+ link: "https://example.com",
+ copyright: "c",
+ });
+
+ feed.addItem({
+ title: "post",
+ id: "https://example.com/p",
+ link: "https://example.com/p",
+ date: new Date("2025-01-01"),
+ audio: { url: "https://example.com/audio.ogg", type: "audio/ogg" },
+ });
+
+ const xml = feed.rss2();
+ expect(xml).toContain(` {
*/
const formatEnclosure = (enclosure: string | Enclosure, mimeCategory = "image") => {
if (typeof enclosure === "string") {
- const type = new URL(enclosure).pathname.split(".").slice(-1)[0];
- return { _attributes: { rel: "enclosure", href: enclosure, type: `${mimeCategory}/${type}` } };
+ const detectedType = new URL(enclosure).pathname.split(".").slice(-1)[0];
+ return { _attributes: { rel: "enclosure", href: enclosure, type: `${mimeCategory}/${detectedType}` } };
+ }
+ // For object enclosures, respect the explicit type if provided.
+ // Otherwise fall back to MIME category plus detected extension.
+ let type: string | undefined = enclosure.type;
+ if (!type || type.trim() === "") {
+ const detectedType = new URL(enclosure.url).pathname.split(".").slice(-1)[0];
+ type = `${mimeCategory}/${detectedType}`;
}
-
- const type = new URL(enclosure.url).pathname.split(".").slice(-1)[0];
return {
_attributes: {
rel: "enclosure",
href: enclosure.url,
title: enclosure.title,
- type: `${mimeCategory}/${type}`,
+ type,
length: enclosure.length,
},
};
diff --git a/src/rss2.ts b/src/rss2.ts
index 397a1ff..5b42d17 100644
--- a/src/rss2.ts
+++ b/src/rss2.ts
@@ -264,12 +264,22 @@ export default (ins: Feed) => {
*/
const formatEnclosure = (enclosure: string | Enclosure, mimeCategory = "image") => {
if (typeof enclosure === "string") {
- const type = new URL(sanitize(enclosure)!).pathname.split(".").slice(-1)[0];
- return { _attributes: { url: enclosure, length: 0, type: `${mimeCategory}/${type}` } };
+ const detectedType = new URL(sanitize(enclosure)!).pathname.split(".").slice(-1)[0];
+ return { _attributes: { url: enclosure, length: 0, type: `${mimeCategory}/${detectedType}` } };
}
- const type = new URL(sanitize(enclosure.url)!).pathname.split(".").slice(-1)[0];
- return { _attributes: { length: 0, type: `${mimeCategory}/${type}`, ...enclosure } };
+ // For object enclosures, respect the explicit type if provided.
+ // Otherwise fall back to MIME category plus detected extension.
+ let type: string | undefined = enclosure.type;
+ if (!type || type.trim() === "") {
+ const detectedType = new URL(sanitize(enclosure.url)!).pathname.split(".").slice(-1)[0];
+ type = `${mimeCategory}/${detectedType}`;
+ }
+
+ // Create the attributes, ensuring computed type takes precedence over enclosure.type
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const { type: _, ...otherEnclosureProps } = enclosure;
+ return { _attributes: { length: 0, type, ...otherEnclosureProps } };
};
/**