Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Fixes calendar jumping when switching months #3178

Merged
merged 2 commits into from
Jan 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -212,14 +212,15 @@ exports[`test-utils selectors 1`] = `
],
"date-range-picker": [
"awsui_apply-button_mgja0",
"awsui_calendar-date_1afkv",
"awsui_calendar-header_mgja0",
"awsui_calendar-next-month-btn_mgja0",
"awsui_calendar-prev-month-btn_mgja0",
"awsui_calendar-week_1afkv",
"awsui_cancel-button_mgja0",
"awsui_clear-button_mgja0",
"awsui_custom-range-duration-input_16zmw",
"awsui_custom-range-unit-select_16zmw",
"awsui_day_1mfbn",
"awsui_disabled-reason-tooltip_1mfbn",
"awsui_dropdown_mgja0",
"awsui_end-date-input_mgja0",
Expand All @@ -235,7 +236,6 @@ exports[`test-utils selectors 1`] = `
"awsui_start-date_1mfbn",
"awsui_start-time-input_mgja0",
"awsui_validation-error_mgja0",
"awsui_week_1mfbn",
],
"drawer": [
"awsui_drawer_1sxt8",
Expand Down
51 changes: 30 additions & 21 deletions src/calendar/__tests__/calendar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -42,13 +44,20 @@ 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();
});

const eachMonthOfTheYear = range(0, 11).map(month => addMonths(new Date('2025-01-01'), month).toISOString().split('T')[0]);
test.each(eachMonthOfTheYear)(
'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' });
Expand All @@ -60,6 +69,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', () => {
Expand All @@ -75,22 +100,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', () => {
Expand Down
27 changes: 10 additions & 17 deletions src/calendar/grid/use-calendar-grid-rows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@
// SPDX-License-Identifier: Apache-2.0

import { useMemo } from 'react';
import { getCalendarMonth } from 'mnth';

import { getCalendarMonthWithSixRows, getCalendarYear } from '../../internal/utils/date-time/calendar.js';
import { normalizeStartOfWeek } from '../../internal/utils/locale/index.js';
import { CalendarProps } from '../interfaces.js';

export default function useCalendarGridRows({
baseDate,
granularity,
locale,
startOfWeek,
startOfWeek: rawStartOfWeek,
}: {
baseDate: Date;
granularity: CalendarProps.Granularity;
Expand All @@ -20,21 +20,14 @@ export default function useCalendarGridRows({
}) {
const isMonthPicker = granularity === 'month';

const rows = useMemo<Date[][]>(
() =>
isMonthPicker
? getCalendarYear(baseDate)
: getCalendarMonth(baseDate, { firstDayOfWeek: normalizeStartOfWeek(startOfWeek, locale) }),
[baseDate, isMonthPicker, startOfWeek, locale]
);
const rows = useMemo<Date[][]>(() => {
if (isMonthPicker) {
return getCalendarYear(baseDate);
} else {
const startOfWeek = normalizeStartOfWeek(rawStartOfWeek, locale);
return getCalendarMonthWithSixRows(baseDate, { startOfWeek, padDates: 'after' });
}
}, [baseDate, isMonthPicker, rawStartOfWeek, locale]);

return rows;
}

// Returns a 3-by-4 matrix with dates corresponding to the initial date-time of each month of the year for a given date.
function getCalendarYear(date: Date): Date[][] {
const year = date.getFullYear();
return new Array(4)
.fill(0)
.map((_, i: number) => new Array(3).fill(0).map((_, j: number) => new Date(year, i * 3 + j)));
}
21 changes: 13 additions & 8 deletions src/date-picker/__tests__/date-picker-calendar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -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;
Expand Down Expand Up @@ -116,18 +121,18 @@ 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;
const localStringMock = jest.fn().mockReturnValue('translated');
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);
expect(findCalendarHeaderText(wrapper)).toBe('translated');
// 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', () => {
Expand Down
2 changes: 2 additions & 0 deletions src/date-range-picker/calendar/grids/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ export const Grids = ({
<InternalSpaceBetween size="xs" direction="horizontal">
{!isSingleGrid && (
<MonthlyGrid
padDates="before"
className={styles['first-grid']}
baseDate={addMonths(baseDate, -1)}
selectedEndDate={selectedEndDate}
Expand All @@ -195,6 +196,7 @@ export const Grids = ({
/>
)}
<MonthlyGrid
padDates="after"
className={styles['second-grid']}
baseDate={baseDate}
selectedEndDate={selectedEndDate}
Expand Down
Loading
Loading