Skip to content

Commit

Permalink
Load the data (#7)
Browse files Browse the repository at this point in the history
* header task: first commit

* header task: fixed missing default 'javascript'

* header task: fixed default link: '/search/javascript'

* header after first review

* header fixing test

* implemented unit test

* header: before rebase after app-skeleton-review

* resolved conflicts. Fixed unit tests

* footer: initial commit

* implemented Footer.js and Footer.style.js

* footer: completed unit tests

* hero-section: initial commit with with fixes from previous PR

* hero-section: completed unit tests

* hero-section: fixed linting issues in unit tests

* hero-section: fixed Apps to have all unit tests pass

* hero-section: added missing test descriptions in footer.js

* hero-section: refactor components and file structure after review

* info-section: initial commit

* info-section: implemented About and How it works sections

* info-section: fix link to https://ooloo.io/employers

* info-section: implemented react-router-hash-link

* info-section: fixed ooloo.io link

* info-section: implemented unit tests

* info-section: fixed linting errors

* info-section: fixed linting errors

* info-section: fix unit test using this: testing-library/dom-testing-library#477 (comment)

* info-section: commit after review

* subreddit-form: initial commit. Setup folder structure

* subreddit-form: implement tests

* subreddit-form: fix lint error in Header.js

* subreddit-form: completed unit and e2e tests

* subreddit-form: fixes and cleanup after review

* load-the-data: initial commit: added LoadTheData.js

* load-the-data: fetching data implemented. But unit test and e2e test filing

* load-the-data: troubleshooting tests

* load-the-data: continue troubleshooting tests

* load-the-data: fix linting error

* load-the-data: fix the code to fetch top 500 posts

* load-the-data: troubleshooting unit test - 'invalid json response body at  reason: Unexpected end of JSON input'

* load-the-data: integration test passing

* load-the-data: refactor code after review. still 1 test to fix in SearchPage.js

* laod-the-data: all tests pass
  • Loading branch information
bahobab authored Oct 5, 2020
1 parent fbb75da commit b5167cf
Show file tree
Hide file tree
Showing 27 changed files with 1,200 additions and 54 deletions.
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,19 @@
"dependencies": {
"@testing-library/jest-dom": "^5.11.0",
"@testing-library/react": "^10.4.5",
"@testing-library/react-hooks": "^3.4.2",
"@testing-library/user-event": "^12.0.11",
"eslint-plugin-styled-components-a11y": "^0.0.16",
"history": "^5.0.0",
"jest-fetch-mock": "^3.0.3",
"msw": "^0.21.2",
"prop-types": "^15.7.2",
"react": "^16.13.0",
"react-dom": "^16.13.0",
"react-router-dom": "^5.2.0",
"react-router-hash-link": "^2.1.0",
"react-scripts": "3.4.1",
"react-test-renderer": "^16.13.1",
"styled-components": "^5.2.0"
},
"scripts": {
Expand Down Expand Up @@ -44,7 +48,7 @@
]
},
"devDependencies": {
"cypress": "^4.10.0",
"cypress": "5.3.0",
"eslint": "^6.8.0",
"eslint-config-airbnb": "^18.2.0",
"eslint-plugin-cypress": "^2.11.1",
Expand Down
Binary file added public/images/loading-spinner.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions public/images/loading-spinner.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/loading-spinner@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/loading-spinner@3x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
56 changes: 56 additions & 0 deletions src/__mocks__/handlers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { rest } from 'msw';
import response1 from './response-1.json'; // /r/javascript/top.json?t=year&limit=100
import response2 from './response-2.json'; // /r/javascript/top.json?t=year&limit=100&after=t3_f807cu
import response3 from './response-3.json'; // /r/javascript/top.json?t=year&limit=100&after=t3_gwddb5
import response4 from './response-4.json'; // /r/javascript/top.json?t=year&limit=100&after=t3_czqd6o
import response5 from './response-5.json'; // /r/javascript/top.json?t=year&limit=100&after=t3_e5dnm2

const cursorResponseMap = {
initial: response1,
t3_j0oewq: response2,
t3_i7s9wh: response3,
t3_ex7ieb: response4,
t3_g5a7o5: response5,
};

function getJSONResponseForRequest(req) {
const after = req.url.searchParams.get('after');
return cursorResponseMap[after || 'initial'];
}

const handlers = [
rest.get('https://www.reddit.com/r/less-than-500-posts/top.json', (req, res, ctx) => {
const after = req.url.searchParams.get('after');
const json = getJSONResponseForRequest(req);
if (after === 't3_i7s9wh') {
json.data.dist = 70;
json.data.after = null;
json.data.children = json.data.children.slice(0, 70);
}
return res(
ctx.status(200),
ctx.json(json),
);
}),

rest.get('https://www.reddit.com/r/failing-request/top.json', (req, res, ctx) => {
const after = req.url.searchParams.get('after');
if (after === 't3_i7s9wh') {
return res(
ctx.status(500),
ctx.json({ errorMessage: 'Internal server error' }),
);
}
return res(
ctx.status(200),
ctx.json(getJSONResponseForRequest(req)),
);
}),

rest.get('https://www.reddit.com/*', (req, res, ctx) => res(
ctx.status(200),
ctx.json(getJSONResponseForRequest(req)),
)),
];

export default handlers;
1 change: 1 addition & 0 deletions src/__mocks__/response-1.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/__mocks__/response-2.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/__mocks__/response-3.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/__mocks__/response-4.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/__mocks__/response-5.json

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions src/__mocks__/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { setupServer } from 'msw/node';

import handlers from './handlers';

const server = setupServer(...handlers);

export default server;
19 changes: 19 additions & 0 deletions src/__tests__/SearchPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,25 @@ const setup = (initialPath = '/') => {
return { history };
};

describe('search page', () => {
it('loads top post for subreddit in URL', async () => {
setup('/search/reactjs');

screen.getByText('loading-spinner.svg');

// this is just a placeholder assertion that tests if the result
// was rendered correctly
expect(await screen.findByText('500')).toBeInTheDocument();
expect(screen.queryByText('loading-spinner.svg')).not.toBeInTheDocument();
});

test('renders error message', async () => {
setup('/search/failing-request');

expect(await screen.findByText(/something went wrong/i)).toBeInTheDocument();
});
});

describe('subreddit form', () => {
it('updates the URL when submitting the form', () => {
const { history } = setup('/search/python');
Expand Down
71 changes: 71 additions & 0 deletions src/__tests__/_LoadTheData.js_
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import React from 'react';
import { MemoryRouter, Route } from 'react-router-dom';
import { render, screen } from '@testing-library/react';
// import userEvent from '@testing-library/user-event';
import '@testing-library/jest-dom/extend-expect';
import fetchMock from 'jest-fetch-mock';

// import { defaultSubReddit } from '../config';
import App from '../app';
import mockResponse1 from './__mocks__/subreddit-reactjs-response1.json';
import mockResponse2 from './__mocks__/subreddit-reactjs-response2.json';
import mockResponse3 from './__mocks__/subreddit-reactjs-response3.json';
import mockResponse4 from './__mocks__/subreddit-reactjs-response4.json';
import mockResponse5 from './__mocks__/subreddit-reactjs-response5.json';

fetchMock.enableMocks();

const setup = (initialPath = '/') => {
let history;
// let subredditName;
// const redditUrl = `https://www.reddit.com/r/${subredditName}/top.json?t=year&limit=100`;

render(
<MemoryRouter initialEntries={[initialPath]}>
<App />
<Route
path="*"
render={(props) => {
history = props.history;
return null;
}}
/>
</MemoryRouter>,
);
return { history };
};

describe('load the data', () => {
it('fetch the top 500 posts of the last year for the specified subreddit on form submit', async () => {
fetch
.once(JSON.stringify(mockResponse1))
.once(JSON.stringify(mockResponse2))
.once(JSON.stringify(mockResponse3))
.once(JSON.stringify(mockResponse4))
.once(JSON.stringify(mockResponse5));

setup('/search/reactjs');

// const searchLink = within(screen.getByRole('banner')).getByRole('link', { name: /search/i });
// userEvent.click(searchLink);

// const subredditInput = screen.getByLabelText('r /');
// const submitButton = screen.getByRole('button', { name: /search/i });

// userEvent.clear(subredditInput);
// userEvent.type(subredditInput, 'reactjs');
// expect(subredditInput.value).toBe('reactjs');

// await userEvent.click(submitButton);

// expect(history.location.pathname).toEqual('/search/reactjs');

const loadingImage = screen.getByAltText(/is loading/i);
expect(loadingImage).toBeInTheDocument();

expect(await screen.findByText(/500/i)).toBeInTheDocument();
expect(fetch).toHaveBeenCalledTimes(5);
// expect(fetch).toHaveBeenCalledWith('https://www.reddit.com/r/javascript/top.json?t=year&limit=100');
// screen.debug(searchResults);
});
});
32 changes: 32 additions & 0 deletions src/page-search/Heatmap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react';
import { useParams } from 'react-router-dom';

import useFetchPosts from './useFetchPosts';
import { ErrorContainer, LoadingContainer, LoadingSpinner } from './Heatmap.style';

function Heatmap() {
const { subreddit } = useParams(); // route param set in App
const { isLoading, hasError, posts } = useFetchPosts(subreddit);

if (isLoading) {
return (
<LoadingContainer>
<LoadingSpinner />
</LoadingContainer>
);
}

if (hasError) {
return (
<ErrorContainer>
Something went wrong. Please check the subreddit you entered then try again.
</ErrorContainer>
);
}

return (
<div>{posts.length}</div>
);
}

export default Heatmap;
29 changes: 29 additions & 0 deletions src/page-search/Heatmap.style.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import styled, { keyframes } from 'styled-components';
import { ReactComponent as UnstyledSpinner } from './loading-spinner.svg';

export const LoadingContainer = styled.div`
width: 100%;
display: flex;
justify-content: center;
margin-top: 56px;
`;

export const ErrorContainer = styled.div`
padding: 30px;
color: red;
font-size: ${(props) => props.theme.font.size.small};
`;

const rotate = keyframes`
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
`;

export const LoadingSpinner = styled(UnstyledSpinner)`
animation: ${rotate} 1.5s linear infinite;
`;
3 changes: 3 additions & 0 deletions src/page-search/SearchPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ import React from 'react';

import { Container, Headline } from './SearchPage.style';
import SubredditForm from './SubredditForm';
import Heatmap from './Heatmap';

function SearchPage() {
return (
<Container>
<Headline>Find the best time for a subreddit</Headline>
<SubredditForm />

<Heatmap />
</Container>
);
}
Expand Down
75 changes: 63 additions & 12 deletions src/page-search/SubredditForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,41 +5,92 @@ import {
Form, Input, Label,
} from './SubredditForm.style';
import Button from '../common/button';
import Container from '../common/container';

function SubredditForm() {
const { subreddit: initialSubreddit } = useParams();
const [subreddit, setSubreddit] = useState(initialSubreddit);
// const [posts, setPosts] = useState([]);
// const [isLoading, setLoading] = useState(false);

function handleChange(event) {
setSubreddit(event.target.value);
}

// async function fetch100SubReddit(url) {
// let response;
// try {
// response = await fetch(url);
// const redditData = await response.json();
// return redditData.data;
// } catch (error) {
// return null;
// }
// }

// async function fetchSubReddit(subredditName) {
// setLoading(true);
// let allSubreddit = [];
// let subRedditData;

// try {
// subRedditData = await fetch100SubReddit(`https://www.reddit.com/r/${subredditName}/top.json?t=year&limit=100`);
// allSubreddit = [...allSubreddit, ...subRedditData.children];

// subRedditData = await fetch100SubReddit(`https://www.reddit.com/r/${subredditName}/top.json?t=year&limit=100&after=${subRedditData.after}`);
// allSubreddit = [...allSubreddit, ...subRedditData.children];

// subRedditData = await fetch100SubReddit(`https://www.reddit.com/r/${subredditName}/top.json?t=year&limit=100&after=${subRedditData.after}`);
// allSubreddit = [...allSubreddit, ...subRedditData.children];

// subRedditData = await fetch100SubReddit(`https://www.reddit.com/r/${subredditName}/top.json?t=year&limit=100&after=${subRedditData.after}`);
// allSubreddit = [...allSubreddit, ...subRedditData.children];

// subRedditData = await fetch100SubReddit(`https://www.reddit.com/r/${subredditName}/top.json?t=year&limit=100&after=${subRedditData.after}`);
// allSubreddit = [...allSubreddit, ...subRedditData.children];
// } catch (error) {
// // console.log('ERROR in fetchSubreddit', error);
// }

// setPosts(allSubreddit);

// setLoading(false);
// }

const history = useHistory();

function handleSubmit(evt) {
evt.preventDefault();
// const subRedditUrl = `/search/${subreddit}`;
history.push(`/search/${subreddit}`);

// fetchSubReddit(`${subreddit}`);
}

// update input value when URL is updated externally
// (e.g. when user clicks on search link in header)
useEffect(() => {
setSubreddit(initialSubreddit);
// fetchSubReddit(initialSubreddit);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [initialSubreddit]);

return (
<Form onSubmit={handleSubmit}>
<Label>
r /
<Input
type="text"
value={subreddit}
name="subreddit"
onChange={handleChange}
/>
</Label>
<Button>Search</Button>
</Form>
<Container>
<Form onSubmit={handleSubmit}>
<Label>
r /
<Input
type="text"
value={subreddit}
name="subreddit"
onChange={handleChange}
/>
</Label>
<Button>Search</Button>
</Form>
</Container>

);
}

Expand Down
Loading

0 comments on commit b5167cf

Please sign in to comment.