const assert = require('assert'); const xml2js = require('xml2js'); const feed = require('..'); const Metalsmith = require('metalsmith'); const collections = require('metalsmith-collections'); const {promisify} = require('util'); const parseString = promisify(xml2js.parseString); const parse = async function(files, filename = 'rss.xml') { const result = await parseString(files[filename].contents); return result.rss; }; Metalsmith.prototype.buildAsPromised = promisify(Metalsmith.prototype.build); const build = async function(metalsmith) { return await metalsmith.buildAsPromised(); }; const metalsmithWithComplexFixtures = function() { return Metalsmith('test/fixtures/complex') .metadata({site}) .use(collections({posts: '*.html'})); }; const parseComplexFixtures = async function(files) { const rss = await parse(files); const channel = rss.channel[0]; return { postWithoutMeta: channel.item[0], postWithMeta: channel.item[1] }; }; const site = { title: 'Geocities', url: 'http://example.com', author: 'Philodemus' }; describe('metalsmith-feed', function() { beforeEach(async function() {}); it('renders an RSS feed', async function() { const metalsmith = Metalsmith('test/fixtures/simple') .metadata({site}) .use(collections({posts: '*.html'})) .use( feed({ collection: 'posts' }) ); const rss = await parse(await build(metalsmith)); assert.equal(rss['$']['xmlns:atom'], 'http://www.w3.org/2005/Atom'); const channel = rss.channel[0]; assert.equal(channel.title[0], site.title); assert.equal(channel.author[0], site.author); assert.equal(channel.item.length, 1); const post = channel.item[0]; assert.equal(post.title[0], 'Theory of Juice'); assert.equal(post.description[0], '

juice appeal

\n'); }); it('renders multiple feeds', async function() { const metalsmith = Metalsmith('test/fixtures/many_posts') .metadata({site}) .use( collections({ posts1: 'post1*.html', posts2: 'post2*.html' }) ) .use( feed({ collection: 'posts1', destination: 'rss1.xml' }) ) .use( feed({ collection: 'posts2', destination: 'rss2.xml' }) ); const files = await build(metalsmith); const rss1 = await parse(files, 'rss1.xml'); assert(rss1['channel'][0].item.length > 0); const rss2 = await parse(files, 'rss2.xml'); assert(rss2['channel'][0].item.length > 0); }); describe('metalsmith integration', function() { it('sets url to permalink', async function() { const metalsmith = metalsmithWithComplexFixtures().use( feed({collection: 'posts'}) ); const {postWithoutMeta} = await parseComplexFixtures( await build(metalsmith) ); assert.equal( postWithoutMeta.link[0], 'http://example.com/post_without_meta.html' ); assert.equal( postWithoutMeta.guid[0]._, 'http://example.com/post_without_meta.html' ); }); it('sets url to external link from file meta', async function() { const metalsmith = metalsmithWithComplexFixtures().use( feed({collection: 'posts'}) ); const {postWithMeta} = await parseComplexFixtures( await build(metalsmith) ); assert.equal(postWithMeta.link[0], 'https://theory.com/juice/'); assert.equal( postWithMeta.guid[0]._, 'http://example.com/post_with_meta.html' ); }); }); describe('preprocessor option', function() { it('can customize description', async function() { const metalsmith = Metalsmith('test/fixtures/simple') .metadata({site}) .use(collections({posts: '*.html'})) .use( feed({ collection: 'posts', preprocess: file => ({ ...file, description: `

${file.title}

${file.contents}` }) }) ); const rss = await parse(await build(metalsmith)); const post = rss.channel[0].item[0]; assert.equal(post.title[0], 'Theory of Juice'); assert.equal( post.description[0], '

Theory of Juice

juice appeal

\n' ); }); it('can add custom_elements for podcasts', async function() { const makeFeaturedImage = file => { if (!file.featuredImage) { return null; } return [ { 'media:image': [ { _attr: { url: `http://example.com${file.featuredImage}`, medium: 'image' } } ] } ]; }; const metalsmith = Metalsmith('test/fixtures/complex') .metadata({site}) .use(collections({posts: '*.html'})) .use( feed({ collection: 'posts', preprocess: file => ({ ...file, custom_elements: makeFeaturedImage(file) }) }) ); const {postWithMeta, postWithoutMeta} = await parseComplexFixtures( await build(metalsmith) ); assert.equal( postWithMeta['media:image'][0]['$']['url'], 'http://example.com/foo.jpg' ); assert.equal(postWithMeta['media:image'][0]['$']['medium'], 'image'); assert.equal(postWithoutMeta['media:image'], undefined); }); }); describe('limit option', function() { beforeEach(function() { this.metalsmith = Metalsmith('test/fixtures/many_posts') .metadata({site}) .use(collections({posts: '*.html'})); }); it('limits the number of documents included in the feed', async function() { this.metalsmith.use( feed({ collection: 'posts', limit: 10 }) ); const rss = await parse(await build(this.metalsmith)); assert.equal(rss.channel[0].item.length, 10); }); it('is unlimited when set to false', async function() { this.metalsmith.use( feed({ collection: 'posts', limit: false }) ); const rss = await parse(await build(this.metalsmith)); assert.equal(rss.channel[0].item.length, 25); }); }); it('complains if metalsmith-collections isnt setup', async function() { const metalsmith = Metalsmith('test/fixtures/simple') .metadata({site}) .use( feed({ collection: 'posts' }) ); try { await metalsmith.buildAsPromised(); } catch (e) { var err = e; } assert(err); }); it('complains without a site_url', async function() { const metalsmith = Metalsmith('test/fixtures/simple') .use(collections({posts: '*.html'})) .use( feed({ collection: 'posts' }) ); try { await metalsmith.buildAsPromised(); } catch (e) { var err = e; } assert(err); }); });