Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds extra elements to RSS items. #6707

Merged
merged 8 commits into from
Apr 26, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 107 additions & 18 deletions packages/astro-rss/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,24 +112,7 @@ Type: `RSSFeedItem[] (required)`

A list of formatted RSS feed items. See [Astro's RSS items documentation](https://docs.astro.build/en/guides/rss/#generating-items) for usage examples to choose the best option for you.

When providing a formatted RSS item list, see the `RSSFeedItem` type reference below:

```ts
type RSSFeedItem = {
/** Link to item */
link: string;
/** Title of item */
title: string;
/** Publication date of item */
pubDate: Date;
/** Item description */
description?: string;
/** Full content of the item, should be valid HTML */
content?: string;
/** Append some other XML-valid data to this item */
customData?: string;
};
```
When providing a formatted RSS item list, see the [`RSSFeedItem` type reference below](#rssfeeditem).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: the link name doesn't need to address the position in the page.

Suggested change
When providing a formatted RSS item list, see the [`RSSFeedItem` type reference below](#rssfeeditem).
When providing a formatted RSS item list, see the [`RSSFeedItem` type reference](#rssfeeditem) below.


### drafts

Expand Down Expand Up @@ -202,6 +185,112 @@ export const get = () => rss({
});
```

## `RSSFeedItem`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like how well you've restructured the docs and how well you explained this


An `RSSFeedItem` is a single item in the list of items in your feed. It represents a story, with `link`, `title` and `pubDate` fields. There are further optional fields defined below. You can also check the definitions for the fields in the [RSS spec](https://validator.w3.org/feed/docs/rss2.html#ltpubdategtSubelementOfLtitemgt).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
An `RSSFeedItem` is a single item in the list of items in your feed. It represents a story, with `link`, `title` and `pubDate` fields. There are further optional fields defined below. You can also check the definitions for the fields in the [RSS spec](https://validator.w3.org/feed/docs/rss2.html#ltpubdategtSubelementOfLtitemgt).
An `RSSFeedItem` is a single item in the list of items in your feed. It represents a story, with `link`, `title`, and `pubDate` fields. There are further optional fields defined below. You can also check the definitions for the fields in the [RSS spec](https://validator.w3.org/feed/docs/rss2.html#ltpubdategtSubelementOfLtitemgt).


An example feed item might look like:

```js
const item = {
title: "Alpha Centauri: so close you can touch it",
link: "/blog/alpha-centuari",
pubDate: new Date("2023-06-04"),
description: "Alpha Centauri is a triple star system, containing Proxima Centauri, the closest star to our sun at only 4.24 light-years away.",
categories: ["stars", "space"]
}
```

### `title`

Type: `string (required)`

The `<title>` attribute of the item in the feed.

### `link`

Type: `string (required)`

The `<link>` attribute of the item in the feed containing the URL of the item on the web.

### `pubDate`

Type: `Date (required)`

Indicates when the item was published.

### `description`

Type: `string (optional)`

If the item is complete within itself, that is you are publishing the full content of the item in the feed, the `description` field may contain the full text (entity-encoded HTML is permitted). If the item is a stub, then the `description` may contain a synopsis of the item.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
If the item is complete within itself, that is you are publishing the full content of the item in the feed, the `description` field may contain the full text (entity-encoded HTML is permitted). If the item is a stub, then the `description` may contain a synopsis of the item.
A synopsis of your item when you are publishing the full content of the item in the `content` field. The `description` may alternatively be the full content of the item in the feed if you are not using the `content` field (entity-coded HTML is permitted).

I read a bit of backstory on this item, and it appears that the standard is terrible. 😅

In terms of describing this field, it is either one or the other, and if content is used, then this should be only a summary. But, content is newer and not yet universally supported, which is why some will use description for full content, and that's apparently.... fine? So I think the best we can do is simply state that this is what this field is, both if you do and do not also use the content field.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, RSS is old and a mess! And feed readers have inconsistent implementations. Such fun!


### `content`

Type: `string (optional)`

If you want to supply both a short description and also the full content in an item, set the `content` field to the full, encoded text. See the [recommendations from the RSS spec for how to use and differentiate between `description` and `content`](https://www.rssboard.org/rss-profile#namespace-elements-content-encoded).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
If you want to supply both a short description and also the full content in an item, set the `content` field to the full, encoded text. See the [recommendations from the RSS spec for how to use and differentiate between `description` and `content`](https://www.rssboard.org/rss-profile#namespace-elements-content-encoded).
The full text content of the item suitable for presentation as HTML. If used, you should also provide a short article summary in the `description` field.
See the [recommendations from the RSS spec for how to use and differentiate between `description` and `content`](https://www.rssboard.org/rss-profile#namespace-elements-content-encoded).

Something like this maybe? I prefer to start these descriptions by stating what they are, vs a "if you're doing this..."


### `categories`

Type: `string[] (optional)`

If you use tags or categories to categorize your content, you can add them as the `categories` field. They will be output as multiple `<category>` elements.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
If you use tags or categories to categorize your content, you can add them as the `categories` field. They will be output as multiple `<category>` elements.
A list of any tags or categories used to categorize your content. They will be output as multiple `<category>` elements.


### `author`

Type: `string (optional)`

Useful for multi-author blogs, the `author` field provides the email address of the person who wrote the item.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Useful for multi-author blogs, the `author` field provides the email address of the person who wrote the item.
The email address of the item author. This is useful for indicating the author of a post on multi-author blogs.

It seems a little odd to me that author is an email address, and not just a name. So I'm just commenting on that here!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, but that's what the spec says. 🤷‍♂️


### `commentsUrl`

Type: `string (optional)`

The `commentsUrl` defines a URL of a web page that contains comments on the item.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The `commentsUrl` defines a URL of a web page that contains comments on the item.
The URL of a web page that contains comments on the item.


### `source`

Type: `object (optional)`

Items that are republished from other publications may define a `source` which defines the `title` and `url` of the original feed in which it was published.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Items that are republished from other publications may define a `source` which defines the `title` and `url` of the original feed in which it was published.
An object that defines the `title` and `url` of the original feed for items that have been republished from another source. Both are required propeties of `source` for proper attribution.
```js
const item = {
title: "Alpha Centauri: so close you can touch it",
link: "/blog/alpha-centuari",
pubDate: new Date("2023-06-04"),
source:
{
title: "The Galactic Times",
url: "https://galactictimes.space/feed.xml"
}
}
```

I also think an example (choose one that makes sense, formatted properly!) would make sense here.


#### `title`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#### `title`
#### `source.title`

I'm wondering if this is helpful, especially as a heading that might show up in Algolia if someone searches for title in another context, like frontmatter of a Markdown file. Also helps to connect the properties to their object.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, thanks.


Type: `string (required)`

If you define a `source` you must define that source's `title`. It is the name of the original feed in which the item was published.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
If you define a `source` you must define that source's `title`. It is the name of the original feed in which the item was published.
The name of the original feed in which the item was published. (Note that this is the the feed's title, not the individual article title.)


#### `url`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#### `url`
#### `source.url`


Type: `string (required)`

If you define a `source` you must also define that source's `url` which identifies the URL of the original feed in which the item was published.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
If you define a `source` you must also define that source's `url` which identifies the URL of the original feed in which the item was published.
The URL of the original feed in which the item was published.


### `enclosure`

Type: `object (optional)`

Items that include media as part of the feed, like a podcast, can define an `enclosure` which is made of three required fields, a `url`, `length`, and `type`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Items that include media as part of the feed, like a podcast, can define an `enclosure` which is made of three required fields, a `url`, `length`, and `type`.
An object to specify properties for an included media source (e.g. a podcast) with three required values: `url`, `length`, and `type`.
```js
const item = {
title: "Alpha Centauri: so close you can touch it",
link: "/blog/alpha-centuari",
pubDate: new Date("2023-06-04"),
enclosure:
{
url: "https://mydomain/media/alpha-centauri.aac",
length: 124568,
type: "audio/aac"
}
}
```

I also think an example would be helpful here! Note, if the URL is from your own site, do you still need to use a full URL here? This is just an example. Adjust to make this correct!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The RSS spec says that you do need this to be a full URL. However, we can treat this like link, check if it's a valid URL and if it isn't run it through createCanonicalURL to make a full URL for a relative path.


#### `url`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#### `url`
#### `enclosure.url`


Type: `string (required)`

The `url` field for the `enclosure` defines a URL where the media can be found.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The `url` field for the `enclosure` defines a URL where the media can be found.
The URL where the media can be found.

If you only need to use e.g. /media/filename for media hosted on your own site, and not the full URL, then I would also add a sentence mentioning that full URLs are only required for external sources. (And, make sure the example code in enclosure demonstrates a correct URL!)


#### `length`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#### `length`
#### `enclosure.length`


Type: `number (required)`

The `length` field defines the size of the file found at the `url` in bytes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The `length` field defines the size of the file found at the `url` in bytes.
The size of the file found at the `url` in bytes.


#### `type`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#### `type`
#### `enclosure.type`


Type: `string (required)`

The `type` field defines the MIME type for the media item found at the `url`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The `type` field defines the MIME type for the media item found at the `url`.
The [MIME type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types) for the media item found at the `url`.


## `rssSchema`

When using content collections, you can configure your collection schema to enforce expected [`RSSFeedItem`](#items) properties. Import and apply `rssSchema` to ensure that each collection entry produces a valid RSS feed item:
Expand Down
32 changes: 31 additions & 1 deletion packages/astro-rss/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ type RSSFeedItem = {
customData?: z.infer<typeof rssSchema>['customData'];
/** Whether draft or not */
draft?: z.infer<typeof rssSchema>['draft'];
/** Categories or tags related to the item */
categories?: z.infer<typeof rssSchema>['categories'];
/** The item author's email address */
author?: z.infer<typeof rssSchema>['author'];
/** A URL of a page for comments related to the item */
commentsUrl?: z.infer<typeof rssSchema>['commentsUrl'];
/** The RSS channel that the item came from */
source?: z.infer<typeof rssSchema>['source'];
/** A media object that belongs to the item */
enclosure?: z.infer<typeof rssSchema>['enclosure'];
};

type ValidatedRSSFeedItem = z.infer<typeof rssFeedItemValidator>;
Expand Down Expand Up @@ -148,6 +158,7 @@ async function generateRSS(rssOptions: ValidatedRSSOptions): Promise<string> {
// when using `customData`
// https://github.com/withastro/astro/issues/5794
suppressEmptyNode: true,
suppressBooleanAttributes: false,
};
const parser = new XMLParser(xmlOptions);
const root: any = { '?xml': { '@_version': '1.0', '@_encoding': 'UTF-8' } };
Expand Down Expand Up @@ -196,7 +207,7 @@ async function generateRSS(rssOptions: ValidatedRSSOptions): Promise<string> {
const item: any = {
title: result.title,
link: itemLink,
guid: itemLink,
guid: { '#text': itemLink, '@_isPermaLink': 'true' },
};
if (result.description) {
item.description = result.description;
Expand All @@ -211,6 +222,25 @@ async function generateRSS(rssOptions: ValidatedRSSOptions): Promise<string> {
if (typeof result.customData === 'string') {
Object.assign(item, parser.parse(`<item>${result.customData}</item>`).item);
}
if (Array.isArray(result.categories)) {
item.category = result.categories;
}
if (typeof result.author === 'string') {
item.author = result.author;
}
if (typeof result.commentsUrl === 'string') {
item.comments = result.commentsUrl;
}
if (result.source) {
item.source = parser.parse(
`<source url="${result.source.url}">${result.source.title}</source>`
).source;
}
if (result.enclosure) {
item.enclosure = parser.parse(
`<enclosure url="${result.enclosure.url}" length="${result.enclosure.length}" type="${result.enclosure.type}"/>`
).enclosure;
}
return item;
});

Expand Down
11 changes: 11 additions & 0 deletions packages/astro-rss/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,15 @@ export const rssSchema = z.object({
description: z.string().optional(),
customData: z.string().optional(),
draft: z.boolean().optional(),
categories: z.array(z.string()).optional(),
author: z.string().optional(),
commentsUrl: z.string().url().optional(),
source: z.object({ url: z.string().url(), title: z.string() }).optional(),
enclosure: z
.object({
url: z.string().url(),
length: z.number().positive().int().finite(),
type: z.string(),
})
.optional(),
});
22 changes: 18 additions & 4 deletions packages/astro-rss/test/rss.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
phpFeedItemWithCustomData,
web1FeedItem,
web1FeedItemWithContent,
web1FeedItemWithAllData,
} from './test-utils.js';

chai.use(chaiPromises);
Expand All @@ -19,13 +20,15 @@ chai.use(chaiXml);
// note: I spent 30 minutes looking for a nice node-based snapshot tool
// ...and I gave up. Enjoy big strings!
// prettier-ignore
const validXmlResult = `<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title><![CDATA[${title}]]></title><description><![CDATA[${description}]]></description><link>${site}/</link><item><title><![CDATA[${phpFeedItem.title}]]></title><link>${site}${phpFeedItem.link}/</link><guid>${site}${phpFeedItem.link}/</guid><description><![CDATA[${phpFeedItem.description}]]></description><pubDate>${new Date(phpFeedItem.pubDate).toUTCString()}</pubDate></item><item><title><![CDATA[${web1FeedItem.title}]]></title><link>${site}${web1FeedItem.link}/</link><guid>${site}${web1FeedItem.link}/</guid><description><![CDATA[${web1FeedItem.description}]]></description><pubDate>${new Date(web1FeedItem.pubDate).toUTCString()}</pubDate></item></channel></rss>`;
const validXmlResult = `<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title><![CDATA[${title}]]></title><description><![CDATA[${description}]]></description><link>${site}/</link><item><title><![CDATA[${phpFeedItem.title}]]></title><link>${site}${phpFeedItem.link}/</link><guid isPermaLink="true">${site}${phpFeedItem.link}/</guid><description><![CDATA[${phpFeedItem.description}]]></description><pubDate>${new Date(phpFeedItem.pubDate).toUTCString()}</pubDate></item><item><title><![CDATA[${web1FeedItem.title}]]></title><link>${site}${web1FeedItem.link}/</link><guid isPermaLink="true">${site}${web1FeedItem.link}/</guid><description><![CDATA[${web1FeedItem.description}]]></description><pubDate>${new Date(web1FeedItem.pubDate).toUTCString()}</pubDate></item></channel></rss>`;
// prettier-ignore
const validXmlWithoutWeb1FeedResult = `<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title><![CDATA[${title}]]></title><description><![CDATA[${description}]]></description><link>${site}/</link><item><title><![CDATA[${phpFeedItem.title}]]></title><link>${site}${phpFeedItem.link}/</link><guid>${site}${phpFeedItem.link}/</guid><description><![CDATA[${phpFeedItem.description}]]></description><pubDate>${new Date(phpFeedItem.pubDate).toUTCString()}</pubDate></item></channel></rss>`;
const validXmlWithoutWeb1FeedResult = `<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title><![CDATA[${title}]]></title><description><![CDATA[${description}]]></description><link>${site}/</link><item><title><![CDATA[${phpFeedItem.title}]]></title><link>${site}${phpFeedItem.link}/</link><guid isPermaLink="true">${site}${phpFeedItem.link}/</guid><description><![CDATA[${phpFeedItem.description}]]></description><pubDate>${new Date(phpFeedItem.pubDate).toUTCString()}</pubDate></item></channel></rss>`;
// prettier-ignore
const validXmlWithContentResult = `<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title><![CDATA[${title}]]></title><description><![CDATA[${description}]]></description><link>${site}/</link><item><title><![CDATA[${phpFeedItemWithContent.title}]]></title><link>${site}${phpFeedItemWithContent.link}/</link><guid>${site}${phpFeedItemWithContent.link}/</guid><description><![CDATA[${phpFeedItemWithContent.description}]]></description><pubDate>${new Date(phpFeedItemWithContent.pubDate).toUTCString()}</pubDate><content:encoded><![CDATA[${phpFeedItemWithContent.content}]]></content:encoded></item><item><title><![CDATA[${web1FeedItemWithContent.title}]]></title><link>${site}${web1FeedItemWithContent.link}/</link><guid>${site}${web1FeedItemWithContent.link}/</guid><description><![CDATA[${web1FeedItemWithContent.description}]]></description><pubDate>${new Date(web1FeedItemWithContent.pubDate).toUTCString()}</pubDate><content:encoded><![CDATA[${web1FeedItemWithContent.content}]]></content:encoded></item></channel></rss>`;
const validXmlWithContentResult = `<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title><![CDATA[${title}]]></title><description><![CDATA[${description}]]></description><link>${site}/</link><item><title><![CDATA[${phpFeedItemWithContent.title}]]></title><link>${site}${phpFeedItemWithContent.link}/</link><guid isPermaLink="true">${site}${phpFeedItemWithContent.link}/</guid><description><![CDATA[${phpFeedItemWithContent.description}]]></description><pubDate>${new Date(phpFeedItemWithContent.pubDate).toUTCString()}</pubDate><content:encoded><![CDATA[${phpFeedItemWithContent.content}]]></content:encoded></item><item><title><![CDATA[${web1FeedItemWithContent.title}]]></title><link>${site}${web1FeedItemWithContent.link}/</link><guid isPermaLink="true">${site}${web1FeedItemWithContent.link}/</guid><description><![CDATA[${web1FeedItemWithContent.description}]]></description><pubDate>${new Date(web1FeedItemWithContent.pubDate).toUTCString()}</pubDate><content:encoded><![CDATA[${web1FeedItemWithContent.content}]]></content:encoded></item></channel></rss>`;
// prettier-ignore
const validXmlWithCustomDataResult = `<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title><![CDATA[${title}]]></title><description><![CDATA[${description}]]></description><link>${site}/</link><item><title><![CDATA[${phpFeedItemWithCustomData.title}]]></title><link>${site}${phpFeedItemWithCustomData.link}/</link><guid>${site}${phpFeedItemWithCustomData.link}/</guid><description><![CDATA[${phpFeedItemWithCustomData.description}]]></description><pubDate>${new Date(phpFeedItemWithCustomData.pubDate).toUTCString()}</pubDate>${phpFeedItemWithCustomData.customData}</item><item><title><![CDATA[${web1FeedItemWithContent.title}]]></title><link>${site}${web1FeedItemWithContent.link}/</link><guid>${site}${web1FeedItemWithContent.link}/</guid><description><![CDATA[${web1FeedItemWithContent.description}]]></description><pubDate>${new Date(web1FeedItemWithContent.pubDate).toUTCString()}</pubDate><content:encoded><![CDATA[${web1FeedItemWithContent.content}]]></content:encoded></item></channel></rss>`;
const validXmlResultWithAllData = `<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title><![CDATA[${title}]]></title><description><![CDATA[${description}]]></description><link>${site}/</link><item><title><![CDATA[${phpFeedItem.title}]]></title><link>${site}${phpFeedItem.link}/</link><guid isPermaLink="true">${site}${phpFeedItem.link}/</guid><description><![CDATA[${phpFeedItem.description}]]></description><pubDate>${new Date(phpFeedItem.pubDate).toUTCString()}</pubDate></item><item><title><![CDATA[${web1FeedItemWithAllData.title}]]></title><link>${site}${web1FeedItemWithAllData.link}/</link><guid isPermaLink="true">${site}${web1FeedItemWithAllData.link}/</guid><description><![CDATA[${web1FeedItemWithAllData.description}]]></description><pubDate>${new Date(web1FeedItemWithAllData.pubDate).toUTCString()}</pubDate><category>${web1FeedItemWithAllData.categories[0]}</category><category>${web1FeedItemWithAllData.categories[1]}</category><author>${web1FeedItemWithAllData.author}</author><comments>${web1FeedItemWithAllData.commentsUrl}</comments><source url="${web1FeedItemWithAllData.source.url}">${web1FeedItemWithAllData.source.title}</source><enclosure url="${web1FeedItemWithAllData.enclosure.url}" length="${web1FeedItemWithAllData.enclosure.length}" type="${web1FeedItemWithAllData.enclosure.type}"/></item></channel></rss>`;
// prettier-ignore
const validXmlWithCustomDataResult = `<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title><![CDATA[${title}]]></title><description><![CDATA[${description}]]></description><link>${site}/</link><item><title><![CDATA[${phpFeedItemWithCustomData.title}]]></title><link>${site}${phpFeedItemWithCustomData.link}/</link><guid isPermaLink="true">${site}${phpFeedItemWithCustomData.link}/</guid><description><![CDATA[${phpFeedItemWithCustomData.description}]]></description><pubDate>${new Date(phpFeedItemWithCustomData.pubDate).toUTCString()}</pubDate>${phpFeedItemWithCustomData.customData}</item><item><title><![CDATA[${web1FeedItemWithContent.title}]]></title><link>${site}${web1FeedItemWithContent.link}/</link><guid isPermaLink="true">${site}${web1FeedItemWithContent.link}/</guid><description><![CDATA[${web1FeedItemWithContent.description}]]></description><pubDate>${new Date(web1FeedItemWithContent.pubDate).toUTCString()}</pubDate><content:encoded><![CDATA[${web1FeedItemWithContent.content}]]></content:encoded></item></channel></rss>`;
// prettier-ignore
const validXmlWithStylesheet = `<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/feedstylesheet.css"?><rss version="2.0"><channel><title><![CDATA[${title}]]></title><description><![CDATA[${description}]]></description><link>${site}/</link></channel></rss>`;
// prettier-ignore
Expand Down Expand Up @@ -54,6 +57,17 @@ describe('rss', () => {
chai.expect(body).xml.to.equal(validXmlWithContentResult);
});

it('should generate on valid RSSFeedItem array with all RSS content included', async () => {
const { body } = await rss({
title,
description,
items: [phpFeedItem, web1FeedItemWithAllData],
site,
});

chai.expect(body).xml.to.equal(validXmlResultWithAllData);
});

it('should generate on valid RSSFeedItem array with custom data included', async () => {
const { body } = await rss({
xmlns: {
Expand Down
15 changes: 15 additions & 0 deletions packages/astro-rss/test/test-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,18 @@ export const web1FeedItemWithContent = {
...web1FeedItem,
content: `<h1>${web1FeedItem.title}</h1><p>${web1FeedItem.description}</p>`,
};
export const web1FeedItemWithAllData = {
...web1FeedItem,
categories: ['web1', 'history'],
author: '[email protected]',
commentsUrl: 'http://example.com/comments',
source: {
url: 'http://example.com/source',
title: 'The Web 1.0 blog',
},
enclosure: {
url: 'http://example.com/podcast.mp3',
length: 256,
type: 'audio/mpeg',
},
};