From 4d36a0707a21ee883d1ab167781b7b198798aad5 Mon Sep 17 00:00:00 2001 From: Saumya Talwani <68903741+saumyatalwani@users.noreply.github.com> Date: Thu, 19 Feb 2026 07:57:11 +0530 Subject: [PATCH] test: add tests for base > date-time-picker (#32396) --- .../calendar/days-of-week.spec.tsx | 24 + .../calendar/index.spec.tsx | 114 ++++ .../calendar/item.spec.tsx | 137 ++++ .../common/option-list-item.spec.tsx | 137 ++++ .../date-picker/footer.spec.tsx | 97 +++ .../date-picker/header.spec.tsx | 78 +++ .../date-picker/index.spec.tsx | 616 +++++++++++++++++ .../base/date-and-time-picker/hooks.spec.ts | 94 +++ .../time-picker/footer.spec.tsx | 50 ++ .../time-picker/header.spec.tsx | 30 + .../time-picker/index.spec.tsx | 644 ++++++++++++++++-- .../time-picker/options.spec.tsx | 97 +++ .../utils/dayjs-extended.spec.ts | 366 ++++++++++ .../year-and-month-picker/footer.spec.tsx | 50 ++ .../year-and-month-picker/header.spec.tsx | 47 ++ .../year-and-month-picker/options.spec.tsx | 81 +++ 16 files changed, 2624 insertions(+), 38 deletions(-) create mode 100644 web/app/components/base/date-and-time-picker/calendar/days-of-week.spec.tsx create mode 100644 web/app/components/base/date-and-time-picker/calendar/index.spec.tsx create mode 100644 web/app/components/base/date-and-time-picker/calendar/item.spec.tsx create mode 100644 web/app/components/base/date-and-time-picker/common/option-list-item.spec.tsx create mode 100644 web/app/components/base/date-and-time-picker/date-picker/footer.spec.tsx create mode 100644 web/app/components/base/date-and-time-picker/date-picker/header.spec.tsx create mode 100644 web/app/components/base/date-and-time-picker/date-picker/index.spec.tsx create mode 100644 web/app/components/base/date-and-time-picker/hooks.spec.ts create mode 100644 web/app/components/base/date-and-time-picker/time-picker/footer.spec.tsx create mode 100644 web/app/components/base/date-and-time-picker/time-picker/header.spec.tsx create mode 100644 web/app/components/base/date-and-time-picker/time-picker/options.spec.tsx create mode 100644 web/app/components/base/date-and-time-picker/utils/dayjs-extended.spec.ts create mode 100644 web/app/components/base/date-and-time-picker/year-and-month-picker/footer.spec.tsx create mode 100644 web/app/components/base/date-and-time-picker/year-and-month-picker/header.spec.tsx create mode 100644 web/app/components/base/date-and-time-picker/year-and-month-picker/options.spec.tsx diff --git a/web/app/components/base/date-and-time-picker/calendar/days-of-week.spec.tsx b/web/app/components/base/date-and-time-picker/calendar/days-of-week.spec.tsx new file mode 100644 index 0000000000..334b6fdbe9 --- /dev/null +++ b/web/app/components/base/date-and-time-picker/calendar/days-of-week.spec.tsx @@ -0,0 +1,24 @@ +import { render, screen } from '@testing-library/react' +import { DaysOfWeek } from './days-of-week' + +describe('DaysOfWeek', () => { + // Rendering test + describe('Rendering', () => { + it('should render 7 day labels', () => { + render() + + // The global i18n mock returns keys like "time.daysInWeek.Sun" + const dayElements = screen.getAllByText(/daysInWeek/) + expect(dayElements).toHaveLength(7) + }) + + it('should render each day of the week', () => { + render() + + const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] + days.forEach((day) => { + expect(screen.getByText(new RegExp(day))).toBeInTheDocument() + }) + }) + }) +}) diff --git a/web/app/components/base/date-and-time-picker/calendar/index.spec.tsx b/web/app/components/base/date-and-time-picker/calendar/index.spec.tsx new file mode 100644 index 0000000000..51104864bb --- /dev/null +++ b/web/app/components/base/date-and-time-picker/calendar/index.spec.tsx @@ -0,0 +1,114 @@ +import type { CalendarProps, Day } from '../types' +import { fireEvent, render, screen } from '@testing-library/react' +import dayjs from '../utils/dayjs' +import Calendar from './index' + +// Mock scrollIntoView since jsdom doesn't implement it +beforeAll(() => { + Element.prototype.scrollIntoView = vi.fn() +}) + +// Factory for creating mock days +const createMockDays = (count: number = 7): Day[] => { + return Array.from({ length: count }, (_, i) => ({ + date: dayjs('2024-06-01').add(i, 'day'), + isCurrentMonth: true, + })) +} + +// Factory for Calendar props +const createCalendarProps = (overrides: Partial = {}): CalendarProps => ({ + days: createMockDays(), + selectedDate: undefined, + onDateClick: vi.fn(), + ...overrides, +}) + +describe('Calendar', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + // Rendering tests + describe('Rendering', () => { + it('should render days of week header', () => { + const props = createCalendarProps() + render() + + // DaysOfWeek component renders day labels + const dayLabels = screen.getAllByText(/daysInWeek/) + expect(dayLabels).toHaveLength(7) + }) + + it('should render all calendar day items', () => { + const days = createMockDays(7) + const props = createCalendarProps({ days }) + render() + + const buttons = screen.getAllByRole('button') + expect(buttons).toHaveLength(7) + }) + + it('should accept wrapperClassName prop without errors', () => { + const props = createCalendarProps({ wrapperClassName: 'custom-class' }) + const { container } = render() + + // Verify the component renders successfully with wrapperClassName + const dayLabels = screen.getAllByText(/daysInWeek/) + expect(dayLabels).toHaveLength(7) + expect(container.firstChild).toHaveClass('custom-class') + }) + }) + + // Interaction tests + describe('Interactions', () => { + it('should call onDateClick when a day is clicked', () => { + const onDateClick = vi.fn() + const days = createMockDays(3) + const props = createCalendarProps({ days, onDateClick }) + render() + + const dayButtons = screen.getAllByRole('button') + fireEvent.click(dayButtons[1]) + + expect(onDateClick).toHaveBeenCalledTimes(1) + expect(onDateClick).toHaveBeenCalledWith(days[1].date) + }) + }) + + // Disabled dates tests + describe('Disabled Dates', () => { + it('should not call onDateClick for disabled dates', () => { + const onDateClick = vi.fn() + const days = createMockDays(3) + // Disable all dates + const getIsDateDisabled = vi.fn().mockReturnValue(true) + const props = createCalendarProps({ days, onDateClick, getIsDateDisabled }) + render() + + const dayButtons = screen.getAllByRole('button') + fireEvent.click(dayButtons[0]) + + expect(onDateClick).not.toHaveBeenCalled() + }) + + it('should pass getIsDateDisabled to CalendarItem', () => { + const getIsDateDisabled = vi.fn().mockReturnValue(false) + const days = createMockDays(2) + const props = createCalendarProps({ days, getIsDateDisabled }) + render() + + expect(getIsDateDisabled).toHaveBeenCalled() + }) + }) + + // Edge cases + describe('Edge Cases', () => { + it('should render empty calendar when days array is empty', () => { + const props = createCalendarProps({ days: [] }) + render() + + expect(screen.queryAllByRole('button')).toHaveLength(0) + }) + }) +}) diff --git a/web/app/components/base/date-and-time-picker/calendar/item.spec.tsx b/web/app/components/base/date-and-time-picker/calendar/item.spec.tsx new file mode 100644 index 0000000000..7fcfcaae1f --- /dev/null +++ b/web/app/components/base/date-and-time-picker/calendar/item.spec.tsx @@ -0,0 +1,137 @@ +import type { CalendarItemProps, Day } from '../types' +import { fireEvent, render, screen } from '@testing-library/react' +import dayjs from '../utils/dayjs' +import Item from './item' + +const createMockDay = (overrides: Partial = {}): Day => ({ + date: dayjs('2024-06-15'), + isCurrentMonth: true, + ...overrides, +}) + +const createItemProps = (overrides: Partial = {}): CalendarItemProps => ({ + day: createMockDay(), + selectedDate: undefined, + onClick: vi.fn(), + isDisabled: false, + ...overrides, +}) + +describe('CalendarItem', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + describe('Rendering', () => { + it('should render the day number', () => { + const props = createItemProps() + + render() + + expect(screen.getByRole('button', { name: '15' })).toBeInTheDocument() + }) + }) + + describe('Visual States', () => { + it('should have selected styles when date matches selectedDate', () => { + const selectedDate = dayjs('2024-06-15') + const props = createItemProps({ selectedDate }) + + render() + const button = screen.getByRole('button', { name: '15' }) + expect(button).toHaveClass('bg-components-button-primary-bg', 'text-components-button-primary-text') + }) + + it('should not have selected styles when date does not match selectedDate', () => { + const selectedDate = dayjs('2024-06-16') + const props = createItemProps({ selectedDate }) + + render() + const button = screen.getByRole('button', { name: '15' }) + expect(button).not.toHaveClass('bg-components-button-primary-bg', 'text-components-button-primary-text') + }) + + it('should have different styles when day is not in current month', () => { + const props = createItemProps({ + day: createMockDay({ isCurrentMonth: false }), + }) + + render() + const button = screen.getByRole('button', { name: '15' }) + expect(button).toHaveClass('text-text-quaternary') + }) + + it('should have different styles when day is in current month', () => { + const props = createItemProps({ + day: createMockDay({ isCurrentMonth: true }), + }) + + render() + const button = screen.getByRole('button', { name: '15' }) + expect(button).toHaveClass('text-text-secondary') + }) + }) + + describe('Click Behavior', () => { + it('should call onClick with the date when clicked', () => { + const onClick = vi.fn() + const day = createMockDay() + const props = createItemProps({ day, onClick }) + + render() + fireEvent.click(screen.getByRole('button')) + + expect(onClick).toHaveBeenCalledTimes(1) + expect(onClick).toHaveBeenCalledWith(day.date) + }) + + it('should not call onClick when isDisabled is true', () => { + const onClick = vi.fn() + const props = createItemProps({ onClick, isDisabled: true }) + + render() + fireEvent.click(screen.getByRole('button')) + + expect(onClick).not.toHaveBeenCalled() + }) + }) + + describe('Today Indicator', () => { + it('should render today indicator when date is today', () => { + const today = dayjs() + const props = createItemProps({ + day: createMockDay({ date: today }), + }) + + render() + + const button = screen.getByRole('button') + expect(button).toBeInTheDocument() + // Today's button should contain a child indicator element + expect(button.children.length).toBeGreaterThan(0) + }) + + it('should not render today indicator when date is not today', () => { + const notToday = dayjs('2020-01-01') + const props = createItemProps({ + day: createMockDay({ date: notToday }), + }) + + render() + + const button = screen.getByRole('button') + // Non-today button should only contain the day number text, no extra children + expect(button.children.length).toBe(0) + }) + }) + + describe('Edge Cases', () => { + it('should handle undefined selectedDate', () => { + const props = createItemProps({ selectedDate: undefined }) + + render() + + expect(screen.getByRole('button')).toBeInTheDocument() + }) + }) +}) diff --git a/web/app/components/base/date-and-time-picker/common/option-list-item.spec.tsx b/web/app/components/base/date-and-time-picker/common/option-list-item.spec.tsx new file mode 100644 index 0000000000..760ba62ddc --- /dev/null +++ b/web/app/components/base/date-and-time-picker/common/option-list-item.spec.tsx @@ -0,0 +1,137 @@ +import { fireEvent, render, screen } from '@testing-library/react' +import OptionListItem from './option-list-item' + +describe('OptionListItem', () => { + let originalScrollIntoView: Element['scrollIntoView'] + beforeEach(() => { + vi.clearAllMocks() + originalScrollIntoView = Element.prototype.scrollIntoView + Element.prototype.scrollIntoView = vi.fn() + }) + + afterEach(() => { + Element.prototype.scrollIntoView = originalScrollIntoView + }) + + describe('Rendering', () => { + it('should render children content', () => { + render( + + Test Item + , + ) + + expect(screen.getByText('Test Item')).toBeInTheDocument() + }) + + it('should render as a list item element', () => { + render( + + Item + , + ) + + expect(screen.getByRole('listitem')).toBeInTheDocument() + }) + }) + + describe('Selection State', () => { + it('should have selected styles when isSelected is true', () => { + render( + + Selected + , + ) + + const item = screen.getByRole('listitem') + expect(item).toHaveClass('bg-components-button-ghost-bg-hover') + }) + + it('should not have selected styles when isSelected is false', () => { + render( + + Not Selected + , + ) + + const item = screen.getByRole('listitem') + expect(item).not.toHaveClass('bg-components-button-ghost-bg-hover') + }) + }) + + describe('Auto-Scroll', () => { + it('should scroll into view on mount when isSelected is true', () => { + render( + + Selected + , + ) + + expect(Element.prototype.scrollIntoView).toHaveBeenCalledWith({ behavior: 'instant' }) + }) + + it('should not scroll into view on mount when isSelected is false', () => { + render( + + Not Selected + , + ) + + expect(Element.prototype.scrollIntoView).not.toHaveBeenCalled() + }) + + it('should not scroll into view on mount when noAutoScroll is true', () => { + render( + + No Scroll + , + ) + + expect(Element.prototype.scrollIntoView).not.toHaveBeenCalled() + }) + }) + + describe('Click Behavior', () => { + it('should call onClick when clicked', () => { + const handleClick = vi.fn() + + render( + + Clickable + , + ) + fireEvent.click(screen.getByRole('listitem')) + + expect(handleClick).toHaveBeenCalledTimes(1) + }) + + it('should scroll into view with smooth behavior on click', () => { + render( + + Item + , + ) + fireEvent.click(screen.getByRole('listitem')) + + expect(Element.prototype.scrollIntoView).toHaveBeenCalledWith({ behavior: 'smooth' }) + }) + }) + + describe('Edge Cases', () => { + it('should handle rapid clicks without errors', () => { + const handleClick = vi.fn() + render( + + Rapid Click + , + ) + + const item = screen.getByRole('listitem') + fireEvent.click(item) + fireEvent.click(item) + fireEvent.click(item) + + expect(handleClick).toHaveBeenCalledTimes(3) + }) + }) +}) diff --git a/web/app/components/base/date-and-time-picker/date-picker/footer.spec.tsx b/web/app/components/base/date-and-time-picker/date-picker/footer.spec.tsx new file mode 100644 index 0000000000..c164044484 --- /dev/null +++ b/web/app/components/base/date-and-time-picker/date-picker/footer.spec.tsx @@ -0,0 +1,97 @@ +import type { DatePickerFooterProps } from '../types' +import { fireEvent, render, screen } from '@testing-library/react' +import { ViewType } from '../types' +import Footer from './footer' + +// Factory for Footer props +const createFooterProps = (overrides: Partial = {}): DatePickerFooterProps => ({ + needTimePicker: true, + displayTime: '02:30 PM', + view: ViewType.date, + handleClickTimePicker: vi.fn(), + handleSelectCurrentDate: vi.fn(), + handleConfirmDate: vi.fn(), + ...overrides, +}) + +describe('DatePicker Footer', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + // Rendering tests + describe('Rendering', () => { + it('should render Now button and confirm button', () => { + const props = createFooterProps() + render(