Release 0.0.2 #17

Open
ksv741 wants to merge 33 commits from krylov into development
49 changed files with 315 additions and 168 deletions
Showing only changes of commit 8c5e4a08bf - Show all commits

5
src/global.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
/// <reference types="ksv741-react-scripts/global" />
declare module '*.css' {
export default Record<string, string>;
}

View File

@ -13,6 +13,7 @@ const CalendarPage = () => (
locale="en" locale="en"
maxDate={new Date(2027, 2, 15)} maxDate={new Date(2027, 2, 15)}
minDate={new Date(1996, 9, 15)} minDate={new Date(1996, 9, 15)}
value={new Date(2023, 9, 15)}
/> />
</div> </div>
</div> </div>

View File

@ -13,6 +13,9 @@ export const MONTHS = {
DECEMBER: 11, DECEMBER: 11,
}; };
export type MonthsKeys = keyof typeof MONTHS;
export type MonthsValues = typeof MONTHS[MonthsKeys];
export const DAYS_IN_WEEK = 7; export const DAYS_IN_WEEK = 7;
export const WEEK_DAYS = { export const WEEK_DAYS = {
@ -24,3 +27,6 @@ export const WEEK_DAYS = {
FRIDAY: 5, FRIDAY: 5,
SATURDAY: 6, SATURDAY: 6,
}; };
export type WeekDaysKeys = keyof typeof WEEK_DAYS;
export type WeekDaysValues = typeof WEEK_DAYS[WeekDaysKeys];

View File

@ -1,2 +1,8 @@
export { DAYS_IN_WEEK, MONTHS, WEEK_DAYS } from './date'; export { DAYS_IN_WEEK, MONTHS, WEEK_DAYS } from './date';
export { MONTHS_LABEL, WEEK_DAYS_LABEL } from './locale'; export { MONTHS_LABEL, WEEK_DAYS_LABEL } from './locale';
export type {
MonthsKeys,
MonthsValues,
WeekDaysKeys,
WeekDaysValues,
} from './date';

View File

@ -1,6 +1,12 @@
import { MONTHS, WEEK_DAYS } from './date'; import { MONTHS, WEEK_DAYS } from 'shared/constants/date';
import type { MonthsValues, WeekDaysValues } from 'shared/constants/date';
export const MONTHS_LABEL = { type Locale<T = string> = {
ru: T;
en: T;
};
export const MONTHS_LABEL: Record<MonthsValues, Locale> = {
[MONTHS.JANUARY]: { [MONTHS.JANUARY]: {
ru: 'Январь', ru: 'Январь',
en: 'January', en: 'January',
@ -51,7 +57,14 @@ export const MONTHS_LABEL = {
}, },
}; };
export const WEEK_DAYS_LABEL = { type WeekDaysLabel = {
full: string;
short: string;
};
type WeekDaysLabelLocale = Locale<WeekDaysLabel>;
export const WEEK_DAYS_LABEL: Record<WeekDaysValues, WeekDaysLabelLocale> = {
[WEEK_DAYS.SUNDAY]: { [WEEK_DAYS.SUNDAY]: {
ru: { ru: {
full: 'Воскресенье', full: 'Воскресенье',

View File

@ -2,13 +2,19 @@ import { fireEvent, render, screen } from '@testing-library/react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { useContext, useMemo, useState } from 'react'; import React, { useContext, useMemo, useState } from 'react';
import { withProvider } from 'shared/hoc/withProvider/index'; import withProvider from './withProvider.ui';
import type { PropsWithChildren } from 'react';
describe('Test withProvider', () => { describe('Test withProvider', () => {
const renderCounter = () => { const renderCounter = () => {
const TestContext = React.createContext({}); type TestContextValue = {
testState: number;
setTestState: React.Dispatch<React.SetStateAction<number>>;
};
const TestContext = React.createContext<TestContextValue>({} as TestContextValue);
const TestProvider = (props) => { const TestProvider = (props: PropsWithChildren) => {
const { children } = props; const { children } = props;
const [testState, setTestState] = useState(100); const [testState, setTestState] = useState(100);
@ -74,7 +80,7 @@ describe('Test withProvider', () => {
renderCounter(); renderCounter();
const counterElem = screen.getByTestId('counter'); const counterElem = screen.getByTestId('counter');
expect(counterElem).toHaveTextContent(100); expect(counterElem).toHaveTextContent('100');
}); });
test('change state - increment', () => { test('change state - increment', () => {
@ -84,7 +90,7 @@ describe('Test withProvider', () => {
fireEvent.click(plusBtn); fireEvent.click(plusBtn);
const counterElem = screen.getByTestId('counter'); const counterElem = screen.getByTestId('counter');
expect(counterElem).toHaveTextContent(101); expect(counterElem).toHaveTextContent('101');
}); });
test('change state - decrement', () => { test('change state - decrement', () => {
@ -94,6 +100,6 @@ describe('Test withProvider', () => {
fireEvent.click(minusBtn); fireEvent.click(minusBtn);
const counterElem = screen.getByTestId('counter'); const counterElem = screen.getByTestId('counter');
expect(counterElem).toHaveTextContent(99); expect(counterElem).toHaveTextContent('99');
}); });
}); });

View File

@ -1,9 +0,0 @@
// todo fix eslint
// eslint-disable-next-line react/function-component-definition
const withProvider = (Provider) => (Component) => (...params) => (
<Provider>
<Component {...params} />
</Provider>
);
export default withProvider;

View File

@ -0,0 +1,13 @@
// todo fix eslint
import type { FC, PropsWithChildren } from 'react';
// eslint-disable-next-line react/function-component-definition
const withProvider = <T,>(Provider: FC<PropsWithChildren>) => (Component: FC<T>) => (...params: T[]) => (
<Provider>
{/* @ts-ignore */}
<Component {...params} />
</Provider>
);
export default withProvider;

View File

@ -1,11 +1,11 @@
export const firstDayOfMonthDate = (date) => { export const firstDayOfMonthDate = (date: Date) => {
const monthIndex = date.getMonth(); const monthIndex = date.getMonth();
const year = date.getFullYear(); const year = date.getFullYear();
return new Date(year, monthIndex, 1); return new Date(year, monthIndex, 1);
}; };
export const lastDayOfMonthDate = (date) => { export const lastDayOfMonthDate = (date: Date) => {
const monthIndex = date.getMonth(); const monthIndex = date.getMonth();
const year = date.getFullYear(); const year = date.getFullYear();

View File

@ -0,0 +1,46 @@
import { render, screen } from '@testing-library/react';
import CalendarWidget from './CalendarWidget.ui';
import type { CalendarWidgetProps } from './CalendarWidget.ui';
describe('Test CalendarWidget', () => {
const setup = (props?: CalendarWidgetProps) => {
render(<CalendarWidget {...props} />);
};
test('Basic render', () => {
setup();
const calendarWidgetElem = screen.getByTestId('calendar-widget');
expect(calendarWidgetElem).toBeInTheDocument();
expect(calendarWidgetElem).toBeVisible();
});
test('Render components', () => {
setup();
const calendarWidgetElem = screen.getByTestId('calendar-widget');
const headerRowElem = screen.getByTestId('header-row');
const dayOfWeekRowElem = screen.getByTestId('day-of-week-row');
const dateWrapperElem = screen.getByTestId('date-wrapper');
expect(calendarWidgetElem).toContainElement(headerRowElem);
expect(calendarWidgetElem).toContainElement(dayOfWeekRowElem);
expect(calendarWidgetElem).toContainElement(dateWrapperElem);
});
// todo
test('Render with value', () => {
setup({ value: new Date(2023, 1, 15) });
const calendarWidgetElem = screen.getByTestId('calendar-widget');
const headerRowElem = screen.getByTestId('header-row');
const dayOfWeekRowElem = screen.getByTestId('day-of-week-row');
const dateWrapperElem = screen.getByTestId('date-wrapper');
expect(calendarWidgetElem).toContainElement(headerRowElem);
expect(calendarWidgetElem).toContainElement(dayOfWeekRowElem);
expect(calendarWidgetElem).toContainElement(dateWrapperElem);
});
});

View File

@ -1,14 +1,20 @@
import PropTypes from 'prop-types';
import classes from './CalendarWidget.module.css'; import classes from './CalendarWidget.module.css';
import { CalendarWidgetProvider } from './CalendarWidgetContext'; import { CalendarWidgetProvider } from './CalendarWidgetContext';
import { DateWrapper } from './components/DateWrapper'; import { DateWrapper } from './components/DateWrapper';
import { DayOfWeekRow } from './components/DayOfWeekRow'; import { DayOfWeekRow } from './components/DayOfWeekRow';
import { HeaderRow } from './components/HeaderRow'; import { HeaderRow } from './components/HeaderRow';
const CalendarWidget = (props) => ( import type { FC } from 'react';
export type CalendarWidgetProps = {
maxDate?: Date;
minDate?: Date;
value?: Date;
locale?: 'en' | 'ru';
};
const CalendarWidget: FC<CalendarWidgetProps> = (props) => (
<CalendarWidgetProvider {...props}> <CalendarWidgetProvider {...props}>
<div className={classes.wrapper}> <div className={classes.wrapper} data-testid="calendar-widget">
<HeaderRow /> <HeaderRow />
<DayOfWeekRow /> <DayOfWeekRow />
@ -18,10 +24,4 @@ const CalendarWidget = (props) => (
</CalendarWidgetProvider> </CalendarWidgetProvider>
); );
CalendarWidget.propTypes = {
maxDate: PropTypes.instanceOf(Date),
minDate: PropTypes.instanceOf(Date),
value: PropTypes.instanceOf(Date),
};
export default CalendarWidget; export default CalendarWidget;

View File

@ -1,41 +0,0 @@
import PropTypes from 'prop-types';
import { createContext, useMemo, useState } from 'react';
export const CalendarWidgetContext = createContext({});
export const CalendarWidgetProvider = (props) => {
const {
children,
maxDate = new Date(2030, 0, 1),
minDate = new Date(1990, 0, 1),
locale = 'ru',
...restProps
} = props;
const [targetDate, setTargetDate] = useState(new Date());
const contextValue = useMemo(() => ({
targetYear: targetDate.getFullYear(),
targetMonthIndex: targetDate.getMonth(),
targetDate,
setTargetDate,
minDate,
maxDate,
locale,
...restProps,
}), [maxDate, minDate, targetDate, locale, restProps]);
return (
<CalendarWidgetContext.Provider value={contextValue}>
{children}
</CalendarWidgetContext.Provider>
);
};
CalendarWidgetProvider.propTypes = {
children: PropTypes.node.isRequired,
maxDate: PropTypes.instanceOf(Date),
minDate: PropTypes.instanceOf(Date),
locale: PropTypes.oneOf(['ru', 'en']),
};

View File

@ -0,0 +1,54 @@
import PropTypes from 'prop-types';
import React, { createContext, useMemo, useState } from 'react';
import type { CalendarWidgetProps } from './CalendarWidget.ui';
import type { FC, PropsWithChildren } from 'react';
type CalendarWidgetContextValue = Required<CalendarWidgetProps> & {
targetYear: number;
targetMonthIndex: number;
targetDate: Date;
setTargetDate: React.Dispatch<React.SetStateAction<Date>>;
};
export const CalendarWidgetContext = createContext({} as CalendarWidgetContextValue);
export type CalendarWidgetProviderProps = PropsWithChildren<CalendarWidgetProps>;
export const CalendarWidgetProvider: FC<CalendarWidgetProviderProps> = (props) => {
const {
children,
maxDate = new Date(2030, 0, 1),
minDate = new Date(1990, 0, 1),
locale = 'ru',
value = new Date(),
...restProps
} = props;
const [targetDate, setTargetDate] = useState(value);
const contextValue = useMemo<CalendarWidgetContextValue>(() => ({
targetYear: targetDate.getFullYear(),
targetMonthIndex: targetDate.getMonth(),
targetDate,
setTargetDate,
minDate,
maxDate,
locale,
value,
...restProps,
}), [maxDate, minDate, targetDate, locale, restProps, value]);
return (
<CalendarWidgetContext.Provider value={contextValue}>
{children}
</CalendarWidgetContext.Provider>
);
};
CalendarWidgetProvider.propTypes = {
children: PropTypes.node.isRequired,
maxDate: PropTypes.instanceOf(Date),
minDate: PropTypes.instanceOf(Date),
locale: PropTypes.oneOf(['ru', 'en']),
};

View File

@ -3,7 +3,7 @@ import { render, screen } from '@testing-library/react';
import DateCell from './DateCell.ui'; import DateCell from './DateCell.ui';
describe('Base Render', () => { describe('Base Render', () => {
const setup = (date) => { const setup = (date: Date) => {
render(<DateCell date={date} />); render(<DateCell date={date} />);
}; };

View File

@ -16,13 +16,25 @@
border-right: 1px solid black; border-right: 1px solid black;
} }
&.today { .today {
color: red; color: red;
font-size: 1.2em; font-size: 1.2em;
font-weight: bold; font-weight: bold;
} }
&.outMothRange { .outMothRange {
color: darkgrey; color: darkgrey;
} }
.valueDate {
color: gold;
background: red;
border-radius: 50%;
padding: 5px;
height: 1.2em;
width: 1.2em;
display: inline-flex;
justify-content: center;
align-items: center;
}
} }

View File

@ -1,44 +1,49 @@
import classnames from 'classnames'; import classnames from 'classnames';
import PropTypes from 'prop-types';
import { useContext } from 'react'; import { useContext } from 'react';
import { CalendarWidgetContext } from '../../../CalendarWidgetContext'; import { CalendarWidgetContext } from '../../../CalendarWidgetContext';
import classes from './DateCell.style.module.css'; import classes from './DateCell.style.module.css';
const DateCell = (props) => { import type { FC } from 'react';
type DateCellProps = {
date: Date;
};
const DateCell: FC<DateCellProps> = (props) => {
const { const {
date, date,
} = props; } = props;
const format = 'en-En'; const format = 'en-En';
const { targetMonthIndex } = useContext(CalendarWidgetContext); const { targetMonthIndex, value } = useContext(CalendarWidgetContext);
const todayDate = new Date(); const todayDate = new Date();
const dataDate = new Intl.DateTimeFormat(format).format(date); const dataDate = new Intl.DateTimeFormat(format).format(date);
const dataToday = new Intl.DateTimeFormat(format).format(todayDate); const dataToday = new Intl.DateTimeFormat(format).format(todayDate);
const dataValue = new Intl.DateTimeFormat(format).format(value);
const isOutMonthRange = date.getMonth() !== targetMonthIndex; const isOutMonthRange = date.getMonth() !== targetMonthIndex;
return ( return (
<div <div
key={dataDate} key={dataDate}
className={classnames( className={classes.dateCell}
{
[classes.dateCell]: true,
[classes.today]: dataDate === dataToday,
[classes.outMothRange]: isOutMonthRange,
},
)}
data-date={dataDate} data-date={dataDate}
data-testid="date-cell" data-testid="date-cell"
> >
{date.getDate()} <span
className={classnames(
{
[classes.today as string]: dataDate === dataToday,
[classes.outMothRange as string]: isOutMonthRange,
[classes.valueDate as string]: dataDate === dataValue,
},
)}
>
{date.getDate()}
</span>
</div> </div>
); );
}; };
DateCell.propTypes = {
date: PropTypes.instanceOf(Date).isRequired,
};
export default DateCell; export default DateCell;

View File

@ -2,8 +2,10 @@ import { render, screen } from '@testing-library/react';
import DateRow from './DateRow.ui'; import DateRow from './DateRow.ui';
import type { DateRowType } from './DateRow.ui';
describe('Test Date Row', () => { describe('Test Date Row', () => {
const setup = (cells) => { const setup = (cells: DateRowType['cells']) => {
render(<DateRow cells={cells} />); render(<DateRow cells={cells} />);
}; };
@ -53,13 +55,13 @@ describe('Test Date Row', () => {
setupWeek(); setupWeek();
const dateCellElems = screen.getAllByTestId('date-cell'); const dateCellElems = screen.getAllByTestId('date-cell');
expect(dateCellElems[0]).toHaveTextContent(15); expect(dateCellElems[0]).toHaveTextContent('15');
expect(dateCellElems[1]).toHaveTextContent(16); expect(dateCellElems[1]).toHaveTextContent('16');
expect(dateCellElems[2]).toHaveTextContent(17); expect(dateCellElems[2]).toHaveTextContent('17');
expect(dateCellElems[3]).toHaveTextContent(18); expect(dateCellElems[3]).toHaveTextContent('18');
expect(dateCellElems[4]).toHaveTextContent(19); expect(dateCellElems[4]).toHaveTextContent('19');
expect(dateCellElems[5]).toHaveTextContent(20); expect(dateCellElems[5]).toHaveTextContent('20');
expect(dateCellElems[6]).toHaveTextContent(21); expect(dateCellElems[6]).toHaveTextContent('21');
}); });
}); });
}); });

View File

@ -1,10 +1,13 @@
import PropTypes from 'prop-types';
import { DateCell } from '../../DateCell'; import { DateCell } from '../../DateCell';
import classes from './DateRow.style.module.css'; import classes from './DateRow.style.module.css';
const DateRow = (props) => { import type { FC } from 'react';
export type DateRowType = {
cells: Date[];
};
const DateRow: FC<DateRowType> = (props) => {
const { const {
cells, cells,
} = props; } = props;
@ -28,8 +31,4 @@ const DateRow = (props) => {
); );
}; };
DateRow.propTypes = {
cells: PropTypes.arrayOf(PropTypes.instanceOf(Date)),
};
export default DateRow; export default DateRow;

View File

@ -7,14 +7,17 @@ import { CalendarWidgetProvider } from '../../../CalendarWidgetContext';
import DayOfWeekCell from './DayOfWeekCell.ui'; import DayOfWeekCell from './DayOfWeekCell.ui';
import type { DayOfWeekCellProps } from './DayOfWeekCell.ui';
import type { CalendarWidgetProviderProps } from '../../../CalendarWidgetContext';
describe('Test DayOfWeekCell', () => { describe('Test DayOfWeekCell', () => {
const setup = (day, locale) => { const setup = (day: DayOfWeekCellProps['day'], locale: CalendarWidgetProviderProps['locale']) => {
const DayOfWeekCellComponent = () => ( const DayOfWeekCellComponent = () => (
<DayOfWeekCell day={day} /> <DayOfWeekCell day={day} />
); );
const Provider = (props) => ( const Provider = (props: CalendarWidgetProviderProps) => (
<CalendarWidgetProvider {...props} locale={locale} /> <CalendarWidgetProvider {...props} locale={locale ?? 'ru'} />
); );
const DayOfWeekCellWithProvider = withProvider(Provider)(DayOfWeekCellComponent); const DayOfWeekCellWithProvider = withProvider(Provider)(DayOfWeekCellComponent);

View File

@ -1,16 +1,21 @@
import PropTypes from 'prop-types';
import { useContext } from 'react'; import { useContext } from 'react';
import { WEEK_DAYS_LABEL, WEEK_DAYS } from 'shared/constants'; import type { WeekDaysValues } from 'shared/constants';
import { WEEK_DAYS_LABEL } from 'shared/constants';
import { CalendarWidgetContext } from '../../../CalendarWidgetContext'; import { CalendarWidgetContext } from '../../../CalendarWidgetContext';
import classes from './DayOfWeekCell.style.module.css'; import classes from './DayOfWeekCell.style.module.css';
const DayOfWeekCell = (props) => { import type { FC } from 'react';
export type DayOfWeekCellProps = {
day: WeekDaysValues;
};
const DayOfWeekCell: FC<DayOfWeekCellProps> = (props) => {
const { day } = props; const { day } = props;
const { locale } = useContext(CalendarWidgetContext); const { locale } = useContext(CalendarWidgetContext);
const content = WEEK_DAYS_LABEL[day][locale]?.short; const content = WEEK_DAYS_LABEL[day]?.[locale].short;
return ( return (
<span className={classes.content} data-testid="day-of-week-cell"> <span className={classes.content} data-testid="day-of-week-cell">
@ -20,8 +25,4 @@ const DayOfWeekCell = (props) => {
); );
}; };
DayOfWeekCell.propTypes = {
day: PropTypes.oneOf(Object.values(WEEK_DAYS)).isRequired,
};
export default DayOfWeekCell; export default DayOfWeekCell;

View File

@ -15,11 +15,15 @@ describe('Test MonthSelect', () => {
user = userEvent.setup(); user = userEvent.setup();
}); });
const setup = (props) => { const setup = (props, providerProps) => {
const MonthSelectComponent = () => ( const MonthSelectComponent = () => (
<MonthSelect {...props} /> <MonthSelect {...props} />
); );
const MonthSelectWithProvider = withProvider(CalendarWidgetProvider)(MonthSelectComponent);
const Provider = (params) => (
<CalendarWidgetProvider {...params} {...providerProps} />
);
const MonthSelectWithProvider = withProvider(Provider)(MonthSelectComponent);
render(<MonthSelectWithProvider />); render(<MonthSelectWithProvider />);
}; };
@ -40,13 +44,13 @@ describe('Test MonthSelect', () => {
test('onChange handler', async () => { test('onChange handler', async () => {
const onChange = jest.fn(); const onChange = jest.fn();
setup({ onChange }); setup({ onChange }, { value: new Date(2020, 9, 15) });
const monthSelectElem = screen.getByTestId('month-select'); const monthSelectElem = screen.getByTestId('month-select');
await user.selectOptions(monthSelectElem, String(MONTHS.JULY)); await user.selectOptions(monthSelectElem, String(MONTHS.JULY));
expect(onChange).toHaveBeenCalled(); expect(onChange).toHaveBeenCalled();
const newDate = new Date(2024, 6); const newDate = new Date(2020, 6);
expect(onChange).toHaveBeenCalledWith(newDate); expect(onChange).toHaveBeenCalledWith(newDate);
}); });
}); });

View File

@ -7,18 +7,25 @@ import { CalendarWidgetProvider } from '../../../CalendarWidgetContext';
import NextMonthButton from './NextMonthButton.ui'; import NextMonthButton from './NextMonthButton.ui';
import type { NextMonthButtonProps } from './NextMonthButton.ui';
import type { CalendarWidgetProviderProps } from '../../../CalendarWidgetContext';
import type { UserEvent } from '@testing-library/user-event';
describe('Test NextMonthButton', () => { describe('Test NextMonthButton', () => {
let user; let user: UserEvent;
beforeEach(() => { beforeEach(() => {
user = userEvent.setup(); user = userEvent.setup();
}); });
const setup = (props) => { const setup = (props?: NextMonthButtonProps, providerProps?: CalendarWidgetProviderProps) => {
const NextMonthButtonComponent = () => ( const NextMonthButtonComponent = () => (
<NextMonthButton {...props} /> <NextMonthButton {...props} />
); );
const NextMonthButtonWithProvider = withProvider(CalendarWidgetProvider)(NextMonthButtonComponent); const Provider = (params: CalendarWidgetProviderProps) => (
<CalendarWidgetProvider {...providerProps} {...params} />
);
const NextMonthButtonWithProvider = withProvider(Provider)(NextMonthButtonComponent);
render(<NextMonthButtonWithProvider />); render(<NextMonthButtonWithProvider />);
}; };
@ -33,11 +40,12 @@ describe('Test NextMonthButton', () => {
test('onClick handler', async () => { test('onClick handler', async () => {
const onClick = jest.fn(); const onClick = jest.fn();
setup({ onClick }); setup({ onClick }, { value: new Date(2020, 9, 15) });
const nextMontButtonElem = screen.getByTestId('next-month-button'); const nextMontButtonElem = screen.getByTestId('next-month-button');
await user.click(nextMontButtonElem); await user.click(nextMontButtonElem);
expect(onClick).toHaveBeenCalled(); expect(onClick).toHaveBeenCalled();
expect(onClick).toHaveBeenCalledWith(new Date(2020, 10, 1));
}); });
}); });

View File

@ -1,9 +1,15 @@
import PropTypes from 'prop-types';
import { useContext } from 'react'; import { useContext } from 'react';
import { CalendarWidgetContext } from '../../../CalendarWidgetContext'; import { CalendarWidgetContext } from '../../../CalendarWidgetContext';
const NextMonthButton = (props) => { import type { ReactNode, FC } from 'react';
export type NextMonthButtonProps = {
children?: ReactNode;
onClick?: (newDate: Date) => void;
disabled?: boolean;
};
const NextMonthButton: FC<NextMonthButtonProps> = (props) => {
const { const {
children = 'next', children = 'next',
disabled, disabled,
@ -27,7 +33,7 @@ const NextMonthButton = (props) => {
return ( return (
<button <button
data-testid="next-month-button" data-testid="next-month-button"
disabled={disabled || maxDate < nextDate} disabled={disabled ?? maxDate < nextDate}
type="button" type="button"
onClick={onClickHandler} onClick={onClickHandler}
{...restProps} {...restProps}
@ -37,10 +43,4 @@ const NextMonthButton = (props) => {
); );
}; };
NextMonthButton.propTypes = {
children: PropTypes.node,
onClick: PropTypes.func,
disabled: PropTypes.bool,
};
export default NextMonthButton; export default NextMonthButton;

View File

@ -3,22 +3,28 @@ import { userEvent } from '@testing-library/user-event';
import { withProvider } from 'shared/hoc/withProvider'; import { withProvider } from 'shared/hoc/withProvider';
import { CalendarWidgetProvider } from '../../../CalendarWidgetContext'; import { CalendarWidgetProvider, type CalendarWidgetProviderProps } from '../../../CalendarWidgetContext';
import PrevMonthButton from './PrevMonthButton.ui'; import PrevMonthButton from './PrevMonthButton.ui';
import type { PrevMonthButtonProps } from './PrevMonthButton.ui';
import type { UserEvent } from '@testing-library/user-event';
describe('Test NextMonthButton', () => { describe('Test NextMonthButton', () => {
let user; let user: UserEvent;
beforeEach(() => { beforeEach(() => {
user = userEvent.setup(); user = userEvent.setup();
}); });
const setup = (props) => { const setup = (props?: PrevMonthButtonProps, providerProps?: CalendarWidgetProviderProps) => {
const PrevMonthButtonComponent = () => ( const PrevMonthButtonComponent = () => (
<PrevMonthButton {...props} /> <PrevMonthButton {...props} />
); );
const PrevMonthButtonWithProvider = withProvider(CalendarWidgetProvider)(PrevMonthButtonComponent); const Provider = (params: CalendarWidgetProviderProps) => (
<CalendarWidgetProvider {...providerProps} {...params} />
);
const PrevMonthButtonWithProvider = withProvider(Provider)(PrevMonthButtonComponent);
render(<PrevMonthButtonWithProvider />); render(<PrevMonthButtonWithProvider />);
}; };
@ -33,11 +39,12 @@ describe('Test NextMonthButton', () => {
test('onClick handler', async () => { test('onClick handler', async () => {
const onClick = jest.fn(); const onClick = jest.fn();
setup({ onClick }); setup({ onClick }, { value: new Date(2020, 9, 15) });
const prevMontButtonElem = screen.getByTestId('prev-month-button'); const prevMontButtonElem = screen.getByTestId('prev-month-button');
await user.click(prevMontButtonElem); await user.click(prevMontButtonElem);
expect(onClick).toHaveBeenCalled(); expect(onClick).toHaveBeenCalled();
expect(onClick).toHaveBeenCalledWith(new Date(2020, 9, 0));
}); });
}); });

View File

@ -1,9 +1,15 @@
import PropTypes from 'prop-types';
import { useContext } from 'react'; import { useContext } from 'react';
import { CalendarWidgetContext } from '../../../CalendarWidgetContext'; import { CalendarWidgetContext } from '../../../CalendarWidgetContext';
const PrevMonthButton = (props) => { import type { FC, ReactNode } from 'react';
export type PrevMonthButtonProps = {
children?: ReactNode;
onClick?: (newDate: Date) => void;
disabled?: boolean;
};
const PrevMonthButton: FC<PrevMonthButtonProps> = (props) => {
const { const {
children = 'prev', children = 'prev',
onClick, onClick,
@ -27,7 +33,7 @@ const PrevMonthButton = (props) => {
return ( return (
<button <button
data-testid="prev-month-button" data-testid="prev-month-button"
disabled={disabled || prevDate < minDate} disabled={disabled ?? prevDate < minDate}
type="button" type="button"
onClick={onClickHandler} onClick={onClickHandler}
{...restProps} {...restProps}
@ -37,10 +43,4 @@ const PrevMonthButton = (props) => {
); );
}; };
PrevMonthButton.propTypes = {
children: PropTypes.node,
onClick: PropTypes.func,
disabled: PropTypes.bool,
};
export default PrevMonthButton; export default PrevMonthButton;

View File

@ -7,14 +7,17 @@ import { CalendarWidgetProvider } from '../../../CalendarWidgetContext';
import TodayButton from './TodayButton.ui'; import TodayButton from './TodayButton.ui';
import type { TodayButtonProps } from './TodayButton.ui';
import type { UserEvent } from '@testing-library/user-event';
describe('Test NextMonthButton', () => { describe('Test NextMonthButton', () => {
let user; let user: UserEvent;
beforeEach(() => { beforeEach(() => {
user = userEvent.setup(); user = userEvent.setup();
}); });
const setup = (props) => { const setup = (props?: TodayButtonProps) => {
const TodayButtonComponent = () => ( const TodayButtonComponent = () => (
<TodayButton {...props} /> <TodayButton {...props} />
); );
@ -40,7 +43,8 @@ describe('Test NextMonthButton', () => {
expect(onClick).toHaveBeenCalled(); expect(onClick).toHaveBeenCalled();
const onClickArgument = onClick.mock.calls[0][0]; // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const onClickArgument = onClick.mock.calls[0][0] as number;
const calledDate = new Date(onClickArgument); const calledDate = new Date(onClickArgument);
const calledYear = calledDate.getFullYear(); const calledYear = calledDate.getFullYear();
const calledMonthIndex = calledDate.getMonth(); const calledMonthIndex = calledDate.getMonth();

View File

@ -1,9 +1,14 @@
import PropTypes from 'prop-types';
import { useContext } from 'react'; import { useContext } from 'react';
import { CalendarWidgetContext } from '../../../CalendarWidgetContext'; import { CalendarWidgetContext } from '../../../CalendarWidgetContext';
const TodayButton = (props) => { import type { FC, ReactNode } from 'react';
export type TodayButtonProps = {
children?: ReactNode;
onClick?: (newDate: Date) => void;
};
const TodayButton: FC<TodayButtonProps> = (props) => {
const { const {
children = 'today', children = 'today',
onClick, onClick,
@ -30,9 +35,4 @@ const TodayButton = (props) => {
); );
}; };
TodayButton.propTypes = {
children: PropTypes.node,
onClick: PropTypes.func,
};
export default TodayButton; export default TodayButton;

View File

@ -7,14 +7,17 @@ import { CalendarWidgetProvider } from '../../../CalendarWidgetContext';
import YearSelect from './YearSelect.ui'; import YearSelect from './YearSelect.ui';
import type { YearSelectProps } from './YearSelect.ui';
import type { UserEvent } from '@testing-library/user-event';
describe('Test YearSelect', () => { describe('Test YearSelect', () => {
let user; let user: UserEvent;
beforeEach(() => { beforeEach(() => {
user = userEvent.setup(); user = userEvent.setup();
}); });
const setup = (props) => { const setup = (props?: YearSelectProps) => {
const YearSelectComponent = () => ( const YearSelectComponent = () => (
<YearSelect {...props} /> <YearSelect {...props} />
); );

View File

@ -1,20 +1,25 @@
import PropTypes from 'prop-types';
import { useContext } from 'react'; import { useContext } from 'react';
import { CalendarWidgetContext } from '../../../CalendarWidgetContext'; import { CalendarWidgetContext } from '../../../CalendarWidgetContext';
const YearSelect = (props) => { import type { FC, ChangeEventHandler } from 'react';
export type YearSelectProps = {
onChange?: (newDate: Date) => void;
};
const YearSelect: FC<YearSelectProps> = (props) => {
const { const {
onChange, onChange,
...restProps ...restProps
} = props; } = props;
// todo eslint fix // todo eslint fix
const { const {
targetYear, targetMonthIndex, setTargetDate, minDate, maxDate, targetYear, targetMonthIndex, setTargetDate, minDate, maxDate,
} = useContext(CalendarWidgetContext); } = useContext(CalendarWidgetContext);
const onChangeHandler = (event) => { const onChangeHandler: ChangeEventHandler<HTMLSelectElement> = (event) => {
let newDate = new Date(event.target.value, targetMonthIndex); let newDate = new Date(Number(event.target.value), targetMonthIndex);
if (newDate < minDate) { if (newDate < minDate) {
newDate = minDate; newDate = minDate;
@ -54,8 +59,6 @@ const YearSelect = (props) => {
return ( return (
<select <select
data-testid="year-select" data-testid="year-select"
id=""
name="year"
value={targetYear} value={targetYear}
onChange={onChangeHandler} onChange={onChangeHandler}
{...restProps} {...restProps}
@ -65,8 +68,4 @@ const YearSelect = (props) => {
); );
}; };
YearSelect.propTypes = {
onChange: PropTypes.func,
};
export default YearSelect; export default YearSelect;