From 2c586b67a8ecf63569ab2b87b6da9cbc1723b679 Mon Sep 17 00:00:00 2001 From: Anurag Hazra Date: Thu, 23 May 2024 14:40:33 +0530 Subject: [PATCH] test(blade): add datepicker tests (#2186) * test: add single datepicker tests * test: add date range tests * chore: minor changes * feat: add more tests * chore: update web snaps * chore: update rn snaps * chore: update --- .../BottomSheet.ssr.test.tsx.snap | 5 +- .../BottomSheet.web.test.tsx.snap | 9 +- .../BaseButton.native.test.tsx.snap | 2 +- .../BaseButton.web.test.tsx.snap | 2 +- .../__snapshots__/Button.native.test.tsx.snap | 2 +- .../__snapshots__/Button.web.test.tsx.snap | 2 +- .../ButtonGroup.web.test.tsx.snap | 6 +- .../DatePicker/CalendarHeader.web.tsx | 2 + .../DatePicker/DatePicker.stories.tsx | 2 +- .../__tests__/DatePicker.test.stories.tsx | 453 ++++++++++++++++++ .../__snapshots__/Drawer.web.test.tsx.snap | 4 +- .../AutoComplete.web.test.tsx.snap | 3 +- .../getFocusRingStyles.web.ts | 2 +- 13 files changed, 477 insertions(+), 17 deletions(-) create mode 100644 packages/blade/src/components/DatePicker/__tests__/DatePicker.test.stories.tsx diff --git a/packages/blade/src/components/BottomSheet/__tests__/__snapshots__/BottomSheet.ssr.test.tsx.snap b/packages/blade/src/components/BottomSheet/__tests__/__snapshots__/BottomSheet.ssr.test.tsx.snap index 0c9d92b76db..69151dd2adf 100644 --- a/packages/blade/src/components/BottomSheet/__tests__/__snapshots__/BottomSheet.ssr.test.tsx.snap +++ b/packages/blade/src/components/BottomSheet/__tests__/__snapshots__/BottomSheet.ssr.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[` should render a BottomSheet ssr 1`] = `"
"`; +exports[` should render a BottomSheet ssr 1`] = `"
"`; exports[` should render a BottomSheet ssr 2`] = ` .c3.c3.c3.c3.c3 { @@ -159,6 +159,7 @@ exports[` should render a BottomSheet ssr 2`] = ` } .c28.c28.c28.c28.c28 { + overflow: auto; -webkit-box-flex: 1; -webkit-flex-grow: 1; -ms-flex-positive: 1; @@ -820,7 +821,7 @@ exports[` should render a BottomSheet ssr 2`] = ` class="c28" data-blade-component="bottom-sheet-body" data-testid="bottomsheet-body" - style="-webkit-tap-highlight-color:revert;-webkit-touch-callout:revert;-webkit-user-select:auto;overscroll-behavior:contain;-webkit-overflow-scrolling:touch;user-select:auto;overflow:auto;touch-action:none" + style="-webkit-tap-highlight-color:revert;-webkit-touch-callout:revert;-webkit-user-select:auto;overscroll-behavior:contain;-webkit-overflow-scrolling:touch;user-select:auto;touch-action:none" >
should compose with DropdownButton 1`] = ` } .c11.c11.c11.c11.c11 { + overflow: auto; -webkit-box-flex: 1; -webkit-flex-grow: 1; -ms-flex-positive: 1; @@ -551,7 +552,7 @@ exports[` should compose with DropdownButton 1`] = ` class="c11" data-blade-component="bottom-sheet-body" data-testid="bottomsheet-body" - style="user-select: auto; overflow: auto;" + style="user-select: auto;" >
should render Header/Footer/Body properly on opened sta } .c28.c28.c28.c28.c28 { + overflow: auto; -webkit-box-flex: 1; -webkit-flex-grow: 1; -ms-flex-positive: 1; @@ -1555,7 +1557,7 @@ exports[` should render Header/Footer/Body properly on opened sta class="c28" data-blade-component="bottom-sheet-body" data-testid="bottomsheet-body" - style="user-select: auto; overflow: auto;" + style="user-select: auto;" >
should render empty header with padding 0 1`] = ` } .c9.c9.c9.c9.c9 { + overflow: auto; -webkit-box-flex: 1; -webkit-flex-grow: 1; -ms-flex-positive: 1; @@ -1904,7 +1907,7 @@ exports[` should render empty header with padding 0 1`] = ` class="c9" data-blade-component="bottom-sheet-body" data-testid="bottomsheet-body" - style="user-select: auto; overflow: auto;" + style="user-select: auto;" >
should render xsmall size button 1`] = ` } textAlign="center" > - PAY NOW + Pay Now diff --git a/packages/blade/src/components/Button/BaseButton/__tests__/__snapshots__/BaseButton.web.test.tsx.snap b/packages/blade/src/components/Button/BaseButton/__tests__/__snapshots__/BaseButton.web.test.tsx.snap index 78c380c3cd0..f772f29c3b6 100644 --- a/packages/blade/src/components/Button/BaseButton/__tests__/__snapshots__/BaseButton.web.test.tsx.snap +++ b/packages/blade/src/components/Button/BaseButton/__tests__/__snapshots__/BaseButton.web.test.tsx.snap @@ -6710,7 +6710,7 @@ exports[` should render xsmall size button 1`] = ` class="c4" data-blade-component="base-text" > - PAY NOW + Pay Now
diff --git a/packages/blade/src/components/Button/Button/__tests__/__snapshots__/Button.native.test.tsx.snap b/packages/blade/src/components/Button/Button/__tests__/__snapshots__/Button.native.test.tsx.snap index e1b5c984483..8cd1027a307 100644 --- a/packages/blade/src/components/Button/Button/__tests__/__snapshots__/Button.native.test.tsx.snap +++ b/packages/blade/src/components/Button/Button/__tests__/__snapshots__/Button.native.test.tsx.snap @@ -5246,7 +5246,7 @@ exports[`
diff --git a/packages/blade/src/components/ButtonGroup/__tests__/__snapshots__/ButtonGroup.web.test.tsx.snap b/packages/blade/src/components/ButtonGroup/__tests__/__snapshots__/ButtonGroup.web.test.tsx.snap index d9e7b216e2e..06eaec22a53 100644 --- a/packages/blade/src/components/ButtonGroup/__tests__/__snapshots__/ButtonGroup.web.test.tsx.snap +++ b/packages/blade/src/components/ButtonGroup/__tests__/__snapshots__/ButtonGroup.web.test.tsx.snap @@ -1957,7 +1957,7 @@ exports[` should render ButtonGroup with xsmall size 1`] = ` class="c5" data-blade-component="base-text" > - ONE + One @@ -1984,7 +1984,7 @@ exports[` should render ButtonGroup with xsmall size 1`] = ` class="c5" data-blade-component="base-text" > - TWO + Two @@ -2011,7 +2011,7 @@ exports[` should render ButtonGroup with xsmall size 1`] = ` class="c5" data-blade-component="base-text" > - THREE + Three diff --git a/packages/blade/src/components/DatePicker/CalendarHeader.web.tsx b/packages/blade/src/components/DatePicker/CalendarHeader.web.tsx index 0967f8876af..cfcf1a66e61 100644 --- a/packages/blade/src/components/DatePicker/CalendarHeader.web.tsx +++ b/packages/blade/src/components/DatePicker/CalendarHeader.web.tsx @@ -133,6 +133,7 @@ const CalendarHeader = ({ color="neutral" iconPosition="right" icon={ChevronDownIcon} + accessibilityLabel="Change month" > {month} {year} @@ -147,6 +148,7 @@ const CalendarHeader = ({ color="neutral" iconPosition="right" icon={ChevronDownIcon} + accessibilityLabel="Change decade" > {year} diff --git a/packages/blade/src/components/DatePicker/DatePicker.stories.tsx b/packages/blade/src/components/DatePicker/DatePicker.stories.tsx index 9fd6c4898e6..23c11bc4ed2 100644 --- a/packages/blade/src/components/DatePicker/DatePicker.stories.tsx +++ b/packages/blade/src/components/DatePicker/DatePicker.stories.tsx @@ -11,7 +11,7 @@ export default { tags: ['autodocs'], } as Meta>; -export const Calendar: StoryFn = ({ ...args }) => { +export const Calendar: StoryFn = () => { const [isOpen, setIsOpen] = React.useState(true); const [date, setDate] = React.useState([ new Date(), diff --git a/packages/blade/src/components/DatePicker/__tests__/DatePicker.test.stories.tsx b/packages/blade/src/components/DatePicker/__tests__/DatePicker.test.stories.tsx new file mode 100644 index 00000000000..f29710934a3 --- /dev/null +++ b/packages/blade/src/components/DatePicker/__tests__/DatePicker.test.stories.tsx @@ -0,0 +1,453 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import type { StoryFn } from '@storybook/react'; +import { within, userEvent } from '@storybook/testing-library'; +import { expect, jest } from '@storybook/jest'; +import type { Mock } from 'jest-mock'; +import React from 'react'; +import { DatesProvider } from '@mantine/dates'; +import { HeadlessMantineProvider } from '@mantine/core'; +import dayjs from 'dayjs'; +import type { DatesRangeValue, DateValue } from '../types'; +import type { DatePickerProps } from '../'; +import { DatePicker as DatePickerComponent } from '../'; +import { Box } from '~components/Box'; +import { Button } from '~components/Button'; + +const sleep = (ms: number): Promise => new Promise((resolve) => setTimeout(resolve, ms)); + +let onOpenChange: Mock | null = null; + +const BasicDatePicker = (props: DatePickerProps<'range' | 'range'>): React.ReactElement => ( + + + + + +); + +export const DatePickerShouldShow: StoryFn = (): React.ReactElement => { + onOpenChange = jest.fn(); + return ; +}; + +DatePickerShouldShow.play = async () => { + const { getByRole, queryByText } = within(document.body); + const input = getByRole('combobox', { name: /Select Date/i }); + // open + await userEvent.click(input); + await sleep(400); + await expect(onOpenChange).toBeCalledWith({ isOpen: true }); + await expect(queryByText('Sun')).toBeVisible(); + // close + await userEvent.click(input); + await sleep(400); + await expect(onOpenChange).toBeCalledWith({ isOpen: false }); + await expect(queryByText('Sun')).not.toBeInTheDocument(); + await expect(input).toHaveFocus(); + await expect(onOpenChange).toBeCalledTimes(2); +}; + +export const DatePickerDisabled: StoryFn = (): React.ReactElement => { + onOpenChange = jest.fn(); + return ( + + ); +}; + +DatePickerDisabled.play = async () => { + const { getByRole } = within(document.body); + const input = getByRole('combobox', { name: /Select Date/i }); + await userEvent.click(input); + await sleep(400); + await expect(onOpenChange).not.toBeCalled(); +}; + +export const DatePickerMinMaxDate: StoryFn = (): React.ReactElement => { + onOpenChange = jest.fn(); + return ( + + ); +}; + +DatePickerMinMaxDate.play = async () => { + const { getByRole } = within(document.body); + const input = getByRole('combobox', { name: /Select Date/i }); + // open + await userEvent.click(input); + await sleep(400); + // expect date to be disabled + const disabledDate = getByRole('button', { + name: dayjs().subtract(6, 'day').format('DD MMMM YYYY'), + }); + await expect(disabledDate).toBeDisabled(); + // expect month to be disabled + const month = getByRole('button', { name: /Change month/i }); + await userEvent.click(month); + const disabledMonth = getByRole('button', { + name: dayjs().subtract(1, 'month').format('MMM'), + }); + await expect(disabledMonth).toBeDisabled(); + // expect year to be disabled + const year = getByRole('button', { name: /Change decade/i }); + await userEvent.click(year); + const disabledYear = getByRole('button', { name: dayjs().subtract(1, 'year').format('YYYY') }); + await expect(disabledYear).toBeDisabled(); +}; + +export const DatePickerSingleSelect: StoryFn< + typeof DatePickerComponent +> = (): React.ReactElement => { + onOpenChange = jest.fn(); + return ; +}; + +DatePickerSingleSelect.play = async () => { + const { getByRole, queryByText } = within(document.body); + const input = getByRole('combobox', { name: /Select Date/i }); + // open + await userEvent.click(input); + await sleep(400); + await expect(onOpenChange).toBeCalledWith({ isOpen: true }); + await expect(queryByText('Sun')).toBeVisible(); + // select + const dateToSelect = dayjs().add(1, 'day'); + const date = getByRole('button', { name: dateToSelect.format('DD MMMM YYYY') }); + await userEvent.click(date); + // press apply button + const applyButton = getByRole('button', { name: /Apply/i }); + await userEvent.click(applyButton); + await sleep(400); + await expect(date).not.toBeVisible(); + // assert inputs value + await expect(input).toHaveValue(dateToSelect.format('DD/MM/YYYY')); + await expect(onOpenChange).toBeCalledTimes(2); +}; + +export const DatePickerSingleSelectCancel: StoryFn< + typeof DatePickerComponent +> = (): React.ReactElement => { + onOpenChange = jest.fn(); + return ; +}; + +DatePickerSingleSelectCancel.play = async () => { + const { getByRole, queryByText } = within(document.body); + const input = getByRole('combobox', { name: /Select Date/i }); + // open + await userEvent.click(input); + await sleep(400); + await expect(onOpenChange).toBeCalledWith({ isOpen: true }); + await expect(queryByText('Sun')).toBeVisible(); + // select + const dateToSelect = dayjs().add(1, 'day'); + const date = getByRole('button', { name: dateToSelect.format('DD MMMM YYYY') }); + await userEvent.click(date); + // assert inputs value + await expect(input).toHaveValue(dateToSelect.format('DD/MM/YYYY')); + // press cancel button + const cancelButton = getByRole('button', { name: /Cancel/i }); + await userEvent.click(cancelButton); + await sleep(400); + await expect(date).not.toBeVisible(); + await expect(input).toHaveValue(''); + await expect(onOpenChange).toBeCalledTimes(2); +}; + +export const DatePickerSingleSelectControlled: StoryFn< + typeof DatePickerComponent +> = (): React.ReactElement => { + const [value, setValue] = React.useState(dayjs().add(1, 'day').toDate()); + + return ( + + + + + { + setValue(date); + }} + /> + + + + ); +}; + +DatePickerSingleSelectControlled.play = async () => { + const { getByRole, queryByText } = within(document.body); + // assert inputs value + const input = getByRole('combobox', { name: /Select Date/i }); + await expect(input).toHaveValue(dayjs().add(1, 'day').format('DD/MM/YYYY')); + // open + await userEvent.click(input); + await sleep(400); + await expect(queryByText('Sun')).toBeVisible(); + // select + const changeButton = getByRole('button', { name: 'Change Date', hidden: true }); + await userEvent.click(changeButton); + await sleep(400); + // open again + await userEvent.click(input); + await sleep(400); + await expect(queryByText('Sun')).toBeVisible(); + // assert inputs value + await expect(input).toHaveValue(dayjs().add(5, 'day').format('DD/MM/YYYY')); + // select another date + const dateToSelect = dayjs().add(6, 'day'); + const date = getByRole('button', { name: dateToSelect.format('DD MMMM YYYY') }); + await userEvent.click(date); + // press apply button + const applyButton = getByRole('button', { name: /Apply/i }); + await userEvent.click(applyButton); + await sleep(400); + await expect(date).not.toBeVisible(); + // assert inputs value + await expect(input).toHaveValue(dateToSelect.format('DD/MM/YYYY')); +}; + +export const DatePickerSingleChangePicker: StoryFn< + typeof DatePickerComponent +> = (): React.ReactElement => { + return ( + + + + + + + + ); +}; + +DatePickerSingleChangePicker.play = async () => { + const { getByRole, queryByText } = within(document.body); + const input = getByRole('combobox', { name: /Select Date/i }); + // open + await userEvent.click(input); + await sleep(400); + await expect(queryByText('Sun')).toBeVisible(); + await userEvent.tab(); + await userEvent.tab(); + // go to month + const month = getByRole('button', { name: /Change month/i }); + await expect(month).toHaveFocus(); + await userEvent.click(month); + const year = getByRole('button', { name: /Change decade/i }); + await userEvent.click(year); + getByRole('button', { name: /previous/i }).focus(); + await userEvent.tab(); + await userEvent.tab(); + await sleep(400); + await expect(getByRole('button', { name: dayjs().format('YYYY') })).toHaveFocus(); + await sleep(400); + getByRole('button', { name: /previous/i }).focus(); + await userEvent.tab(); + await userEvent.tab(); + await userEvent.keyboard('{ArrowRight}'); + await userEvent.keyboard('{Enter}'); + await sleep(400); + getByRole('button', { name: /previous/i }).focus(); + await userEvent.tab(); + await userEvent.tab(); + await userEvent.tab(); + await userEvent.keyboard('{ArrowRight}'); + await userEvent.keyboard('{Enter}'); + await sleep(400); + getByRole('button', { name: /previous/i }).focus(); + await userEvent.tab(); + await userEvent.tab(); + await userEvent.tab(); + await userEvent.keyboard('{ArrowRight}'); + await userEvent.keyboard('{Enter}'); + // press apply + const applyButton = getByRole('button', { name: /Apply/i }); + await userEvent.click(applyButton); + await sleep(400); + // assert inputs value + await expect(input).toHaveValue(dayjs('02/02/2025').format('DD/MM/YYYY')); +}; + +export const DatePickerRangeSelect: StoryFn< + typeof DatePickerComponent +> = (): React.ReactElement => { + onOpenChange = jest.fn(); + return ( + + ); +}; + +DatePickerRangeSelect.play = async () => { + const { getByRole, queryByText } = within(document.body); + const startInput = getByRole('combobox', { name: /Start Date/i }); + const endInput = getByRole('combobox', { name: /End Date/i }); + // open + await userEvent.click(startInput); + await sleep(400); + await expect(onOpenChange).toBeCalledWith({ isOpen: true }); + await expect(queryByText('Apply')).toBeVisible(); + // select start date + const startDateToSelect = dayjs().add(1, 'day'); + await userEvent.click(getByRole('button', { name: startDateToSelect.format('DD MMMM YYYY') })); + // press next button + const nextButton = getByRole('button', { name: /Next/i }); + await userEvent.click(nextButton); + const endDateToSelect = dayjs().add(2, 'month').add(2, 'day'); + await userEvent.click(getByRole('button', { name: endDateToSelect.format('DD MMMM YYYY') })); + // press apply button + const applyButton = getByRole('button', { name: /Apply/i }); + await userEvent.click(applyButton); + await sleep(400); + await expect(queryByText('Apply')).toBeNull(); + // assert inputs value + await expect(startInput).toHaveValue(startDateToSelect.format('DD/MM/YYYY')); + await expect(endInput).toHaveValue(endDateToSelect.format('DD/MM/YYYY')); + await expect(onOpenChange).toBeCalledTimes(2); +}; + +export const DatePickerRangeSelectControlled: StoryFn< + typeof DatePickerComponent +> = (): React.ReactElement => { + const [value, setValue] = React.useState(() => [ + new Date(), + dayjs().add(6, 'day').toDate(), + ]); + + return ( + { + setValue(date); + }} + selectionType="range" + label={{ start: 'Start Date', end: 'End Date' }} + /> + ); +}; + +DatePickerRangeSelectControlled.play = async () => { + const { getByRole, queryByText } = within(document.body); + const startInput = getByRole('combobox', { name: /Start Date/i }); + const endInput = getByRole('combobox', { name: /End Date/i }); + // assert inputs value + await expect(startInput).toHaveValue(dayjs().format('DD/MM/YYYY')); + await expect(endInput).toHaveValue(dayjs().add(6, 'day').format('DD/MM/YYYY')); + + // open + await userEvent.click(startInput); + await sleep(400); + await expect(queryByText('Apply')).toBeVisible(); + // select start date + const startDateToSelect = dayjs().add(1, 'day'); + await userEvent.click(getByRole('button', { name: startDateToSelect.format('DD MMMM YYYY') })); + // press next button + const nextButton = getByRole('button', { name: /Next/i }); + await userEvent.click(nextButton); + const endDateToSelect = dayjs().add(2, 'month').add(2, 'day'); + await userEvent.click(getByRole('button', { name: endDateToSelect.format('DD MMMM YYYY') })); + // press apply button + const applyButton = getByRole('button', { name: /Apply/i }); + await userEvent.click(applyButton); + await sleep(400); + await expect(queryByText('Apply')).toBeNull(); + // assert inputs value + await expect(startInput).toHaveValue(startDateToSelect.format('DD/MM/YYYY')); + await expect(endInput).toHaveValue(endDateToSelect.format('DD/MM/YYYY')); +}; + +export const DatePickerPresets: StoryFn = (): React.ReactElement => { + onOpenChange = jest.fn(); + return ( + [dayjs(date).subtract(3, 'day').toDate(), date], + }, + { + label: 'Past 7 days', + value: (date) => [dayjs(date).subtract(7, 'day').toDate(), date], + }, + ]} + onOpenChange={onOpenChange} + /> + ); +}; + +DatePickerPresets.play = async () => { + const { getByRole, queryByText } = within(document.body); + const startInput = getByRole('combobox', { name: /Start Date/i }); + const endInput = getByRole('combobox', { name: /End Date/i }); + // open + await userEvent.click(startInput); + await sleep(400); + await expect(onOpenChange).toBeCalledWith({ isOpen: true }); + await expect(queryByText('Apply')).toBeVisible(); + // select a preset + await userEvent.click(getByRole('option', { name: /Past 3 days/i })); + // assert inputs value + await expect(startInput).toHaveValue(dayjs().subtract(3, 'day').format('DD/MM/YYYY')); + await expect(endInput).toHaveValue(dayjs().format('DD/MM/YYYY')); + // press apply button + const applyButton = getByRole('button', { name: /Apply/i }); + await userEvent.click(applyButton); + + // open again + await userEvent.click(startInput); + await sleep(400); + // assert past 3 days to be selected + await expect(getByRole('option', { name: /Past 3 days/i })).toHaveAttribute( + 'aria-selected', + 'true', + ); + + // change date to past 7 days manually + await userEvent.click( + getByRole('button', { name: dayjs().subtract(7, 'day').format('DD MMMM YYYY') }), + ); + await userEvent.click(getByRole('button', { name: dayjs().format('DD MMMM YYYY') })); + await sleep(400); + // assert past 3 days to be not selected anymore + await expect(getByRole('option', { name: /Past 3 days/i })).toHaveAttribute( + 'aria-selected', + 'false', + ); + // assert past 7 days to be selected + await expect(getByRole('option', { name: /Past 7 days/i })).toHaveAttribute( + 'aria-selected', + 'true', + ); + // press apply button + await userEvent.click(applyButton); + await sleep(400); + // assert inputs value + await expect(startInput).toHaveValue(dayjs().subtract(7, 'day').format('DD/MM/YYYY')); + await expect(endInput).toHaveValue(dayjs().format('DD/MM/YYYY')); +}; + +export default { + title: 'Components/Interaction Tests/DatePicker', + component: DatePickerComponent, + parameters: { + controls: { + disable: true, + }, + a11y: { disable: true }, + essentials: { disable: true }, + actions: { disable: true }, + }, +}; diff --git a/packages/blade/src/components/Drawer/__tests__/__snapshots__/Drawer.web.test.tsx.snap b/packages/blade/src/components/Drawer/__tests__/__snapshots__/Drawer.web.test.tsx.snap index ab2a6447fb9..45914428a5d 100644 --- a/packages/blade/src/components/Drawer/__tests__/__snapshots__/Drawer.web.test.tsx.snap +++ b/packages/blade/src/components/Drawer/__tests__/__snapshots__/Drawer.web.test.tsx.snap @@ -638,9 +638,9 @@ exports[`Drawer renders a Drawer 1`] = ` class="Svgweb__StyledSvg-vcmjs8-0" data-blade-component="icon" fill="none" - height="12px" + height="16px" viewBox="0 0 24 24" - width="12px" + width="16px" > & with should render Bott } .c44.c44.c44.c44.c44 { + overflow: auto; -webkit-box-flex: 1; -webkit-flex-grow: 1; -ms-flex-positive: 1; @@ -1636,7 +1637,7 @@ exports[` & with should render Bott class="c44" data-blade-component="bottom-sheet-body" data-testid="bottomsheet-body" - style="user-select: auto; overflow: auto;" + style="user-select: auto;" >