@conorheffron/ironoc-frontend 9.1.5 → 9.1.6
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 +1 -1
- package/src/App.test.js +12 -0
- package/src/AppNavbar.js +10 -5
- package/src/components/Donate.js +53 -0
- package/src/components/RepoIssues.js +7 -2
- package/src/components/__tests__/Donate.test.js +29 -1
- package/src/components/__tests__/RepoIssues.test.js +37 -1
- package/src/utils/activityTracker.js +24 -0
package/package.json
CHANGED
package/src/App.test.js
CHANGED
|
@@ -9,6 +9,11 @@ import AppNavBar from './AppNavbar';
|
|
|
9
9
|
import About from './components/About';
|
|
10
10
|
import Footer from './Footer';
|
|
11
11
|
import { Router, useLocation, MemoryRouter } from 'react-router';
|
|
12
|
+
import { trackClickOut } from './utils/activityTracker';
|
|
13
|
+
|
|
14
|
+
jest.mock('./utils/activityTracker', () => ({
|
|
15
|
+
trackClickOut: jest.fn(),
|
|
16
|
+
}));
|
|
12
17
|
|
|
13
18
|
describe('AppNavBar', () => {
|
|
14
19
|
test('renders AppNavBar component correctly', () => {
|
|
@@ -79,6 +84,13 @@ describe('AppNavBar', () => {
|
|
|
79
84
|
fireEvent.click(toggler);
|
|
80
85
|
await waitFor(() => expect(collapse).not.toHaveClass('show'));
|
|
81
86
|
});
|
|
87
|
+
|
|
88
|
+
test('tracks click-outs for GitHub project links', () => {
|
|
89
|
+
render(<AppNavBar />);
|
|
90
|
+
const githubProjectLink = screen.getByText('iRonoc-DB');
|
|
91
|
+
fireEvent.click(githubProjectLink);
|
|
92
|
+
expect(trackClickOut).toHaveBeenCalledWith('github', 'https://github.com/conorheffron/ironoc-db');
|
|
93
|
+
});
|
|
82
94
|
});
|
|
83
95
|
|
|
84
96
|
describe('Footer Component', () => {
|
package/src/AppNavbar.js
CHANGED
|
@@ -2,6 +2,7 @@ import React, { Component } from 'react';
|
|
|
2
2
|
import "bootstrap/dist/css/bootstrap.min.css";
|
|
3
3
|
import {Navbar, NavbarText, Container, Collapse, NavbarBrand, NavbarToggler, Nav, UncontrolledDropdown, DropdownToggle, DropdownItem, DropdownMenu } from "reactstrap";
|
|
4
4
|
import logo from './img/robot-logo.png';
|
|
5
|
+
import { trackClickOut } from './utils/activityTracker';
|
|
5
6
|
import "@fontsource/montserrat/700.css";
|
|
6
7
|
import "@fontsource/open-sans/400-italic.css";
|
|
7
8
|
|
|
@@ -34,6 +35,10 @@ class AppNavBar extends Component {
|
|
|
34
35
|
this.setState({ isOpen: !this.state.isOpen });
|
|
35
36
|
};
|
|
36
37
|
|
|
38
|
+
handleGitHubClick = (target) => {
|
|
39
|
+
trackClickOut('github', target);
|
|
40
|
+
};
|
|
41
|
+
|
|
37
42
|
render() {
|
|
38
43
|
const { color, light, dark, fixed, container, expand, className, isOpen } = this.state;
|
|
39
44
|
|
|
@@ -76,12 +81,12 @@ class AppNavBar extends Component {
|
|
|
76
81
|
<UncontrolledDropdown inNavbar nav>
|
|
77
82
|
<DropdownToggle caret nav>GitHub Projects</DropdownToggle>
|
|
78
83
|
<DropdownMenu end>
|
|
79
|
-
<DropdownItem target="_blank" href="https://github.com/conorheffron/ironoc">iRonoc</DropdownItem>
|
|
84
|
+
<DropdownItem target="_blank" href="https://github.com/conorheffron/ironoc" onClick={() => this.handleGitHubClick('https://github.com/conorheffron/ironoc')}>iRonoc</DropdownItem>
|
|
80
85
|
<DropdownItem divider />
|
|
81
|
-
<DropdownItem target="_blank" href="https://github.com/conorheffron/ironoc-db">iRonoc-DB</DropdownItem>
|
|
82
|
-
<DropdownItem target="_blank" href="https://github.com/conorheffron/booking-sys">Booking System Sample</DropdownItem>
|
|
83
|
-
<DropdownItem target="_blank" href="https://github.com/conorheffron/nba-stats">NBA Stats Analysis</DropdownItem>
|
|
84
|
-
<DropdownItem target="_blank" href="https://github.com/conorheffron/ironoc-pytest">PyTest GitHub Client Package</DropdownItem>
|
|
86
|
+
<DropdownItem target="_blank" href="https://github.com/conorheffron/ironoc-db" onClick={() => this.handleGitHubClick('https://github.com/conorheffron/ironoc-db')}>iRonoc-DB</DropdownItem>
|
|
87
|
+
<DropdownItem target="_blank" href="https://github.com/conorheffron/booking-sys" onClick={() => this.handleGitHubClick('https://github.com/conorheffron/booking-sys')}>Booking System Sample</DropdownItem>
|
|
88
|
+
<DropdownItem target="_blank" href="https://github.com/conorheffron/nba-stats" onClick={() => this.handleGitHubClick('https://github.com/conorheffron/nba-stats')}>NBA Stats Analysis</DropdownItem>
|
|
89
|
+
<DropdownItem target="_blank" href="https://github.com/conorheffron/ironoc-pytest" onClick={() => this.handleGitHubClick('https://github.com/conorheffron/ironoc-pytest')}>PyTest GitHub Client Package</DropdownItem>
|
|
85
90
|
</DropdownMenu>
|
|
86
91
|
</UncontrolledDropdown>
|
|
87
92
|
<UncontrolledDropdown inNavbar nav>
|
package/src/components/Donate.js
CHANGED
|
@@ -8,6 +8,7 @@ import AppNavbar from '../AppNavbar';
|
|
|
8
8
|
import red from '../img/red-bg.png';
|
|
9
9
|
import { Container } from 'reactstrap';
|
|
10
10
|
import LoadingSpinner from '.././LoadingSpinner';
|
|
11
|
+
import { trackClickOut } from '../utils/activityTracker';
|
|
11
12
|
|
|
12
13
|
// Define the GraphQL query to fetch donate items
|
|
13
14
|
export const GET_DONATE_ITEMS = gql`
|
|
@@ -78,6 +79,58 @@ class Donate extends Component {
|
|
|
78
79
|
);
|
|
79
80
|
}
|
|
80
81
|
|
|
82
|
+
return (
|
|
83
|
+
<div className="App">
|
|
84
|
+
<AppNavbar />
|
|
85
|
+
<Container>
|
|
86
|
+
<Carousel className="App-header">
|
|
87
|
+
{donateItems.map((item, index) => (
|
|
88
|
+
<Carousel.Item key={index} interval={500}>
|
|
89
|
+
<img className="d-block w-100" src={red} alt={item.alt} />
|
|
90
|
+
|
|
91
|
+
<Carousel.Caption>
|
|
92
|
+
<h1 className="mb-3">
|
|
93
|
+
<span style={{ textDecoration: 'underline' }}>{item.name}</span>
|
|
94
|
+
</h1>
|
|
95
|
+
|
|
96
|
+
<p>
|
|
97
|
+
<b>Contact & Help by Phone: </b>
|
|
98
|
+
<span dangerouslySetInnerHTML={{ __html: item.phone }} />
|
|
99
|
+
</p>
|
|
100
|
+
|
|
101
|
+
<p>
|
|
102
|
+
<b>Home page: </b>
|
|
103
|
+
<a
|
|
104
|
+
href={item.link}
|
|
105
|
+
target="_blank"
|
|
106
|
+
rel="noreferrer"
|
|
107
|
+
onClick={() => trackClickOut('charity', item.link)}
|
|
108
|
+
>
|
|
109
|
+
{item.link}
|
|
110
|
+
</a>
|
|
111
|
+
</p>
|
|
112
|
+
|
|
113
|
+
<p className="overview-text">
|
|
114
|
+
<b>Overview:</b> Founded in {item.founded}, {item.overview}
|
|
115
|
+
</p>
|
|
116
|
+
|
|
117
|
+
<p>
|
|
118
|
+
<a
|
|
119
|
+
href={item.donate}
|
|
120
|
+
target="_blank"
|
|
121
|
+
rel="noreferrer"
|
|
122
|
+
onClick={() => trackClickOut('charity', item.donate)}
|
|
123
|
+
>
|
|
124
|
+
Donate here
|
|
125
|
+
</a>
|
|
126
|
+
</p>
|
|
127
|
+
</Carousel.Caption>
|
|
128
|
+
</Carousel.Item>
|
|
129
|
+
))}
|
|
130
|
+
</Carousel>
|
|
131
|
+
</Container>
|
|
132
|
+
</div>
|
|
133
|
+
);
|
|
81
134
|
return (
|
|
82
135
|
<div className="App">
|
|
83
136
|
<AppNavbar />
|
|
@@ -6,7 +6,6 @@ import { useParams, useNavigate } from 'react-router';
|
|
|
6
6
|
import {
|
|
7
7
|
MaterialReactTable,
|
|
8
8
|
useMaterialReactTable,
|
|
9
|
-
type MRT_ColumnDef,
|
|
10
9
|
} from 'material-react-table';
|
|
11
10
|
import { darken, lighten, useTheme } from '@mui/material';
|
|
12
11
|
|
|
@@ -126,7 +125,13 @@ const RepoIssues = () => {
|
|
|
126
125
|
data: repoIssueList,
|
|
127
126
|
enableFacetedValues: true,
|
|
128
127
|
enableStickyHeader: true,
|
|
129
|
-
initialState: {
|
|
128
|
+
initialState: {
|
|
129
|
+
showColumnFilters: true,
|
|
130
|
+
columnVisibility: {
|
|
131
|
+
state: false,
|
|
132
|
+
body: false,
|
|
133
|
+
},
|
|
134
|
+
},
|
|
130
135
|
muiTablePaperProps: {
|
|
131
136
|
elevation: 0,
|
|
132
137
|
sx: {
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { render, screen, waitFor } from '@testing-library/react';
|
|
2
|
+
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
|
|
3
3
|
import { MockedProvider } from '@apollo/client/testing';
|
|
4
4
|
import Donate, { GET_DONATE_ITEMS } from '../Donate';
|
|
5
5
|
import '@testing-library/jest-dom';
|
|
6
|
+
import { trackClickOut } from '../../utils/activityTracker';
|
|
7
|
+
|
|
8
|
+
jest.mock('../../utils/activityTracker', () => ({
|
|
9
|
+
trackClickOut: jest.fn(),
|
|
10
|
+
}));
|
|
6
11
|
|
|
7
12
|
// Mock data for testing
|
|
8
13
|
const mockDonateItems = [
|
|
@@ -94,4 +99,27 @@ describe('Donate Component', () => {
|
|
|
94
99
|
});
|
|
95
100
|
});
|
|
96
101
|
});
|
|
102
|
+
|
|
103
|
+
it('tracks charity click-outs for donate and homepage links', async () => {
|
|
104
|
+
render(
|
|
105
|
+
<MockedProvider mocks={mocks}>
|
|
106
|
+
<Donate />
|
|
107
|
+
</MockedProvider>
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
await waitFor(() => {
|
|
111
|
+
expect(screen.getByText('Item 1')).toBeInTheDocument();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const donateLink = document.querySelector('a[href="https://example.com/donate1"]');
|
|
115
|
+
const homepageLink = document.querySelector('a[href="https://example.com/home1"]');
|
|
116
|
+
expect(donateLink).toBeInTheDocument();
|
|
117
|
+
expect(homepageLink).toBeInTheDocument();
|
|
118
|
+
|
|
119
|
+
fireEvent.click(donateLink);
|
|
120
|
+
fireEvent.click(homepageLink);
|
|
121
|
+
|
|
122
|
+
expect(trackClickOut).toHaveBeenCalledWith('charity', 'https://example.com/donate1');
|
|
123
|
+
expect(trackClickOut).toHaveBeenCalledWith('charity', 'https://example.com/home1');
|
|
124
|
+
});
|
|
97
125
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
3
3
|
import RepoIssues from '../RepoIssues';
|
|
4
|
+
import { useMaterialReactTable } from 'material-react-table';
|
|
4
5
|
|
|
5
6
|
// Mock react-router
|
|
6
7
|
jest.mock('react-router', () => ({
|
|
@@ -10,7 +11,11 @@ jest.mock('react-router', () => ({
|
|
|
10
11
|
|
|
11
12
|
// Mock react-bootstrap
|
|
12
13
|
jest.mock('react-bootstrap', () => ({
|
|
13
|
-
Container: ({ children, ...props }) =>
|
|
14
|
+
Container: ({ children, fluid, ...props }) => (
|
|
15
|
+
<div data-testid="container" data-fluid={fluid ? 'true' : undefined} {...props}>
|
|
16
|
+
{children}
|
|
17
|
+
</div>
|
|
18
|
+
),
|
|
14
19
|
InputGroup: ({ children, ...props }) => <div data-testid="input-group" {...props}>{children}</div>,
|
|
15
20
|
Form: {
|
|
16
21
|
Control: ({ ...props }) => <input data-testid="form-control" {...props} />,
|
|
@@ -50,14 +55,23 @@ import { useParams, useNavigate } from 'react-router';
|
|
|
50
55
|
|
|
51
56
|
describe('RepoIssues', () => {
|
|
52
57
|
const mockNavigate = jest.fn();
|
|
58
|
+
const mockIssuesResponse = [];
|
|
53
59
|
|
|
54
60
|
beforeEach(() => {
|
|
55
61
|
jest.clearAllMocks();
|
|
56
62
|
useNavigate.mockReturnValue(mockNavigate);
|
|
63
|
+
global.fetch = jest.fn(() =>
|
|
64
|
+
Promise.resolve({
|
|
65
|
+
json: () => Promise.resolve(mockIssuesResponse),
|
|
66
|
+
})
|
|
67
|
+
);
|
|
68
|
+
window.fetch = global.fetch;
|
|
57
69
|
});
|
|
58
70
|
|
|
59
71
|
it('shows loading spinner and navbar initially', () => {
|
|
60
72
|
useParams.mockReturnValue({ id: 'user', repo: 'repo' });
|
|
73
|
+
global.fetch = jest.fn(() => new Promise(() => {}));
|
|
74
|
+
window.fetch = global.fetch;
|
|
61
75
|
render(<RepoIssues />);
|
|
62
76
|
expect(screen.getByTestId('navbar')).toBeInTheDocument();
|
|
63
77
|
expect(screen.getByTestId('spinner')).toBeInTheDocument();
|
|
@@ -78,6 +92,7 @@ describe('RepoIssues', () => {
|
|
|
78
92
|
]),
|
|
79
93
|
})
|
|
80
94
|
);
|
|
95
|
+
window.fetch = global.fetch;
|
|
81
96
|
render(<RepoIssues />);
|
|
82
97
|
await waitFor(() =>
|
|
83
98
|
expect(screen.queryByTestId('spinner')).not.toBeInTheDocument()
|
|
@@ -85,9 +100,30 @@ describe('RepoIssues', () => {
|
|
|
85
100
|
expect(screen.getByTestId('mrt-table')).toBeInTheDocument();
|
|
86
101
|
});
|
|
87
102
|
|
|
103
|
+
it('hides state and description columns by default', async () => {
|
|
104
|
+
useParams.mockReturnValue({ id: 'user', repo: 'repo' });
|
|
105
|
+
render(<RepoIssues />);
|
|
106
|
+
await waitFor(() =>
|
|
107
|
+
expect(screen.queryByTestId('spinner')).not.toBeInTheDocument()
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
expect(useMaterialReactTable).toHaveBeenCalledWith(
|
|
111
|
+
expect.objectContaining({
|
|
112
|
+
initialState: expect.objectContaining({
|
|
113
|
+
showColumnFilters: true,
|
|
114
|
+
columnVisibility: {
|
|
115
|
+
state: false,
|
|
116
|
+
body: false,
|
|
117
|
+
},
|
|
118
|
+
}),
|
|
119
|
+
})
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
|
|
88
123
|
it('navigates on form submit', async () => {
|
|
89
124
|
useParams.mockReturnValue({ id: 'user', repo: 'repo' });
|
|
90
125
|
global.fetch = jest.fn(() => Promise.resolve({ json: () => Promise.resolve([]) }));
|
|
126
|
+
window.fetch = global.fetch;
|
|
91
127
|
render(<RepoIssues />);
|
|
92
128
|
await waitFor(() => expect(screen.queryByTestId('spinner')).not.toBeInTheDocument());
|
|
93
129
|
const input = screen.getByTestId('form-control');
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export const trackClickOut = (category, target) => {
|
|
2
|
+
if (!category || !target) {
|
|
3
|
+
return;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const payload = JSON.stringify({ category, target });
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
if (typeof navigator !== 'undefined' && navigator.sendBeacon) {
|
|
10
|
+
const blob = new Blob([payload], { type: 'application/json' });
|
|
11
|
+
navigator.sendBeacon('/api/activity/click-out', blob);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
fetch('/api/activity/click-out', {
|
|
16
|
+
method: 'POST',
|
|
17
|
+
headers: { 'Content-Type': 'application/json' },
|
|
18
|
+
body: payload,
|
|
19
|
+
keepalive: true
|
|
20
|
+
}).catch(() => null);
|
|
21
|
+
} catch {
|
|
22
|
+
// no-op to avoid blocking navigation
|
|
23
|
+
}
|
|
24
|
+
};
|