@@ -48,6 +48,16 @@ export function CustomCalendarNavigation(props: CaptionProps) { | |||
const intl = useIntl(); | |||
const formatChevronLabel = (date?: Date) => { | |||
if (date === undefined) { | |||
return intl.formatMessage({ id: 'disabled_' }); | |||
} | |||
return `${intl.formatDate(date, { month: 'long', format: 'M' })} ${intl.formatDate(date, { | |||
year: 'numeric', | |||
format: 'y', | |||
})}`; | |||
}; | |||
const baseDate = startOfMonth(displayMonth); // reference date | |||
const months = range(MONTHS_IN_A_YEAR).map((month) => { | |||
@@ -74,8 +84,12 @@ export function CustomCalendarNavigation(props: CaptionProps) { | |||
<nav className="sw-flex sw-items-center sw-justify-between sw-py-1"> | |||
<InteractiveIcon | |||
Icon={ChevronLeftIcon} | |||
aria-label={intl.formatMessage({ id: 'previous_' })} | |||
aria-label={intl.formatMessage( | |||
{ id: 'previous_month_x' }, | |||
{ month: formatChevronLabel(previousMonth) }, | |||
)} | |||
className="sw-mr-2" | |||
disabled={previousMonth === undefined} | |||
onClick={() => { | |||
if (previousMonth) { | |||
goToMonth(previousMonth); | |||
@@ -116,8 +130,14 @@ export function CustomCalendarNavigation(props: CaptionProps) { | |||
<InteractiveIcon | |||
Icon={ChevronRightIcon} | |||
aria-label={intl.formatMessage({ id: 'next_' })} | |||
aria-label={intl.formatMessage( | |||
{ id: 'next_month_x' }, | |||
{ | |||
month: formatChevronLabel(nextMonth), | |||
}, | |||
)} | |||
className="sw-ml-2" | |||
disabled={nextMonth === undefined} | |||
onClick={() => { | |||
if (nextMonth) { | |||
goToMonth(nextMonth); |
@@ -18,9 +18,10 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { screen, within } from '@testing-library/react'; | |||
import { screen } from '@testing-library/react'; | |||
import userEvent from '@testing-library/user-event'; | |||
import { getMonth, getYear, parseISO } from 'date-fns'; | |||
import { byRole } from '../../../../../src/main/js/helpers/testSelector'; | |||
import { renderWithContext } from '../../../helpers/testUtils'; | |||
import { DatePicker } from '../DatePicker'; | |||
@@ -40,7 +41,7 @@ it('behaves correctly', async () => { | |||
const nav = screen.getByRole('navigation'); | |||
expect(nav).toBeInTheDocument(); | |||
await user.click(within(nav).getByRole('button', { name: 'previous_' })); | |||
await user.click(byRole('navigation').byRole('button', { name: 'previous_month_x' }).get()); | |||
await user.click(screen.getByText('7')); | |||
expect(onChange).toHaveBeenCalled(); | |||
@@ -54,7 +55,7 @@ it('behaves correctly', async () => { | |||
* Then check that onChange was correctly called with a date in the following month | |||
*/ | |||
await user.click(screen.getByRole('textbox')); | |||
await user.click(screen.getByRole('button', { name: 'next_' })); | |||
await user.click(screen.getByRole('button', { name: 'next_month_x' })); | |||
await user.click(screen.getByText('12')); | |||
expect(onChange).toHaveBeenCalled(); | |||
@@ -84,6 +85,25 @@ it('behaves correctly', async () => { | |||
expect(getYear(newDate3)).toBe(2019); | |||
}); | |||
it('should disable next navigation when not in the accepted range', async () => { | |||
const user = userEvent.setup(); | |||
const currentDate = parseISO('2022-11-13'); | |||
renderDatePicker({ | |||
currentMonth: currentDate, | |||
maxDate: parseISO('2022-12-30'), | |||
value: currentDate, | |||
// eslint-disable-next-line jest/no-conditional-in-test | |||
valueFormatter: (date?: Date) => (date ? 'formatted date' : 'no date'), | |||
}); | |||
await user.click(screen.getByRole('textbox')); | |||
await user.click(screen.getByRole('button', { name: 'next_month_x' })); | |||
expect(screen.getByRole('button', { name: 'next_month_x' })).toBeDisabled(); | |||
}); | |||
it('should clear the value', async () => { | |||
const user = userEvent.setup(); | |||
@@ -136,6 +156,6 @@ function renderDatePicker(overrides: Partial<DatePicker['props']> = {}) { | |||
placeholder="placeholder" | |||
valueFormatter={defaultFormatter} | |||
{...overrides} | |||
/> | |||
/>, | |||
); | |||
} |
@@ -17,9 +17,10 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { screen, within } from '@testing-library/react'; | |||
import { screen } from '@testing-library/react'; | |||
import userEvent from '@testing-library/user-event'; | |||
import { formatISO, parseISO } from 'date-fns'; | |||
import { byRole } from '../../../../../src/main/js/helpers/testSelector'; | |||
import { IntlWrapper, render } from '../../../helpers/testUtils'; | |||
import { DateRangePicker } from '../DateRangePicker'; | |||
@@ -33,6 +34,7 @@ afterEach(() => { | |||
}); | |||
it('behaves correctly', async () => { | |||
const nav = byRole('navigation'); | |||
// Remove delay to play nice with fake timers | |||
const user = userEvent.setup({ delay: null }); | |||
@@ -41,13 +43,12 @@ it('behaves correctly', async () => { | |||
await user.click(screen.getByRole('textbox', { name: 'from' })); | |||
const fromDateNav = screen.getByRole('navigation'); | |||
expect(fromDateNav).toBeInTheDocument(); | |||
expect(nav.get()).toBeInTheDocument(); | |||
await user.click(within(fromDateNav).getByRole('button', { name: 'previous' })); | |||
await user.click(nav.byRole('button', { name: 'previous_month_x' }).get()); | |||
await user.click(screen.getByText('7')); | |||
expect(screen.queryByRole('navigation')).not.toBeInTheDocument(); | |||
expect(nav.query()).not.toBeInTheDocument(); | |||
expect(onChange).toHaveBeenCalled(); | |||
const { from } = onChange.mock.calls[0][0]; // first argument | |||
@@ -58,15 +59,14 @@ it('behaves correctly', async () => { | |||
jest.runAllTimers(); | |||
const toDateNav = await screen.findByRole('navigation'); | |||
const previousButton = within(toDateNav).getByRole('button', { name: 'previous' }); | |||
const nextButton = within(toDateNav).getByRole('button', { name: 'next' }); | |||
await user.click(previousButton); | |||
await user.click(nextButton); | |||
await user.click(previousButton); | |||
const previousButton = nav.byRole('button', { name: 'previous_month_x' }); | |||
const nextButton = nav.byRole('button', { name: 'next_month_x' }); | |||
await user.click(previousButton.get()); | |||
await user.click(nextButton.get()); | |||
await user.click(previousButton.get()); | |||
await user.click(screen.getByText('12')); | |||
expect(screen.queryByRole('navigation')).not.toBeInTheDocument(); | |||
expect(nav.query()).not.toBeInTheDocument(); | |||
expect(onChange).toHaveBeenCalled(); | |||
const { to } = onChange.mock.calls[0][0]; // first argument | |||
@@ -88,6 +88,6 @@ function renderDateRangePicker(overrides: Partial<DateRangePicker['props']> = {} | |||
valueFormatter={defaultFormatter} | |||
{...overrides} | |||
/> | |||
</IntlWrapper> | |||
</IntlWrapper>, | |||
); | |||
} |
@@ -30,7 +30,7 @@ import { MemoryRouter, Route, Routes } from 'react-router-dom'; | |||
export function render( | |||
ui: React.ReactElement, | |||
options?: RenderOptions, | |||
userEventOptions?: UserEventsOptions | |||
userEventOptions?: UserEventsOptions, | |||
) { | |||
return { ...rtlRender(ui, options), user: userEvent.setup(userEventOptions) }; | |||
} | |||
@@ -42,7 +42,7 @@ type RenderContextOptions = Omit<RenderOptions, 'wrapper'> & { | |||
export function renderWithContext( | |||
ui: React.ReactElement, | |||
{ userEventOptions, ...options }: RenderContextOptions = {} | |||
{ userEventOptions, ...options }: RenderContextOptions = {}, | |||
) { | |||
return render(ui, { ...options, wrapper: getContextWrapper() }, userEventOptions); | |||
} | |||
@@ -53,7 +53,7 @@ interface RenderRouterOptions { | |||
export function renderWithRouter( | |||
ui: React.ReactElement, | |||
options: RenderContextOptions & RenderRouterOptions = {} | |||
options: RenderContextOptions & RenderRouterOptions = {}, | |||
) { | |||
const { additionalRoutes, userEventOptions, ...renderOptions } = options; | |||
@@ -128,7 +128,10 @@ export function IntlWrapper({ | |||
messages={messages} | |||
onError={(e) => { | |||
// ignore missing translations, there are none! | |||
if (e.code !== ReactIntlErrorCode.MISSING_TRANSLATION) { | |||
if ( | |||
e.code !== ReactIntlErrorCode.MISSING_TRANSLATION && | |||
e.code !== ReactIntlErrorCode.UNSUPPORTED_FORMATTER | |||
) { | |||
// eslint-disable-next-line no-console | |||
console.error(e); | |||
} |
@@ -73,6 +73,7 @@ descending=Descending | |||
description=Description | |||
directories=Directories | |||
directory=Directory | |||
disabled_=disabled | |||
dismiss=Dismiss | |||
dismiss_permanently=Dismiss permanently | |||
display=Display | |||
@@ -146,6 +147,7 @@ navigation=Navigation | |||
never=Never | |||
new=New | |||
next=Next | |||
next_month_x=next month {month} | |||
new_name=New name | |||
next_=next | |||
none=None | |||
@@ -166,6 +168,7 @@ path=Path | |||
permalink=Permanent Link | |||
plugin=Plugin | |||
previous_=previous | |||
previous_month_x=previous month {month} | |||
project=Project | |||
project_x=Project: {0} | |||
projects=Projects |