test: update test coverage UI component Joi (#3707)
* test: update test coverage joi * test: update test export all components * test: update clear mock useOs * test: remove delete global window during test case getInitialValue * test: update getValueInEffect with mock userAgent
This commit is contained in:
parent
3091bb0e5e
commit
5b7f0c1308
@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { useEffect, useRef } from 'react'
|
||||
|
||||
const DEFAULT_EVENTS = ['mousedown', 'touchstart']
|
||||
@ -8,34 +7,43 @@ export function useClickOutside<T extends HTMLElement = any>(
|
||||
events?: string[] | null,
|
||||
nodes?: (HTMLElement | null)[]
|
||||
) {
|
||||
const ref = useRef<T>()
|
||||
const ref = useRef<T>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const listener = (event: any) => {
|
||||
const { target } = event ?? {}
|
||||
const listener = (event: Event) => {
|
||||
const target = event.target as HTMLElement
|
||||
|
||||
// Check if the target or any ancestor has the data-ignore-outside-clicks attribute
|
||||
const shouldIgnore =
|
||||
target.closest('[data-ignore-outside-clicks]') !== null
|
||||
|
||||
if (Array.isArray(nodes)) {
|
||||
const shouldIgnore =
|
||||
target?.hasAttribute('data-ignore-outside-clicks') ||
|
||||
(!document.body.contains(target) && target.tagName !== 'HTML')
|
||||
const shouldTrigger = nodes.every(
|
||||
(node) => !!node && !event.composedPath().includes(node)
|
||||
)
|
||||
shouldTrigger && !shouldIgnore && handler()
|
||||
} else if (ref.current && !ref.current.contains(target)) {
|
||||
if (shouldTrigger && !shouldIgnore) {
|
||||
handler()
|
||||
}
|
||||
} else if (
|
||||
ref.current &&
|
||||
!ref.current.contains(target) &&
|
||||
!shouldIgnore
|
||||
) {
|
||||
handler()
|
||||
}
|
||||
}
|
||||
|
||||
;(events || DEFAULT_EVENTS).forEach((fn) =>
|
||||
document.addEventListener(fn, listener)
|
||||
const eventList = events || DEFAULT_EVENTS
|
||||
eventList.forEach((event) =>
|
||||
document.documentElement.addEventListener(event, listener)
|
||||
)
|
||||
|
||||
return () => {
|
||||
;(events || DEFAULT_EVENTS).forEach((fn) =>
|
||||
document.removeEventListener(fn, listener)
|
||||
eventList.forEach((event) =>
|
||||
document.documentElement.removeEventListener(event, listener)
|
||||
)
|
||||
}
|
||||
}, [ref, handler, nodes])
|
||||
}, [handler, nodes, events])
|
||||
|
||||
return ref
|
||||
}
|
||||
|
||||
@ -1,55 +1,84 @@
|
||||
import React from 'react'
|
||||
import { render, fireEvent, act } from '@testing-library/react'
|
||||
import { render, screen, fireEvent, cleanup } from '@testing-library/react'
|
||||
import { useClickOutside } from './index'
|
||||
|
||||
// Mock component to test the hook
|
||||
const TestComponent: React.FC<{ onClickOutside: () => void }> = ({
|
||||
onClickOutside,
|
||||
const TestComponent = ({
|
||||
handler,
|
||||
nodes,
|
||||
}: {
|
||||
handler: () => void
|
||||
nodes?: (HTMLElement | null)[]
|
||||
}) => {
|
||||
const ref = useClickOutside(onClickOutside)
|
||||
return <div ref={ref as React.RefObject<HTMLDivElement>}>Test</div>
|
||||
const ref = useClickOutside(handler, undefined, nodes)
|
||||
|
||||
return (
|
||||
<div ref={ref} data-testid="clickable">
|
||||
Click me
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
describe('@joi/hooks/useClickOutside', () => {
|
||||
it('should call handler when clicking outside', () => {
|
||||
const handleClickOutside = jest.fn()
|
||||
const { container } = render(
|
||||
<TestComponent onClickOutside={handleClickOutside} />
|
||||
)
|
||||
describe('useClickOutside', () => {
|
||||
afterEach(cleanup)
|
||||
|
||||
act(() => {
|
||||
fireEvent.mouseDown(document.body)
|
||||
})
|
||||
it('should call handler when clicking outside the element', () => {
|
||||
const handler = jest.fn()
|
||||
render(<TestComponent handler={handler} />)
|
||||
|
||||
expect(handleClickOutside).toHaveBeenCalledTimes(1)
|
||||
fireEvent.mouseDown(document.body)
|
||||
expect(handler).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should not call handler when clicking inside', () => {
|
||||
const handleClickOutside = jest.fn()
|
||||
const { getByText } = render(
|
||||
<TestComponent onClickOutside={handleClickOutside} />
|
||||
)
|
||||
it('should not call handler when clicking inside the element', () => {
|
||||
const handler = jest.fn()
|
||||
render(<TestComponent handler={handler} />)
|
||||
|
||||
act(() => {
|
||||
fireEvent.mouseDown(getByText('Test'))
|
||||
})
|
||||
|
||||
expect(handleClickOutside).not.toHaveBeenCalled()
|
||||
fireEvent.mouseDown(screen.getByTestId('clickable'))
|
||||
expect(handler).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>
|
||||
}
|
||||
it('should not call handler if target has data-ignore-outside-clicks attribute', () => {
|
||||
const handler = jest.fn()
|
||||
render(
|
||||
<>
|
||||
<TestComponent handler={handler} />
|
||||
<div data-ignore-outside-clicks>Ignore this</div>
|
||||
</>
|
||||
)
|
||||
|
||||
render(<TestComponentWithCustomEvent />)
|
||||
// Ensure that the div with the attribute is correctly queried
|
||||
fireEvent.mouseDown(screen.getByText('Ignore this'))
|
||||
expect(handler).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(document.body)
|
||||
})
|
||||
it('should call handler when clicking outside if nodes is an empty array', () => {
|
||||
const handler = jest.fn()
|
||||
render(<TestComponent handler={handler} nodes={[]} />)
|
||||
|
||||
expect(handleClickOutside).toHaveBeenCalledTimes(1)
|
||||
fireEvent.mouseDown(document.body)
|
||||
expect(handler).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should not call handler if clicking inside nodes', () => {
|
||||
const handler = jest.fn()
|
||||
const node = document.createElement('div')
|
||||
document.body.appendChild(node)
|
||||
|
||||
render(
|
||||
<>
|
||||
<TestComponent handler={handler} nodes={[node]} />
|
||||
</>
|
||||
)
|
||||
|
||||
fireEvent.mouseDown(node)
|
||||
expect(handler).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should call handler if nodes is undefined', () => {
|
||||
const handler = jest.fn()
|
||||
render(<TestComponent handler={handler} nodes={undefined} />)
|
||||
|
||||
fireEvent.mouseDown(document.body)
|
||||
expect(handler).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
@ -23,7 +23,7 @@ function attachMediaListener(
|
||||
}
|
||||
}
|
||||
|
||||
function getInitialValue(query: string, initialValue?: boolean) {
|
||||
export function getInitialValue(query: string, initialValue?: boolean) {
|
||||
if (typeof initialValue === 'boolean') {
|
||||
return initialValue
|
||||
}
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
import { renderHook, act } from '@testing-library/react'
|
||||
import { useMediaQuery } from './index'
|
||||
import { useMediaQuery, getInitialValue } from './index'
|
||||
|
||||
const global = globalThis
|
||||
const originalWindow = global.window
|
||||
|
||||
describe('@joi/hooks/useMediaQuery', () => {
|
||||
const matchMediaMock = jest.fn()
|
||||
@ -10,6 +13,39 @@ describe('@joi/hooks/useMediaQuery', () => {
|
||||
|
||||
afterEach(() => {
|
||||
matchMediaMock.mockClear()
|
||||
global.window = originalWindow
|
||||
})
|
||||
|
||||
it('should return undetermined when window is undefined', () => {
|
||||
delete (global as any).window
|
||||
expect(getInitialValue('(max-width: 600px)', true)).toBe(true)
|
||||
expect(getInitialValue('(max-width: 600px)', false)).toBe(false)
|
||||
})
|
||||
|
||||
it('should return default return false', () => {
|
||||
delete (global as any).window
|
||||
expect(getInitialValue('(max-width: 600px)')).toBe(false)
|
||||
})
|
||||
|
||||
it('should return matchMedia result when window is defined and matchMedia exists', () => {
|
||||
// Mock window.matchMedia
|
||||
const matchMediaMock = jest.fn().mockImplementation((query) => ({
|
||||
matches: query === '(max-width: 600px)',
|
||||
media: query,
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
}))
|
||||
|
||||
// Mock window and matchMedia
|
||||
;(global as any).window = { matchMedia: matchMediaMock }
|
||||
|
||||
// Test the function behavior
|
||||
expect(getInitialValue('(max-width: 600px)')).toBe(true) // Query should match
|
||||
expect(matchMediaMock).toHaveBeenCalledWith('(max-width: 600px)')
|
||||
|
||||
// Test with a non-matching query
|
||||
expect(getInitialValue('(min-width: 1200px)')).toBe(false) // Query should not match
|
||||
expect(matchMediaMock).toHaveBeenCalledWith('(min-width: 1200px)')
|
||||
})
|
||||
|
||||
it('should return initial value when getInitialValueInEffect is true', () => {
|
||||
@ -87,4 +123,38 @@ describe('@joi/hooks/useMediaQuery', () => {
|
||||
|
||||
expect(result.current).toBe(true)
|
||||
})
|
||||
|
||||
it('should return undefined when matchMedia is not available', () => {
|
||||
delete (global as any).window.matchMedia
|
||||
|
||||
const { result } = renderHook(() => useMediaQuery('(max-width: 600px)'))
|
||||
expect(result.current).toBe(undefined)
|
||||
})
|
||||
|
||||
it('should use initialValue when getInitialValueInEffect is true', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useMediaQuery('(max-width: 600px)', true, {
|
||||
getInitialValueInEffect: true,
|
||||
})
|
||||
)
|
||||
expect(result.current).toBe(true)
|
||||
})
|
||||
|
||||
it('should use getInitialValue when getInitialValueInEffect is false', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useMediaQuery('(max-width: 600px)', undefined, {
|
||||
getInitialValueInEffect: false,
|
||||
})
|
||||
)
|
||||
expect(result.current).toBe(false)
|
||||
})
|
||||
|
||||
it('should use initialValue as false when getInitialValueInEffect is true', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useMediaQuery('(max-width: 600px)', false, {
|
||||
getInitialValueInEffect: true,
|
||||
})
|
||||
)
|
||||
expect(result.current).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
@ -8,7 +8,7 @@ export type OS =
|
||||
| 'android'
|
||||
| 'linux'
|
||||
|
||||
function getOS(): OS {
|
||||
export function getOS(): OS {
|
||||
if (typeof window === 'undefined') {
|
||||
return 'undetermined'
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { renderHook } from '@testing-library/react'
|
||||
import { useOs } from './index'
|
||||
import { useOs, getOS } from './index'
|
||||
import '@testing-library/jest-dom'
|
||||
|
||||
const platforms = {
|
||||
windows: [
|
||||
@ -21,10 +22,28 @@ const platforms = {
|
||||
} as const
|
||||
|
||||
describe('@joi/hooks/useOS', () => {
|
||||
const global = globalThis
|
||||
const originalWindow = global.window
|
||||
|
||||
afterEach(() => {
|
||||
global.window = originalWindow
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it('should return undetermined when window is undefined', () => {
|
||||
delete (global as any).window
|
||||
expect(getOS()).toBe('undetermined')
|
||||
})
|
||||
|
||||
it('should return undetermined when getValueInEffect is false', () => {
|
||||
jest
|
||||
.spyOn(window.navigator, 'userAgent', 'get')
|
||||
.mockReturnValueOnce('UNKNOWN_USER_AGENT')
|
||||
|
||||
const { result } = renderHook(() => useOs({ getValueInEffect: false }))
|
||||
expect(result.current).toBe('undetermined')
|
||||
})
|
||||
|
||||
Object.entries(platforms).forEach(([os, userAgents]) => {
|
||||
it.each(userAgents)(`should detect %s platform on ${os}`, (userAgent) => {
|
||||
jest
|
||||
|
||||
43
joi/src/index.test.ts
Normal file
43
joi/src/index.test.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import * as components from './index'
|
||||
|
||||
// Mock styles globally for all components in this test
|
||||
jest.mock('./core/Tooltip/styles.scss', () => ({}))
|
||||
jest.mock('./core/ScrollArea/styles.scss', () => ({}))
|
||||
jest.mock('./core/Button/styles.scss', () => ({}))
|
||||
jest.mock('./core/Switch/styles.scss', () => ({}))
|
||||
jest.mock('./core/Progress/styles.scss', () => ({}))
|
||||
jest.mock('./core/Checkbox/styles.scss', () => ({}))
|
||||
jest.mock('./core/Badge/styles.scss', () => ({}))
|
||||
jest.mock('./core/Modal/styles.scss', () => ({}))
|
||||
jest.mock('./core/Slider/styles.scss', () => ({}))
|
||||
jest.mock('./core/Input/styles.scss', () => ({}))
|
||||
jest.mock('./core/Select/styles.scss', () => ({}))
|
||||
jest.mock('./core/TextArea/styles.scss', () => ({}))
|
||||
jest.mock('./core/Tabs/styles.scss', () => ({}))
|
||||
jest.mock('./core/Accordion/styles.scss', () => ({}))
|
||||
|
||||
describe('Exports', () => {
|
||||
it('exports all components and hooks', () => {
|
||||
expect(components.Tooltip).toBeDefined()
|
||||
expect(components.ScrollArea).toBeDefined()
|
||||
expect(components.Button).toBeDefined()
|
||||
expect(components.Switch).toBeDefined()
|
||||
expect(components.Progress).toBeDefined()
|
||||
expect(components.Checkbox).toBeDefined()
|
||||
expect(components.Badge).toBeDefined()
|
||||
expect(components.Modal).toBeDefined()
|
||||
expect(components.Slider).toBeDefined()
|
||||
expect(components.Input).toBeDefined()
|
||||
expect(components.Select).toBeDefined()
|
||||
expect(components.TextArea).toBeDefined()
|
||||
expect(components.Tabs).toBeDefined()
|
||||
expect(components.Accordion).toBeDefined()
|
||||
|
||||
expect(components.useClipboard).toBeDefined()
|
||||
expect(components.usePageLeave).toBeDefined()
|
||||
expect(components.useTextSelection).toBeDefined()
|
||||
expect(components.useClickOutside).toBeDefined()
|
||||
expect(components.useOs).toBeDefined()
|
||||
expect(components.useMediaQuery).toBeDefined()
|
||||
})
|
||||
})
|
||||
Loading…
x
Reference in New Issue
Block a user