chore: setup jest for unit test hooks and component from joi (#3540)
* chore: setup jest for unit test hooks and component from joi * chore: update gitignore * chore: exclude jest setup file from tsconfig
This commit is contained in:
parent
edf5c77dd6
commit
1ffb7f213d
2
.gitignore
vendored
2
.gitignore
vendored
@ -20,7 +20,7 @@ electron/themes
|
|||||||
electron/playwright-report
|
electron/playwright-report
|
||||||
server/pre-install
|
server/pre-install
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
coverage
|
||||||
*.log
|
*.log
|
||||||
core/lib/**
|
core/lib/**
|
||||||
|
|
||||||
|
|||||||
8
joi/jest.config.js
Normal file
8
joi/jest.config.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
module.exports = {
|
||||||
|
preset: 'ts-jest',
|
||||||
|
testEnvironment: 'node',
|
||||||
|
roots: ['<rootDir>/src'],
|
||||||
|
testMatch: ['**/*.test.*'],
|
||||||
|
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
|
||||||
|
testEnvironment: 'jsdom',
|
||||||
|
}
|
||||||
0
joi/jest.setup.js
Normal file
0
joi/jest.setup.js
Normal file
@ -21,7 +21,8 @@
|
|||||||
"bugs": "https://github.com/codecentrum/piksel/issues",
|
"bugs": "https://github.com/codecentrum/piksel/issues",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "rollup -c -w",
|
"dev": "rollup -c -w",
|
||||||
"build": "rimraf ./dist && rollup -c"
|
"build": "rimraf ./dist && rollup -c",
|
||||||
|
"test": "jest"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
@ -38,13 +39,22 @@
|
|||||||
"@radix-ui/react-slot": "^1.0.2",
|
"@radix-ui/react-slot": "^1.0.2",
|
||||||
"@radix-ui/react-tabs": "^1.0.4",
|
"@radix-ui/react-tabs": "^1.0.4",
|
||||||
"@radix-ui/react-tooltip": "^1.0.7",
|
"@radix-ui/react-tooltip": "^1.0.7",
|
||||||
"tailwind-merge": "^2.2.0",
|
"@types/jest": "^29.5.12",
|
||||||
"autoprefixer": "10.4.16",
|
"autoprefixer": "10.4.16",
|
||||||
"tailwindcss": "^3.4.1"
|
"jest": "^29.7.0",
|
||||||
|
"tailwind-merge": "^2.2.0",
|
||||||
|
"tailwindcss": "^3.4.1",
|
||||||
|
"ts-jest": "^29.2.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||||
"@rollup/plugin-terser": "^0.4.4",
|
"@rollup/plugin-terser": "^0.4.4",
|
||||||
|
"@testing-library/dom": "^10.4.0",
|
||||||
|
"@testing-library/jest-dom": "^6.5.0",
|
||||||
|
"@testing-library/react": "^16.0.1",
|
||||||
|
"@types/jest": "^29.5.12",
|
||||||
|
"jest-environment-jsdom": "^29.7.0",
|
||||||
|
"jest-transform-css": "^6.0.1",
|
||||||
"prettier": "^3.0.3",
|
"prettier": "^3.0.3",
|
||||||
"prettier-plugin-tailwindcss": "^0.5.6",
|
"prettier-plugin-tailwindcss": "^0.5.6",
|
||||||
"rollup": "^4.12.0",
|
"rollup": "^4.12.0",
|
||||||
|
|||||||
64
joi/src/core/Accordion/Accordion.test.tsx
Normal file
64
joi/src/core/Accordion/Accordion.test.tsx
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import '@testing-library/jest-dom'
|
||||||
|
import { render, screen, fireEvent } from '@testing-library/react'
|
||||||
|
import { Accordion, AccordionItem } from './index'
|
||||||
|
|
||||||
|
// Mock the SCSS import
|
||||||
|
jest.mock('./styles.scss', () => ({}))
|
||||||
|
|
||||||
|
describe('Accordion', () => {
|
||||||
|
it('renders accordion with items', () => {
|
||||||
|
render(
|
||||||
|
<Accordion defaultValue={['item1']}>
|
||||||
|
<AccordionItem value="item1" title="Item 1">
|
||||||
|
Content 1
|
||||||
|
</AccordionItem>
|
||||||
|
<AccordionItem value="item2" title="Item 2">
|
||||||
|
Content 2
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(screen.getByText('Item 1')).toBeInTheDocument()
|
||||||
|
expect(screen.getByText('Item 2')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('expands and collapses accordion items', () => {
|
||||||
|
render(
|
||||||
|
<Accordion defaultValue={[]}>
|
||||||
|
<AccordionItem value="item1" title="Item 1">
|
||||||
|
Content 1
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
)
|
||||||
|
|
||||||
|
const trigger = screen.getByText('Item 1')
|
||||||
|
|
||||||
|
// Initially, content should not be visible
|
||||||
|
expect(screen.queryByText('Content 1')).not.toBeInTheDocument()
|
||||||
|
|
||||||
|
// Click to expand
|
||||||
|
fireEvent.click(trigger)
|
||||||
|
expect(screen.getByText('Content 1')).toBeInTheDocument()
|
||||||
|
|
||||||
|
// Click to collapse
|
||||||
|
fireEvent.click(trigger)
|
||||||
|
expect(screen.queryByText('Content 1')).not.toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('respects defaultValue prop', () => {
|
||||||
|
render(
|
||||||
|
<Accordion defaultValue={['item2']}>
|
||||||
|
<AccordionItem value="item1" title="Item 1">
|
||||||
|
Content 1
|
||||||
|
</AccordionItem>
|
||||||
|
<AccordionItem value="item2" title="Item 2">
|
||||||
|
Content 2
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(screen.queryByText('Content 1')).not.toBeInTheDocument()
|
||||||
|
expect(screen.getByText('Content 2')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
||||||
83
joi/src/core/Badge/Badge.test.tsx
Normal file
83
joi/src/core/Badge/Badge.test.tsx
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render, screen } from '@testing-library/react'
|
||||||
|
import '@testing-library/jest-dom'
|
||||||
|
import { Badge, badgeConfig } from './index'
|
||||||
|
|
||||||
|
// Mock the styles
|
||||||
|
jest.mock('./styles.scss', () => ({}))
|
||||||
|
|
||||||
|
describe('@joi/core/Badge', () => {
|
||||||
|
it('renders with default props', () => {
|
||||||
|
render(<Badge>Test Badge</Badge>)
|
||||||
|
const badge = screen.getByText('Test Badge')
|
||||||
|
expect(badge).toBeInTheDocument()
|
||||||
|
expect(badge).toHaveClass('badge')
|
||||||
|
expect(badge).toHaveClass('badge--primary')
|
||||||
|
expect(badge).toHaveClass('badge--medium')
|
||||||
|
expect(badge).toHaveClass('badge--solid')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('applies custom className', () => {
|
||||||
|
render(<Badge className="custom-class">Test Badge</Badge>)
|
||||||
|
const badge = screen.getByText('Test Badge')
|
||||||
|
expect(badge).toHaveClass('custom-class')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders with different themes', () => {
|
||||||
|
const themes = Object.keys(badgeConfig.variants.theme)
|
||||||
|
themes.forEach((theme) => {
|
||||||
|
render(<Badge theme={theme as any}>Test Badge {theme}</Badge>)
|
||||||
|
const badge = screen.getByText(`Test Badge ${theme}`)
|
||||||
|
expect(badge).toHaveClass(`badge--${theme}`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders with different variants', () => {
|
||||||
|
const variants = Object.keys(badgeConfig.variants.variant)
|
||||||
|
variants.forEach((variant) => {
|
||||||
|
render(<Badge variant={variant as any}>Test Badge {variant}</Badge>)
|
||||||
|
const badge = screen.getByText(`Test Badge ${variant}`)
|
||||||
|
expect(badge).toHaveClass(`badge--${variant}`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders with different sizes', () => {
|
||||||
|
const sizes = Object.keys(badgeConfig.variants.size)
|
||||||
|
sizes.forEach((size) => {
|
||||||
|
render(<Badge size={size as any}>Test Badge {size}</Badge>)
|
||||||
|
const badge = screen.getByText(`Test Badge ${size}`)
|
||||||
|
expect(badge).toHaveClass(`badge--${size}`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('fails when a new theme is added without updating the test', () => {
|
||||||
|
const expectedThemes = [
|
||||||
|
'primary',
|
||||||
|
'secondary',
|
||||||
|
'warning',
|
||||||
|
'success',
|
||||||
|
'info',
|
||||||
|
'destructive',
|
||||||
|
]
|
||||||
|
const actualThemes = Object.keys(badgeConfig.variants.theme)
|
||||||
|
expect(actualThemes).toEqual(expectedThemes)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('fails when a new variant is added without updating the test', () => {
|
||||||
|
const expectedVariant = ['solid', 'soft', 'outline']
|
||||||
|
const actualVariants = Object.keys(badgeConfig.variants.variant)
|
||||||
|
expect(actualVariants).toEqual(expectedVariant)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('fails when a new size is added without updating the test', () => {
|
||||||
|
const expectedSizes = ['small', 'medium', 'large']
|
||||||
|
const actualSizes = Object.keys(badgeConfig.variants.size)
|
||||||
|
expect(actualSizes).toEqual(expectedSizes)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('fails when a new variant CVA is added without updating the test', () => {
|
||||||
|
const expectedVariantsCVA = ['theme', 'variant', 'size']
|
||||||
|
const actualVariant = Object.keys(badgeConfig.variants)
|
||||||
|
expect(actualVariant).toEqual(expectedVariantsCVA)
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -6,7 +6,7 @@ import { twMerge } from 'tailwind-merge'
|
|||||||
|
|
||||||
import './styles.scss'
|
import './styles.scss'
|
||||||
|
|
||||||
const badgeVariants = cva('badge', {
|
export const badgeConfig = {
|
||||||
variants: {
|
variants: {
|
||||||
theme: {
|
theme: {
|
||||||
primary: 'badge--primary',
|
primary: 'badge--primary',
|
||||||
@ -28,11 +28,13 @@ const badgeVariants = cva('badge', {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
theme: 'primary',
|
theme: 'primary' as const,
|
||||||
size: 'medium',
|
size: 'medium' as const,
|
||||||
variant: 'solid',
|
variant: 'solid' as const,
|
||||||
},
|
},
|
||||||
})
|
}
|
||||||
|
|
||||||
|
const badgeVariants = cva('badge', badgeConfig)
|
||||||
|
|
||||||
export interface BadgeProps
|
export interface BadgeProps
|
||||||
extends HTMLAttributes<HTMLDivElement>,
|
extends HTMLAttributes<HTMLDivElement>,
|
||||||
|
|||||||
68
joi/src/core/Button/Button.test.tsx
Normal file
68
joi/src/core/Button/Button.test.tsx
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render, screen } from '@testing-library/react'
|
||||||
|
import '@testing-library/jest-dom'
|
||||||
|
import { Button, buttonConfig } from './index'
|
||||||
|
|
||||||
|
// Mock the styles
|
||||||
|
jest.mock('./styles.scss', () => ({}))
|
||||||
|
|
||||||
|
describe('Button', () => {
|
||||||
|
it('renders with default props', () => {
|
||||||
|
render(<Button>Click me</Button>)
|
||||||
|
const button = screen.getByRole('button', { name: /click me/i })
|
||||||
|
expect(button).toBeInTheDocument()
|
||||||
|
expect(button).toHaveClass('btn btn--primary btn--medium btn--solid')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders as a child component when asChild is true', () => {
|
||||||
|
render(
|
||||||
|
<Button asChild>
|
||||||
|
<a href="/">Link Button</a>
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
const link = screen.getByRole('link', { name: /link button/i })
|
||||||
|
expect(link).toBeInTheDocument()
|
||||||
|
expect(link).toHaveClass('btn btn--primary btn--medium btn--solid')
|
||||||
|
})
|
||||||
|
|
||||||
|
it.each(Object.keys(buttonConfig.variants.theme))(
|
||||||
|
'renders with theme %s',
|
||||||
|
(theme) => {
|
||||||
|
render(<Button theme={theme as any}>Theme Button</Button>)
|
||||||
|
const button = screen.getByRole('button', { name: /theme button/i })
|
||||||
|
expect(button).toHaveClass(`btn btn--${theme}`)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
it.each(Object.keys(buttonConfig.variants.variant))(
|
||||||
|
'renders with variant %s',
|
||||||
|
(variant) => {
|
||||||
|
render(<Button variant={variant as any}>Variant Button</Button>)
|
||||||
|
const button = screen.getByRole('button', { name: /variant button/i })
|
||||||
|
expect(button).toHaveClass(`btn btn--${variant}`)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
it.each(Object.keys(buttonConfig.variants.size))(
|
||||||
|
'renders with size %s',
|
||||||
|
(size) => {
|
||||||
|
render(<Button size={size as any}>Size Button</Button>)
|
||||||
|
const button = screen.getByRole('button', { name: /size button/i })
|
||||||
|
expect(button).toHaveClass(`btn btn--${size}`)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
it('renders with block prop', () => {
|
||||||
|
render(<Button block>Block Button</Button>)
|
||||||
|
const button = screen.getByRole('button', { name: /block button/i })
|
||||||
|
expect(button).toHaveClass('btn btn--block')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('merges custom className with generated classes', () => {
|
||||||
|
render(<Button className="custom-class">Custom Class Button</Button>)
|
||||||
|
const button = screen.getByRole('button', { name: /custom class button/i })
|
||||||
|
expect(button).toHaveClass(
|
||||||
|
'btn btn--primary btn--medium btn--solid custom-class'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -7,7 +7,7 @@ import { twMerge } from 'tailwind-merge'
|
|||||||
|
|
||||||
import './styles.scss'
|
import './styles.scss'
|
||||||
|
|
||||||
const buttonVariants = cva('btn', {
|
export const buttonConfig = {
|
||||||
variants: {
|
variants: {
|
||||||
theme: {
|
theme: {
|
||||||
primary: 'btn--primary',
|
primary: 'btn--primary',
|
||||||
@ -30,12 +30,13 @@ const buttonVariants = cva('btn', {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
theme: 'primary',
|
theme: 'primary' as const,
|
||||||
size: 'medium',
|
size: 'medium' as const,
|
||||||
variant: 'solid',
|
variant: 'solid' as const,
|
||||||
block: false,
|
block: false as const,
|
||||||
},
|
},
|
||||||
})
|
}
|
||||||
|
const buttonVariants = cva('btn', buttonConfig)
|
||||||
|
|
||||||
export interface ButtonProps
|
export interface ButtonProps
|
||||||
extends ButtonHTMLAttributes<HTMLButtonElement>,
|
extends ButtonHTMLAttributes<HTMLButtonElement>,
|
||||||
|
|||||||
55
joi/src/hooks/useClickOutside/useClickOutside.test.tsx
Normal file
55
joi/src/hooks/useClickOutside/useClickOutside.test.tsx
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render, fireEvent, act } from '@testing-library/react'
|
||||||
|
import { useClickOutside } from './index'
|
||||||
|
|
||||||
|
// Mock component to test the hook
|
||||||
|
const TestComponent: React.FC<{ onClickOutside: () => void }> = ({
|
||||||
|
onClickOutside,
|
||||||
|
}) => {
|
||||||
|
const ref = useClickOutside(onClickOutside)
|
||||||
|
return <div ref={ref as React.RefObject<HTMLDivElement>}>Test</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('@joi/hooks/useClickOutside', () => {
|
||||||
|
it('should call handler when clicking outside', () => {
|
||||||
|
const handleClickOutside = jest.fn()
|
||||||
|
const { container } = render(
|
||||||
|
<TestComponent onClickOutside={handleClickOutside} />
|
||||||
|
)
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
fireEvent.mouseDown(document.body)
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(handleClickOutside).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not call handler when clicking inside', () => {
|
||||||
|
const handleClickOutside = jest.fn()
|
||||||
|
const { getByText } = render(
|
||||||
|
<TestComponent onClickOutside={handleClickOutside} />
|
||||||
|
)
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
fireEvent.mouseDown(getByText('Test'))
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(handleClickOutside).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should work with custom events', () => {
|
||||||
|
const handleClickOutside = jest.fn()
|
||||||
|
const TestComponentWithCustomEvent: React.FC = () => {
|
||||||
|
const ref = useClickOutside(handleClickOutside, ['click'])
|
||||||
|
return <div ref={ref as React.RefObject<HTMLDivElement>}>Test</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
render(<TestComponentWithCustomEvent />)
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
fireEvent.click(document.body)
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(handleClickOutside).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
})
|
||||||
102
joi/src/hooks/useClipboard/useClipboard.test.ts
Normal file
102
joi/src/hooks/useClipboard/useClipboard.test.ts
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import { renderHook, act } from '@testing-library/react'
|
||||||
|
import { useClipboard } from './index'
|
||||||
|
|
||||||
|
// Mock the navigator.clipboard
|
||||||
|
const mockClipboard = {
|
||||||
|
writeText: jest.fn(() => Promise.resolve()),
|
||||||
|
}
|
||||||
|
Object.assign(navigator, { clipboard: mockClipboard })
|
||||||
|
|
||||||
|
describe('@joi/hooks/useClipboard', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.useFakeTimers()
|
||||||
|
jest.spyOn(window, 'setTimeout')
|
||||||
|
jest.spyOn(window, 'clearTimeout')
|
||||||
|
mockClipboard.writeText.mockClear()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.useRealTimers()
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should copy text to clipboard', async () => {
|
||||||
|
const { result } = renderHook(() => useClipboard())
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
result.current.copy('Test text')
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(navigator.clipboard.writeText).toHaveBeenCalledWith('Test text')
|
||||||
|
expect(result.current.copied).toBe(true)
|
||||||
|
expect(result.current.error).toBe(null)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should set error if clipboard write fails', async () => {
|
||||||
|
mockClipboard.writeText.mockRejectedValueOnce(
|
||||||
|
new Error('Clipboard write failed')
|
||||||
|
)
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useClipboard())
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
result.current.copy('Test text')
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.current.error).toBeInstanceOf(Error)
|
||||||
|
expect(result.current.error?.message).toBe('Clipboard write failed')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should set error if clipboard is not supported', async () => {
|
||||||
|
const originalClipboard = navigator.clipboard
|
||||||
|
// @ts-ignore
|
||||||
|
delete navigator.clipboard
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useClipboard())
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
result.current.copy('Test text')
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.current.error).toBeInstanceOf(Error)
|
||||||
|
expect(result.current.error?.message).toBe(
|
||||||
|
'useClipboard: navigator.clipboard is not supported'
|
||||||
|
)
|
||||||
|
|
||||||
|
// Restore clipboard support
|
||||||
|
Object.assign(navigator, { clipboard: originalClipboard })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should reset copied state after timeout', async () => {
|
||||||
|
const { result } = renderHook(() => useClipboard({ timeout: 1000 }))
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
result.current.copy('Test text')
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.current.copied).toBe(true)
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
jest.advanceTimersByTime(1000)
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.current.copied).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should reset state when reset is called', async () => {
|
||||||
|
const { result } = renderHook(() => useClipboard())
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
result.current.copy('Test text')
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.current.copied).toBe(true)
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
result.current.reset()
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.current.copied).toBe(false)
|
||||||
|
expect(result.current.error).toBe(null)
|
||||||
|
})
|
||||||
|
})
|
||||||
90
joi/src/hooks/useMediaQuery/useMediaQuery.test.ts
Normal file
90
joi/src/hooks/useMediaQuery/useMediaQuery.test.ts
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import { renderHook, act } from '@testing-library/react'
|
||||||
|
import { useMediaQuery } from './index'
|
||||||
|
|
||||||
|
describe('@joi/hooks/useMediaQuery', () => {
|
||||||
|
const matchMediaMock = jest.fn()
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
window.matchMedia = matchMediaMock
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
matchMediaMock.mockClear()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return initial value when getInitialValueInEffect is true', () => {
|
||||||
|
matchMediaMock.mockImplementation(() => ({
|
||||||
|
matches: true,
|
||||||
|
addListener: jest.fn(),
|
||||||
|
removeListener: jest.fn(),
|
||||||
|
}))
|
||||||
|
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
useMediaQuery('(min-width: 768px)', true, {
|
||||||
|
getInitialValueInEffect: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(result.current).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return correct value based on media query', () => {
|
||||||
|
matchMediaMock.mockImplementation(() => ({
|
||||||
|
matches: true,
|
||||||
|
addEventListener: jest.fn(),
|
||||||
|
removeEventListener: jest.fn(),
|
||||||
|
}))
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useMediaQuery('(min-width: 768px)'))
|
||||||
|
|
||||||
|
expect(result.current).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should update value when media query changes', () => {
|
||||||
|
let listener: ((event: { matches: boolean }) => void) | null = null
|
||||||
|
|
||||||
|
matchMediaMock.mockImplementation(() => ({
|
||||||
|
matches: false,
|
||||||
|
addEventListener: (_, cb) => {
|
||||||
|
listener = cb
|
||||||
|
},
|
||||||
|
removeEventListener: jest.fn(),
|
||||||
|
}))
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useMediaQuery('(min-width: 768px)'))
|
||||||
|
|
||||||
|
expect(result.current).toBe(false)
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
if (listener) {
|
||||||
|
listener({ matches: true })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.current).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should handle older browsers without addEventListener', () => {
|
||||||
|
let listener: ((event: { matches: boolean }) => void) | null = null
|
||||||
|
|
||||||
|
matchMediaMock.mockImplementation(() => ({
|
||||||
|
matches: false,
|
||||||
|
addListener: (cb) => {
|
||||||
|
listener = cb
|
||||||
|
},
|
||||||
|
removeListener: jest.fn(),
|
||||||
|
}))
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useMediaQuery('(min-width: 768px)'))
|
||||||
|
|
||||||
|
expect(result.current).toBe(false)
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
if (listener) {
|
||||||
|
listener({ matches: true })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.current).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
39
joi/src/hooks/useOs/useOs.test.ts
Normal file
39
joi/src/hooks/useOs/useOs.test.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { renderHook } from '@testing-library/react'
|
||||||
|
import { useOs } from './index'
|
||||||
|
|
||||||
|
const platforms = {
|
||||||
|
windows: [
|
||||||
|
'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0',
|
||||||
|
],
|
||||||
|
macos: [
|
||||||
|
'Mozilla/5.0 (Macintosh; Intel Mac OS X x.y; rv:42.0) Gecko/20100101 Firefox/42.0',
|
||||||
|
],
|
||||||
|
linux: [
|
||||||
|
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36',
|
||||||
|
],
|
||||||
|
ios: [
|
||||||
|
'Mozilla/5.0 (iPhone; CPU iPhone OS 13_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Mobile/15E148 Safari/604.1',
|
||||||
|
],
|
||||||
|
android: [
|
||||||
|
'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36',
|
||||||
|
],
|
||||||
|
undetermined: ['UNKNOWN'],
|
||||||
|
} as const
|
||||||
|
|
||||||
|
describe('@joi/hooks/useOS', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
Object.entries(platforms).forEach(([os, userAgents]) => {
|
||||||
|
it.each(userAgents)(`should detect %s platform on ${os}`, (userAgent) => {
|
||||||
|
jest
|
||||||
|
.spyOn(window.navigator, 'userAgent', 'get')
|
||||||
|
.mockReturnValueOnce(userAgent)
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useOs())
|
||||||
|
|
||||||
|
expect(result.current).toBe(os)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
32
joi/src/hooks/usePageLeave/usePageLeave.test.ts
Normal file
32
joi/src/hooks/usePageLeave/usePageLeave.test.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { renderHook } from '@testing-library/react'
|
||||||
|
import { fireEvent } from '@testing-library/react'
|
||||||
|
import { usePageLeave } from './index'
|
||||||
|
|
||||||
|
describe('@joi/hooks/usePageLeave', () => {
|
||||||
|
it('should call onPageLeave when mouse leaves the document', () => {
|
||||||
|
const onPageLeaveMock = jest.fn()
|
||||||
|
const { result } = renderHook(() => usePageLeave(onPageLeaveMock))
|
||||||
|
|
||||||
|
fireEvent.mouseLeave(document.documentElement)
|
||||||
|
|
||||||
|
expect(onPageLeaveMock).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should remove event listener on unmount', () => {
|
||||||
|
const onPageLeaveMock = jest.fn()
|
||||||
|
const removeEventListenerSpy = jest.spyOn(
|
||||||
|
document.documentElement,
|
||||||
|
'removeEventListener'
|
||||||
|
)
|
||||||
|
|
||||||
|
const { unmount } = renderHook(() => usePageLeave(onPageLeaveMock))
|
||||||
|
|
||||||
|
unmount()
|
||||||
|
|
||||||
|
expect(removeEventListenerSpy).toHaveBeenCalledWith(
|
||||||
|
'mouseleave',
|
||||||
|
expect.any(Function)
|
||||||
|
)
|
||||||
|
removeEventListenerSpy.mockRestore()
|
||||||
|
})
|
||||||
|
})
|
||||||
56
joi/src/hooks/useTextSelection/useTextSelection.test.ts
Normal file
56
joi/src/hooks/useTextSelection/useTextSelection.test.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { renderHook, act } from '@testing-library/react'
|
||||||
|
import { useTextSelection } from './index'
|
||||||
|
|
||||||
|
describe('@joi/hooks/useTextSelection', () => {
|
||||||
|
let mockSelection: Selection
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockSelection = {
|
||||||
|
toString: jest.fn(),
|
||||||
|
removeAllRanges: jest.fn(),
|
||||||
|
addRange: jest.fn(),
|
||||||
|
} as unknown as Selection
|
||||||
|
|
||||||
|
jest.spyOn(document, 'getSelection').mockReturnValue(mockSelection)
|
||||||
|
jest.spyOn(document, 'addEventListener')
|
||||||
|
jest.spyOn(document, 'removeEventListener')
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.restoreAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return the initial selection', () => {
|
||||||
|
const { result } = renderHook(() => useTextSelection())
|
||||||
|
expect(result.current).toBe(mockSelection)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should add and remove event listener', () => {
|
||||||
|
const { unmount } = renderHook(() => useTextSelection())
|
||||||
|
|
||||||
|
expect(document.addEventListener).toHaveBeenCalledWith(
|
||||||
|
'selectionchange',
|
||||||
|
expect.any(Function)
|
||||||
|
)
|
||||||
|
|
||||||
|
unmount()
|
||||||
|
|
||||||
|
expect(document.removeEventListener).toHaveBeenCalledWith(
|
||||||
|
'selectionchange',
|
||||||
|
expect.any(Function)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should update selection when selectionchange event is triggered', () => {
|
||||||
|
const { result } = renderHook(() => useTextSelection())
|
||||||
|
|
||||||
|
const newMockSelection = { toString: jest.fn() } as unknown as Selection
|
||||||
|
jest.spyOn(document, 'getSelection').mockReturnValue(newMockSelection)
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
document.dispatchEvent(new Event('selectionchange'))
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.current).toBe(newMockSelection)
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -3,6 +3,7 @@
|
|||||||
"target": "esnext",
|
"target": "esnext",
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"declarationDir": "dist/types",
|
"declarationDir": "dist/types",
|
||||||
|
"types": ["jest", "@testing-library/jest-dom"],
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
"lib": ["es6", "dom", "es2016", "es2017"],
|
"lib": ["es6", "dom", "es2016", "es2017"],
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user