-
-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
351 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,270 @@ | ||
import assert from 'node:assert/strict'; | ||
import { describe, it } from 'node:test'; | ||
|
||
import { z } from 'astro/zod'; | ||
import atom, { getAtomString } from '../dist/index.js'; | ||
import { atomSchema } from '../dist/schema.js'; | ||
import { | ||
subtitle, | ||
parseXmlString, | ||
phpFeedEntry, | ||
phpFeedEntryWithContent, | ||
phpFeedEntryWithCustomData, | ||
site, | ||
title, | ||
web1FeedEntry, | ||
web1FeedEntryWithAllData, | ||
web1FeedEntryWithContent, | ||
} from './test-utils.js'; | ||
|
||
// note: I spent 30 minutes looking for a nice node-based snapshot tool | ||
// ...and I gave up. Enjoy big strings! | ||
|
||
// biome-ignore format: keep in one line | ||
const validXmlResult = `<?xml version="1.0" encoding="UTF-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title><![CDATA[${title}]]></title><subtitle><![CDATA[${subtitle}]]></subtitle><link href="${site}/" /><entry><title><![CDATA[${phpFeedEntry.title}]]></title><link href="${site}${phpFeedEntry.link}/" /><id>${site}${phpFeedEntry.link}/</id><summary><![CDATA[${phpFeedEntry.summary}]]></summary><updated>${new Date(phpFeedEntry.updated).toUTCString()}</updated></entry><entry><title><![CDATA[${web1FeedEntry.title}]]></title><link href="${site}${web1FeedEntry.link}/" /><id>${site}${web1FeedEntry.link}/</id><summary><![CDATA[${web1FeedEntry.summary}]]></summary><updated>${new Date(web1FeedEntry.updated).toUTCString()}</updated></entry></feed>`; | ||
// biome-ignore format: keep in one line | ||
const validXmlWithContentResult = `<?xml version="1.0" encoding="UTF-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title><![CDATA[${title}]]></title><subtitle><![CDATA[${subtitle}]]></subtitle><link href="${site}/" /><entry><title><![CDATA[${phpFeedEntryWithContent.title}]]></title><link href="${site}${phpFeedEntryWithContent.link}/" /><id>${site}${phpFeedEntryWithContent.link}/</id><summary><![CDATA[${phpFeedEntryWithContent.summary}]]></summary><updated>${new Date(phpFeedEntryWithContent.updated).toUTCString()}</updated><content type="html"><![CDATA[${phpFeedEntryWithContent.content}]]></content></entry><entry><title><![CDATA[${web1FeedEntryWithContent.title}]]></title><link href="${site}${web1FeedEntryWithContent.link}/" /><id>${site}${web1FeedEntryWithContent.link}/</id><summary><![CDATA[${web1FeedEntryWithContent.summary}]]></summary><updated>${new Date(web1FeedEntryWithContent.updated).toUTCString()}</updated><content type="html"><![CDATA[${web1FeedEntryWithContent.content}]]></content></entry></feed>`; | ||
// biome-ignore format: keep in one line | ||
const validXmlResultWithAllData = `<?xml version="1.0" encoding="UTF-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title><![CDATA[${title}]]></title><subtitle><![CDATA[${subtitle}]]></subtitle><link href="${site}/" /><entry><title><![CDATA[${phpFeedEntry.title}]]></title><link href="${site}${phpFeedEntry.link}/" /><id>${site}${phpFeedEntry.link}/</id><summary><![CDATA[${phpFeedEntry.summary}]]></summary><updated>${new Date(phpFeedEntry.updated).toUTCString()}</updated></entry><entry><title><![CDATA[${web1FeedEntryWithAllData.title}]]></title><link href="${site}${web1FeedEntryWithAllData.link}/" /><id>${site}${web1FeedEntryWithAllData.link}/</id><summary><![CDATA[${web1FeedEntryWithAllData.summary}]]></summary><updated>${new Date(web1FeedEntryWithAllData.updated).toUTCString()}</updated><category term="${web1FeedEntryWithAllData.categories[0]}" /><category term="${web1FeedEntryWithAllData.categories[1]}" /><author><name>${web1FeedEntryWithAllData.author.name}</name><email>${web1FeedEntryWithAllData.author.email}</email></author><source><title>${web1FeedEntryWithAllData.source.title}</title><link href="${web1FeedEntryWithAllData.source.url}" /></source><link rel="enclosure" href="${site}${web1FeedEntryWithAllData.enclosure.url}" length="${web1FeedEntryWithAllData.enclosure.length}" type="${web1FeedEntryWithAllData.enclosure.type}"/></entry></feed>`; | ||
// biome-ignore format: keep in one line | ||
const validXmlWithCustomDataResult = `<?xml version="1.0" encoding="UTF-8"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/"><title><![CDATA[${title}]]></title><subtitle><![CDATA[${subtitle}]]></subtitle><link href="${site}/" /><entry><title><![CDATA[${phpFeedEntryWithCustomData.title}]]></title><link href="${site}${phpFeedEntryWithCustomData.link}/" /><id>${site}${phpFeedEntryWithCustomData.link}/</id><summary><![CDATA[${phpFeedEntryWithCustomData.summary}]]></summary><updated>${new Date(phpFeedEntryWithCustomData.updated).toUTCString()}</updated>${phpFeedEntryWithCustomData.customData}</entry><entry><title><![CDATA[${web1FeedEntryWithContent.title}]]></title><link href="${site}${web1FeedEntryWithContent.link}/" /><id>${site}${web1FeedEntryWithContent.link}/</id><summary><![CDATA[${web1FeedEntryWithContent.summary}]]></summary><updated>${new Date(web1FeedEntryWithContent.updated).toUTCString()}</updated><content type="html"><![CDATA[${web1FeedEntryWithContent.content}]]></content></entry></feed>`; | ||
// biome-ignore format: keep in one line | ||
const validXmlWithStylesheet = `<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/feedstylesheet.css"?><feed xmlns="http://www.w3.org/2005/Atom"><title><![CDATA[${title}]]></title><subtitle><![CDATA[${subtitle}]]></subtitle><link href="${site}/" /></feed>`; | ||
// biome-ignore format: keep in one line | ||
const validXmlWithXSLStylesheet = `<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/feedstylesheet.xsl" type="text/xsl"?><feed xmlns="http://www.w3.org/2005/Atom"><title><![CDATA[${title}]]></title><subtitle><![CDATA[${subtitle}]]></subtitle><link href="${site}/" /></feed>`; | ||
|
||
function assertXmlDeepEqual(a, b) { | ||
const parsedA = parseXmlString(a); | ||
const parsedB = parseXmlString(b); | ||
|
||
assert.equal(parsedA.err, null); | ||
assert.equal(parsedB.err, null); | ||
assert.deepEqual(parsedA.result, parsedB.result); | ||
} | ||
|
||
describe('atom', () => { | ||
it('should return a response', async () => { | ||
const response = await atom({ | ||
title, | ||
subtitle, | ||
entries: [phpFeedEntry, web1FeedEntry], | ||
site, | ||
}); | ||
|
||
const str = await response.text(); | ||
|
||
// NOTE: Chai used the below parser to perform the tests, but I have omitted it for now. | ||
// parser = new xml2js.Parser({ trim: flag(this, 'deep') }); | ||
|
||
assertXmlDeepEqual(str, validXmlResult); | ||
|
||
const contentType = response.headers.get('Content-Type'); | ||
assert.equal(contentType, 'application/xml'); | ||
}); | ||
|
||
it('should be the same string as getAtomString', async () => { | ||
const options = { | ||
title, | ||
subtitle, | ||
entries: [phpFeedEntry, web1FeedEntry], | ||
site, | ||
}; | ||
|
||
const response = await atom(options); | ||
const str1 = await response.text(); | ||
const str2 = await getAtomString(options); | ||
|
||
assert.equal(str1, str2); | ||
}); | ||
}); | ||
|
||
describe('getAtomString', () => { | ||
it('should generate on valid AtomFeedEntry array', async () => { | ||
const str = await getAtomString({ | ||
title, | ||
subtitle, | ||
entries: [phpFeedEntry, web1FeedEntry], | ||
site, | ||
}); | ||
|
||
assertXmlDeepEqual(str, validXmlResult); | ||
}); | ||
|
||
it('should generate on valid AtomFeedEntry array with HTML content included', async () => { | ||
const str = await getAtomString({ | ||
title, | ||
subtitle, | ||
entries: [phpFeedEntryWithContent, web1FeedEntryWithContent], | ||
site, | ||
}); | ||
|
||
assertXmlDeepEqual(str, validXmlWithContentResult); | ||
}); | ||
|
||
it('should generate on valid AtomFeedEntry array with all Atom content included', async () => { | ||
const str = await getAtomString({ | ||
title, | ||
subtitle, | ||
entries: [phpFeedEntry, web1FeedEntryWithAllData], | ||
site, | ||
}); | ||
|
||
assertXmlDeepEqual(str, validXmlResultWithAllData); | ||
}); | ||
|
||
it('should generate on valid AtomFeedEntry array with custom data included', async () => { | ||
const str = await getAtomString({ | ||
xmlns: { | ||
dc: 'http://purl.org/dc/elements/1.1/', | ||
}, | ||
title, | ||
subtitle, | ||
entries: [phpFeedEntryWithCustomData, web1FeedEntryWithContent], | ||
site, | ||
}); | ||
|
||
assertXmlDeepEqual(str, validXmlWithCustomDataResult); | ||
}); | ||
|
||
it('should include xml-stylesheet instruction when stylesheet is defined', async () => { | ||
const str = await getAtomString({ | ||
title, | ||
subtitle, | ||
entries: [], | ||
site, | ||
stylesheet: '/feedstylesheet.css', | ||
}); | ||
|
||
assertXmlDeepEqual(str, validXmlWithStylesheet); | ||
}); | ||
|
||
it('should include xml-stylesheet instruction with xsl type when stylesheet is set to xsl file', async () => { | ||
const str = await getAtomString({ | ||
title, | ||
subtitle, | ||
entries: [], | ||
site, | ||
stylesheet: '/feedstylesheet.xsl', | ||
}); | ||
|
||
assertXmlDeepEqual(str, validXmlWithXSLStylesheet); | ||
}); | ||
|
||
it('should preserve self-closing tags on `customData`', async () => { | ||
const customData = | ||
'<atom:link href="https://example.com/feed.xml" rel="self" type="application/rss+xml"/>'; | ||
const str = await getAtomString({ | ||
title, | ||
subtitle, | ||
entries: [], | ||
site, | ||
xmlns: { | ||
atom: 'http://www.w3.org/2005/Atom', | ||
}, | ||
customData, | ||
}); | ||
|
||
assert.ok(str.includes(customData)); | ||
}); | ||
|
||
it('should not append trailing slash to URLs with the given option', async () => { | ||
const str = await getAtomString({ | ||
title, | ||
subtitle, | ||
entries: [phpFeedEntry], | ||
site, | ||
trailingSlash: false, | ||
}); | ||
|
||
assert.ok(str.includes('https://example.com/"')); | ||
assert.ok(str.includes('https://example.com/php"')); | ||
}); | ||
|
||
// it('Deprecated import.meta.glob mapping still works', async () => { | ||
// const globResult = { | ||
// './posts/php.md': () => | ||
// new Promise((resolve) => | ||
// resolve({ | ||
// url: phpFeedEntry.link, | ||
// frontmatter: { | ||
// title: phpFeedEntry.title, | ||
// updated: phpFeedEntry.updated, | ||
// summary: phpFeedEntry.summary, | ||
// }, | ||
// }) | ||
// ), | ||
// './posts/nested/web1.md': () => | ||
// new Promise((resolve) => | ||
// resolve({ | ||
// url: web1FeedEntry.link, | ||
// frontmatter: { | ||
// title: web1FeedEntry.title, | ||
// updated: web1FeedEntry.updated, | ||
// summary: web1FeedEntry.summary, | ||
// }, | ||
// }) | ||
// ), | ||
// }; | ||
|
||
// const str = await getAtomString({ | ||
// title, | ||
// summary, | ||
// entries: globResult, | ||
// site, | ||
// }); | ||
|
||
// assertXmlDeepEqual(str, validXmlResult); | ||
// }); | ||
|
||
it('should fail when an invalid date string is provided', async () => { | ||
const res = atomSchema.safeParse({ | ||
title: phpFeedEntry.title, | ||
updated: 'invalid date', | ||
summary: phpFeedEntry.summary, | ||
link: phpFeedEntry.link, | ||
}); | ||
|
||
assert.equal(res.success, false); | ||
assert.equal(res.error.issues[0].path[0], 'updated'); | ||
}); | ||
|
||
it('should be extendable', () => { | ||
let error = null; | ||
try { | ||
atomSchema.extend({ | ||
category: z.string().optional(), | ||
}); | ||
} catch (e) { | ||
error = e.message; | ||
} | ||
assert.equal(error, null); | ||
}); | ||
|
||
it('should not fail when an enclosure has a length of 0', async () => { | ||
let error = null; | ||
try { | ||
await getAtomString({ | ||
title, | ||
summary: subtitle, | ||
entries: [ | ||
{ | ||
title: 'Title', | ||
updated: new Date().toISOString(), | ||
summary: 'Description', | ||
link: '/link', | ||
enclosure: { | ||
url: '/enclosure', | ||
length: 0, | ||
type: 'audio/mpeg', | ||
}, | ||
}, | ||
], | ||
site, | ||
}); | ||
} catch (e) { | ||
error = e.message; | ||
} | ||
|
||
assert.equal(error, null); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import xml2js from 'xml2js'; | ||
|
||
export const title = 'My Atom feed'; | ||
export const subtitle = 'This sure is a nice Atom feed'; | ||
export const site = 'https://example.com'; | ||
|
||
export const phpFeedEntry = { | ||
link: '/php', | ||
title: 'Remember PHP?', | ||
updated: '1994-05-03', | ||
summary: | ||
'PHP is a general-purpose scripting language geared toward web development. It was originally created by Danish-Canadian programmer Rasmus Lerdorf in 1994.', | ||
}; | ||
export const phpFeedEntryWithContent = { | ||
...phpFeedEntry, | ||
content: `<h1>${phpFeedEntry.title}</h1><p>${phpFeedEntry.summary}</p>`, | ||
}; | ||
export const phpFeedEntryWithCustomData = { | ||
...phpFeedEntry, | ||
customData: '<dc:creator><![CDATA[Buster Bluth]]></dc:creator>', | ||
}; | ||
|
||
export const web1FeedEntry = { | ||
// Should support empty string as a URL (possible for homepage route) | ||
link: '', | ||
title: 'Web 1.0', | ||
updated: '1997-05-03', | ||
summary: | ||
'Web 1.0 is the term used for the earliest version of the Internet as it emerged from its origins with Defense Advanced Research Projects Agency (DARPA) and became, for the first time, a global network representing the future of digital communications.', | ||
}; | ||
export const web1FeedEntryWithContent = { | ||
...web1FeedEntry, | ||
content: `<h1>${web1FeedEntry.title}</h1><p>${web1FeedEntry.summary}</p>`, | ||
}; | ||
export const web1FeedEntryWithAllData = { | ||
...web1FeedEntry, | ||
categories: ['web1', 'history'], | ||
author: { | ||
name: 'test', | ||
email: 'test@example.com' | ||
}, | ||
// commentsUrl: 'http://example.com/comments', | ||
source: { | ||
url: 'http://example.com/source', | ||
title: 'The Web 1.0 blog', | ||
}, | ||
enclosure: { | ||
url: '/podcast.mp3', | ||
length: 256, | ||
type: 'audio/mpeg', | ||
}, | ||
}; | ||
|
||
const parser = new xml2js.Parser({ trim: true }); | ||
|
||
/** | ||
* | ||
* Utility function to parse an XML string into an object using `xml2js`. | ||
* | ||
* @param {string} xmlString - Stringified XML to parse. | ||
* @return {{ err: Error, result: any }} Represents an option containing the parsed XML string or an Error. | ||
*/ | ||
export function parseXmlString(xmlString) { | ||
let res; | ||
parser.parseString(xmlString, (err, result) => { | ||
res = { err, result }; | ||
}); | ||
return res; | ||
} |