Skip to content

Commit

Permalink
Merge branch 'release/2020.12.14'
Browse files Browse the repository at this point in the history
  • Loading branch information
AndreMiras committed Dec 14, 2020
2 parents c5f218e + 5fff3e4 commit db0efa8
Show file tree
Hide file tree
Showing 15 changed files with 345 additions and 37 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
# Change Log

## [2020.12.14]
- Prevent submit on empty input
- Improve repo input validation regex
- Handle search error, refs #6

## [2020.12.10]
- Initial UI release
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "gitpop3",
"homepage": "https://andremiras.github.io/gitpop3/",
"version": "2020.12.10",
"version": "2020.12.14",
"private": true,
"dependencies": {
"@apollo/client": "^3.2.7",
Expand Down
13 changes: 12 additions & 1 deletion src/components/Container.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,29 @@ import React, { useState } from 'react';
import { Container as ReactContainer } from 'react-bootstrap';
import PopForm from './PopForm';
import ResultTable from './ResultTable';
import ErrorDialog from './ErrorDialog';
import searchPopularForks from '../utils/search';

const Container = () => {
const [forks, setForks] = useState();
const [errorDetail, setErrorDetail] = useState(null);
const [activePage, setActivePage] = useState(1);
const [loading, setLoading] = useState(false);
const onResult = (result) => {
setForks(result.data.repository.forks.nodes);
setErrorDetail(null);
setLoading(false);
};
const onError = (error) => {
setErrorDetail(error);
setLoading(false);
};
const errorDialog = errorDetail ? (
<ErrorDialog detail={errorDetail.message} />
) : null;
const onSubmit = (url) => {
setLoading(true);
searchPopularForks(url, onResult);
searchPopularForks(url, onResult, onError);
};
const resultTable = (
forks
Expand All @@ -30,6 +40,7 @@ const Container = () => {
);
return (
<ReactContainer>
{errorDialog}
<PopForm onSubmit={onSubmit} loading={loading} />
{resultTable}
</ReactContainer>
Expand Down
70 changes: 45 additions & 25 deletions src/components/Container.test.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import { ApolloError } from '@apollo/client';
import { fireEvent, render, screen } from '@testing-library/react';
import renderer from 'react-test-renderer';
import { library } from '@fortawesome/fontawesome-svg-core';
Expand All @@ -10,30 +11,6 @@ import Container from './Container';
library.add(fab, fas);
jest.mock('../utils/search');

const forks = [
{
nameWithOwner: 'django-nonrel/django',
stargazerCount: 214,
forkCount: 84,
pushedAt: '2020-08-29T14:23:26Z',
object: {
history: {
totalCount: 13990,
},
},
},
];

const searchResult = {
data: {
repository: {
forks: {
nodes: forks,
},
},
},
};

test('renders', () => {
const tree = renderer.create(
<Container />,
Expand All @@ -46,12 +23,55 @@ test('search forks', (done) => {
const searchInput = screen.getByPlaceholderText(/github.com/);
const submitButton = screen.getByRole('button', { type: 'submit' });
const forkId = 'django-nonrel/django';
searchPopularForks.mockImplementationOnce((url, onResult) => {
const forks = [
{
nameWithOwner: forkId,
stargazerCount: 214,
forkCount: 84,
pushedAt: '2020-08-29T14:23:26Z',
object: {
history: {
totalCount: 13990,
},
},
},
];

const searchResult = {
data: {
repository: {
forks: {
nodes: forks,
},
},
},
};
searchPopularForks.mockImplementationOnce((url, onResult, onError) => {
onResult(searchResult);
onError; // peaces linter mind
done();
});
expect(screen.queryByText(forkId)).toBeNull();
fireEvent.change(searchInput, { target: { value: 'https://github.com/django/django' } });
fireEvent.click(submitButton);
expect(screen.queryByText(forkId)).toBeInTheDocument();
});

test('search forks onError', (done) => {
render(<Container />);
const searchInput = screen.getByPlaceholderText(/github.com/);
const submitButton = screen.getByRole('button', { type: 'submit' });
const forkId = 'django-nonrel/django-404';
const expected = `Could not resolve to a Repository with the name '${forkId}'.`;
const searchError = new ApolloError({ errorMessage: expected });
searchPopularForks.mockImplementationOnce((url, onResult, onError) => {
onResult; // pieaces linter mind
onError(searchError);
done();
});
expect(screen.queryByText(forkId)).toBeNull();
fireEvent.change(searchInput, { target: { value: `https://github.com/${forkId}` } });
expect(screen.queryByText('Error')).not.toBeInTheDocument();
fireEvent.click(submitButton);
expect(screen.queryByText('Error')).toBeInTheDocument();
});
31 changes: 31 additions & 0 deletions src/components/ErrorDialog.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { Modal } from 'react-bootstrap';

const ErrorDialog = ({ detail, onClose }) => {
const [show, setShow] = useState(true);
const handleClose = () => {
setShow(false);
onClose();
};

// note animation is disabled to workaround an upstream issue:
// https://github.com/react-bootstrap/react-bootstrap/issues/5075
return (
<Modal show={show} onHide={handleClose} animation={false}>
<Modal.Header closeButton className="bg-warning">
<Modal.Title>Error</Modal.Title>
</Modal.Header>
<Modal.Body>{detail}</Modal.Body>
</Modal>
);
};
ErrorDialog.propTypes = {
detail: PropTypes.string.isRequired,
onClose: PropTypes.func,
};
ErrorDialog.defaultProps = {
onClose: null,
};

export default ErrorDialog;
46 changes: 46 additions & 0 deletions src/components/ErrorDialog.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react';
import ReactDOM from 'react-dom';
import renderer from 'react-test-renderer';
import { fireEvent, render, screen } from '@testing-library/react';
import ErrorDialog from './ErrorDialog';

describe('ErrorDialog', () => {
/**
* Workaround for the modal not rendering properly in tests.
* https://stackoverflow.com/a/58076920/185510
*/
beforeAll(() => {
// Not too sure why using jest.fn() won't work here
// ReactDOM.createPortal = jest.fn(node => node);
ReactDOM.createPortal = (node) => node;
ReactDOM.createPortalOld = ReactDOM.createPortal;
});

afterEach(() => {
// ReactDOM.createPortal.mockClear();
ReactDOM.createPortal = ReactDOM.createPortalOld;
});

test('renders', () => {
const onClose = () => ({});
const tree = renderer.create(
<ErrorDialog detail="Error details renders" onClose={onClose} />,
).toJSON();
expect(tree).toMatchSnapshot();
});

test('onClose omitted', () => {
const tree = renderer.create(
<ErrorDialog detail="Error details onClose omitted" />,
).toJSON();
expect(tree).toMatchSnapshot();
});

test('onClose', (done) => {
render(
<ErrorDialog detail="Error details onClose" onClose={done} />,
);
const closeButton = screen.getByRole('button', { type: 'button', hidden: true });
fireEvent.click(closeButton);
});
});
3 changes: 2 additions & 1 deletion src/components/PopForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,14 @@ const PopForm = ({ onSubmit, loading }) => {
onSubmit(url);
}
};
const isInvalid = url && urlMatch(url) === null;
return (
<Form onSubmit={handleSubmit}>
<InputGroup className="mb-3">
<FormControl
placeholder="https://github.com/django/django"
onChange={(e) => setUrl(e.target.value)}
isInvalid={urlMatch(url) === null}
isInvalid={isInvalid}
/>
<InputGroup.Append>
<Button type="submit" variant="outline-secondary" onClick={handleSubmit}>
Expand Down
13 changes: 13 additions & 0 deletions src/components/PopForm.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,16 @@ test('submit', () => {
fireEvent.submit(searchInput);
expect(onSubmit).toHaveBeenNthCalledWith(2, expectedUrl);
});

/**
* Empty form should not trigger the submit callback
*/
test('submit empty', () => {
const onSubmit = jest.fn();
render(<PopForm onSubmit={onSubmit} loading={false} />);
const searchInput = screen.getByPlaceholderText(/github.com/);
const url = '';
fireEvent.change(searchInput, { target: { value: url } });
fireEvent.submit(searchInput);
expect(onSubmit).not.toHaveBeenCalled();
});
2 changes: 2 additions & 0 deletions src/components/ResultTable.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,10 @@ test('renders', () => {
},
},
];
Date.now = jest.fn(() => new Date('2020-12-08T19:18:03.135Z').valueOf());
const tree = renderer.create(
<ResultTable forks={forks} activePage={1} itemsCountPerPage={2} onPageChange={() => null} />,
).toJSON();
Date.now.mockRestore();
expect(tree).toMatchSnapshot();
});
Loading

0 comments on commit db0efa8

Please sign in to comment.