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

feat: added test for build-rss.js #3101

Open
wants to merge 46 commits into
base: master
Choose a base branch
from

Conversation

vishvamsinh28
Copy link
Contributor

@vishvamsinh28 vishvamsinh28 commented Jul 21, 2024

This PR adds test for build-rss.js script

Summary by CodeRabbit

  • New Features

    • Enhanced error handling for RSS feed generation, ensuring more robust performance.
    • Added comprehensive unit tests for the RSS feed functionality, covering various scenarios and edge cases.
    • Introduced mock data structures for testing RSS feed generation.
  • Bug Fixes

    • Improved handling of missing fields and date attributes in posts, preventing errors during RSS feed generation.
  • Documentation

    • Updated test suite documentation to reflect new test cases and scenarios.

Copy link

netlify bot commented Jul 21, 2024

Deploy Preview for asyncapi-website ready!

Built without sensitive environment variables

Name Link
🔨 Latest commit e04cfba
🔍 Latest deploy log https://app.netlify.com/sites/asyncapi-website/deploys/66ff7feef6e0b00008a8dba6
😎 Deploy Preview https://deploy-preview-3101--asyncapi-website.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

@asyncapi-bot
Copy link
Contributor

asyncapi-bot commented Jul 21, 2024

⚡️ Lighthouse report for the changes in this PR:

Category Score
🔴 Performance 44
🟢 Accessibility 98
🟢 Best practices 92
🟢 SEO 100
🔴 PWA 33

Lighthouse ran on https://deploy-preview-3101--asyncapi-website.netlify.app/

@sambhavgupta0705 sambhavgupta0705 added the gsoc This label should be used for issues or discussions related to ideas for Google Summer of Code label Jul 23, 2024
@anshgoyalevil
Copy link
Member

@vishvamsinh28 Statement and branch coverage aren't 100% for this file.

image

@vishvamsinh28
Copy link
Contributor Author

vishvamsinh28 commented Aug 16, 2024

@anshgoyalevil updated the test, now it has 98.33% coverage. I will push another change to make it 100%

const xml = json2xml.getXml(feed, '@', '', 2)
fs.writeFileSync(`./public/${outputPath}`, xml, 'utf8')
} catch (err) {
throw new Error(`Failed to generate RSS feed: ${err.message}`);
Copy link
Member

Choose a reason for hiding this comment

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

instead of throwing error, can we return error as promise?

@vishvamsinh28 @anshgoyalevil

Copy link
Member

Choose a reason for hiding this comment

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

We are processing the script synchronously, so returning a promise wouldn't be appropriate here

Copy link
Member

Choose a reason for hiding this comment

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

Catching of error doesn't depend on whether the function is synchronous or asynchronous. It will still return the error, based on the function return type.

});

it('should generate RSS feed and write to file', () => {
rssFeed(type, title, desc, outputPath);
Copy link
Member

Choose a reason for hiding this comment

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

can we add a try/catch block here to track the err variable here and see if any error is not returned from function execution? Try to do it for each test.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

Copy link
Member

Choose a reason for hiding this comment

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

Why are we not following eslint rules inside this file? I see lot of indentation spaces 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.

I'm not getting any lint errors locally, and I have removed unnecessary indentation in previous commits.

@anshgoyalevil
Copy link
Member

There's still room for improvement, @vishvamsinh28
image

try {
rssFeed(type, title, desc, outputPath);
} catch (err) {
console.error('Error encountered during test execution:', err)
Copy link
Member

Choose a reason for hiding this comment

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

Instead of using console.error, store the err object inside a variable and during assertions, check that error object should be empty. This leads to the right approach to test the functions if they are executed perfectly or not.

Copy link
Member

Choose a reason for hiding this comment

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

Also, add the scenarios where the function should actually lead to an error, instead of executing it perfectly.

Copy link
Member

Choose a reason for hiding this comment

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

@vishvamsinh28 @anshgoyalevil Comment not addressed

Copy link
Contributor Author

@vishvamsinh28 vishvamsinh28 Sep 28, 2024

Choose a reason for hiding this comment

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

I'll add more tests for it. 👍 I can add a test to check if the user doesn't have a posts.json file, but for that, I'll have to temporarily rename the file. Should I add that test?

Copy link
Member

@akshatnema akshatnema Sep 28, 2024

Choose a reason for hiding this comment

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

No, consider a scenario if data inside posts.json is in wrong manner. Then it should return error, right? Make the tests for those cases as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@akshatnema updated the test

@vishvamsinh28
Copy link
Contributor Author

@akshatnema @anshgoyalevil Updated the test

Comment on lines 128 to 130
jest.doMock('../config/posts.json', () => {
throw new Error('Cannot find module');
}, { virtual: true });
Copy link
Member

@anshgoyalevil anshgoyalevil Sep 20, 2024

Choose a reason for hiding this comment

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

Why are you throwing an error here. The build-rss.js file will do that.

Create the error situation for the file that needs to be tested, and let it throw the error.

@vishvamsinh28
Copy link
Contributor Author

@anshgoyalevil Any review for this PR ?

Copy link

coderabbitai bot commented Oct 4, 2024

Walkthrough

The changes introduce significant enhancements to the rssFeed function in scripts/build-rss.js, focusing on error handling and control flow restructuring. The function now returns a Promise, encapsulating the RSS feed generation process in a try-catch block. Key updates include checks for missing fields and improved sorting logic for posts. Additionally, a new test suite in tests/build-rss.test.js thoroughly validates the functionality of the rssFeed function, supported by mock data in tests/fixtures/rssData.js to cover various scenarios.

Changes

File Change Summary
scripts/build-rss.js Updated rssFeed function to return a Promise, added error handling for missing fields, restructured control flow, and moved RSS object initialization inside the Promise.
tests/build-rss.test.js Introduced a comprehensive unit test suite for rssFeed, covering scenarios such as feed generation, post prioritization, sorting, enclosure types, error handling, and validation for malformed and empty posts.
tests/fixtures/rssData.js Added mock data structures for testing, including mockRssData, missingDateMockData, and incompletePostMockData, along with constants for RSS feed metadata and output path, facilitating various test scenarios.

Poem

In the meadow where the data flows,
A rabbit hops where the RSS grows.
With promises wrapped in a cozy embrace,
Errors caught, we quicken the pace.
Mocking the posts, we test with delight,
A feed full of wonders, all shining bright! 🐇✨


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@akshatnema
Copy link
Member

@coderabbitai full review

Copy link

coderabbitai bot commented Oct 5, 2024

✅ Actions performed

Full review triggered.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 16

🧹 Outside diff range and nitpick comments (7)
tests/fixtures/rssData.js (4)

1-43: LGTM! Consider adding more edge cases.

The mockRssData structure is well-designed for testing various scenarios, including featured and non-featured posts, different image formats, and consistent date formats.

Consider adding more edge cases to enhance test coverage, such as:

  • A post with an empty excerpt
  • A post with a very long title or excerpt
  • A post with special characters in the title or slug

63-72: Good test case for incomplete posts. Consider expanding.

The incompletePostMockData provides a valuable test case for handling posts with missing title attributes.

Consider expanding this test case to include other scenarios of incomplete data, such as:

  • A post missing an excerpt
  • A post with an empty slug
    This would provide more comprehensive coverage for error handling in the RSS builder.

74-77: LGTM! Consider using an object for configuration.

The constants type, title, desc, and outputPath provide clear configuration for the RSS feed generation.

Consider grouping these configuration constants into a single object for better organization:

const rssConfig = {
  type: 'blog',
  title: 'Test Blog RSS',
  desc: 'Test blog RSS feed',
  outputPath: 'test-output/blog.xml'
};

This approach would make it easier to add or modify configuration options in the future.


79-79: Consider reordering exports for consistency.

The module exports include all necessary constants, which is good.

Consider reordering the exports to match the order of declaration in the file for better readability:

module.exports = { 
  mockRssData, 
  missingDateMockData, 
  incompletePostMockData, 
  type, 
  title, 
  desc, 
  outputPath 
};

This makes it easier to locate exported items when referencing the file.

scripts/build-rss.js (2)

57-58: Enhance error messages with post identifiers

When throwing an error due to missing required fields, including the post's identifier can help in debugging.

Modify the error message:

- throw new Error('Missing required fields in post data');
+ throw new Error(`Missing required fields in post data for post: ${post.slug || post.title || 'unknown'}`);

67-72: Simplify image type detection logic

The current logic checks for multiple image types using multiple if statements. Simplify it by using a mapping of extensions to MIME types.

Refactor the code:

if (typeof enclosure["@url"] === 'string') {
  let tmp = enclosure["@url"].toLowerCase();
- if (tmp.indexOf('.png') >= 0) enclosure["@type"] = 'image/png';
- if (tmp.indexOf('.svg') >= 0) enclosure["@type"] = 'image/svg+xml';
- if (tmp.indexOf('.webp') >= 0) enclosure["@type"] = 'image/webp';
+ const extension = tmp.split('.').pop().split('?')[0];
+ const mimeTypes = {
+   png: 'image/png',
+   jpg: 'image/jpeg',
+   jpeg: 'image/jpeg',
+   svg: 'image/svg+xml',
+   webp: 'image/webp'
+ };
+ enclosure["@type"] = mimeTypes[extension] || 'image/jpeg';
}

This reduces code repetition and makes it easier to add support for more image types in the future.

tests/build-rss.test.js (1)

17-23: Handle potential errors during cleanup in afterAll

When deleting files and directories during cleanup, it's good practice to handle potential errors to prevent tests from failing unexpectedly.

Example:

afterAll(() => {
  if (fs.existsSync(testOutputDir)) {
    fs.readdirSync(testOutputDir).forEach(file => {
      try {
        fs.unlinkSync(path.join(testOutputDir, file));
      } catch (err) {
        console.error(`Error deleting file: ${file}`, err);
      }
    });
    try {
      fs.rmdirSync(testOutputDir);
    } catch (err) {
      console.error(`Error removing directory: ${testOutputDir}`, err);
    }
  }
});
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between e56289b and e04cfba.

📒 Files selected for processing (3)
  • scripts/build-rss.js (2 hunks)
  • tests/build-rss.test.js (1 hunks)
  • tests/fixtures/rssData.js (1 hunks)
🔇 Additional comments (3)
tests/fixtures/rssData.js (2)

45-61: Good test case for handling missing dates.

The missingDateMockData provides a valuable test case for ensuring the RSS builder can handle posts with missing date attributes.


1-79: Overall structure looks good. Indentation issues resolved.

The file is well-structured with clear separation between different mock data sets. The indentation appears consistent throughout the file, addressing the concerns raised in previous reviews.

As mentioned in a previous review by @akshatnema, it's important to follow eslint rules. The current version of the file seems to adhere to consistent indentation practices, resolving the earlier concerns.

tests/build-rss.test.js (1)

100-110: 🛠️ Refactor suggestion

Check for the existence of enclosure elements before assertions

Currently, the test assumes that all enclosure elements exist in the RSS feed. To prevent potential errors, ensure that the enclosure elements are present before making assertions on them.

Example:

const parser = require('fast-xml-parser');
const xmlData = fs.readFileSync(filePath, 'utf8');
const parsedData = parser.parse(xmlData);

// Verify that each item has an enclosure
parsedData.rss.channel.item.forEach(item => {
  expect(item.enclosure).toBeDefined();
});

throw new Error('Missing required fields in post data');
}
const link = `${base}${post.slug}${tracking}`;
const item = { title: post.title, description: clean(post.excerpt), link, category: type, guid: { '@isPermaLink': true, '': link }, pubDate: new Date(post.date).toUTCString() }
Copy link

@coderabbitai coderabbitai bot Oct 5, 2024

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve readability by destructuring and formatting

The construction of the item object can be made more readable by destructuring post and formatting the object properties.

Refactor as follows:

- const item = { title: post.title, description: clean(post.excerpt), link, category: type, guid: { '@isPermaLink': true, '': link }, pubDate: new Date(post.date).toUTCString() }
+ const { title, excerpt, date } = post;
+ const pubDate = new Date(date).toUTCString();
+ const description = clean(excerpt);
+ const guid = { '@isPermaLink': true, '': link };
+ const item = {
+   title,
+   description,
+   link,
+   category: type,
+   guid,
+   pubDate
+ };

This enhances readability and maintainability.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const item = { title: post.title, description: clean(post.excerpt), link, category: type, guid: { '@isPermaLink': true, '': link }, pubDate: new Date(post.date).toUTCString() }
const { title, excerpt, date } = post;
const pubDate = new Date(date).toUTCString();
const description = clean(excerpt);
const guid = { '@isPermaLink': true, '': link };
const item = {
title,
description,
link,
category: type,
guid,
pubDate
};

Copy link
Member

Choose a reason for hiding this comment

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

@vishvamsinh28 Apply this suggestion in the code.

Copy link

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Comment on lines +19 to +20
return new Promise((resolve, reject) => {
try {
Copy link

@coderabbitai coderabbitai bot Oct 5, 2024

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider using asynchronous file operations with Promises

The function rssFeed is now returning a Promise but still uses synchronous file operations like fs.writeFileSync. To fully leverage asynchronous behavior and avoid blocking the event loop, consider using fs.writeFile from the fs/promises module.

Apply this change:

- const fs = require('fs')
+ const fs = require('fs').promises
...
- fs.writeFileSync(`./public/${outputPath}`, xml, 'utf8')
+ await fs.writeFile(`./public/${outputPath}`, xml, 'utf8')

Remember to make the function async:

- module.exports = function rssFeed(type, title, desc, outputPath) {
+ module.exports = async function rssFeed(type, title, desc, outputPath) {

Committable suggestion was skipped due to low confidence.

Copy link
Member

Choose a reason for hiding this comment

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

@vishvamsinh28 apply this suggestion

Copy link

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Comment on lines +19 to +86
})

if (i1.featured && !i2.featured) return -1
if (!i1.featured && i2.featured) return 1
return i2Date - i1Date
})
const base = 'https://www.asyncapi.com'
const tracking = '?utm_source=rss';

const base = 'https://www.asyncapi.com'
const tracking = '?utm_source=rss';
const feed = {}
const rss = {}
rss['@version'] = '2.0'
rss["@xmlns:atom"] = 'http://www.w3.org/2005/Atom'
rss.channel = {}
rss.channel.title = title
rss.channel.link = `${base}/${outputPath}`
rss.channel["atom:link"] = {}
rss.channel["atom:link"]["@rel"] = 'self'
rss.channel["atom:link"]["@href"] = rss.channel.link
rss.channel["atom:link"]["@type"] = 'application/rss+xml'
rss.channel.description = desc
rss.channel.language = 'en-gb';
rss.channel.copyright = 'Made with :love: by the AsyncAPI Initiative.';
rss.channel.webMaster = 'info@asyncapi.io (AsyncAPI Initiative)'
rss.channel.pubDate = new Date().toUTCString()
rss.channel.generator = 'next.js'
rss.channel.item = []

const feed = {}
const rss = {}
rss['@version'] = '2.0'
rss["@xmlns:atom"] = 'http://www.w3.org/2005/Atom'
rss.channel = {}
rss.channel.title = title
rss.channel.link = `${base}/${outputPath}`
rss.channel["atom:link"] = {}
rss.channel["atom:link"]["@rel"] = 'self'
rss.channel["atom:link"]["@href"] = rss.channel.link
rss.channel["atom:link"]["@type"] = 'application/rss+xml'
rss.channel.description = desc
rss.channel.language = 'en-gb';
rss.channel.copyright = 'Made with :love: by the AsyncAPI Initiative.';
rss.channel.webMaster = 'info@asyncapi.io (AsyncAPI Initiative)'
rss.channel.pubDate = new Date().toUTCString()
rss.channel.generator = 'next.js'
rss.channel.item = []

for (let post of posts) {
const link = `${base}${post.slug}${tracking}`;
const item = { title: post.title, description: clean(post.excerpt), link, category: type, guid: { '@isPermaLink': true, '': link }, pubDate: new Date(post.date).toUTCString() }
if (post.cover) {
const enclosure = {};
enclosure["@url"] = base+post.cover;
enclosure["@length"] = 15026; // dummy value, anything works
enclosure["@type"] = 'image/jpeg';
if (typeof enclosure["@url"] === 'string') {
let tmp = enclosure["@url"].toLowerCase();
if (tmp.indexOf('.png')>=0) enclosure["@type"] = 'image/png';
if (tmp.indexOf('.svg')>=0) enclosure["@type"] = 'image/svg+xml';
if (tmp.indexOf('.webp')>=0) enclosure["@type"] = 'image/webp';
for (let post of posts) {
if (!post.title || !post.slug || !post.excerpt || !post.date) {
throw new Error('Missing required fields in post data');
}
const link = `${base}${post.slug}${tracking}`;
const item = { title: post.title, description: clean(post.excerpt), link, category: type, guid: { '@isPermaLink': true, '': link }, pubDate: new Date(post.date).toUTCString() }
if (post.cover) {
const enclosure = {};
enclosure["@url"] = base + post.cover;
enclosure["@length"] = 15026; // dummy value, anything works
enclosure["@type"] = 'image/jpeg';
if (typeof enclosure["@url"] === 'string') {
let tmp = enclosure["@url"].toLowerCase();
if (tmp.indexOf('.png') >= 0) enclosure["@type"] = 'image/png';
if (tmp.indexOf('.svg') >= 0) enclosure["@type"] = 'image/svg+xml';
if (tmp.indexOf('.webp') >= 0) enclosure["@type"] = 'image/webp';
}
item.enclosure = enclosure;
}
rss.channel.item.push(item)
}
item.enclosure = enclosure;
}
rss.channel.item.push(item)
}

feed.rss = rss
feed.rss = rss

const xml = json2xml.getXml(feed,'@','',2)
fs.writeFileSync(`./public/${outputPath}`, xml, 'utf8')
const xml = json2xml.getXml(feed, '@', '', 2);
fs.writeFileSync(`./public/${outputPath}`, xml, 'utf8')
resolve(`RSS feed generated successfully at ${outputPath}`);
} catch (err) {
reject(new Error(`Failed to generate RSS feed: ${err.message}`));
}
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Leverage async/await for cleaner asynchronous code

By using async/await, the code becomes more readable and maintains the asynchronous behavior.

Modify the function definition and use await:

- module.exports = function rssFeed(type, title, desc, outputPath) {
+ module.exports = async function rssFeed(type, title, desc, outputPath) {

Replace return new Promise and the try...catch block:

- return new Promise((resolve, reject) => {
-   try {
...
-     resolve(`RSS feed generated successfully at ${outputPath}`);
-   } catch (err) {
-     reject(new Error(`Failed to generate RSS feed: ${err.message}`));
-   }
- });
+ try {
...
+   await fs.writeFile(`./public/${outputPath}`, xml, 'utf8');
+   return `RSS feed generated successfully at ${outputPath}`;
+ } catch (err) {
+   throw new Error(`Failed to generate RSS feed: ${err.message}`);
+ }

This refactoring streamlines the asynchronous operations and error handling.

Committable suggestion was skipped due to low confidence.

Comment on lines +23 to +32
.sort((i1, i2) => {
if (!i1.date || !i2.date) {
throw new Error('Missing date in post data');
}
const i1Date = new Date(i1.date)
const i2Date = new Date(i2.date)
if (i1.featured && !i2.featured) return -1
if (!i1.featured && i2.featured) return 1
return i2Date - i1Date
})
Copy link

@coderabbitai coderabbitai bot Oct 5, 2024

Choose a reason for hiding this comment

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

⚠️ Potential issue

Avoid throwing errors inside the sort comparator function

Throwing an error within the comparator can disrupt the sorting process and lead to unexpected behavior. It's better to validate the data before sorting.

Refactor the code to filter out posts missing dates before sorting:

- const posts = getAllPosts()[`${type}`]
-   .sort((i1, i2) => {
-     if (!i1.date || !i2.date) {
-       throw new Error('Missing date in post data');
-     }
-     const i1Date = new Date(i1.date)
-     const i2Date = new Date(i2.date)
-     if (i1.featured && !i2.featured) return -1
-     if (!i1.featured && i2.featured) return 1
-     return i2Date - i1Date
-   })
+ let posts = getAllPosts()[`${type}`]
+ posts = posts.filter(post => {
+   if (!post.date) {
+     console.warn(`Post "${post.title || post.slug}" is missing a date and will be excluded.`);
+     return false;
+   }
+   return true;
+ });
+ posts.sort((i1, i2) => {
+   const i1Date = new Date(i1.date)
+   const i2Date = new Date(i2.date)
+   if (i1.featured && !i2.featured) return -1
+   if (!i1.featured && i2.featured) return 1
+   return i2Date - i1Date
+ })

This ensures the sorting function only handles valid data and improves robustness.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.sort((i1, i2) => {
if (!i1.date || !i2.date) {
throw new Error('Missing date in post data');
}
const i1Date = new Date(i1.date)
const i2Date = new Date(i2.date)
if (i1.featured && !i2.featured) return -1
if (!i1.featured && i2.featured) return 1
return i2Date - i1Date
})
let posts = getAllPosts()[`${type}`]
posts = posts.filter(post => {
if (!post.date) {
console.warn(`Post "${post.title || post.slug}" is missing a date and will be excluded.`);
return false;
}
return true;
});
posts.sort((i1, i2) => {
const i1Date = new Date(i1.date)
const i2Date = new Date(i2.date)
if (i1.featured && !i2.featured) return -1
if (!i1.featured && i2.featured) return 1
return i2Date - i1Date
})

Copy link
Member

Choose a reason for hiding this comment

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

@vishvamsinh28 apply this suggestion in your code.

Copy link

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Comment on lines +32 to +39
let error;
try {
await rssFeed(type, title, desc, outputPath);
} catch (err) {
error = err;
}

expect(error).toBeUndefined();
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Simplify asynchronous tests using Jest's resolves

Instead of using a try/catch block to handle the Promise, you can utilize Jest's expect().resolves for cleaner and more readable tests.

Refactored code:

await expect(rssFeed(type, title, desc, outputPath)).resolves.not.toThrow();

This approach automatically handles the Promise resolution and failure.

Comment on lines +163 to +170
let error;
try {
await rssFeed(type, title, desc, outputPath);
} catch (err) {
error = err;
expect(error).toBeDefined();
expect(error.message).toContain('Missing required fields');
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use Jest's rejects.toThrow for expected errors

Simplify the test by asserting the Promise rejection directly.

Refactored code:

await expect(rssFeed(type, title, desc, outputPath)).rejects.toThrow('Missing required fields');

Comment on lines +177 to +184
let error;
try {
await rssFeed(type, title, desc, outputPath);
} catch (err) {
error = err;
expect(error).toBeDefined();
expect(error.message).toContain('Missing date in post data');
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use Jest's rejects.toThrow for expected errors

Enhance the test by using expect().rejects.toThrow().

Refactored code:

await expect(rssFeed(type, title, desc, outputPath)).rejects.toThrow('Missing date in post data');

Comment on lines +59 to +65
const itemTitles = fileContent.match(/<title>(.*?)<\/title>/g);

expect(error).toBeUndefined();
expect(itemTitles[1]).toContain('Test Post 1');
expect(itemTitles[2]).toContain('Another Featured Post');
expect(itemTitles[3]).toContain('Non-Featured Post 1');
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use an XML parser instead of regex for parsing RSS feed

Parsing XML with regular expressions can be error-prone and brittle. Consider using an XML parser like xml2js or fast-xml-parser to reliably extract item titles from the RSS feed.

Example using fast-xml-parser:

const parser = require('fast-xml-parser');
const xmlData = fs.readFileSync(filePath, 'utf8');
const parsedData = parser.parse(xmlData);
const itemTitles = parsedData.rss.channel.item.map(item => item.title);

// Now you can assert on itemTitles array
expect(itemTitles[0]).toContain('Test Post 1');

This approach ensures accurate parsing of RSS data regardless of formatting or additional whitespace.

Comment on lines +80 to +88
const itemTitles = fileContent.match(/<title>(.*?)<\/title>/g);

expect(error).toBeUndefined();
expect(itemTitles[1]).toContain('Test Post 1');
expect(itemTitles[2]).toContain('Another Featured Post');
expect(itemTitles[3]).toContain('Non-Featured Post 1');
expect(itemTitles[4]).toContain('Non-Featured Post 3');
expect(itemTitles[5]).toContain('Non-Featured Post 2');
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use an XML parser instead of regex for parsing RSS feed

For consistent and reliable parsing of the RSS feed, utilize an XML parser.

Example using fast-xml-parser:

const parser = require('fast-xml-parser');
const xmlData = fs.readFileSync(filePath, 'utf8');
const parsedData = parser.parse(xmlData);
const itemTitles = parsedData.rss.channel.item.map(item => item.title);

// Now you can assert on itemTitles array
expect(itemTitles[0]).toContain('Test Post 1');

This method avoids potential issues with regex parsing and handles XML structures appropriately.

Comment on lines +25 to +27
afterEach(() => {
jest.resetModules();
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Reset mocks instead of modules in afterEach

Instead of using jest.resetModules(), consider using jest.resetAllMocks() or jest.clearAllMocks() to reset your mocks between tests. This is generally more efficient and avoids unnecessary module resets.

Refactored code:

afterEach(() => {
  jest.resetAllMocks();
});

});

afterEach(() => {
jest.resetModules();
Copy link
Member

Choose a reason for hiding this comment

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

@vishvamsinh28 any specific reason on why are we resetting all modules instead of mocks here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
gsoc This label should be used for issues or discussions related to ideas for Google Summer of Code
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants