({
},
}));
-describe('TopBar', () => {
- const useRouter = jest.spyOn(nextRouter, 'useRouter');
- const mocks = [getTopBarMultipleMock(), ...getNotificationsMocks()];
- beforeEach(() => {
- (
- useRouter as jest.SpyInstance<
- Pick
- >
- ).mockImplementation(() => ({
- query: { accountListId },
- isReady: true,
- }));
- });
+interface TestComponentProps {
+ onSetupTour?: boolean;
+}
+const TestComponent: React.FC = ({ onSetupTour }) => (
+
+
+
+
+
+
+
+
+
+
+
+);
+
+describe('TopBar', () => {
it('default', () => {
- const { getByTestId } = render(
-
-
-
-
-
-
-
-
- ,
- );
+ const { getByTestId, getByText } = render();
expect(getByTestId('TopBar')).toBeInTheDocument();
+ expect(getByText('Dashboard')).toBeInTheDocument();
+ });
+
+ it('hides links during the setup tour', () => {
+ const { queryByText } = render();
+
+ expect(queryByText('Dashboard')).not.toBeInTheDocument();
});
});
diff --git a/src/components/Layouts/Primary/TopBar/TopBar.tsx b/src/components/Layouts/Primary/TopBar/TopBar.tsx
index 40316d84f..f98f63b31 100644
--- a/src/components/Layouts/Primary/TopBar/TopBar.tsx
+++ b/src/components/Layouts/Primary/TopBar/TopBar.tsx
@@ -1,4 +1,3 @@
-import NextLink from 'next/link';
import React, { ReactElement } from 'react';
import MenuIcon from '@mui/icons-material/Menu';
import {
@@ -11,6 +10,8 @@ import {
useScrollTrigger,
} from '@mui/material';
import { styled } from '@mui/material/styles';
+import { useSetupContext } from 'src/components/Setup/SetupProvider';
+import { LogoLink } from '../LogoLink/LogoLink';
import AddMenu from './Items/AddMenu/AddMenu';
import NavMenu from './Items/NavMenu/NavMenu';
import NotificationMenu from './Items/NotificationMenu/NotificationMenu';
@@ -32,6 +33,7 @@ const TopBar = ({
accountListId,
onMobileNavOpen,
}: TopBarProps): ReactElement => {
+ const { onSetupTour } = useSetupContext();
const trigger = useScrollTrigger({
disableHysteresis: true,
threshold: 0,
@@ -54,15 +56,10 @@ const TopBar = ({
)}
-
-
-
+
- {accountListId && (
+ {onSetupTour && }
+ {!onSetupTour && accountListId && (
<>
diff --git a/src/components/Setup/SetupProvider.test.tsx b/src/components/Setup/SetupProvider.test.tsx
index 18fd0d69e..8dd148e9e 100644
--- a/src/components/Setup/SetupProvider.test.tsx
+++ b/src/components/Setup/SetupProvider.test.tsx
@@ -14,11 +14,13 @@ interface TestComponentProps {
}
const ContextTestingComponent = () => {
- const { settingUp } = useSetupContext();
+ const { onSetupTour } = useSetupContext();
return (
- {typeof settingUp === 'undefined' ? 'undefined' : settingUp.toString()}
+ {typeof onSetupTour === 'undefined'
+ ? 'undefined'
+ : onSetupTour.toString()}
);
};
@@ -100,7 +102,7 @@ describe('SetupProvider', () => {
await waitFor(() => expect(push).not.toHaveBeenCalled());
});
- describe('settingUp context', () => {
+ describe('onSetupTour context', () => {
it('is undefined while data is loading', () => {
const { getByTestId } = render(
,
@@ -109,11 +111,12 @@ describe('SetupProvider', () => {
expect(getByTestId('setting-up')).toHaveTextContent('undefined');
});
- it('is true when setup is set', async () => {
+ it('is true when setup is set on a tour page', async () => {
const { getByTestId } = render(
,
);
@@ -122,9 +125,13 @@ describe('SetupProvider', () => {
);
});
- it('is true when setup_position is set', async () => {
+ it('is true when setup_position is set on a tour page', async () => {
const { getByTestId } = render(
- ,
+ ,
);
await waitFor(() =>
@@ -132,6 +139,16 @@ describe('SetupProvider', () => {
);
});
+ it('is false when not on a tour page', async () => {
+ const { getByTestId } = render(
+ ,
+ );
+
+ await waitFor(() =>
+ expect(getByTestId('setting-up')).toHaveTextContent('false'),
+ );
+ });
+
it('is false when setup_position is not set', async () => {
const { getByTestId } = render(
,
diff --git a/src/components/Setup/SetupProvider.tsx b/src/components/Setup/SetupProvider.tsx
index b84974af9..4ec1da508 100644
--- a/src/components/Setup/SetupProvider.tsx
+++ b/src/components/Setup/SetupProvider.tsx
@@ -1,5 +1,6 @@
import { useRouter } from 'next/router';
import React, {
+ PropsWithChildren,
ReactNode,
createContext,
useContext,
@@ -10,29 +11,37 @@ import { UserSetupStageEnum } from 'src/graphql/types.generated';
import { useSetupStageQuery } from './Setup.generated';
export interface SetupContext {
- settingUp?: boolean;
+ /**
+ * `true` if the user is on a setup page and is in the process of completing
+ * the setup tour. `false` if the user isn't on a setup page or isn't setting
+ * up their account. `undefined` if the data needed to determine whether the
+ * user is on the setup tour hasn't loaded yet.
+ */
+ onSetupTour?: boolean;
}
-const SetupContext = createContext(null);
+const SetupContext = createContext({ onSetupTour: undefined });
-export const useSetupContext = (): SetupContext => {
- const setupContext = useContext(SetupContext);
- if (!setupContext) {
- throw new Error(
- 'SetupProvider not found! Make sure that you are calling useSetupContext inside a component wrapped by .',
- );
- }
+export const useSetupContext = (): SetupContext => useContext(SetupContext);
- return setupContext;
-};
+// The list of page pathnames that are part of the setup tour
+const setupPages = new Set([
+ '/setup/start',
+ '/setup/connect',
+ '/setup/account',
+ '/accountLists/[accountListId]/settings/preferences',
+ '/accountLists/[accountListId]/settings/notifications',
+ '/accountLists/[accountListId]/settings/integrations',
+ '/accountLists/[accountListId]/setup/finish',
+]);
-interface Props {
+interface SetupProviderProps {
children: ReactNode;
}
// This context component ensures that users have gone through the setup process
// and provides the setup state to the rest of the application
-export const SetupProvider: React.FC = ({ children }) => {
+export const SetupProvider: React.FC = ({ children }) => {
const { data } = useSetupStageQuery();
const { push, pathname } = useRouter();
@@ -59,7 +68,7 @@ export const SetupProvider: React.FC = ({ children }) => {
}
}, [data]);
- const settingUp = useMemo(() => {
+ const onSetupTour = useMemo(() => {
if (!data) {
return undefined;
}
@@ -68,16 +77,29 @@ export const SetupProvider: React.FC = ({ children }) => {
return false;
}
- return (
+ const onSetupPage = setupPages.has(pathname);
+ const settingUp =
data.userOptions.some(
(option) => option.key === 'setup_position' && option.value !== '',
- ) || data.user.setup !== null
- );
- }, [data]);
+ ) || data.user.setup !== null;
+ return onSetupPage && settingUp;
+ }, [data, pathname]);
return (
-
+
{children}
);
};
+
+// This provider is meant for use in tests. It lets tests easily override the
+// onSetupTour without needing to mock useSetupProvider or the pathname and
+// SetupStage GraphQL query.
+export const TestSetupProvider: React.FC> = ({
+ children,
+ onSetupTour,
+}) => (
+
+ {children}
+
+);
diff --git a/src/components/Shared/MultiPageLayout/MultiPageHeader.test.tsx b/src/components/Shared/MultiPageLayout/MultiPageHeader.test.tsx
index 4dac70fa9..ebce11a75 100644
--- a/src/components/Shared/MultiPageLayout/MultiPageHeader.test.tsx
+++ b/src/components/Shared/MultiPageLayout/MultiPageHeader.test.tsx
@@ -2,6 +2,7 @@ import React from 'react';
import { ThemeProvider } from '@mui/material/styles';
import { render, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
+import { TestSetupProvider } from 'src/components/Setup/SetupProvider';
import theme from 'src/theme';
import { HeaderTypeEnum, MultiPageHeader } from './MultiPageHeader';
@@ -9,19 +10,33 @@ const totalBalance = 'CA111';
const title = 'test title';
const onNavListToggle = jest.fn();
+interface TestComponentProps {
+ headerType?: HeaderTypeEnum;
+ noRightExtra?: boolean;
+ onSetupTour?: boolean;
+}
+
+const TestComponent: React.FC = ({
+ headerType = HeaderTypeEnum.Report,
+ noRightExtra = false,
+ onSetupTour,
+}) => (
+
+
+
+
+
+);
+
describe('MultiPageHeader', () => {
it('default', async () => {
- const { getByRole, getByText } = render(
-
-
- ,
- );
+ const { getByRole, getByText } = render();
expect(getByText(title)).toBeInTheDocument();
expect(getByText('CA111')).toBeInTheDocument();
@@ -32,49 +47,41 @@ describe('MultiPageHeader', () => {
});
it('should not render rightExtra if undefined', async () => {
- const { queryByText } = render(
-
-
- ,
+ const { queryByText } = render();
+
+ expect(queryByText('CA111')).not.toBeInTheDocument();
+ });
+
+ it('should render the Reports menu', async () => {
+ const { getByTestId, getByText } = render(
+ ,
);
- expect(queryByText('CA111')).toBeNull();
+ expect(getByText('Toggle Navigation Panel')).toBeInTheDocument();
+ expect(getByTestId('ReportsFilterIcon')).toBeInTheDocument();
});
it('should render the Settings menu', async () => {
const { getByTestId, getByText } = render(
-
-
- ,
+ ,
);
expect(getByText('Toggle Preferences Menu')).toBeInTheDocument();
expect(getByTestId('SettingsMenuIcon')).toBeInTheDocument();
});
+ it('should not render the Settings menu during the setup tour', async () => {
+ const { queryByTestId, queryByText } = render(
+ ,
+ );
+
+ expect(queryByText('Toggle Preferences Menu')).not.toBeInTheDocument();
+ expect(queryByTestId('SettingsMenuIcon')).not.toBeInTheDocument();
+ });
+
it('should render the Tools menu', async () => {
const { getByTestId, getByText } = render(
-
-
- ,
+ ,
);
expect(getByText('Toggle Tools Menu')).toBeInTheDocument();
diff --git a/src/components/Shared/MultiPageLayout/MultiPageHeader.tsx b/src/components/Shared/MultiPageLayout/MultiPageHeader.tsx
index c8d0f571d..c677c308e 100644
--- a/src/components/Shared/MultiPageLayout/MultiPageHeader.tsx
+++ b/src/components/Shared/MultiPageLayout/MultiPageHeader.tsx
@@ -4,6 +4,7 @@ import MenuIcon from '@mui/icons-material/Menu';
import { Box, IconButton, Typography } from '@mui/material';
import { styled } from '@mui/material/styles';
import { useTranslation } from 'react-i18next';
+import { useSetupContext } from 'src/components/Setup/SetupProvider';
import theme from 'src/theme';
export enum HeaderTypeEnum {
@@ -65,6 +66,7 @@ export const MultiPageHeader: FC = ({
headerType,
}) => {
const { t } = useTranslation();
+ const { onSetupTour } = useSetupContext();
let titleAccess;
if (headerType === HeaderTypeEnum.Report) {
@@ -90,7 +92,7 @@ export const MultiPageHeader: FC = ({
data-testid="ReportsFilterIcon"
/>
)}
- {headerType === HeaderTypeEnum.Settings && (
+ {!onSetupTour && headerType === HeaderTypeEnum.Settings && (