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

Heatmap #9

Merged
merged 9 commits into from
Jun 23, 2020
Merged

Heatmap #9

merged 9 commits into from
Jun 23, 2020

Conversation

estuelke
Copy link
Owner

Got all the code in place and working but having problems implementing the tests. I keep getting:

Warning: An update to Search inside a test was not wrapped in act(...).
    
    When testing, code that causes React state updates should be wrapped into act(...):
    
    act(() => {
      /* fire events that update state */
    });
    /* assert on the output */

I've tried wrapping everything from just the render to both the render and fetch mocks. Nothing works. However, I did stop getting the message if I only called the render method without const { getByRole }. When I did some console prints, I noticed that the heatmap isn't getting written into the test. I am guessing that's why the tests are throwing that warning message. But I am not sure how to fix it.

Comment on lines 35 to 38
const { waitForNextUpdate } = renderHook(() => useFetchPosts('kittens'));

await waitForNextUpdate();
await act(async () => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I guess there are a couple of smaller problems.

  1. For the test of the search page these three lines are not necessary. They are only required for testing a custom hook, but not when you want to test a component or page
  2. Maybe you did that already: You can use the debug function that is returned from render to see the DOM. So when you have a test like this you will see no heatmap table as you mentioned.
fetch.once...
const { getByRole, debug } = renderSearchPage('/search/kittens');
debug()

That's because the (mock) fetching is not done yet. During the initial render the heatmap is not visible.
3. Instead of the getByRole query you can use findByRole. This one is async, so you have to await it. When you run the test now you should see the act warnings plus a different error: MutationObserver is not a constructor
4. This leads us to this comment where you can find the solution. When you add debug() now after findByRole you should see the table in the output

Copy link
Owner Author

Choose a reason for hiding this comment

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

That worked! Thanks

Copy link
Owner Author

Choose a reason for hiding this comment

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

Oddly, the tests are passing locally, but not remotely even though they should be using the same data, right? Am I missing something?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Perfect, glad that it worked 👍

The problem with the failing tests in the CI is probably that the GitHub pipelines have a different timezone than your local machine. You need to set a fixed timezone. You can check the cypress scripts inside package.json. There's an environment variable TZ that you can use

@estuelke estuelke requested a review from jkettmann June 15, 2020 14:05
Copy link
Collaborator

@jkettmann jkettmann left a comment

Choose a reason for hiding this comment

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

Looks very nice 👍 Of course, I left some comments 😄

package.json Show resolved Hide resolved
src/components/Heatmap.js Outdated Show resolved Hide resolved
src/components/Heatmap.js Outdated Show resolved Hide resolved
src/components/Heatmap.js Outdated Show resolved Hide resolved
src/components/Heatmap.js Outdated Show resolved Hide resolved
Comment on lines 25 to 38
const [clicked, setClicked] = useState(false);

const handleClick = () => {
setClicked(true);
handleCellClick(id);
};

useEffect(() => {
if (clickedCellId === id) {
setClicked(true);
} else {
setClicked(false);
}
}, [clickedCellId, id]);
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is a very common mistake and you might be surprised how much simpler it can be 😄

clicked is just derived from the clickedCellId and id props. So you can simply replace the state variable by const clicked = clickedCellId === id;as you did in theuseEffect`. Try it out, your component will look much simpler

Copy link
Owner Author

Choose a reason for hiding this comment

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

woah! So much better. Thanks! I was overthinking it, it seems

@@ -39,12 +39,30 @@ const mainTheme = {
externalLink: '#0087ff',
searchBoxBorder: '#e6e6e6',
spinner: '#fdb755',
tablecolumnheader: '#787878',
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'd probably use camelCase here 😉

Copy link
Owner Author

Choose a reason for hiding this comment

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

oops... my python is showing 😅

Comment on lines +46 to +58
count: {
0: '#e0e592',
1: '#aed396',
2: '#a9d194',
3: '#a0ce93',
4: '#99cd94',
5: '#8cc894',
6: '#5eb391',
7: '#5db492',
8: '#5cb391',
9: '#5aad8c',
10: '#3984a3',
},
Copy link
Collaborator

Choose a reason for hiding this comment

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

Perfect 🚀

Comment on lines +51 to +59
const originalStyle = window.getComputedStyle(tableCells[1]);
const originalBorder = originalStyle.getPropertyValue('border');
expect(originalBorder).toEqual('1px solid #a0ce93');

fireEvent.click(tableCells[1]);

const updatedStyle = window.getComputedStyle(tableCells[1]);
const updatedBorder = updatedStyle.getPropertyValue('border');
expect(updatedBorder).toEqual('1px solid #1e2537');
Copy link
Collaborator

Choose a reason for hiding this comment

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

Haven't seen anyone testing the borders yet! 🎉


it('cell highlights on click', async () => {
let tableCells;
await act(async () => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is not required. You can remove the act

@estuelke estuelke requested a review from jkettmann June 21, 2020 00:38
Copy link
Collaborator

@jkettmann jkettmann left a comment

Choose a reason for hiding this comment

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

Almost good to go. I just left a few more comments for smaller optimizations

'12:00pm', '2:00pm', '4:00pm', '6:00pm', '8:00pm', '10:00pm',
];

const getCounts = (posts) => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This name is very generic. Can you find a more descriptive name? Remember the email lesson about variable/function naming and the 5 Whats.

This also goes for the counts array below

`;

const Timezone = styled.span`
font-weight: bold;
Copy link
Collaborator

Choose a reason for hiding this comment

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

indentation

return (
<tr key={dayOfWeek}>
<RowHeader key={dayOfWeek}>{dayOfWeek}</RowHeader>
{days.map((hourCount, hour) => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

indentation

Copy link
Owner Author

Choose a reason for hiding this comment

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

I think this is okay? Since days.map... isn't part of RowHeader? I separated out RowHeader into three lines to make it clearer.

@@ -1,5 +1,129 @@
import React from 'react';
import React, { useState } from 'react';
Copy link
Collaborator

Choose a reason for hiding this comment

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

This file looks very simple compared to other solutions I've seen. Good job 👍

{counts.map((days, day) => {
const dayOfWeek = daysOfWeek[day];
return (
<tr key={dayOfWeek}>
Copy link
Collaborator

Choose a reason for hiding this comment

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

You could extract this into its own component. This would potentially allow you to apply performance optimizations with React.memo.

That's a bit premature optimization though. So no problem if you keep it like it is. I just wanted you to know about React.memo 😄

`;

const Heatmap = ({ posts }) => {
const counts = getCounts(posts);
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is run whenever the Heatmap is re-rendered (e.g. when a cell is clicked). Since the function iterates over 500 elements you might want to optimize it performance-wise.

By using useMemo you can call this function only when posts changes.

font-size: ${({ theme }) => theme.font.size.small};
background-color: ${({ theme, colorValue }) => theme.color.count[colorValue]};
border: 1px solid ${({ theme, colorValue, clicked }) => (
clicked ? theme.color.tableRowHeader : theme.color.count[colorValue]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nice usage of styled-components props 👍

}
`;

const HeatmapCell = ({
Copy link
Collaborator

Choose a reason for hiding this comment

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

Just a small suggestion 😄 You could make this component shorter by removing the const.

const HeatmapCell = ({
children, colorValue, handleCellClick, id, clickedCellId,
}) => (
    <TableCell
      colorValue={colorValue}
      clicked={clickedCellId === id}
      onClick={() => handleCellClick(id)}
    >
      {children}
    </TableCell>
  );

Copy link
Owner Author

Choose a reason for hiding this comment

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

I love these kinds of suggestions. I think I get caught up in the tiny details sometimes and I forget to step back and see where I can make things more efficient.

@estuelke estuelke requested a review from jkettmann June 21, 2020 15:35
Copy link
Collaborator

@jkettmann jkettmann left a comment

Choose a reason for hiding this comment

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

Looks great! 😄

@jkettmann
Copy link
Collaborator

Btw change the title 😉 I recently discovered that you can actually create a draft PR instead of WIP prefix.

@estuelke estuelke merged commit c44f8e9 into master Jun 23, 2020
@estuelke estuelke deleted the heatmap branch June 23, 2020 22:54
@estuelke estuelke changed the title [WIP] Heatmap Heatmap Jun 24, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants