@conorheffron/ironoc-frontend 7.0.2 → 7.1.2

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 CHANGED
@@ -1,8 +1,10 @@
1
1
  {
2
2
  "name": "@conorheffron/ironoc-frontend",
3
- "version": "7.0.2",
3
+ "version": "7.1.2",
4
4
  "private": false,
5
5
  "dependencies": {
6
+ "@fontsource/montserrat": "^5.1.1",
7
+ "@fontsource/open-sans": "^5.1.1",
6
8
  "@testing-library/user-event": "^13.5.0",
7
9
  "axios": "^0.28.0",
8
10
  "bootstrap": "5.1",
@@ -15,11 +17,12 @@
15
17
  "react-dom": "^18.3.1",
16
18
  "react-router-dom": "^5.3.0",
17
19
  "reactstrap": "^8.10.0",
20
+ "recharts": "^3.0.0-alpha.5",
18
21
  "web-vitals": "^4.2.3"
19
22
  },
20
23
  "scripts": {
21
24
  "start": "react-scripts start",
22
- "build": "CI=false && react-scripts build",
25
+ "build": "CI=false && GENERATE_SOURCEMAP=false react-scripts build",
23
26
  "test": "react-scripts test",
24
27
  "eject": "react-scripts eject"
25
28
  },
package/src/App.css CHANGED
@@ -37,6 +37,16 @@
37
37
  }
38
38
  }
39
39
 
40
+ .App-header, .body, .table-headers, .table, .mb-3 {
41
+ font-family: "Open Sans";
42
+ font-size: 1.4em;
43
+ }
44
+
45
+ .nav-bar, .ft, h1, h3 {
46
+ font-family: "Montserrat";
47
+ font-size: 1.1em;
48
+ }
49
+
40
50
  .carousel-caption {
41
51
  top: 0;
42
52
  bottom: auto;
@@ -74,6 +84,10 @@
74
84
  width: 51;
75
85
  }
76
86
 
87
+ .carousel-caption {
88
+ font-family: "Open Sans";
89
+ }
90
+
77
91
  .carousel-caption h3, h5 {
78
92
  color: navy;
79
93
  }
@@ -94,3 +108,15 @@ p a {
94
108
  height: auto;
95
109
  margin: 0 auto;
96
110
  }
111
+
112
+ .overview-text {
113
+ display: block;
114
+ font-size: 1rem; /* Adjust the base font-size as needed */
115
+ line-height: 1.5;
116
+ white-space: normal;
117
+ word-wrap: break-word;
118
+ overflow-wrap: break-word;
119
+ max-height: 200px; /* Set a max height to avoid overflow */
120
+ overflow: hidden;
121
+ text-overflow: ellipsis; /* Add ellipsis for text overflow */
122
+ }
package/src/App.js CHANGED
@@ -3,6 +3,7 @@ import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
3
3
  import './App.css';
4
4
  import Home from './components/Home';
5
5
  import CoffeeHome from './components/CoffeeHome';
6
+ import Donate from './components/Donate';
6
7
  import NotFound from './components/NotFound';
7
8
  import About from './components/About';
8
9
  import RepoDetails from './components/RepoDetails';
@@ -11,21 +12,32 @@ import ControlledCarousel from './components/ControlledCarousel';
11
12
 
12
13
  class App extends Component {
13
14
  render() {
15
+ // Default props
16
+ const {
17
+ forceRefresh = true,
18
+ routes = [
19
+ { path: '/', exact: true, component: Home },
20
+ { path: '/about', exact: true, component: About },
21
+ { path: '/portfolio', exact: true, component: ControlledCarousel },
22
+ { path: '/projects', exact: true, component: RepoDetails },
23
+ { path: '/projects/:id', component: RepoDetails },
24
+ { path: '/issues/:id/:repo', component: RepoIssues },
25
+ { path: '/brews', exact: true, component: CoffeeHome },
26
+ { path: '/donate', exact: true, component: Donate },
27
+ { path: '*', component: NotFound }
28
+ ]
29
+ } = this.props;
30
+
14
31
  return (
15
- <Router forceRefresh={true}>
16
- <Switch>
17
- <Route path='/' exact={true} component={Home}/>
18
- <Route path='/about' exact={true} component={About}/>
19
- <Route path='/portfolio' exact={true} component={ControlledCarousel}/>
20
- <Route path='/projects' exact={true} component={RepoDetails}/>
21
- <Route path='/projects/:id' component={RepoDetails}/>
22
- <Route path='/issues/:id/:repo' component={RepoIssues}/>
23
- <Route path='/brews' exact={true} component={CoffeeHome}/>
24
- <Route path="*" component={NotFound} />
25
- </Switch>
26
- </Router>
27
- )
32
+ <Router forceRefresh={forceRefresh}>
33
+ <Switch>
34
+ {routes.map((route, index) => (
35
+ <Route key={index} path={route.path} exact={route.exact} component={route.component} />
36
+ ))}
37
+ </Switch>
38
+ </Router>
39
+ );
28
40
  }
29
41
  }
30
42
 
31
- export default App;
43
+ export default App;
package/src/App.test.js CHANGED
@@ -1,97 +1,381 @@
1
1
  import React from 'react';
2
- import { render, screen, waitFor } from '@testing-library/react';
2
+ import { render, screen, act, waitFor, fireEvent } from '@testing-library/react';
3
3
  import '@testing-library/jest-dom';
4
4
  import Home from './components/Home';
5
5
  import axios from 'axios';
6
6
  import App from './App';
7
7
  import CoffeeCarousel from './components/CoffeeCarousel';
8
8
  import CoffeeHome from './components/CoffeeHome';
9
+ import Donate from './components/Donate';
9
10
  import NotFound from './components/NotFound';
11
+ import { createMemoryHistory } from 'history';
12
+ import { Router, MemoryRouter } from 'react-router-dom';
13
+ import RepoDetails from './components/RepoDetails';
14
+ import RepoIssues from './components/RepoIssues';
15
+ import LoadingSpinner from './LoadingSpinner';
16
+ import AppNavBar from './AppNavbar';
10
17
 
11
18
  // Mocking axios
12
19
  jest.mock('axios');
13
20
 
14
- test('renders learn react link', () => {
15
- render(<Home />);
16
- const element = screen.getByText(/Home/i);
17
- expect(element).toBeInTheDocument();
18
- });
19
-
20
21
  // Sample data for testing
21
22
  const coffeeItems = [
23
+ {
24
+ title: 'Espresso',
25
+ ingredients: ['Water', 'Coffee beans'],
26
+ image: 'https://example.com/espresso.jpg',
27
+ },
28
+ {
29
+ title: 'Cappuccino',
30
+ ingredients: ['Espresso', 'Steamed milk', 'Foam milk'],
31
+ image: 'https://example.com/cappuccino.jpg',
32
+ },
33
+ ];
34
+
35
+ const mockRepoDetails = [
36
+ {
37
+ name: 'repo1',
38
+ fullName: 'User/repo1',
39
+ repoUrl: 'https://github.com/User/repo1',
40
+ description: 'Description of repo1',
41
+ appHome: 'https://repo1.com',
42
+ topics: ['topic1', 'topic2']
43
+ },
44
+ {
45
+ name: 'repo2',
46
+ fullName: 'User/repo2',
47
+ repoUrl: 'https://github.com/User/repo2',
48
+ description: 'Description of repo2',
49
+ appHome: 'https://repo2.com',
50
+ topics: ['topic3', 'topic4']
51
+ }
52
+ ];
53
+
54
+ const mockRepoIssues = [
55
+ {
56
+ number: 1,
57
+ title: 'Issue 1',
58
+ body: 'https://github.com/User/repo/issues/1'
59
+ },
60
+ {
61
+ number: 2,
62
+ title: 'Issue 2',
63
+ body: 'https://github.com/User/repo/issues/2'
64
+ }
65
+ ];
66
+
67
+ const donateItems = [
22
68
  {
23
- title: 'Espresso',
24
- ingredients: ['Water', 'Coffee beans'],
25
- image: 'https://example.com/espresso.jpg',
69
+ donate: "https://www.jackandjill.ie/professionals/ways-to-donate/",
70
+ link: "https://www.jackandjill.ie",
71
+ img: "red1.png",
72
+ alt: "red1",
73
+ name: "The Jack and Jill Children’s Foundation",
74
+ overview: `The Jack and Jill Children’s Foundation is a nationwide charity that funds and provides in-home nursing care
75
+ and respite support for children with severe to profound neurodevelopmental delay, up to the age of 6.
76
+ This may include children with brain injury, genetic diagnosis, cerebral palsy and undiagnosed conditions.
77
+ Another key part of our service is end-of-life care for all children up to the age of 6, irrespective of diagnosis.`,
78
+ founded: 1997,
79
+ phone: "+353 (045) 894 538"
26
80
  },
27
81
  {
28
- title: 'Cappuccino',
29
- ingredients: ['Espresso', 'Steamed milk', 'Foam milk'],
30
- image: 'https://example.com/cappuccino.jpg',
31
- },
82
+ donate: "https://vi.ie/supporting-us/donate-now/",
83
+ link: "https://linktr.ee/vision_ireland",
84
+ img: "red2.png",
85
+ alt: "red2",
86
+ name: "Vision Ireland, the new name for NCBI",
87
+ overview: `Vision Ireland, the name for NCBI is Ireland’s national charity working for the rising number of people affected by sight loss.
88
+ Our practical and emotional advice and support help 8,000 people and their families confidently face their futures every year.`,
89
+ founded: 1931,
90
+ phone: "+353 (0)1 830 7033"
91
+ }
92
+ // Add more items as needed
32
93
  ];
33
94
 
95
+ describe('Home', () => {
96
+ test('renders Home component', async () => {
97
+ await act(async () => {
98
+ render(<Home />);
99
+ });
100
+ const element = screen.getByText(/Home/i);
101
+ expect(element).toBeInTheDocument();
102
+ });
103
+ });
104
+
34
105
  describe('CoffeeCarousel', () => {
35
- test('renders carousel with coffee items', () => {
36
- render(<CoffeeCarousel items={coffeeItems} />);
37
-
38
- // Check that the carousel items are rendered
39
- coffeeItems.forEach((item) => {
40
- expect(screen.getByText(item.title)).toBeInTheDocument();
41
- expect(screen.getByAltText(item.title)).toBeInTheDocument();
42
- expect(screen.getByText('Ingredients: ' + item.ingredients.join(', '))).toBeInTheDocument();
43
- });
106
+ test('renders carousel with coffee items', () => {
107
+ render(<CoffeeCarousel items={coffeeItems} />);
108
+
109
+ // Check that the carousel items are rendered
110
+ coffeeItems.forEach((item) => {
111
+ expect(screen.getByText(item.title)).toBeInTheDocument();
112
+ expect(screen.getByAltText(item.title)).toBeInTheDocument();
113
+ expect(screen.getByText(item.ingredients.join(', '))).toBeInTheDocument();
44
114
  });
115
+ });
45
116
 
46
- test('renders carousel with correct number of items', () => {
47
- render(<CoffeeCarousel items={coffeeItems} />);
117
+ test('renders carousel with correct number of items', () => {
118
+ render(<CoffeeCarousel items={coffeeItems} />);
48
119
 
49
- // Check that the correct number of carousel items are rendered
50
- const carouselItems = screen.getAllByRole('img');
120
+ // Check that the correct number of carousel items are rendered
121
+ const carouselItems = screen.getAllByRole('img');
51
122
 
52
- expect(carouselItems.length).toBe(coffeeItems.length);
53
- });
123
+ expect(carouselItems.length).toBe(coffeeItems.length);
124
+ });
54
125
  });
55
126
 
56
127
  describe('CoffeeHome', () => {
57
- beforeEach(() => {
58
- axios.get.mockResolvedValue({ data: coffeeItems });
128
+ beforeEach(() => {
129
+ axios.get.mockResolvedValueOnce({ data: coffeeItems });
130
+ });
131
+
132
+ test('renders AppNavbar component', () => {
133
+ render(<App />);
134
+ expect(screen.getByRole('banner')).toBeInTheDocument();
135
+ });
136
+
137
+ test('displays loading state initially', () => {
138
+ render(<CoffeeHome />);
139
+ expect(screen.getByText('Loading...')).toBeInTheDocument();
140
+ });
141
+
142
+ test('renders CoffeeCarousel component with coffee items', async () => {
143
+ render(<CoffeeHome />);
144
+
145
+ // Wait for the coffee items to be fetched and rendered
146
+ await waitFor(() => {
147
+ expect(screen.getByText('Espresso')).toBeInTheDocument();
148
+ expect(screen.getByText('Cappuccino')).toBeInTheDocument();
59
149
  });
150
+ });
151
+ });
152
+
153
+ describe('NotFound', () => {
154
+ test('renders AppNavbar component', () => {
155
+ render(<NotFound />);
156
+ expect(screen.getByRole('banner')).toBeInTheDocument();
157
+ });
158
+
159
+ test('displays 404 error message', () => {
160
+ render(<NotFound />);
161
+ expect(screen.getByText('404 - Page Not Found')).toBeInTheDocument();
162
+ });
163
+
164
+ test('displays the apology message', () => {
165
+ render(<NotFound />);
166
+ expect(screen.getByText('Sorry, the page you are looking for could not be found.')).toBeInTheDocument();
167
+ });
168
+ });
60
169
 
61
- test('renders AppNavbar component', () => {
62
- render(<App />);
63
- expect(screen.getByRole('banner')).toBeInTheDocument();
170
+ describe('RepoDetails', () => {
171
+ beforeEach(() => {
172
+ jest.spyOn(global, 'fetch').mockResolvedValue({
173
+ json: jest.fn().mockResolvedValue(mockRepoDetails)
64
174
  });
175
+ });
65
176
 
66
- test('displays loading state initially', () => {
67
- render(<CoffeeHome />);
68
- expect(screen.getByText('Loading...')).toBeInTheDocument();
177
+ afterEach(() => {
178
+ jest.restoreAllMocks();
179
+ });
180
+
181
+ test('renders AppNavbar component', () => {
182
+ render(<App />);
183
+ expect(screen.getByRole('banner')).toBeInTheDocument();
184
+ });
185
+
186
+ test('renders loading state initially', () => {
187
+ render(<RepoDetails match={{ params: { id: 'User' } }} />);
188
+ expect(screen.getByText('Loading...')).toBeInTheDocument();
189
+ });
190
+
191
+ test('displays repo details after fetching data', async () => {
192
+ const history = createMemoryHistory();
193
+ const { container } = render(
194
+ <Router history={history}>
195
+ <RepoDetails match={{ params: { id: 'User' } }} />
196
+ </Router>
197
+ );
198
+
199
+ // Wait for the component to finish loading
200
+ await waitFor(() => {
201
+ expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
69
202
  });
70
203
 
71
- test('renders CoffeeCarousel component with coffee items', async () => {
72
- render(<CoffeeHome />);
204
+ // Check that repo details are displayed
205
+ mockRepoDetails.forEach(repo => {
206
+ expect(screen.getByText(repo.fullName)).toBeInTheDocument();
207
+ expect(screen.queryByText(repo.repoUrl)).not.toBeInTheDocument();
208
+ expect(screen.getByText(repo.description)).toBeInTheDocument();
209
+ expect(screen.queryByText(repo.appHome)).not.toBeInTheDocument();
73
210
 
74
- // Wait for the coffee items to be fetched and rendered
75
- await waitFor(() => {
76
- expect(screen.getByText('Espresso')).toBeInTheDocument();
77
- expect(screen.getByText('Cappuccino')).toBeInTheDocument();
211
+ repo.topics.forEach(topic => {
212
+ // Select the <td> element
213
+ const tdElement = screen.getByText((content, element) => {
214
+ // Ensure the element is a <td>
215
+ return element.tagName.toLowerCase() === 'td' && content.includes(topic);
78
216
  });
217
+ // Check if the <td> contains the expected text
218
+ expect(tdElement).toBeInTheDocument();
219
+ expect(tdElement).toHaveTextContent(topic);
220
+ });
79
221
  });
222
+
223
+ // Verify that the component does not show the loading spinner
224
+ expect(container.querySelector('.LoadingSpinner')).not.toBeInTheDocument();
225
+ });
80
226
  });
81
227
 
82
- describe('NotFound', () => {
83
- test('renders AppNavbar component', () => {
84
- render(<NotFound />);
85
- expect(screen.getByRole('banner')).toBeInTheDocument();
228
+ describe('RepoIssues', () => {
229
+ beforeEach(() => {
230
+ jest.spyOn(global, 'fetch').mockResolvedValue({
231
+ json: jest.fn().mockResolvedValue(mockRepoIssues)
86
232
  });
233
+ });
234
+
235
+ afterEach(() => {
236
+ jest.restoreAllMocks();
237
+ });
238
+
239
+ test('renders AppNavbar component', () => {
240
+ render(<App />);
241
+ expect(screen.getByRole('banner')).toBeInTheDocument();
242
+ });
87
243
 
88
- test('displays 404 error message', () => {
89
- render(<NotFound />);
90
- expect(screen.getByText('404 - Page Not Found')).toBeInTheDocument();
244
+ test('renders loading state initially', () => {
245
+ render(<RepoIssues match={{ params: { id: 'User', repo: 'ironoc-test' } }} />);
246
+ expect(screen.getByText('Loading...')).toBeInTheDocument();
247
+ });
248
+
249
+ test('displays repo issues after fetching data', async () => {
250
+ const history = createMemoryHistory();
251
+ const { container } = render(
252
+ <Router history={history}>
253
+ <RepoIssues match={{ params: { id: 'conors-id', repo: 'ironoc-testing' } }} />
254
+ </Router>
255
+ );
256
+
257
+ // Wait for the component to finish loading
258
+ await waitFor(() => {
259
+ expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
260
+ });
261
+
262
+ expect(screen.queryByText('conors-id')).toBeInTheDocument();
263
+ expect(screen.queryByText('ironoc-testing')).toBeInTheDocument();
264
+
265
+ // Check that repo issues are displayed
266
+ mockRepoIssues.forEach(issue => {
267
+ expect(screen.getByText(issue.title)).toBeInTheDocument();
268
+ expect(screen.getByText(issue.body)).toBeInTheDocument();
269
+ expect(screen.getByText(issue.number)).toBeInTheDocument();
270
+ });
271
+
272
+ // Verify that the component does not show the loading spinner
273
+ expect(container.querySelector('.LoadingSpinner')).not.toBeInTheDocument();
274
+ });
275
+ });
276
+
277
+ describe('LoadingSpinner', () => {
278
+ test('renders LoadingSpinner component correctly', () => {
279
+ // Render the component
280
+ const { getByRole, getByText } = render(<LoadingSpinner />);
281
+
282
+ // Check if the button is present and disabled
283
+ const button = getByRole('button');
284
+ expect(button).toBeInTheDocument();
285
+ expect(button).toBeDisabled();
286
+
287
+ // Check if the spinner is present
288
+ const spinner = getByRole('button');
289
+ expect(spinner).toBeInTheDocument();
290
+
291
+ // Check if the button contains the text "Loading..."
292
+ const loadingText = getByText('Loading...');
293
+ expect(loadingText).toBeInTheDocument();
294
+ });
295
+ });
296
+
297
+ describe('AppNavBar', () => {
298
+ test('renders AppNavBar component correctly', () => {
299
+ // Render the component
300
+ render(<AppNavBar />);
301
+
302
+ // Check if the Navbar is present
303
+ const navbar = screen.getByRole('navigation');
304
+ expect(navbar).toBeInTheDocument();
305
+
306
+ // Check if the logo image is present
307
+ const logo = screen.getByAltText('');
308
+ expect(logo).toBeInTheDocument();
309
+ expect(logo).toHaveAttribute('src', 'robot-logo.png');
310
+
311
+ // Check if the "Brews" dropdown is present
312
+ const brewsDropdown = screen.getByText('Brews');
313
+ expect(brewsDropdown).toBeInTheDocument();
314
+
315
+ // Check if the "Portfolio" dropdown is present
316
+ const portfolioDropdown = screen.getByText('Portfolio');
317
+ expect(portfolioDropdown).toBeInTheDocument();
318
+
319
+ // Check if the "About" dropdown is present
320
+ const aboutDropdown = screen.getByText('About');
321
+ expect(aboutDropdown).toBeInTheDocument();
322
+
323
+ // Check if the "GitHub PM" dropdown is present
324
+ const githubPMDropdown = screen.getByText('GitHub PM');
325
+ expect(githubPMDropdown).toBeInTheDocument();
326
+
327
+ // Check if the "GitHub API" dropdown is present
328
+ const githubAPIDropdown = screen.getByText('GitHub API');
329
+ expect(githubAPIDropdown).toBeInTheDocument();
330
+
331
+ // Check if the "GitHub Projects" dropdown is present
332
+ const githubProjectsDropdown = screen.getByText('GitHub Projects');
333
+ expect(githubProjectsDropdown).toBeInTheDocument();
334
+
335
+ // Check if the "GitHub Projects" dropdown is present
336
+ const donateDropdown = screen.getByText('Charity Options');
337
+ expect(donateDropdown).toBeInTheDocument();
338
+
339
+ // Check if the "Home" link is present
340
+ const homeLink = screen.getByText('Home');
341
+ expect(homeLink).toBeInTheDocument();
342
+ expect(homeLink.closest('a')).toHaveAttribute('href', '/');
343
+ });
344
+ });
345
+
346
+ describe('App Component Routing', () => {
347
+ test("sends the user to about", () => {
348
+ const history = createMemoryHistory({ initialEntries: ["/about"] });
349
+ render(
350
+ <Router history={history}>
351
+ <App />
352
+ </Router>
353
+ );
354
+ expect(history.location.pathname).toBe("/about");
355
+ });
356
+ });
357
+
358
+ describe('Donate Component', () => {
359
+ test('renders without crashing', () => {
360
+ render(<Donate items={donateItems} />);
361
+ expect(screen.getByText('The Jack and Jill Children’s Foundation')).toBeInTheDocument();
362
+ expect(screen.getByText('Vision Ireland, the new name for NCBI')).toBeInTheDocument();
91
363
  });
92
364
 
93
- test('displays the apology message', () => {
94
- render(<NotFound />);
95
- expect(screen.getByText('Sorry, the page you are looking for could not be found.')).toBeInTheDocument();
365
+ test('renders all carousel items', () => {
366
+ render(<Donate items={donateItems} />);
367
+ donateItems.forEach(item => {
368
+ expect(screen.getByText(item.name)).toBeInTheDocument();
369
+ expect(screen.getByText(new RegExp(`Founded in ${item.founded}`))).toBeInTheDocument();
370
+ expect(screen.getByText(item.phone)).toBeInTheDocument();
371
+ expect(screen.getByText(new RegExp(item.link))).toBeInTheDocument();
372
+ });
373
+ });
374
+
375
+ test('renders images with correct alt text', () => {
376
+ render(<Donate items={donateItems} />);
377
+ donateItems.forEach(item => {
378
+ expect(screen.getByAltText(item.alt)).toBeInTheDocument();
379
+ });
96
380
  });
97
381
  });