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>',
],
rootDir: './',
verbose: true,
preset: 'ts-jest/presets/js-with-ts',
moduleNameMapper: {
'\\.(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',
},
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 = () => (
<div>
type AnalyzePageProps = DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
const AnalyzePage: FC<AnalyzePageProps> = (props) => (
<div {...props}>
Analyze page
</div>
);

View File

@ -1,8 +1,12 @@
import Toolbar from '@mui/material/Toolbar';
import Typography from '@mui/material/Typography';
const AnalyzePageToolbar = () => (
<Toolbar>
import type { ToolbarProps } from '@mui/material/Toolbar/Toolbar';
import type { FC } from 'react';
type AnalyzePageToolbarProps = ToolbarProps;
const AnalyzePageToolbar: FC<AnalyzePageToolbarProps> = (props) => (
<Toolbar {...props}>
<Typography noWrap component="div" variant="h6">
Analyze Page Toolbar
</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 OopsImage from 'public/assets/images/oops.png';
import type { GridProps } from '@mui/material/Grid/Grid';
import type { FC } from 'react';
type ErrorPageProps = {
export type ErrorPageProps = GridProps & {
type?: '404' | 'ErrorBoundary';
};
const ErrorPage: FC<ErrorPageProps> = (props) => {
const {
type = 'ErrorBoundary',
...restProps
} = props;
if (type === 'ErrorBoundary') {
@ -22,6 +24,7 @@ const ErrorPage: FC<ErrorPageProps> = (props) => {
direction="column"
justifyContent="center"
rowGap={5}
{...restProps}
>
<Grid item>
<Typography variant="h5">
@ -37,7 +40,7 @@ const ErrorPage: FC<ErrorPageProps> = (props) => {
}
return (
<Grid container justifyContent="center">
<Grid container justifyContent="center" {...restProps}>
<Grid item>
<img alt="asd" src={_404Image} />
</Grid>

View File

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

View File

@ -1,10 +1,12 @@
import Toolbar from '@mui/material/Toolbar';
import Typography from '@mui/material/Typography';
import type { ToolbarProps } from '@mui/material/Toolbar/Toolbar';
import type { FC } from 'react';
const PaymentsPageToolbar: FC = () => (
<Toolbar>
export type PaymentsPageToolbarProps = ToolbarProps;
const PaymentsPageToolbar: FC<PaymentsPageToolbarProps> = (props) => (
<Toolbar {...props}>
<Typography noWrap component="div" variant="h6">
Payments Page Toolbar
</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 = () => (
<div>
export type SchedulePageProps = DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
const SchedulePage: FC<SchedulePageProps> = (props) => (
<div {...props}>
Schedule Page
</div>
);

View File

@ -1,10 +1,12 @@
import Toolbar from '@mui/material/Toolbar';
import Typography from '@mui/material/Typography';
import type { ToolbarProps } from '@mui/material/Toolbar/Toolbar';
import type { FC } from 'react';
const SchedulePageToolbar: FC = () => (
<Toolbar>
type SchedulePageToolbarProps = ToolbarProps;
const SchedulePageToolbar: FC<SchedulePageToolbarProps> = (props) => (
<Toolbar {...props}>
<Typography noWrap component="div" variant="h6">
Schedule Page Toolbar
</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 DrawerHeader from './components/DrawerMenu';
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 { FC, ReactNode } from 'react';
import type { MenuItemProps } from './components/MenuItem/MenuItem.ui';
import type { BoxProps } from '@mui/material/Box/Box';
import type {
FC,
ReactNode,
DetailedHTMLProps,
HTMLAttributes,
} from 'react';
const drawerWidth = 250;
type AsideMenuLayoutProps = {
type AsideMenuLayoutProps = BoxProps & {
pages: MenuItemProps['page'][];
Content: ReactNode;
Toolbar?: ReactNode;
DrawerSwitcherProps?: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
};
const AsideMenuLayout: FC<AsideMenuLayoutProps> = (props) => {
const {
pages,
Content,
Toolbar,
DrawerSwitcherProps,
...restProps
} = props;
const [isOpen, setIsOpen] = useState(true);
@ -39,7 +48,7 @@ const AsideMenuLayout: FC<AsideMenuLayoutProps> = (props) => {
};
return (
<Box sx={{ display: 'flex' }}>
<Box sx={{ display: 'flex' }} {...restProps}>
<CssBaseline />
<Drawer
@ -51,6 +60,7 @@ const AsideMenuLayout: FC<AsideMenuLayoutProps> = (props) => {
<DrawerSwitcher
open={isOpen}
onClick={isOpen ? handleDrawerClose : handleDrawerOpen}
{...DrawerSwitcherProps}
>
{ isOpen ? <ChevronLeftIcon /> : <MenuIcon /> }
</DrawerSwitcher>

View File

@ -8,7 +8,7 @@ type AppBarProps = MuiAppBarProps & {
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 }) => ({
transition: theme.transitions.create(['width', 'margin'], {
easing: theme.transitions.easing.sharp,

View File

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