Skip to content

Commit

Permalink
[EASI-3920] - Notification menu bar (#978)
Browse files Browse the repository at this point in the history
* Added notification naviagation bar item, unit tests

* Altered color of notification icon

* Added feature flag

* Added flag value to test
  • Loading branch information
patrickseguraoddball authored Feb 29, 2024
1 parent 4101f69 commit 630b0bb
Show file tree
Hide file tree
Showing 8 changed files with 258 additions and 24 deletions.
39 changes: 33 additions & 6 deletions src/components/Header/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,30 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import { MemoryRouter } from 'react-router-dom';
import { MockedProvider } from '@apollo/client/testing';
import { render, waitFor } from '@testing-library/react';
import { mount, shallow } from 'enzyme';
import { GetPollNotificationsDocument } from 'gql/gen/graphql';

import { Header } from './index';

const notificationsMock = [
{
request: {
query: GetPollNotificationsDocument
},
result: {
data: {
currentUser: {
notifications: {
numUnreadNotifications: 1
}
}
}
}
}
];

vi.mock('@okta/okta-react', () => ({
useOktaAuth: () => {
return {
Expand All @@ -26,7 +45,9 @@ describe('The Header component', () => {
it('renders without crashing', () => {
shallow(
<MemoryRouter initialEntries={['/']}>
<Header />
<MockedProvider mocks={notificationsMock} addTypename={false}>
<Header />
</MockedProvider>
</MemoryRouter>
);
});
Expand All @@ -35,7 +56,9 @@ describe('The Header component', () => {
it('displays a login button', async () => {
const { getByTestId } = render(
<MemoryRouter initialEntries={['/pre-decisional-notice']}>
<Header />
<MockedProvider mocks={notificationsMock} addTypename={false}>
<Header />
</MockedProvider>
</MemoryRouter>
);

Expand All @@ -49,7 +72,9 @@ describe('The Header component', () => {
await act(async () => {
component = mount(
<MemoryRouter>
<Header />
<MockedProvider mocks={notificationsMock} addTypename={false}>
<Header />
</MockedProvider>
</MemoryRouter>
);
});
Expand All @@ -63,9 +88,11 @@ describe('The Header component', () => {

test.skip('displays children', () => {
const component = shallow(
<Header>
<div className="test-class-name" />
</Header>
<MockedProvider mocks={notificationsMock} addTypename={false}>
<Header>
<div className="test-class-name" />
</Header>
</MockedProvider>
);
expect(component.find('.test-class-name').exists()).toBe(true);
});
Expand Down
37 changes: 36 additions & 1 deletion src/components/NavigationBar/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
}

.usa-nav__primary-item {


.mint-nav {
display: flex;

Expand All @@ -18,7 +20,13 @@

&__link {
color: #565C65;
padding: 1em;
padding: 1em;

}

&__notification {
color: #565C65;
padding: 1em;
}

&__current {
Expand Down Expand Up @@ -48,6 +56,33 @@
}
}

.navigation-link {
.usa-nav__primary-item {
&:last-child {
margin-left: auto;
}

}
}

.align-right {
margin-left: auto;
}

.border-top-light {
border-top: 1px solid #DFE1E2;
}

.notification-container {
margin-top: -14px !important;
}

.notification-active {
height: .45rem;
width: .45rem;
margin-left: 1.2rem;
margin-bottom: .75rem;
border-radius: 50%;
display: inline-block;
background-color: red;
}
58 changes: 43 additions & 15 deletions src/components/NavigationBar/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,29 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { MemoryRouter } from 'react-router-dom';
import { render } from '@testing-library/react';
import { MockedProvider } from '@apollo/client/testing';
import { render, waitFor } from '@testing-library/react';
import { GetPollNotificationsDocument } from 'gql/gen/graphql';

import NavigationBar, { navLinks } from './index';

const notificationsMock = [
{
request: {
query: GetPollNotificationsDocument
},
result: {
data: {
currentUser: {
notifications: {
numUnreadNotifications: 1
}
}
}
}
}
];

vi.mock('react-i18next', () => ({
// this mock makes sure any components using the translate hook can use it without a warning being shown
useTranslation: () => {
Expand All @@ -22,7 +41,8 @@ vi.mock('launchdarkly-react-client-sdk', () => ({
useFlags: () => {
return {
systemProfile: true,
help: true
help: true,
notificationsEnabled: true
};
}
}));
Expand All @@ -31,27 +51,31 @@ describe('The NavigationBar component', () => {
it('renders without errors', async () => {
const { getByTestId } = render(
<MemoryRouter initialEntries={['/']}>
<NavigationBar
mobile
toggle={() => !null}
signout={() => null}
userName="A11Y"
/>
<MockedProvider mocks={notificationsMock} addTypename={false}>
<NavigationBar
mobile
toggle={() => !null}
signout={() => null}
userName="A11Y"
/>
</MockedProvider>
</MemoryRouter>
);

expect(getByTestId('navigation-bar')).toBeInTheDocument();
});

it('displays every navigation element', async () => {
const { getByText } = render(
const { getByText, getByTestId } = render(
<MemoryRouter initialEntries={['/system/making-a-request']}>
<NavigationBar
mobile
toggle={() => !null}
signout={() => null}
userName="A11Y"
/>
<MockedProvider mocks={notificationsMock} addTypename={false}>
<NavigationBar
mobile
toggle={() => !null}
signout={() => null}
userName="A11Y"
/>
</MockedProvider>
</MemoryRouter>
);

Expand All @@ -61,5 +85,9 @@ describe('The NavigationBar component', () => {
const linkTitle = t(`header:${route.label}`);
expect(getByText(linkTitle)).toBeInTheDocument();
});

await waitFor(() => {
expect(getByTestId('has-notifications')).toBeInTheDocument();
});
});
});
65 changes: 63 additions & 2 deletions src/components/NavigationBar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { NavLink } from 'react-router-dom';
import { PrimaryNav } from '@trussworks/react-uswds';
import { Icon, PrimaryNav } from '@trussworks/react-uswds';
import classNames from 'classnames';
import { useGetPollNotificationsQuery } from 'gql/gen/graphql';
import { useFlags } from 'launchdarkly-react-client-sdk';

import './index.scss';

Expand Down Expand Up @@ -35,6 +38,15 @@ const NavigationBar = ({
}: NavigationProps) => {
const { t } = useTranslation();

const flags = useFlags();

const { data } = useGetPollNotificationsQuery({
pollInterval: 5000
});

const hasNotifications = !!data?.currentUser.notifications
.numUnreadNotifications;

const primaryLinks = navLinks().map(route => (
<div className="mint-nav" key={route.label}>
<NavLink
Expand All @@ -54,6 +66,47 @@ const NavigationBar = ({
</div>
));

const notificationLink = (
<div className="mint-nav">
<NavLink
to="/notifications"
activeClassName="usa-current"
className={classNames(
{ 'align-right': !mobile },
'mint-nav__link margin-right-neg-4 display-flex flex-align-center'
)}
onClick={() => toggle(false)}
>
<div className="display-relative width-4">
<div className="position-absolute notification-container">
{hasNotifications ? (
<div data-testid="has-notifications">
<div className="notification-active bg-error position-absolute" />
<Icon.Notifications
className="margin-right-0 text-base-darkest"
size={3}
/>
</div>
) : (
<Icon.NotificationsNone className="margin-right-0" size={3} />
)}
</div>
</div>

<em
className="usa-logo__text mint-nav__label"
aria-label={t(`header:notifications`)}
>
{t(`header:notifications`)}
</em>
</NavLink>
</div>
);

const navItemsWithNotification = flags.notificationsEnabled
? primaryLinks.concat(notificationLink)
: primaryLinks;

const userLinks = (
<div className="mint-nav__signout-container">
<div className="mint-nav__user margin-bottom-1">{userName}</div>
Expand All @@ -75,7 +128,9 @@ const NavigationBar = ({
</div>
);

const navItems = mobile ? primaryLinks.concat(userLinks) : primaryLinks;
const navItems = mobile
? navItemsWithNotification.concat(userLinks)
: navItemsWithNotification;

return (
<nav
Expand All @@ -88,6 +143,12 @@ const NavigationBar = ({
onClick={() => toggle(false)}
mobileExpanded={mobile}
aria-label="Primary navigation"
className={classNames(
{
'navigation-link': flags.notificationsEnabled
},
'width-full'
)}
items={navItems}
/>
</div>
Expand Down
11 changes: 11 additions & 0 deletions src/gql/apolloGQL/Notifications/GetPollNotifications.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { gql } from '@apollo/client';

export default gql(/* GraphQL */ `
query GetPollNotifications {
currentUser {
notifications {
numUnreadNotifications
}
}
}
`);
Loading

0 comments on commit 630b0bb

Please sign in to comment.