@conorheffron/ironoc-frontend 7.3.2 → 7.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -2
- package/src/App.js +9 -4
- package/src/App.test.js +1 -33
- package/src/components/RepoDetails.js +26 -9
- package/src/components/RepoIssues.js +28 -10
- package/src/components/__tests__/Donate.test.js +5 -8
- package/src/components/__tests__/RepoDetails.test.js +123 -76
- package/src/components/__tests__/RepoIssues.test.js +113 -60
- package/src/index.js +5 -5
- package/src/setupTests.js +3 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@conorheffron/ironoc-frontend",
|
|
3
|
-
"version": "7.3.
|
|
3
|
+
"version": "7.3.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@fontsource/montserrat": "^5.1.1",
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"@testing-library/user-event": "^13.5.0",
|
|
9
9
|
"axios": "^1.8.2",
|
|
10
10
|
"bootstrap": "5.1",
|
|
11
|
+
"history": "^5.3.0",
|
|
11
12
|
"react": "^18.3.1",
|
|
12
13
|
"react-bootstrap": "^2.10.5",
|
|
13
14
|
"react-bootstrap-carousel": "^4.1.1",
|
|
@@ -15,7 +16,8 @@
|
|
|
15
16
|
"react-bootstrap-validation": "^0.1.11",
|
|
16
17
|
"react-cookie": "^7.2.2",
|
|
17
18
|
"react-dom": "^18.3.1",
|
|
18
|
-
"react-router
|
|
19
|
+
"react-router": "^7.5.2",
|
|
20
|
+
"react-router-dom": "^7.5.2",
|
|
19
21
|
"reactstrap": "^8.10.0",
|
|
20
22
|
"recharts": "^3.0.0-alpha.5",
|
|
21
23
|
"web-vitals": "^4.2.3"
|
package/src/App.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { Component } from 'react';
|
|
2
|
-
import { BrowserRouter as Router,
|
|
2
|
+
import { BrowserRouter as Router, Routes, Route } from 'react-router';
|
|
3
3
|
import './App.css';
|
|
4
4
|
import Home from './components/Home';
|
|
5
5
|
import CoffeeHome from './components/CoffeeHome';
|
|
@@ -30,11 +30,16 @@ class App extends Component {
|
|
|
30
30
|
|
|
31
31
|
return (
|
|
32
32
|
<Router forceRefresh={forceRefresh}>
|
|
33
|
-
<
|
|
33
|
+
<Routes>
|
|
34
34
|
{routes.map((route, index) => (
|
|
35
|
-
<Route
|
|
35
|
+
<Route
|
|
36
|
+
key={index}
|
|
37
|
+
path={route.path}
|
|
38
|
+
{...(route.exact ? { exact: true } : {})}
|
|
39
|
+
element={<route.component />}
|
|
40
|
+
/>
|
|
36
41
|
))}
|
|
37
|
-
</
|
|
42
|
+
</Routes>
|
|
38
43
|
</Router>
|
|
39
44
|
);
|
|
40
45
|
}
|
package/src/App.test.js
CHANGED
|
@@ -4,31 +4,11 @@ import '@testing-library/jest-dom';
|
|
|
4
4
|
import Home from './components/Home';
|
|
5
5
|
import App from './App';
|
|
6
6
|
import { createMemoryHistory } from 'history';
|
|
7
|
-
import { Router } from 'react-router-dom';
|
|
8
7
|
import LoadingSpinner from './LoadingSpinner';
|
|
9
8
|
import AppNavBar from './AppNavbar';
|
|
10
9
|
import About from './components/About';
|
|
11
10
|
import Footer from './Footer';
|
|
12
|
-
|
|
13
|
-
describe('LoadingSpinner', () => {
|
|
14
|
-
test('renders LoadingSpinner component correctly', () => {
|
|
15
|
-
// Render the component
|
|
16
|
-
const { getByRole, getByText } = render(<LoadingSpinner />);
|
|
17
|
-
|
|
18
|
-
// Check if the button is present and disabled
|
|
19
|
-
const button = getByRole('button');
|
|
20
|
-
expect(button).toBeInTheDocument();
|
|
21
|
-
expect(button).toBeDisabled();
|
|
22
|
-
|
|
23
|
-
// Check if the spinner is present
|
|
24
|
-
const spinner = getByRole('button');
|
|
25
|
-
expect(spinner).toBeInTheDocument();
|
|
26
|
-
|
|
27
|
-
// Check if the button contains the text "Loading..."
|
|
28
|
-
const loadingText = getByText('Loading...');
|
|
29
|
-
expect(loadingText).toBeInTheDocument();
|
|
30
|
-
});
|
|
31
|
-
});
|
|
11
|
+
import { Router, useLocation, MemoryRouter } from 'react-router';
|
|
32
12
|
|
|
33
13
|
describe('AppNavBar', () => {
|
|
34
14
|
test('renders AppNavBar component correctly', () => {
|
|
@@ -87,18 +67,6 @@ describe('AppNavBar', () => {
|
|
|
87
67
|
});
|
|
88
68
|
});
|
|
89
69
|
|
|
90
|
-
describe('App Component Routing', () => {
|
|
91
|
-
test("sends the user to about", () => {
|
|
92
|
-
const history = createMemoryHistory({ initialEntries: ["/about"] });
|
|
93
|
-
render(
|
|
94
|
-
<Router history={history}>
|
|
95
|
-
<App />
|
|
96
|
-
</Router>
|
|
97
|
-
);
|
|
98
|
-
expect(history.location.pathname).toBe("/about");
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
|
|
102
70
|
describe('Footer Component', () => {
|
|
103
71
|
beforeEach(() => {
|
|
104
72
|
// Mock the fetch API
|
|
@@ -3,8 +3,17 @@ import { Button, ButtonGroup, Container, InputGroup, Table } from 'reactstrap';
|
|
|
3
3
|
import '.././App.css';
|
|
4
4
|
import Form from 'react-bootstrap/Form';
|
|
5
5
|
import AppNavbar from '.././AppNavbar';
|
|
6
|
-
import { Link } from 'react-router-dom';
|
|
7
6
|
import LoadingSpinner from '.././LoadingSpinner';
|
|
7
|
+
import { Link, useParams, useNavigate } from 'react-router';
|
|
8
|
+
|
|
9
|
+
// Helper function to inject `params` and `navigate` into class-based components
|
|
10
|
+
function withRouter(Component) {
|
|
11
|
+
return (props) => {
|
|
12
|
+
const params = useParams();
|
|
13
|
+
const navigate = useNavigate();
|
|
14
|
+
return <Component {...props} params={params} navigate={navigate} />;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
8
17
|
|
|
9
18
|
class RepoDetails extends Component {
|
|
10
19
|
constructor(props) {
|
|
@@ -26,21 +35,29 @@ class RepoDetails extends Component {
|
|
|
26
35
|
onSubmit(event) {
|
|
27
36
|
event.preventDefault();
|
|
28
37
|
const { value } = this.state;
|
|
29
|
-
this.props.
|
|
30
|
-
|
|
38
|
+
this.props.navigate(`/projects/${value}`, {
|
|
39
|
+
replace: true,
|
|
40
|
+
state: {
|
|
41
|
+
id: value
|
|
42
|
+
}
|
|
31
43
|
});
|
|
44
|
+
this.props.navigate(0)
|
|
32
45
|
}
|
|
33
46
|
|
|
34
47
|
async componentDidMount() {
|
|
35
|
-
const {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
48
|
+
const { id } = this.props.params;
|
|
49
|
+
if (id) {
|
|
50
|
+
const response = await fetch(`/api/get-repo-detail?username=${id}`);
|
|
51
|
+
const body = await response.json();
|
|
52
|
+
this.setState({ repoDetailList: body, isLoading: false });
|
|
53
|
+
} else {
|
|
54
|
+
this.setState({ isLoading: false });
|
|
55
|
+
}
|
|
39
56
|
}
|
|
40
57
|
|
|
41
58
|
render() {
|
|
42
59
|
const { repoDetailList = [], isLoading = true, value = '' } = this.state;
|
|
43
|
-
const {
|
|
60
|
+
const { id: gitUser = '' } = this.props.params;
|
|
44
61
|
|
|
45
62
|
if (isLoading) {
|
|
46
63
|
return (
|
|
@@ -107,4 +124,4 @@ class RepoDetails extends Component {
|
|
|
107
124
|
}
|
|
108
125
|
}
|
|
109
126
|
|
|
110
|
-
export default RepoDetails;
|
|
127
|
+
export default withRouter(RepoDetails);
|
|
@@ -4,6 +4,16 @@ import '.././App.css';
|
|
|
4
4
|
import Form from 'react-bootstrap/Form';
|
|
5
5
|
import AppNavbar from '.././AppNavbar';
|
|
6
6
|
import LoadingSpinner from '.././LoadingSpinner';
|
|
7
|
+
import { useParams, useNavigate } from 'react-router';
|
|
8
|
+
|
|
9
|
+
// Helper function to inject `params` and `navigate` into class-based components
|
|
10
|
+
function withRouter(Component) {
|
|
11
|
+
return (props) => {
|
|
12
|
+
const params = useParams();
|
|
13
|
+
const navigate = useNavigate();
|
|
14
|
+
return <Component {...props} params={params} navigate={navigate} />;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
7
17
|
|
|
8
18
|
class RepoIssues extends Component {
|
|
9
19
|
constructor(props) {
|
|
@@ -25,23 +35,31 @@ class RepoIssues extends Component {
|
|
|
25
35
|
onSubmit(event) {
|
|
26
36
|
event.preventDefault();
|
|
27
37
|
const { value } = this.state;
|
|
28
|
-
const {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
38
|
+
const { id } = this.props.params;
|
|
39
|
+
this.props.navigate(`/issues/${id}/${value}`, {
|
|
40
|
+
replace: true,
|
|
41
|
+
state: {
|
|
42
|
+
id: id,
|
|
43
|
+
repo: value
|
|
44
|
+
}
|
|
32
45
|
});
|
|
46
|
+
this.props.navigate(0)
|
|
33
47
|
}
|
|
34
48
|
|
|
35
49
|
async componentDidMount() {
|
|
36
|
-
const {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
50
|
+
const { id, repo } = this.props.params;
|
|
51
|
+
if (id && repo) {
|
|
52
|
+
const response = await fetch(`/api/get-repo-issue/${id}/${repo}/`);
|
|
53
|
+
const body = await response.json();
|
|
54
|
+
this.setState({ repoIssueList: body, isLoading: false });
|
|
55
|
+
} else {
|
|
56
|
+
this.setState({ isLoading: false });
|
|
57
|
+
}
|
|
40
58
|
}
|
|
41
59
|
|
|
42
60
|
render() {
|
|
43
61
|
const { repoIssueList = [], isLoading = true, value = '' } = this.state;
|
|
44
|
-
const {
|
|
62
|
+
const { id = '', repo = '' } = this.props.params;
|
|
45
63
|
|
|
46
64
|
if (isLoading) {
|
|
47
65
|
return (
|
|
@@ -103,4 +121,4 @@ class RepoIssues extends Component {
|
|
|
103
121
|
}
|
|
104
122
|
}
|
|
105
123
|
|
|
106
|
-
export default RepoIssues;
|
|
124
|
+
export default withRouter(RepoIssues);
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { render, screen, waitFor } from '@testing-library/react';
|
|
2
2
|
import App from '../../App';
|
|
3
|
-
import {
|
|
4
|
-
import { Router } from 'react-router-dom';
|
|
3
|
+
import { Router, MemoryRouter } from 'react-router';
|
|
5
4
|
import Donate from '../Donate';
|
|
6
5
|
import LoadingSpinner from '../../LoadingSpinner';
|
|
7
6
|
|
|
@@ -49,12 +48,11 @@ describe('Donate', () => {
|
|
|
49
48
|
});
|
|
50
49
|
|
|
51
50
|
test('displays charity options after fetching data', async () => {
|
|
52
|
-
const history = createMemoryHistory();
|
|
53
51
|
const { container } = render(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
<MemoryRouter>
|
|
53
|
+
<Donate />
|
|
54
|
+
</MemoryRouter>
|
|
55
|
+
);
|
|
58
56
|
|
|
59
57
|
// Wait for the component to finish loading
|
|
60
58
|
await waitFor(() => {
|
|
@@ -73,4 +71,3 @@ describe('Donate', () => {
|
|
|
73
71
|
expect(container.querySelector('.LoadingSpinner')).not.toBeInTheDocument();
|
|
74
72
|
});
|
|
75
73
|
});
|
|
76
|
-
|
|
@@ -1,83 +1,130 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import { Router } from 'react-router-dom';
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
3
|
+
import { MemoryRouter } from 'react-router';
|
|
5
4
|
import RepoDetails from '../RepoDetails';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
5
|
+
|
|
6
|
+
jest.mock('react-router', () => ({
|
|
7
|
+
...jest.requireActual('react-router'),
|
|
8
|
+
useParams: jest.fn(),
|
|
9
|
+
useNavigate: jest.fn(),
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
describe('RepoDetails Component', () => {
|
|
13
|
+
const mockNavigate = jest.fn();
|
|
14
|
+
const mockParams = { id: 'testUser' };
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
require('react-router').useNavigate.mockReturnValue(mockNavigate);
|
|
18
|
+
require('react-router').useParams.mockReturnValue(mockParams);
|
|
19
|
+
|
|
20
|
+
global.fetch = jest.fn(() =>
|
|
21
|
+
Promise.resolve({
|
|
22
|
+
json: () =>
|
|
23
|
+
Promise.resolve([
|
|
24
|
+
{
|
|
25
|
+
name: 'testRepo',
|
|
26
|
+
repoUrl: 'https://github.com/testRepo',
|
|
27
|
+
fullName: 'testUser/testRepo',
|
|
28
|
+
description: 'Test repository description',
|
|
29
|
+
appHome: 'https://example.com',
|
|
30
|
+
topics: 'topic1, topic2',
|
|
31
|
+
},
|
|
32
|
+
]),
|
|
33
|
+
})
|
|
34
|
+
);
|
|
31
35
|
});
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
jest.restoreAllMocks();
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test('renders AppNavbar component', () => {
|
|
39
|
-
render(<App />);
|
|
40
|
-
expect(screen.getByRole('banner')).toBeInTheDocument();
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test('renders loading state initially', () => {
|
|
44
|
-
render(<RepoDetails match={{ params: { id: 'User' } }} />);
|
|
45
|
-
expect(screen.getByText('Loading...')).toBeInTheDocument();
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
test('displays repo details after fetching data', async () => {
|
|
49
|
-
const history = createMemoryHistory();
|
|
50
|
-
const { container } = render(
|
|
51
|
-
<Router history={history}>
|
|
52
|
-
<RepoDetails match={{ params: { id: 'User' } }} />
|
|
53
|
-
</Router>
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
// Wait for the component to finish loading
|
|
57
|
-
await waitFor(() => {
|
|
58
|
-
expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
|
|
36
|
+
|
|
37
|
+
afterEach(() => {
|
|
38
|
+
jest.clearAllMocks();
|
|
59
39
|
});
|
|
60
40
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
41
|
+
test('renders loading spinner initially', () => {
|
|
42
|
+
render(
|
|
43
|
+
<MemoryRouter>
|
|
44
|
+
<RepoDetails />
|
|
45
|
+
</MemoryRouter>
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
expect(screen.getByText(/loading/i)).toBeInTheDocument();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('fetches and displays repo details', async () => {
|
|
52
|
+
render(
|
|
53
|
+
<MemoryRouter>
|
|
54
|
+
<RepoDetails />
|
|
55
|
+
</MemoryRouter>
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
await waitFor(() => expect(fetch).toHaveBeenCalledTimes(1));
|
|
59
|
+
|
|
60
|
+
expect(screen.getByText('testUser/testRepo')).toBeInTheDocument();
|
|
61
|
+
expect(screen.getByText('Test repository description')).toBeInTheDocument();
|
|
62
|
+
expect(screen.getByText('topic1, topic2')).toBeInTheDocument();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
test('handles input change', async () => {
|
|
67
|
+
render(
|
|
68
|
+
<MemoryRouter>
|
|
69
|
+
<RepoDetails />
|
|
70
|
+
</MemoryRouter>
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
// Wait for the loading spinner to disappear
|
|
74
|
+
await waitFor(() => expect(screen.queryByText(/loading/i)).not.toBeInTheDocument());
|
|
75
|
+
|
|
76
|
+
const input = screen.getByPlaceholderText(/Enter GitHub User ID/i);
|
|
77
|
+
fireEvent.change(input, { target: { value: 'newUser' } });
|
|
78
|
+
|
|
79
|
+
expect(input.value).toBe('newUser');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('displays table headers correctly', async () => {
|
|
83
|
+
render(
|
|
84
|
+
<MemoryRouter>
|
|
85
|
+
<RepoDetails />
|
|
86
|
+
</MemoryRouter>
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
// Wait for the loading spinner to disappear
|
|
90
|
+
await waitFor(() => expect(screen.queryByText(/loading/i)).not.toBeInTheDocument());
|
|
91
|
+
|
|
92
|
+
// Assert that table headers are displayed correctly using roles
|
|
93
|
+
const repositoryHeader = screen.getByRole('columnheader', { name: /Repository/i });
|
|
94
|
+
const descriptionHeader = screen.getByRole('columnheader', { name: /Description/i });
|
|
95
|
+
const appUrlHeader = screen.getByRole('columnheader', { name: /App URL/i });
|
|
96
|
+
const topicsHeader = screen.getByRole('columnheader', { name: /Topics/i });
|
|
97
|
+
const actionsHeader = screen.getByRole('columnheader', { name: /Actions/i });
|
|
98
|
+
|
|
99
|
+
expect(repositoryHeader).toBeInTheDocument();
|
|
100
|
+
expect(descriptionHeader).toBeInTheDocument();
|
|
101
|
+
expect(appUrlHeader).toBeInTheDocument();
|
|
102
|
+
expect(topicsHeader).toBeInTheDocument();
|
|
103
|
+
expect(actionsHeader).toBeInTheDocument();
|
|
78
104
|
});
|
|
79
105
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
106
|
+
test('navigates on form submission', async () => { // Make the function async
|
|
107
|
+
render(
|
|
108
|
+
<MemoryRouter>
|
|
109
|
+
<RepoDetails />
|
|
110
|
+
</MemoryRouter>
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
// Wait for the loading spinner to disappear
|
|
114
|
+
await waitFor(() => expect(screen.queryByText(/loading/i)).not.toBeInTheDocument());
|
|
115
|
+
|
|
116
|
+
// Use getByRole to target the input field and button
|
|
117
|
+
const input = screen.getByRole('textbox', { name: /Enter GitHub User ID/i });
|
|
118
|
+
const button = screen.getByRole('button', { name: /Search Projects/i });
|
|
119
|
+
|
|
120
|
+
// Simulate user input and form submission
|
|
121
|
+
fireEvent.change(input, { target: { value: 'newUser' } });
|
|
122
|
+
fireEvent.click(button);
|
|
123
|
+
|
|
124
|
+
// Assert that navigation was triggered with the correct arguments
|
|
125
|
+
expect(mockNavigate).toHaveBeenCalledWith('/projects/newUser', {
|
|
126
|
+
replace: true,
|
|
127
|
+
state: { id: 'newUser' },
|
|
128
|
+
});
|
|
129
|
+
});
|
|
83
130
|
});
|
|
@@ -1,68 +1,121 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import { Router } from 'react-router-dom';
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
3
|
+
import { MemoryRouter } from 'react-router';
|
|
5
4
|
import RepoIssues from '../RepoIssues';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
5
|
+
|
|
6
|
+
jest.mock('react-router', () => ({
|
|
7
|
+
...jest.requireActual('react-router'),
|
|
8
|
+
useParams: jest.fn(),
|
|
9
|
+
useNavigate: jest.fn(),
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
describe('RepoIssues Component', () => {
|
|
13
|
+
const mockNavigate = jest.fn();
|
|
14
|
+
const mockParams = { id: 'testUser', repo: 'testRepo' };
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
require('react-router').useNavigate.mockReturnValue(mockNavigate);
|
|
18
|
+
require('react-router').useParams.mockReturnValue(mockParams);
|
|
19
|
+
|
|
20
|
+
global.fetch = jest.fn(() =>
|
|
21
|
+
Promise.resolve({
|
|
22
|
+
json: () =>
|
|
23
|
+
Promise.resolve([
|
|
24
|
+
{
|
|
25
|
+
number: 1,
|
|
26
|
+
title: 'Test Issue Title',
|
|
27
|
+
body: 'Test issue body description',
|
|
28
|
+
},
|
|
29
|
+
]),
|
|
30
|
+
})
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
jest.clearAllMocks();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('renders loading spinner initially', () => {
|
|
39
|
+
render(
|
|
40
|
+
<MemoryRouter>
|
|
41
|
+
<RepoIssues />
|
|
42
|
+
</MemoryRouter>
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
expect(screen.getByText(/loading/i)).toBeInTheDocument();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('fetches and displays repo issues', async () => {
|
|
49
|
+
render(
|
|
50
|
+
<MemoryRouter>
|
|
51
|
+
<RepoIssues />
|
|
52
|
+
</MemoryRouter>
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
await waitFor(() => expect(fetch).toHaveBeenCalledTimes(1));
|
|
56
|
+
|
|
57
|
+
expect(screen.getByText(/Test Issue Title/i)).toBeInTheDocument();
|
|
58
|
+
expect(screen.getByText(/Test issue body description/i)).toBeInTheDocument();
|
|
59
|
+
expect(screen.getByText(/1/i)).toBeInTheDocument();
|
|
25
60
|
});
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test('displays repo issues after fetching data', async () => {
|
|
43
|
-
const history = createMemoryHistory();
|
|
44
|
-
const { container } = render(
|
|
45
|
-
<Router history={history}>
|
|
46
|
-
<RepoIssues match={{ params: { id: 'conors-id', repo: 'ironoc-testing' } }} />
|
|
47
|
-
</Router>
|
|
48
|
-
);
|
|
49
|
-
|
|
50
|
-
// Wait for the component to finish loading
|
|
51
|
-
await waitFor(() => {
|
|
52
|
-
expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
|
|
61
|
+
|
|
62
|
+
test('handles input change', async () => {
|
|
63
|
+
render(
|
|
64
|
+
<MemoryRouter>
|
|
65
|
+
<RepoIssues />
|
|
66
|
+
</MemoryRouter>
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
await waitFor(() => expect(fetch).toHaveBeenCalledTimes(1));
|
|
70
|
+
|
|
71
|
+
const input = screen.getByPlaceholderText(/Enter Project Name/i);
|
|
72
|
+
fireEvent.change(input, { target: { value: 'newRepo' } });
|
|
73
|
+
|
|
74
|
+
expect(input.value).toBe('newRepo');
|
|
53
75
|
});
|
|
54
76
|
|
|
55
|
-
|
|
56
|
-
|
|
77
|
+
test('navigates on form submission', async () => {
|
|
78
|
+
render(
|
|
79
|
+
<MemoryRouter>
|
|
80
|
+
<RepoIssues />
|
|
81
|
+
</MemoryRouter>
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
await waitFor(() => expect(fetch).toHaveBeenCalledTimes(1));
|
|
57
85
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
86
|
+
const input = screen.getByPlaceholderText(/Enter Project Name/i);
|
|
87
|
+
const button = screen.getByText(/Search Issues/i);
|
|
88
|
+
|
|
89
|
+
fireEvent.change(input, { target: { value: 'newRepo' } });
|
|
90
|
+
fireEvent.click(button);
|
|
91
|
+
|
|
92
|
+
expect(mockNavigate).toHaveBeenCalledWith('/issues/testUser/newRepo', {
|
|
93
|
+
replace: true,
|
|
94
|
+
state: {
|
|
95
|
+
id: 'testUser',
|
|
96
|
+
repo: 'newRepo',
|
|
97
|
+
},
|
|
98
|
+
});
|
|
63
99
|
});
|
|
64
100
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
101
|
+
test('displays table headers correctly', async () => {
|
|
102
|
+
render(
|
|
103
|
+
<MemoryRouter>
|
|
104
|
+
<RepoIssues />
|
|
105
|
+
</MemoryRouter>
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
// Wait for the loading spinner to disappear
|
|
109
|
+
await waitFor(() => expect(screen.queryByText(/loading/i)).not.toBeInTheDocument());
|
|
110
|
+
|
|
111
|
+
// Use getByRole for specific headers
|
|
112
|
+
const issueNoHeader = screen.getByRole('columnheader', { name: /Issue No./i });
|
|
113
|
+
const titleHeader = screen.getByRole('columnheader', { name: /Title/i });
|
|
114
|
+
const descriptionHeader = screen.getByRole('columnheader', { name: /Description/i });
|
|
115
|
+
|
|
116
|
+
// Assert that headers are in the document
|
|
117
|
+
expect(issueNoHeader).toBeInTheDocument();
|
|
118
|
+
expect(titleHeader).toBeInTheDocument();
|
|
119
|
+
expect(descriptionHeader).toBeInTheDocument();
|
|
120
|
+
});
|
|
68
121
|
});
|
package/src/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import { StrictMode } from 'react';
|
|
2
|
+
import { createRoot } from 'react-dom/client';
|
|
3
3
|
import reportWebVitals from './reportWebVitals';
|
|
4
4
|
import 'bootstrap/dist/css/bootstrap.min.css';
|
|
5
5
|
import './index.css';
|
|
@@ -7,9 +7,9 @@ import App from './App';
|
|
|
7
7
|
import AppNavBar from './AppNavbar';
|
|
8
8
|
import Footer from './Footer';
|
|
9
9
|
|
|
10
|
-
const root =
|
|
10
|
+
const root = createRoot(document.getElementById('root'));
|
|
11
11
|
root.render(
|
|
12
|
-
<
|
|
12
|
+
<StrictMode>
|
|
13
13
|
<div className="app-wrapper">
|
|
14
14
|
<AppNavBar />
|
|
15
15
|
<div className="content-inner">
|
|
@@ -17,7 +17,7 @@ root.render(
|
|
|
17
17
|
</div>
|
|
18
18
|
<Footer />
|
|
19
19
|
</div>
|
|
20
|
-
</
|
|
20
|
+
</StrictMode>
|
|
21
21
|
);
|
|
22
22
|
|
|
23
23
|
reportWebVitals();
|
package/src/setupTests.js
CHANGED