feat: add pages tests

This commit is contained in:
Sergey Krylov 2024-06-11 06:49:05 +03:00
parent 8db726a505
commit 9ed5af6857
21 changed files with 420 additions and 44 deletions

View File

@ -0,0 +1,20 @@
import { ReactNode } from 'react';
import { createMemoryRouter, RouterProvider } from 'react-router-dom';
import { render } from './utils';
export const renderWithProvider = (Component: ReactNode) => {
const routes = [
{
path: '/',
element: Component,
},
];
const router = createMemoryRouter(routes, {
initialEntries: ['/'],
initialIndex: 0,
});
return render(<RouterProvider router={router} />);
};

View File

@ -9,11 +9,12 @@ const config: Config = {
'<rootDir>src', '<rootDir>src',
'<rootDir>', '<rootDir>',
], ],
rootDir: './',
verbose: true, verbose: true,
preset: 'ts-jest/presets/js-with-ts', preset: 'ts-jest/presets/js-with-ts',
moduleNameMapper: { moduleNameMapper: {
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
'./configs/jest/__mocks__/fileMock.ts', '<rootDir>/configs/jest/__mocks__/fileMock.ts',
'\\.(css|less|scss)$': 'identity-obj-proxy', '\\.(css|less|scss)$': 'identity-obj-proxy',
}, },
transformIgnorePatterns: [ transformIgnorePatterns: [

View File

@ -1,12 +0,0 @@
import { screen, render } from '@testing-library/react';
import MoneyControllApp from './MoneyControllApp.ui';
describe('Test MoneyControllApp', () => {
test('Render MoneyControllApp', () => {
render(<MoneyControllApp />);
const appElem = screen.getByTestId('money-contoll-app');
expect(appElem).toBeInTheDocument();
});
});

View File

@ -0,0 +1,32 @@
import { render, screen } from '@testing-library/react';
import AnalyzePage from './AnalyzePage.ui';
import AnalyzePageToolbar from './AnalyzePageToolbar.ui';
describe('Test AnalyzePage', () => {
const setup = () => render(
<AnalyzePage data-testid="analyze-page" />,
);
test('should render correctly', () => {
setup();
const pageElem = screen.getByTestId('analyze-page');
expect(pageElem).toBeVisible();
expect(pageElem).toHaveTextContent('Analyze page');
});
});
describe('Test AnalyzePageToolbar', () => {
const setup = () => render(
<AnalyzePageToolbar data-testid="analyze-page-toolbar" />,
);
test('should render correctly', () => {
setup();
const toolbarElem = screen.getByTestId('analyze-page-toolbar');
expect(toolbarElem).toBeVisible();
expect(toolbarElem).toHaveTextContent('Analyze Page Toolbar');
});
});

View File

@ -1,7 +1,8 @@
import type { FC } from 'react'; import type { DetailedHTMLProps, FC, HTMLAttributes } from 'react';
const AnalyzePage: FC = () => ( type AnalyzePageProps = DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
<div> const AnalyzePage: FC<AnalyzePageProps> = (props) => (
<div {...props}>
Analyze page Analyze page
</div> </div>
); );

View File

@ -1,8 +1,12 @@
import Toolbar from '@mui/material/Toolbar'; import Toolbar from '@mui/material/Toolbar';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
const AnalyzePageToolbar = () => ( import type { ToolbarProps } from '@mui/material/Toolbar/Toolbar';
<Toolbar> import type { FC } from 'react';
type AnalyzePageToolbarProps = ToolbarProps;
const AnalyzePageToolbar: FC<AnalyzePageToolbarProps> = (props) => (
<Toolbar {...props}>
<Typography noWrap component="div" variant="h6"> <Typography noWrap component="div" variant="h6">
Analyze Page Toolbar Analyze Page Toolbar
</Typography> </Typography>

View File

@ -0,0 +1,52 @@
import { render, screen } from '@testing-library/react';
import { ErrorPageToolbar } from 'pages/ErrorPage';
import type { ErrorPageToolbarProps } from 'pages/ErrorPage/ui/ErrorPageToolbar.ui';
import ErrorPage from './ErrorPage.ui';
import type { ErrorPageProps } from './ErrorPage.ui';
describe('Test ErrorPage', () => {
const setup = (props?: Partial<ErrorPageProps>) => render(
<ErrorPage data-testid="error-page" {...props} />,
);
test('should render ErrorBoundary', () => {
setup();
const pageElem = screen.getByTestId('error-page');
expect(pageElem).toBeVisible();
expect(pageElem).toHaveTextContent('Что-то пошло не так');
});
test('should render 404', () => {
setup({ type: '404' });
const pageElem = screen.getByTestId('error-page');
expect(pageElem).toBeVisible();
expect(pageElem).not.toHaveTextContent('Что-то пошло не так');
});
});
describe('Test ErrorPageToolbar', () => {
const setup = (props?: Partial<ErrorPageToolbarProps>) => render(
<ErrorPageToolbar data-testid="error-page-toolbar" {...props} />,
);
test('should render ErrorBoundary', () => {
setup();
const toolbarElem = screen.getByTestId('error-page-toolbar');
expect(toolbarElem).toBeVisible();
expect(toolbarElem).toHaveTextContent('Непредвиденная ошибка');
});
test('should render 404', () => {
setup({ type: '404' });
const toolbarElem = screen.getByTestId('error-page-toolbar');
expect(toolbarElem).toBeVisible();
expect(toolbarElem).toHaveTextContent('Страницы не существует');
});
});

View File

@ -4,14 +4,16 @@ import Typography from '@mui/material/Typography';
import _404Image from 'public/assets/images/404.png'; import _404Image from 'public/assets/images/404.png';
import OopsImage from 'public/assets/images/oops.png'; import OopsImage from 'public/assets/images/oops.png';
import type { GridProps } from '@mui/material/Grid/Grid';
import type { FC } from 'react'; import type { FC } from 'react';
type ErrorPageProps = { export type ErrorPageProps = GridProps & {
type?: '404' | 'ErrorBoundary'; type?: '404' | 'ErrorBoundary';
}; };
const ErrorPage: FC<ErrorPageProps> = (props) => { const ErrorPage: FC<ErrorPageProps> = (props) => {
const { const {
type = 'ErrorBoundary', type = 'ErrorBoundary',
...restProps
} = props; } = props;
if (type === 'ErrorBoundary') { if (type === 'ErrorBoundary') {
@ -22,6 +24,7 @@ const ErrorPage: FC<ErrorPageProps> = (props) => {
direction="column" direction="column"
justifyContent="center" justifyContent="center"
rowGap={5} rowGap={5}
{...restProps}
> >
<Grid item> <Grid item>
<Typography variant="h5"> <Typography variant="h5">
@ -37,7 +40,7 @@ const ErrorPage: FC<ErrorPageProps> = (props) => {
} }
return ( return (
<Grid container justifyContent="center"> <Grid container justifyContent="center" {...restProps}>
<Grid item> <Grid item>
<img alt="asd" src={_404Image} /> <img alt="asd" src={_404Image} />
</Grid> </Grid>

View File

@ -1,9 +1,10 @@
import Toolbar from '@mui/material/Toolbar'; import Toolbar from '@mui/material/Toolbar';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import type { ToolbarProps } from '@mui/material/Toolbar/Toolbar';
import type { FC } from 'react'; import type { FC } from 'react';
type ErrorPageToolbarProps = { export type ErrorPageToolbarProps = ToolbarProps & {
type?: '404' | 'ErrorBoundary'; type?: '404' | 'ErrorBoundary';
}; };
@ -15,10 +16,11 @@ const TITLE: Record<NonNullable<ErrorPageToolbarProps['type']>, string> = {
const ErrorPageToolbar: FC<ErrorPageToolbarProps> = (props) => { const ErrorPageToolbar: FC<ErrorPageToolbarProps> = (props) => {
const { const {
type = 'ErrorBoundary', type = 'ErrorBoundary',
...restProps
} = props; } = props;
return ( return (
<Toolbar> <Toolbar {...restProps}>
<Typography noWrap component="div" variant="h6"> <Typography noWrap component="div" variant="h6">
{TITLE[type]} {TITLE[type]}
</Typography> </Typography>

View File

@ -0,0 +1,35 @@
import { render, screen } from '@testing-library/react';
import PaymentsPage from './PaymentsPage.ui';
import PaymentsPageToolbar from './PaymentsPageToolbar.ui';
import type { PaymentsPageProps } from './PaymentsPage.ui';
import type { PaymentsPageToolbarProps } from './PaymentsPageToolbar.ui';
describe('Test PaymentsPage', () => {
const setup = (props?: Partial<PaymentsPageProps>) => render(
<PaymentsPage data-testid="payments-page" {...props} />,
);
test('should render correctly', () => {
setup();
const pageElem = screen.getByTestId('payments-page');
expect(pageElem).toBeVisible();
expect(pageElem).toHaveTextContent('Payments Page');
});
});
describe('Test PaymentsPageToolbar', () => {
const setup = (props?: Partial<PaymentsPageToolbarProps>) => render(
<PaymentsPageToolbar data-testid="payments-page-toolbar" {...props} />,
);
test('should render correctly', () => {
setup();
const toolbarElem = screen.getByTestId('payments-page-toolbar');
expect(toolbarElem).toBeVisible();
expect(toolbarElem).toHaveTextContent('Payments Page Toolbar');
});
});

View File

@ -1,7 +1,8 @@
import type { FC } from 'react'; import type { DetailedHTMLProps, FC, HTMLAttributes } from 'react';
const PaymentsPage: FC = () => ( export type PaymentsPageProps = DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
<div> const PaymentsPage: FC<PaymentsPageProps> = (props) => (
<div {...props}>
Payments Page Payments Page
</div> </div>
); );

View File

@ -1,10 +1,12 @@
import Toolbar from '@mui/material/Toolbar'; import Toolbar from '@mui/material/Toolbar';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import type { ToolbarProps } from '@mui/material/Toolbar/Toolbar';
import type { FC } from 'react'; import type { FC } from 'react';
const PaymentsPageToolbar: FC = () => ( export type PaymentsPageToolbarProps = ToolbarProps;
<Toolbar> const PaymentsPageToolbar: FC<PaymentsPageToolbarProps> = (props) => (
<Toolbar {...props}>
<Typography noWrap component="div" variant="h6"> <Typography noWrap component="div" variant="h6">
Payments Page Toolbar Payments Page Toolbar
</Typography> </Typography>

View File

@ -0,0 +1,31 @@
import { render, screen } from '@testing-library/react';
import { SchedulePage, SchedulePageToolbar } from 'pages/SchedulePage';
describe('Test SchedulePage', () => {
const setup = () => render(
<SchedulePage data-testid="schedule-page" />,
);
test('should render correctly', () => {
setup();
const pageElem = screen.getByTestId('schedule-page');
expect(pageElem).toBeVisible();
expect(pageElem).toHaveTextContent('Schedule Page');
});
});
describe('Test SchedulePageToolabr', () => {
const setup = () => render(
<SchedulePageToolbar data-testid="schedule-page-toolbar" />,
);
test('should render correctly', () => {
setup();
const toolbarElem = screen.getByTestId('schedule-page-toolbar');
expect(toolbarElem).toBeVisible();
expect(toolbarElem).toHaveTextContent('Schedule Page Toolbar');
});
});

View File

@ -1,7 +1,8 @@
import type { FC } from 'react'; import type { DetailedHTMLProps, FC, HTMLAttributes } from 'react';
const SchedulePage: FC = () => ( export type SchedulePageProps = DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
<div> const SchedulePage: FC<SchedulePageProps> = (props) => (
<div {...props}>
Schedule Page Schedule Page
</div> </div>
); );

View File

@ -1,10 +1,12 @@
import Toolbar from '@mui/material/Toolbar'; import Toolbar from '@mui/material/Toolbar';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import type { ToolbarProps } from '@mui/material/Toolbar/Toolbar';
import type { FC } from 'react'; import type { FC } from 'react';
const SchedulePageToolbar: FC = () => ( type SchedulePageToolbarProps = ToolbarProps;
<Toolbar> const SchedulePageToolbar: FC<SchedulePageToolbarProps> = (props) => (
<Toolbar {...props}>
<Typography noWrap component="div" variant="h6"> <Typography noWrap component="div" variant="h6">
Schedule Page Toolbar Schedule Page Toolbar
</Typography> </Typography>

View File

@ -0,0 +1,116 @@
import { renderWithProvider } from '__spec__/custon-renders';
import PieChartIcon from '@mui/icons-material/PieChart';
import { fireEvent, screen } from '@testing-library/react';
import AsideMenuLayout from './AsideMenuLayout.ui';
const toggleSwitcher = (testId = 'drawer-switcher') => {
const switcherElem = screen.getByTestId(testId);
fireEvent.click(switcherElem);
};
describe('Test AsideMenuLayout', () => {
const setup = () => {
const Content = () => (
<div>
Test Content
</div>
);
const Toolbar = () => (
<div>
Test Toolbar
</div>
);
const pages = [
{
icon: <PieChartIcon />,
title: 'Test Route',
path: '/test-route',
},
{
icon: <PieChartIcon />,
title: 'Another Test Route',
path: '/another-test-route',
},
];
return renderWithProvider(
<AsideMenuLayout
Content={<Content />}
data-testid="aside-menu-layout"
DrawerSwitcherProps={{
// @ts-ignore
'data-testid': 'drawer-switcher',
}}
pages={pages}
Toolbar={<Toolbar />}
/>,
);
};
test('should render correctly', () => {
setup();
const layoutElem = screen.getByTestId('aside-menu-layout');
expect(layoutElem).toBeVisible();
});
test('should render toolbar', () => {
setup();
const toolbarElem = screen.getByText('Test Toolbar');
expect(toolbarElem).toBeVisible();
});
test('should render content', () => {
setup();
const toolbarElem = screen.getByText('Test Content');
expect(toolbarElem).toBeVisible();
});
test('should render navigation routes', () => {
setup();
const linkElem = screen.getByText('Test Route');
expect(linkElem).toBeVisible();
const anotherLinkElem = screen.getByText('Another Test Route');
expect(anotherLinkElem).toBeVisible();
});
describe('Test switch open/close drawer', () => {
test('should have correct close icon', () => {
setup();
const closeIconElem = screen.getByTestId('ChevronLeftIcon');
expect(closeIconElem).toBeVisible();
});
test('should have correct open icon', () => {
setup();
toggleSwitcher();
const openIconElem = screen.getByTestId('MenuIcon');
expect(openIconElem).toBeVisible();
});
test('should show link text', () => {
setup();
const linkTextElem = screen.getByText('Test Route');
expect(linkTextElem).toBeVisible();
});
test('should hide link text', () => {
setup();
toggleSwitcher();
const linkTextElem = screen.queryByText('Test Route');
expect(linkTextElem).not.toBeVisible();
});
});
});

View File

@ -10,23 +10,32 @@ import AppBar from './components/AppBar';
import Drawer from './components/Drawer'; import Drawer from './components/Drawer';
import DrawerHeader from './components/DrawerMenu'; import DrawerHeader from './components/DrawerMenu';
import DrawerSwitcher from './components/DrawerSwitcher'; import DrawerSwitcher from './components/DrawerSwitcher';
import MenuItem from './components/MenuItem'; import MenuItem from './components/MenuItem/MenuItem.ui';
import type { MenuItemProps } from './components/MenuItem'; import type { MenuItemProps } from './components/MenuItem/MenuItem.ui';
import type { FC, ReactNode } from 'react'; import type { BoxProps } from '@mui/material/Box/Box';
import type {
FC,
ReactNode,
DetailedHTMLProps,
HTMLAttributes,
} from 'react';
const drawerWidth = 250; const drawerWidth = 250;
type AsideMenuLayoutProps = { type AsideMenuLayoutProps = BoxProps & {
pages: MenuItemProps['page'][]; pages: MenuItemProps['page'][];
Content: ReactNode; Content: ReactNode;
Toolbar?: ReactNode; Toolbar?: ReactNode;
DrawerSwitcherProps?: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
}; };
const AsideMenuLayout: FC<AsideMenuLayoutProps> = (props) => { const AsideMenuLayout: FC<AsideMenuLayoutProps> = (props) => {
const { const {
pages, pages,
Content, Content,
Toolbar, Toolbar,
DrawerSwitcherProps,
...restProps
} = props; } = props;
const [isOpen, setIsOpen] = useState(true); const [isOpen, setIsOpen] = useState(true);
@ -39,7 +48,7 @@ const AsideMenuLayout: FC<AsideMenuLayoutProps> = (props) => {
}; };
return ( return (
<Box sx={{ display: 'flex' }}> <Box sx={{ display: 'flex' }} {...restProps}>
<CssBaseline /> <CssBaseline />
<Drawer <Drawer
@ -51,6 +60,7 @@ const AsideMenuLayout: FC<AsideMenuLayoutProps> = (props) => {
<DrawerSwitcher <DrawerSwitcher
open={isOpen} open={isOpen}
onClick={isOpen ? handleDrawerClose : handleDrawerOpen} onClick={isOpen ? handleDrawerClose : handleDrawerOpen}
{...DrawerSwitcherProps}
> >
{ isOpen ? <ChevronLeftIcon /> : <MenuIcon /> } { isOpen ? <ChevronLeftIcon /> : <MenuIcon /> }
</DrawerSwitcher> </DrawerSwitcher>

View File

@ -8,7 +8,7 @@ type AppBarProps = MuiAppBarProps & {
drawerWidth: number; drawerWidth: number;
}; };
const AppBar = styled(MuiAppBar, { shouldForwardProp: (prop) => prop !== 'open' })<AppBarProps>( const AppBar = styled(MuiAppBar, { shouldForwardProp: (prop) => !['open', 'drawerWidth'].includes(prop as string) })<AppBarProps>(
({ theme, open, drawerWidth }) => ({ ({ theme, open, drawerWidth }) => ({
transition: theme.transitions.create(['width', 'margin'], { transition: theme.transitions.create(['width', 'margin'], {
easing: theme.transitions.easing.sharp, easing: theme.transitions.easing.sharp,

View File

@ -24,7 +24,7 @@ const closedMixin = (theme: Theme): CSSObject => ({
width: `calc(${theme.spacing(8)} + 1px)`, width: `calc(${theme.spacing(8)} + 1px)`,
}); });
const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== 'open' })<DrawerProps>( const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => !['open', 'drawerWidth'].includes(prop as string) })<DrawerProps>(
({ theme, open, drawerWidth }) => ({ ({ theme, open, drawerWidth }) => ({
width: drawerWidth, width: drawerWidth,
flexShrink: 0, flexShrink: 0,

View File

@ -0,0 +1,70 @@
import { renderWithProvider } from '__spec__/custon-renders';
import PieChartIcon from '@mui/icons-material/PieChart';
import { screen } from '@testing-library/react';
import MenuItem from './MenuItem.ui';
import type { MenuItemProps } from './MenuItem.ui';
describe('MenuItem', () => {
const setup = (props?: Partial<MenuItemProps>) => renderWithProvider(
<MenuItem
data-testid="menu-item"
NavLinkProps={{
// @ts-ignore
'data-testid': 'menu-item-link',
}}
page={{
icon: <PieChartIcon />,
title: 'Test Route',
path: '/test-route',
}}
{...props}
/>,
);
test('should render', () => {
setup();
const itemElem = screen.getByTestId('menu-item');
expect(itemElem).toBeVisible();
});
test('should render correct text', () => {
setup();
const textElem = screen.getByText('Test Route');
expect(textElem).toBeVisible();
});
test('should render correct icon', () => {
setup();
const iconElem = screen.getByTestId('PieChartIcon');
expect(iconElem).toBeVisible();
});
test('should have correct href', () => {
setup();
const itemElem = screen.getByTestId('menu-item-link');
expect(itemElem).toHaveAttribute('href', '/test-route');
});
describe('Test closed MenuItem', () => {
test('should not render text if closed', () => {
setup({ isOpen: false });
const textElem = screen.getByText('Test Route');
expect(textElem).not.toBeVisible();
});
test('should render correct icon if closed', () => {
setup({ isOpen: false });
const iconElem = screen.getByTestId('PieChartIcon');
expect(iconElem).toBeVisible();
});
});
});

View File

@ -6,7 +6,9 @@ import { styled, useTheme } from '@mui/material/styles';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import { NavLink as RRDNavLink } from 'react-router-dom'; import { NavLink as RRDNavLink } from 'react-router-dom';
import type { ListItemProps } from '@mui/material/ListItem/ListItem';
import type { FC, ReactNode } from 'react'; import type { FC, ReactNode } from 'react';
import type { NavLinkProps } from 'react-router-dom';
const NavLink = styled(RRDNavLink)(({ theme }) => ({ const NavLink = styled(RRDNavLink)(({ theme }) => ({
textDecoration: 'none', textDecoration: 'none',
@ -14,25 +16,28 @@ const NavLink = styled(RRDNavLink)(({ theme }) => ({
width: '100%', width: '100%',
})); }));
export type MenuItemProps = { export type MenuItemProps = ListItemProps & {
page: { page: {
icon: ReactNode; icon: ReactNode;
title: string; title: string;
path: string; path: string;
}; };
isOpen: boolean; isOpen?: boolean;
NavLinkProps?: NavLinkProps;
}; };
const MenuItem: FC<MenuItemProps> = (props) => { const MenuItem: FC<MenuItemProps> = (props) => {
const { const {
page, page,
isOpen, isOpen = true,
NavLinkProps,
...restProps
} = props; } = props;
const theme = useTheme(); const theme = useTheme();
return ( return (
<ListItem disablePadding> <ListItem disablePadding {...restProps}>
<NavLink to={page.path}> <NavLink to={page.path} {...NavLinkProps}>
{({ isActive }) => ( {({ isActive }) => (
<ListItemButton <ListItemButton
sx={{ sx={{