-
Notifications
You must be signed in to change notification settings - Fork 0
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
Tests #11
Tests #11
Conversation
I've written the following tests, not limited to:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All in all excellent work. Very detailed tests covering every detail afaik. You can simplify some tests by making use of the async findBy*
instead of getBy*
queries.
src/components/Header/Header.js
Outdated
<nav> | ||
{headerLinks.map((item) => ( | ||
<NavLink key={item.title} to={item.link} alt={item.title}> | ||
<nav data-testid="navbar"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be enough to use getByText
here instead of getByTestId
here and below. Here is a document of priorities given to the single queries. It's recommended to try testing like a real user. Test IDs should be the last resort
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've been thinking of doing that, but I'm sitting with a mental dilemma.
I would have to test for the strings in the links in the header, but then these tests will fail if someone ever changes the contents of the links. I thought that tests shouldn't be written this way, something along the lines of "tests shouldn't break if someone changes the content of the site".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't been able to make this change. I will handle the others and come back to it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good point. At the same time the wordings don't change so often typically. And if somebody changed the text inside an important link or button the tests would catch it.
Honestly, I think there are advantages to both approaches. I'd just go for the recommended one because of the reasons above and because you don't need so many test IDs in your components
src/components/Hero/Hero.js
Outdated
@@ -19,14 +19,14 @@ function Hero() { | |||
Great timing, great results! Find the best time to post on your | |||
subreddit. | |||
</Subtitle> | |||
<CTA as={Link} to={`/search/${DEFAULT_SUBREDDIT}`}> | |||
<CTA as={Link} to={`/${DEFAULT_PATH}/${DEFAULT_SUBREDDIT}`} data-testid="hero-button"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a good idea to extract the path into a config, but the name is not so descriptive. Maybe call it SEARCH_PATH
. It might also be good to create a function that builds the path since the /${DEFAULT_PATH}/${DEFAULT_SUBREDDIT}
is spread over multiple components now. Something like getSearchPath
that could look like this
function getSearchPath(subreddit = DEFAULT_SUBREDDIT) {
return `/${SEARCH_PATH}/${DEFAULT_SUBREDDIT}`
}
src/components/Hero/Hero.js
Outdated
Show me the best time | ||
</CTA> | ||
<Caption> | ||
r/ | ||
{DEFAULT_SUBREDDIT} | ||
</Caption> | ||
<Link to={`/search/${DEFAULT_SUBREDDIT}`}> | ||
<Link to={`/${DEFAULT_PATH}/${DEFAULT_SUBREDDIT}`} data-testid="hero-img"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here the testid is not needed as well
package.json
Outdated
@@ -6,6 +6,9 @@ | |||
"@testing-library/jest-dom": "^5.1.1", | |||
"@testing-library/react": "^10.0.1", | |||
"@testing-library/user-event": "^10.0.0", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The testing stuff should go to dev dependencies to not bloat the production build
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"@testing-library/jest-dom": "^5.1.1",
will throw a ESLint error, but the rest were moved.
@@ -18,7 +21,7 @@ | |||
"scripts": { | |||
"start": "react-scripts start", | |||
"build": "react-scripts build", | |||
"test": "react-scripts test", | |||
"test": "TZ=UTC react-scripts test --env=jest-environment-jsdom-sixteen", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is TZ=UTC
used to have a constant timezone? Is there documentation for that in the jest docs or so? Just curious
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found it while Googling for a solution for getting the timezone to always be the same. According to others, it works without problems, however, some say it might not work on Windows. I don't have a dev setup for Windows so I can't verify this. I wasn't able to source the docs for it, but I'm guessing it's part of Webpack.
I will probably switch to a package in the future to emulate a timezone. Still thinking of a way to approach it because if I move to Moment, then I may as well rewrite my dates to use Moment instead of my own functions. In that case, it will be a refactor iteration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great, if it's working then it's fine. I experienced in a lot of companies that they don't care so much about devs on Windows since almost everyone is using Mac anyway with a few outliers using Linux.
I've tried to get rid of IDs (getByIds etc.) such as trying to find a header and footer tag using
|
Don't worry about the ids, just keep it in my for the next time. I'd still suggest you refactor the things I mentioned here. |
Not sure how I missed this, thank you. I will be changing it. |
I've refactored to remove a lot of the redundancy that I had e.g. assigning a variable to the element as well as checking if it was in the document. Now I only assign the variable. I don't know if I've gone too concise though as I've also refactored some parts to not include a variable and use the query within the check e.g.
I feel there is not much further I can reduce but this could just be a limitation of my skills. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great. I added a comment. Change your code if you like. If not feel free to merge ;-)
function getPosts(data, day, hour) { | ||
const currentTable = data[day][hour]; | ||
const posts = []; | ||
for (let i = 0; i < currentTable.length; i += 1) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could use .map
here
const posts = currentTable.map(({ author, title }) => ({
...
)};
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trying to map
it but I'm not sure if it's possible with what I'm doing. I'm creating a 1d array from all of the table cells so I can test each one easily. The reason for doing this was because it became to complex to loop and test the data while it was in its complex object form. This is why there are multiple push
statements in each loop:
posts.push(author);
posts.push(title);
posts.push(created);
posts.push(score);
posts.push(num_comments);
currentTable
's values are rows in the table which are 5 different key/value pairs. map
is only going to give me one opportunity/return to deal with all of the key/values for that particular row so I would only be able to return an entire row instead of returning each value so that the data is in a flattened 1d array.
The only thing I can think of at the moment is to keep it as a row (2d array) from the .map
array and then use .flat
to flatten it before it's returned.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I might be able to do it with 2 maps, the 2nd looping through keys. Attempting this at the moment and then will do a comparison at the end.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, map
won't work. I didn't read the code properly. You can use reduce
though.
const posts = currentTable.map((tmpPosts, { author, title }) => ({
return tmpPosts.concat(author, title, created, score, num_comments);
)}, []);
Didn't test the concat so be careful :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Btw the name posts
is a bit confusing here. That's why I expected it to be an array of post objects.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Btw the name
posts
is a bit confusing here. That's why I expected it to be an array of post objects.
You're right. I changed it as soon as you did the review as I immediately saw it was the cause of confusion. It was originally for each post, but the code evolved quite a bit and naming got lost along the way. Thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it's going to be possible to do it elegantly with array methods, namely for the following reason:
-
I can't map through all of the keys as I have keys in my object, such as the Reddit post URL, but I'm not testing for the Reddit URL in the test.
-
The test itself loops through the array and checks for the item's text to be in the document. Since the data is a 1d array with no keys/values, I can't separate out content, such as the URL, so that it's not being tested for. I can't switch back to passing an object to the test as that creates massive problems when it comes to testing and trying to loop through each of the objects.
What I've got is, but the test will break for reasons above. I have to flatten the solution below otherwise I have nested arrays:
function getPosts(data, day, hour) {
const currentTable = data[day][hour];
const cells = currentTable.map((row) => {
return Object.keys(row).map(key => {
if (key === 'created') {
return displayHHMM(row[key]);
}
return row[key].toString();
});
}).flat();
return cells;
}
I hate giving up on a coding problem, but I'm starting to think that the way I did initially is going to be one of the more elegant solutions to handle it.
I await your input. I don't mind going at it for as long as I need to get it right, just not sure if I'm going to find something that works better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, the code in the comment was wrong. I wanted to use reduce
instead of map
.
const posts = currentTable.reduce((tmpPosts, row) => ({
// Convert date obj to "hh:mm" with am/pm suffixed
const created = displayHHMM(row.created);
// Can't test numeric values, need to convert to string
const num_comments = row.num_comments.toString();
const score = row.score.toString();
return tmpPosts.concat(row.author, row.title, created, score, num_comments);
)}, []);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will give you a 1D array with exactly like the one in your code if I'm right
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Amazing!! I tried it the first time (I knew to switch map
with reduce
) but couldn't figure out the syntax error it had.
Here it is in its glory with the syntax error fixed and variables renamed. Thank you so much!
function getCellData(data, day, hour) {
const currentTable = data[day][hour];
const cells = currentTable.reduce((tmpCells, row) => {
// Convert date obj to "hh:mm" with am/pm suffixed
const created = displayHHMM(row.created);
// Can't test numeric values, need to convert to string
const num_comments = row.num_comments.toString();
const score = row.score.toString();
return tmpCells.concat(row.author, row.title, created, score, num_comments);
}, []);
return cells;
}
}); | ||
test('Heatmap displays the correct timezone message', async () => { | ||
const { getByTestId } = setup('pass', dummyPosts); | ||
await waitForElementToBeRemoved(getByTestId('loading-spinner')); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this assertion is repeated often: a nice way to prevent this kind of repetition is to create a function inside setup.
const setup = () => {
const queries = render(...)
const waitForData = async() => {
await waitForElementToBeRemoved(getByTestId('loading-spinner'));
}
return {
...queries,
waitForData,
}
}
and then use it here like
await waitForData();
This way it's a bit more descriptive what the assertion is doing.
You can also create functions that are more complex like searchNewSubreddit
where you enter a new subreddit and submit the form or so. Just as an example, not saying that you should do that here.
Here is a nice blog post AHA testing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
Writing tests for the app, currently in progress.