test: added tests for some base components (#32370)

This commit is contained in:
Saumya Talwani
2026-02-24 13:52:43 +05:30
committed by GitHub
parent 9819f7d69c
commit 0eaae4f573
8 changed files with 98 additions and 39 deletions

View File

@@ -1,5 +1,4 @@
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { sleep } from '@/utils'
import AutoHeightTextarea from './index'
@@ -18,8 +17,8 @@ describe('AutoHeightTextarea', () => {
describe('Rendering', () => {
it('should render without crashing', () => {
render(<AutoHeightTextarea value="" onChange={vi.fn()} />)
const textarea = document.querySelector('textarea')
const { container } = render(<AutoHeightTextarea value="" onChange={vi.fn()} />)
const textarea = container.querySelector('textarea')
expect(textarea).toBeInTheDocument()
})
@@ -37,26 +36,26 @@ describe('AutoHeightTextarea', () => {
describe('Props', () => {
it('should apply custom className to textarea', () => {
render(<AutoHeightTextarea value="" onChange={vi.fn()} className="custom-class" />)
const textarea = document.querySelector('textarea')
const { container } = render(<AutoHeightTextarea value="" onChange={vi.fn()} className="custom-class" />)
const textarea = container.querySelector('textarea')
expect(textarea).toHaveClass('custom-class')
})
it('should apply custom wrapperClassName to wrapper div', () => {
render(<AutoHeightTextarea value="" onChange={vi.fn()} wrapperClassName="wrapper-class" />)
const wrapper = document.querySelector('div.relative')
const { container } = render(<AutoHeightTextarea value="" onChange={vi.fn()} wrapperClassName="wrapper-class" />)
const wrapper = container.querySelector('div.relative')
expect(wrapper).toHaveClass('wrapper-class')
})
it('should apply minHeight and maxHeight styles to hidden div', () => {
render(<AutoHeightTextarea value="" onChange={vi.fn()} minHeight={50} maxHeight={200} />)
const hiddenDiv = document.querySelector('div.invisible')
const { container } = render(<AutoHeightTextarea value="" onChange={vi.fn()} minHeight={50} maxHeight={200} />)
const hiddenDiv = container.querySelector('div.invisible')
expect(hiddenDiv).toHaveStyle({ minHeight: '50px', maxHeight: '200px' })
})
it('should use default minHeight and maxHeight when not provided', () => {
render(<AutoHeightTextarea value="" onChange={vi.fn()} />)
const hiddenDiv = document.querySelector('div.invisible')
const { container } = render(<AutoHeightTextarea value="" onChange={vi.fn()} />)
const hiddenDiv = container.querySelector('div.invisible')
expect(hiddenDiv).toHaveStyle({ minHeight: '36px', maxHeight: '96px' })
})
@@ -64,6 +63,7 @@ describe('AutoHeightTextarea', () => {
const focusSpy = vi.spyOn(HTMLTextAreaElement.prototype, 'focus')
render(<AutoHeightTextarea value="" onChange={vi.fn()} autoFocus />)
expect(focusSpy).toHaveBeenCalled()
focusSpy.mockRestore()
})
})
@@ -122,7 +122,7 @@ describe('AutoHeightTextarea', () => {
it('should handle newlines in value', () => {
const textWithNewlines = 'line1\nline2\nline3'
render(<AutoHeightTextarea value={textWithNewlines} onChange={vi.fn()} />)
const textarea = document.querySelector('textarea')
const textarea = screen.getByRole('textbox')
expect(textarea).toHaveValue(textWithNewlines)
})

View File

@@ -1,4 +1,4 @@
import { fireEvent, render } from '@testing-library/react'
import { fireEvent, render, screen } from '@testing-library/react'
import AddButton from './add-button'
describe('AddButton', () => {
@@ -9,9 +9,9 @@ describe('AddButton', () => {
})
it('should render an add icon', () => {
const { container } = render(<AddButton onClick={vi.fn()} />)
const svg = container.querySelector('span')
expect(svg).toBeInTheDocument()
render(<AddButton onClick={vi.fn()} />)
const iconSpan = screen.getByTestId('add-button').querySelector('span')
expect(iconSpan).toBeInTheDocument()
})
})

View File

@@ -13,7 +13,7 @@ const AddButton: FC<Props> = ({
onClick,
}) => {
return (
<div className={cn(className, 'cursor-pointer select-none rounded-md p-1 hover:bg-state-base-hover')} onClick={onClick}>
<div className={cn(className, 'cursor-pointer select-none rounded-md p-1 hover:bg-state-base-hover')} onClick={onClick} data-testid="add-button">
<span className="i-ri-add-line h-4 w-4 text-text-tertiary" />
</div>
)

View File

@@ -13,9 +13,9 @@ describe('SyncButton', () => {
})
it('should render a refresh icon', () => {
const { container } = render(<SyncButton onClick={vi.fn()} />)
const svg = container.querySelector('span')
expect(svg).toBeInTheDocument()
render(<SyncButton onClick={vi.fn()} />)
const iconSpan = screen.getByTestId('sync-button').querySelector('span')
expect(iconSpan).toBeInTheDocument()
})
})
@@ -38,7 +38,7 @@ describe('SyncButton', () => {
it('should call onClick when clicked', () => {
const onClick = vi.fn()
render(<SyncButton onClick={onClick} />)
const clickableDiv = screen.getByTestId('sync-button')!
const clickableDiv = screen.getByTestId('sync-button')
fireEvent.click(clickableDiv)
expect(onClick).toHaveBeenCalledTimes(1)
})
@@ -46,7 +46,7 @@ describe('SyncButton', () => {
it('should call onClick multiple times on repeated clicks', () => {
const onClick = vi.fn()
render(<SyncButton onClick={onClick} />)
const clickableDiv = screen.getByTestId('sync-button')!
const clickableDiv = screen.getByTestId('sync-button')
fireEvent.click(clickableDiv)
fireEvent.click(clickableDiv)
fireEvent.click(clickableDiv)

View File

@@ -1,7 +1,6 @@
import type { Mock } from 'vitest'
import { act, fireEvent, render, screen } from '@testing-library/react'
import useEmblaCarousel from 'embla-carousel-react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { Carousel, useCarousel } from './index'
vi.mock('embla-carousel-react', () => ({
@@ -52,7 +51,9 @@ const createMockEmblaApi = (): MockEmblaApi => ({
})
const emitEmblaEvent = (event: EmblaEventName, api: MockEmblaApi | undefined = mockApi) => {
listeners[event].forEach(callback => callback(api))
listeners[event].forEach((callback) => {
callback(api)
})
}
const renderCarouselWithControls = (orientation: 'horizontal' | 'vertical' = 'horizontal') => {
@@ -60,6 +61,8 @@ const renderCarouselWithControls = (orientation: 'horizontal' | 'vertical' = 'ho
<Carousel orientation={orientation}>
<Carousel.Content data-testid="carousel-content">
<Carousel.Item>Slide 1</Carousel.Item>
<Carousel.Item>Slide 2</Carousel.Item>
<Carousel.Item>Slide 3</Carousel.Item>
</Carousel.Content>
<Carousel.Previous>Prev</Carousel.Previous>
<Carousel.Next>Next</Carousel.Next>
@@ -68,6 +71,13 @@ const renderCarouselWithControls = (orientation: 'horizontal' | 'vertical' = 'ho
)
}
const mockPlugin = () => ({
name: 'mock',
options: {},
init: vi.fn(),
destroy: vi.fn(),
})
describe('Carousel', () => {
beforeEach(() => {
vi.clearAllMocks()
@@ -90,22 +100,25 @@ describe('Carousel', () => {
expect(screen.getByRole('region')).toHaveAttribute('aria-roledescription', 'carousel')
expect(screen.getByTestId('carousel-content')).toHaveClass('flex')
expect(screen.getByRole('group')).toHaveAttribute('aria-roledescription', 'slide')
screen.getAllByRole('group').forEach((slide) => {
expect(slide).toHaveAttribute('aria-roledescription', 'slide')
})
})
})
// Props should be translated into Embla options and visible layout.
describe('Props', () => {
it('should configure embla with horizontal axis when orientation is omitted', () => {
const plugin = mockPlugin()
render(
<Carousel opts={{ loop: true }} plugins={['plugin-marker' as unknown as never]}>
<Carousel opts={{ loop: true }} plugins={[plugin]}>
<Carousel.Content />
</Carousel>,
)
expect(mockedUseEmblaCarousel).toHaveBeenCalledWith(
{ loop: true, axis: 'x' },
['plugin-marker'],
[plugin],
)
})

View File

@@ -80,7 +80,18 @@ export default function Drawer({
)}
{showClose && (
<DialogTitle className="mb-4 flex cursor-pointer items-center" as="div">
<span className="i-heroicons-x-mark h-4 w-4 text-text-tertiary" onClick={onClose} data-testid="close-icon" />
<span
className="i-heroicons-x-mark h-4 w-4 text-text-tertiary"
onClick={onClose}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ')
onClose()
}}
role="button"
tabIndex={0}
aria-label={t('operation.close', { ns: 'common' })}
data-testid="close-icon"
/>
</DialogTitle>
)}
</div>

View File

@@ -1,5 +1,5 @@
import { fireEvent, render, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import FloatRightContainer from './index'
describe('FloatRightContainer', () => {
@@ -94,7 +94,47 @@ describe('FloatRightContainer', () => {
const closeIcon = screen.getByTestId('close-icon')
expect(closeIcon).toBeInTheDocument()
fireEvent.click(closeIcon!)
await userEvent.click(closeIcon)
expect(onClose).toHaveBeenCalledTimes(1)
})
it('should call onClose when close is done using escape key', async () => {
const onClose = vi.fn()
render(
<FloatRightContainer
isMobile={true}
isOpen={true}
onClose={onClose}
showClose={true}
>
<div>Closable content</div>
</FloatRightContainer>,
)
const closeIcon = screen.getByTestId('close-icon')
closeIcon.focus()
await userEvent.keyboard('{Enter}')
expect(onClose).toHaveBeenCalledTimes(1)
})
it('should call onClose when close is done using space key', async () => {
const onClose = vi.fn()
render(
<FloatRightContainer
isMobile={true}
isOpen={true}
onClose={onClose}
showClose={true}
>
<div>Closable content</div>
</FloatRightContainer>,
)
const closeIcon = screen.getByTestId('close-icon')
closeIcon.focus()
await userEvent.keyboard(' ')
expect(onClose).toHaveBeenCalledTimes(1)
})
@@ -129,8 +169,9 @@ describe('FloatRightContainer', () => {
isOpen={true}
onClose={vi.fn()}
title="Empty mobile panel"
children={undefined}
/>,
>
{undefined}
</FloatRightContainer>,
)
expect(await screen.findByRole('dialog')).toBeInTheDocument()

View File

@@ -1342,12 +1342,6 @@
},
"react/no-nested-component-definitions": {
"count": 1
},
"tailwindcss/enforce-consistent-class-order": {
"count": 1
},
"tailwindcss/no-unnecessary-whitespace": {
"count": 1
}
},
"app/components/base/button/add-button.stories.tsx": {