Skip to content

Commit

Permalink
PD-558: Add onElementClick prop (#297)
Browse files Browse the repository at this point in the history
* Expose onElmeentClick prop

* tests

* wip

* refactor and pr feedback

* add university glyph

* fix spacing for version link

* fixed ignore
  • Loading branch information
bruugey authored Mar 5, 2020
1 parent e9dc399 commit 0f7d8a3
Show file tree
Hide file tree
Showing 11 changed files with 350 additions and 217 deletions.
5 changes: 5 additions & 0 deletions .changeset/sharp-cougars-swim.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@leafygreen-ui/mongo-nav': minor
---

Add onElementClick prop
47 changes: 24 additions & 23 deletions packages/mongo-nav/README.md

Large diffs are not rendered by default.

86 changes: 85 additions & 1 deletion packages/mongo-nav/src/MongoNav.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import React from 'react';
import { render, cleanup, act } from '@testing-library/react';
import { render, cleanup, act, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import { nullableElement, Queries } from 'packages/lib/src/testHelpers';
import { dataFixtures } from './data';
import MongoNav from './MongoNav';
import { OnElementClick } from './types';

// types
interface ExpectedElements {
Expand Down Expand Up @@ -31,6 +32,12 @@ describe('packages/mongo-nav', () => {
'project-select-active-project',
);
expectedElements.userMenu = queryByTestId('user-menu-trigger');
expectedElements.userMenuLogout = queryByTestId('user-menuitem-logout');
expectedElements.onPremUserMenu = queryByTestId('om-user-menu-trigger');
expectedElements.onPremLogout = queryByTestId('om-user-menuitem-sign-out');
expectedElements.atlas = queryByTestId('project-nav-atlas');
expectedElements.realm = queryByTestId('project-nav-realm');
expectedElements.charts = queryByTestId('project-nav-charts');
};

let onOrganizationChange: jest.Mock;
Expand Down Expand Up @@ -326,4 +333,81 @@ describe('packages/mongo-nav', () => {
).toBe(false);
});
});

describe('when onElementClick is set', () => {
const onElementClick = jest.fn();

beforeEach(() =>
renderComponent({
mode: 'dev',
onElementClick,
}),
);

test('when Atlas is clicked', () => {
const atlas = (expectedElements.atlas as HTMLElement).firstChild;
fireEvent.click(atlas as HTMLElement);
expect(onElementClick).toHaveBeenCalled();
expect(onElementClick).toHaveBeenCalledWith(
OnElementClick.Cloud,
expect.anything(),
);
});

test('when Realm is clicked', () => {
const realm = (expectedElements.realm as HTMLElement).firstChild;
fireEvent.click(realm as HTMLElement);
expect(onElementClick).toHaveBeenCalled();
expect(onElementClick).toHaveBeenCalledWith(
OnElementClick.Realm,
expect.anything(),
);
});

test('when Charts is clicked', () => {
const charts = (expectedElements.charts as HTMLElement).firstChild;
fireEvent.click(charts as HTMLElement);
expect(onElementClick).toHaveBeenCalled();
expect(onElementClick).toHaveBeenCalledWith(
OnElementClick.Charts,
expect.anything(),
);
});

test('when logout is clicked', () => {
const userMenu = expectedElements.userMenu;
fireEvent.click(userMenu as HTMLElement);
setExpectedElements();
const logout = expectedElements.userMenuLogout;
fireEvent.click(logout as HTMLElement);
expect(onElementClick).toHaveBeenCalledWith(
OnElementClick.Logout,
expect.anything(),
);
});
});

describe('when onPrem and onElementClick is set', () => {
const onElementClick = jest.fn();

beforeEach(() =>
renderComponent({
mode: 'dev',
onElementClick,
onPrem: { enabled: true },
}),
);

test('when logout is clicked', () => {
const onPremUserMenu = expectedElements.onPremUserMenu;
fireEvent.click(onPremUserMenu as HTMLElement);
setExpectedElements();
const logout = expectedElements.onPremLogout;
fireEvent.click(logout as HTMLElement);
expect(onElementClick).toHaveBeenCalledWith(
OnElementClick.Logout,
expect.anything(),
);
});
});
});
75 changes: 40 additions & 35 deletions packages/mongo-nav/src/MongoNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ import {
DataInterface,
ErrorCode,
OnPremInterface,
OnElementClick,
PostBodyInterface,
} from './types';
import { dataFixtures, hostDefaults } from './data';
import defaultsDeep from 'lodash/defaultsDeep';
import OnElementClickProvider from './OnElementClickProvider';

const ErrorCodeMap = {
401: ErrorCode.NO_AUTHORIZATION,
Expand Down Expand Up @@ -96,11 +98,6 @@ interface MongoNavInterface {
*/
onSuccess?: (response: DataInterface) => void;

/**
* Callback executed when user logs out
*/
onLogout?: React.MouseEventHandler;

/**
* onPrem config object with three keys: enabled, version and mfa
*/
Expand All @@ -123,6 +120,12 @@ interface MongoNavInterface {
*/
className?: string;

/**
* Click EventHandler that receives a `type` as its first argument and the associated `MouseEvent` as its second
* This prop provides a hook into product link and logout link clicks and allows consuming applications to handle routing internally
*/
onElementClick?: (type: OnElementClick, event: React.MouseEvent) => void;

/**
* Determines whether or not the component will fetch data from cloud
*/
Expand Down Expand Up @@ -158,7 +161,8 @@ interface MongoNavInterface {
* @param props.onSuccess Callback that receives the response of the fetched data, having been converted from JSON into an object.
* @param props.onError Function that is passed an error code as a string, so that consuming application can handle fetch failures.
* @param props.onPrem onPrem config object with three keys: enabled, version and mfa
* @param props.onLogout Callback executed when user logs out
* @param props.className Applies a className to the root element
* @param props.onElementClick Click EventHandler that receives a `type` as its first argument and the associated `MouseEvent` as its second. This prop provides a hook into product link and logout link clicks and allows consuming applications to handle routing internally.
* @param props.activeOrgId ID for active organization, will cause a POST request to cloud to update current active organization.
* @param props.activeProjectId ID for active project, will cause a POST request to cloud to update current active project.
* @param props.className Applies a className to the root element
Expand All @@ -179,7 +183,7 @@ function MongoNav({
constructProjectURL: constructProjectURLProp,
onError = () => {},
onSuccess = () => {},
onLogout = () => {},
onElementClick = (_type: OnElementClick, _event: React.MouseEvent) => {}, // eslint-disable-line @typescript-eslint/no-unused-vars
onPrem = { mfa: false, enabled: false, version: '' },
activeOrgId,
activeProjectId,
Expand Down Expand Up @@ -309,37 +313,38 @@ function MongoNav({
const constructProjectURL = constructProjectURLProp ?? defaultProjectURL;

return (
<section {...rest} className={cx(navContainerStyle, className)}>
<OrgNav
account={data?.account}
activeProduct={activeProduct}
current={data?.currentOrganization}
data={data?.organizations}
constructOrganizationURL={constructOrganizationURL}
urls={urls}
activeNav={activeNav}
onOrganizationChange={onOrganizationChange}
admin={admin}
hosts={hosts}
currentProjectName={data?.currentProject?.projectName}
onLogout={onLogout}
onPremEnabled={onPrem.enabled}
onPremVersion={onPrem.version}
onPremMFA={onPrem.mfa}
/>
{showProjNav && !onPrem.enabled && (
<ProjectNav
<OnElementClickProvider onElementClick={onElementClick}>
<section {...rest} className={cx(navContainerStyle, className)}>
<OrgNav
account={data?.account}
activeProduct={activeProduct}
current={data?.currentProject}
data={data?.projects}
constructProjectURL={constructProjectURL}
current={data?.currentOrganization}
data={data?.organizations}
constructOrganizationURL={constructOrganizationURL}
urls={urls}
alerts={data?.currentProject?.alertsOpen}
onProjectChange={onProjectChange}
activeNav={activeNav}
onOrganizationChange={onOrganizationChange}
admin={admin}
hosts={hosts}
currentProjectName={data?.currentProject?.projectName}
onPremEnabled={onPrem.enabled}
onPremVersion={onPrem.version}
onPremMFA={onPrem.mfa}
/>
)}
</section>
{showProjNav && !onPrem.enabled && (
<ProjectNav
activeProduct={activeProduct}
current={data?.currentProject}
data={data?.projects}
constructProjectURL={constructProjectURL}
urls={urls}
alerts={data?.currentProject?.alertsOpen}
onProjectChange={onProjectChange}
hosts={hosts}
/>
)}
</section>
</OnElementClickProvider>
);
}

Expand All @@ -364,7 +369,7 @@ MongoNav.propTypes = {
enabled: PropTypes.bool,
version: PropTypes.string,
}),
onLogout: PropTypes.func,
onElementClick: PropTypes.func,
activeOrgId: PropTypes.string,
activeProjectId: PropTypes.string,
};
Expand Down
29 changes: 29 additions & 0 deletions packages/mongo-nav/src/OnElementClickProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React, { createContext, useContext } from 'react';
import { OnElementClick } from './types';

type OnElementClickType = (
type: OnElementClick,
event: React.MouseEvent,
) => void;

interface OnElementClickProviderProps {
children: React.ReactNode;
onElementClick: OnElementClickType;
}

const OnElementClickContext = createContext<OnElementClickType>(() => {});

export function useOnElementClick() {
return useContext(OnElementClickContext);
}

export default function OnElementClickProvider({
children,
onElementClick,
}: OnElementClickProviderProps) {
return (
<OnElementClickContext.Provider value={onElementClick}>
{children}
</OnElementClickContext.Provider>
);
}
14 changes: 10 additions & 4 deletions packages/mongo-nav/src/helpers/OnPremUserMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React from 'react';
import { Menu, MenuItem } from '@leafygreen-ui/menu';
import { css } from '@leafygreen-ui/emotion';
import { URLSInterface } from '../types';
import { URLSInterface, OnElementClick } from '../types';
import { UserMenuTrigger } from '../user-menu/index';
import { useOnElementClick } from '../OnElementClickProvider';

const onPremMenuWrapper = css`
display: inline-block;
Expand All @@ -15,7 +16,6 @@ interface OnPremUserMenuProps {
open: boolean;
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
urls: Required<URLSInterface>;
onLogout: React.MouseEventHandler;
mfa: boolean;
}

Expand All @@ -24,9 +24,10 @@ export default function OnPremUserMenu({
open,
setOpen,
urls,
onLogout,
mfa,
}: OnPremUserMenuProps) {
const onElementClick = useOnElementClick();

return (
<div className={onPremMenuWrapper}>
<UserMenuTrigger
Expand Down Expand Up @@ -78,7 +79,12 @@ export default function OnPremUserMenu({
Feature Request
</MenuItem>

<MenuItem onClick={onLogout} data-testid="om-user-menuitem-sign-out">
<MenuItem
onClick={(event: React.MouseEvent) =>
onElementClick(OnElementClick.Logout, event)
}
data-testid="om-user-menuitem-sign-out"
>
Sign Out
</MenuItem>
</Menu>
Expand Down
22 changes: 1 addition & 21 deletions packages/mongo-nav/src/org-nav/OrgNav.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { MouseEventHandler } from 'react';
import React from 'react';
import '@testing-library/jest-dom/extend-expect';
import { render, cleanup, fireEvent } from '@testing-library/react';
import { nullableElement, Queries } from 'packages/lib/src/testHelpers';
Expand Down Expand Up @@ -87,7 +87,6 @@ describe('packages/mongo-nav/src/org-nav', () => {
urls={urlDefaults}
admin={false}
hosts={hostDefaults}
onLogout={jest.fn()}
{...props}
/>,
),
Expand Down Expand Up @@ -157,21 +156,6 @@ describe('packages/mongo-nav/src/org-nav', () => {
});
};

const testForLogoutCallback = (onLogout: MouseEventHandler) => {
it('fires onLogout callback when OnPremUserMenu is rendered and the prop is set', () => {
const onPremUserMenu = expectedElements['onPremUserMenu'];
fireEvent.click(onPremUserMenu as Element);
setExpectedElements();
const onPremUserMenuSignOut = expectedElements['onPremUserMenuSignOut'];

fireEvent.click(onPremUserMenu as Element);
setExpectedElements();
expect(onPremUserMenuSignOut).toBeInTheDocument();
fireEvent.click(onPremUserMenuSignOut as Element);
expect(onLogout).toHaveBeenCalled();
});
};

const testForNavLink = (linkName: string, isVisible = true) => {
it(`${isVisible ? 'displays' : 'does not display'} the ${startCase(
linkName,
Expand Down Expand Up @@ -238,21 +222,17 @@ describe('packages/mongo-nav/src/org-nav', () => {
});

describe('when rendered onPrem', () => {
const onLogout = jest.fn();

beforeEach(() =>
renderComponent({
onPremEnabled: true,
onPremVersion: '4.4.0',
onLogout: onLogout,
}),
);

testForPaymentStatus(false);
testForVersion(true);
testForUserMenu(false);
testForMFA(false);
testForLogoutCallback(onLogout);

Object.keys(linkNamesToUrls).forEach(linkName =>
testForNavLink(linkName, ['billing', 'admin'].indexOf(linkName) === -1),
Expand Down
Loading

0 comments on commit 0f7d8a3

Please sign in to comment.