diff --git a/src/calendar/__tests__/calendar.test.tsx b/src/calendar/__tests__/calendar.test.tsx index a2fc95ac459..e9c77b2d963 100644 --- a/src/calendar/__tests__/calendar.test.tsx +++ b/src/calendar/__tests__/calendar.test.tsx @@ -3,6 +3,8 @@ import * as React from 'react'; import { fireEvent, render } from '@testing-library/react'; +import addMonths from 'date-fns/addMonths'; +import range from 'lodash/range'; import MockDate from 'mockdate'; import '../../__a11y__/to-validate-a11y'; @@ -42,13 +44,19 @@ function getDayText(wrapper: CalendarWrapper, row: number, col: number) { return wrapper.findDateAt(row, col).findByClassName(styles['date-inner'])!.getElement().textContent; } -describe('Calendar', () => { - test('check a11y', async () => { - const { container } = renderCalendar(); - await expect(container).toValidateA11y(); - }); +test('check a11y', async () => { + const { container } = renderCalendar(); + await expect(container).toValidateA11y(); }); +test.each(range(0, 11).map(month => addMonths(new Date('2025-01-01'), month).toISOString().split('T')[0]))( + 'always renders 42 days, value=%s', + value => { + renderCalendar({ value }); + expect(document.querySelectorAll(`.${styles['calendar-date']}`)).toHaveLength(42); + } +); + describe('Calendar locale US', () => { beforeEach(() => { const locale = new Intl.DateTimeFormat('en-US', { timeZone: 'EST' }); @@ -60,6 +68,22 @@ describe('Calendar locale US', () => { const { wrapper } = renderCalendar(); expect(findCalendarWeekdays(wrapper)[0]).toBe('Sun'); }); + + describe('Calendar header', () => { + test('previous button navigates to previous month', () => { + const { wrapper } = renderCalendar({ value: '2022-01-07' }); + expect(wrapper.findHeader().getElement()).toHaveTextContent('January 2022'); + wrapper.findPreviousButton().click(); + expect(wrapper.findHeader().getElement()).toHaveTextContent('December 2021'); + }); + + test('next button navigates to next month', () => { + const { wrapper } = renderCalendar({ value: '2022-01-07' }); + expect(wrapper.findHeader().getElement()).toHaveTextContent('January 2022'); + wrapper.findNextButton().click(); + expect(wrapper.findHeader().getElement()).toHaveTextContent('February 2022'); + }); + }); }); describe('Calendar locale DE', () => { @@ -75,22 +99,6 @@ describe('Calendar locale DE', () => { }); }); -describe('Calendar header', () => { - test('previous button navigates to previous month', () => { - const { wrapper } = renderCalendar({ value: '2022-01-07' }); - expect(wrapper.findHeader().getElement()).toHaveTextContent('January 2022'); - wrapper.findPreviousButton().click(); - expect(wrapper.findHeader().getElement()).toHaveTextContent('December 2021'); - }); - - test('next button navigates to next month', () => { - const { wrapper } = renderCalendar({ value: '2022-01-07' }); - expect(wrapper.findHeader().getElement()).toHaveTextContent('January 2022'); - wrapper.findNextButton().click(); - expect(wrapper.findHeader().getElement()).toHaveTextContent('February 2022'); - }); -}); - describe('aria labels', () => { describe('aria-label', () => { test('can be set', () => { diff --git a/src/calendar/grid/use-calendar-grid-rows.ts b/src/calendar/grid/use-calendar-grid-rows.ts index 66d0e25cdd2..153eec8a47f 100644 --- a/src/calendar/grid/use-calendar-grid-rows.ts +++ b/src/calendar/grid/use-calendar-grid-rows.ts @@ -2,9 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 import { useMemo } from 'react'; +import { addMonths } from 'date-fns'; import { getCalendarMonth } from 'mnth'; -import { normalizeStartOfWeek } from '../../internal/utils/locale/index.js'; +import { DayIndex, normalizeStartOfWeek } from '../../internal/utils/locale/index.js'; import { CalendarProps } from '../interfaces.js'; export default function useCalendarGridRows({ @@ -24,7 +25,7 @@ export default function useCalendarGridRows({ () => isMonthPicker ? getCalendarYear(baseDate) - : getCalendarMonth(baseDate, { firstDayOfWeek: normalizeStartOfWeek(startOfWeek, locale) }), + : getCalendarMonthWithSixRows(baseDate, normalizeStartOfWeek(startOfWeek, locale)), [baseDate, isMonthPicker, startOfWeek, locale] ); @@ -38,3 +39,12 @@ function getCalendarYear(date: Date): Date[][] { .fill(0) .map((_, i: number) => new Array(3).fill(0).map((_, j: number) => new Date(year, i * 3 + j))); } + +function getCalendarMonthWithSixRows(date: Date, firstDayOfWeek: DayIndex) { + const rows = getCalendarMonth(date, { firstDayOfWeek }); + if (rows.length === 6) { + return rows; + } + const nextRow = getCalendarMonth(addMonths(date, 1), { firstDayOfWeek })[1]; + return [...rows, nextRow]; +} diff --git a/src/date-picker/__tests__/date-picker-calendar.test.tsx b/src/date-picker/__tests__/date-picker-calendar.test.tsx index 4e0ee915582..f979c16873a 100644 --- a/src/date-picker/__tests__/date-picker-calendar.test.tsx +++ b/src/date-picker/__tests__/date-picker-calendar.test.tsx @@ -18,6 +18,8 @@ import { import calendarStyles from '../../../lib/components/calendar/styles.selectors.js'; import screenreaderOnlyStyles from '../../../lib/components/internal/components/screenreader-only/styles.selectors.js'; +const toLocaleDateString = window.Date.prototype.toLocaleDateString; + describe('Date picker calendar', () => { const defaultProps: DatePickerProps = { i18nStrings: { @@ -40,7 +42,10 @@ describe('Date picker calendar', () => { const locale = new Intl.DateTimeFormat('en-US', { timeZone: 'UTC' }); jest.spyOn(Intl, 'DateTimeFormat').mockImplementation(() => locale); }); - afterEach(() => jest.restoreAllMocks()); + afterEach(() => { + jest.restoreAllMocks(); + window.Date.prototype.toLocaleDateString = toLocaleDateString; + }); describe('basic calendar interaction', () => { let wrapper: DatePickerWrapper, getByTestId: (selector: string) => HTMLElement; @@ -117,17 +122,17 @@ describe('Date picker calendar', () => { test('should allow locale override', () => { const locale = 'de-DE'; const localStringMock = jest.fn().mockReturnValue('März 2018'); - const oldImpl = window.Date.prototype.toLocaleDateString; window.Date.prototype.toLocaleDateString = localStringMock; const { wrapper } = renderDatePicker({ ...defaultProps, locale }); wrapper.findOpenCalendarButton().click(); expect(findCalendarHeaderText(wrapper)).toBe('März 2018'); - // we render 2018/03/22 which results in - // -> 35 (5 weeks á 7 days) + 7 (weekday names) * 2 + 1 (month name) - expect(localStringMock).toHaveBeenCalledTimes(51); + // For each calendar we render 6 weeks (42 days) and each requires a label. + // Additionally, we generate short and full labels for weekday names (14 in total), + // and 2 labels for month name. + // 42 + 14 + 2 = 58. + expect(localStringMock).toHaveBeenCalledTimes(58); expect(localStringMock).toHaveBeenCalledWith(locale, expect.any(Object)); - window.Date.prototype.toLocaleDateString = oldImpl; }); test('should override start day of week', () => { diff --git a/src/internal/utils/locale/index.ts b/src/internal/utils/locale/index.ts index db10bf2376a..f678a43261b 100644 --- a/src/internal/utils/locale/index.ts +++ b/src/internal/utils/locale/index.ts @@ -3,4 +3,4 @@ export { mergeLocales } from './merge-locales'; export { normalizeLocale } from './normalize-locale'; -export { normalizeStartOfWeek } from './normalize-start-of-week'; +export { normalizeStartOfWeek, DayIndex } from './normalize-start-of-week'; diff --git a/src/internal/utils/locale/normalize-start-of-week.ts b/src/internal/utils/locale/normalize-start-of-week.ts index 62269cc6607..3e8f45bc126 100644 --- a/src/internal/utils/locale/normalize-start-of-week.ts +++ b/src/internal/utils/locale/normalize-start-of-week.ts @@ -3,7 +3,7 @@ import { getWeekStartByLocale } from 'weekstart'; -type DayIndex = 0 | 1 | 2 | 3 | 4 | 5 | 6; +export type DayIndex = 0 | 1 | 2 | 3 | 4 | 5 | 6; export function normalizeStartOfWeek(startOfWeek: number | undefined, locale: string) { return (typeof startOfWeek === 'number' ? startOfWeek % 7 : getWeekStartByLocale(locale)) as DayIndex;