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
|
||||
server/pre-install
|
||||
package-lock.json
|
||||
|
||||
coverage
|
||||
*.log
|
||||
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",
|
||||
"scripts": {
|
||||
"dev": "rollup -c -w",
|
||||
"build": "rimraf ./dist && rollup -c"
|
||||
"build": "rimraf ./dist && rollup -c",
|
||||
"test": "jest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"class-variance-authority": "^0.7.0",
|
||||
@ -38,13 +39,22 @@
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-tabs": "^1.0.4",
|
||||
"@radix-ui/react-tooltip": "^1.0.7",
|
||||
"tailwind-merge": "^2.2.0",
|
||||
"@types/jest": "^29.5.12",
|
||||
"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": {
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@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-plugin-tailwindcss": "^0.5.6",
|
||||
"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'
|
||||
|
||||
const badgeVariants = cva('badge', {
|
||||
export const badgeConfig = {
|
||||
variants: {
|
||||
theme: {
|
||||
primary: 'badge--primary',
|
||||
@ -28,11 +28,13 @@ const badgeVariants = cva('badge', {
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
theme: 'primary',
|
||||
size: 'medium',
|
||||
variant: 'solid',
|
||||
theme: 'primary' as const,
|
||||
size: 'medium' as const,
|
||||
variant: 'solid' as const,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const badgeVariants = cva('badge', badgeConfig)
|
||||
|
||||
export interface BadgeProps
|
||||
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'
|
||||
|
||||
const buttonVariants = cva('btn', {
|
||||
export const buttonConfig = {
|
||||
variants: {
|
||||
theme: {
|
||||
primary: 'btn--primary',
|
||||
@ -30,12 +30,13 @@ const buttonVariants = cva('btn', {
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
theme: 'primary',
|
||||
size: 'medium',
|
||||
variant: 'solid',
|
||||
block: false,
|
||||
theme: 'primary' as const,
|
||||
size: 'medium' as const,
|
||||
variant: 'solid' as const,
|
||||
block: false as const,
|
||||
},
|
||||
})
|
||||
}
|
||||
const buttonVariants = cva('btn', buttonConfig)
|
||||
|
||||
export interface ButtonProps
|
||||
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",
|
||||
"declaration": true,
|
||||
"declarationDir": "dist/types",
|
||||
"types": ["jest", "@testing-library/jest-dom"],
|
||||
"module": "esnext",
|
||||
"lib": ["es6", "dom", "es2016", "es2017"],
|
||||
"sourceMap": true,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user