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'
|
import { useEffect, useRef } from 'react'
|
||||||
|
|
||||||
const DEFAULT_EVENTS = ['mousedown', 'touchstart']
|
const DEFAULT_EVENTS = ['mousedown', 'touchstart']
|
||||||
@ -8,34 +7,43 @@ export function useClickOutside<T extends HTMLElement = any>(
|
|||||||
events?: string[] | null,
|
events?: string[] | null,
|
||||||
nodes?: (HTMLElement | null)[]
|
nodes?: (HTMLElement | null)[]
|
||||||
) {
|
) {
|
||||||
const ref = useRef<T>()
|
const ref = useRef<T>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const listener = (event: any) => {
|
const listener = (event: Event) => {
|
||||||
const { target } = 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)) {
|
if (Array.isArray(nodes)) {
|
||||||
const shouldIgnore =
|
|
||||||
target?.hasAttribute('data-ignore-outside-clicks') ||
|
|
||||||
(!document.body.contains(target) && target.tagName !== 'HTML')
|
|
||||||
const shouldTrigger = nodes.every(
|
const shouldTrigger = nodes.every(
|
||||||
(node) => !!node && !event.composedPath().includes(node)
|
(node) => !!node && !event.composedPath().includes(node)
|
||||||
)
|
)
|
||||||
shouldTrigger && !shouldIgnore && handler()
|
if (shouldTrigger && !shouldIgnore) {
|
||||||
} else if (ref.current && !ref.current.contains(target)) {
|
handler()
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
ref.current &&
|
||||||
|
!ref.current.contains(target) &&
|
||||||
|
!shouldIgnore
|
||||||
|
) {
|
||||||
handler()
|
handler()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
;(events || DEFAULT_EVENTS).forEach((fn) =>
|
const eventList = events || DEFAULT_EVENTS
|
||||||
document.addEventListener(fn, listener)
|
eventList.forEach((event) =>
|
||||||
|
document.documentElement.addEventListener(event, listener)
|
||||||
)
|
)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
;(events || DEFAULT_EVENTS).forEach((fn) =>
|
eventList.forEach((event) =>
|
||||||
document.removeEventListener(fn, listener)
|
document.documentElement.removeEventListener(event, listener)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}, [ref, handler, nodes])
|
}, [handler, nodes, events])
|
||||||
|
|
||||||
return ref
|
return ref
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,55 +1,84 @@
|
|||||||
import React from 'react'
|
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'
|
import { useClickOutside } from './index'
|
||||||
|
|
||||||
// Mock component to test the hook
|
const TestComponent = ({
|
||||||
const TestComponent: React.FC<{ onClickOutside: () => void }> = ({
|
handler,
|
||||||
onClickOutside,
|
nodes,
|
||||||
|
}: {
|
||||||
|
handler: () => void
|
||||||
|
nodes?: (HTMLElement | null)[]
|
||||||
}) => {
|
}) => {
|
||||||
const ref = useClickOutside(onClickOutside)
|
const ref = useClickOutside(handler, undefined, nodes)
|
||||||
return <div ref={ref as React.RefObject<HTMLDivElement>}>Test</div>
|
|
||||||
|
return (
|
||||||
|
<div ref={ref} data-testid="clickable">
|
||||||
|
Click me
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('@joi/hooks/useClickOutside', () => {
|
describe('useClickOutside', () => {
|
||||||
it('should call handler when clicking outside', () => {
|
afterEach(cleanup)
|
||||||
const handleClickOutside = jest.fn()
|
|
||||||
const { container } = render(
|
|
||||||
<TestComponent onClickOutside={handleClickOutside} />
|
|
||||||
)
|
|
||||||
|
|
||||||
act(() => {
|
it('should call handler when clicking outside the element', () => {
|
||||||
fireEvent.mouseDown(document.body)
|
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', () => {
|
it('should not call handler when clicking inside the element', () => {
|
||||||
const handleClickOutside = jest.fn()
|
const handler = jest.fn()
|
||||||
const { getByText } = render(
|
render(<TestComponent handler={handler} />)
|
||||||
<TestComponent onClickOutside={handleClickOutside} />
|
|
||||||
)
|
|
||||||
|
|
||||||
act(() => {
|
fireEvent.mouseDown(screen.getByTestId('clickable'))
|
||||||
fireEvent.mouseDown(getByText('Test'))
|
expect(handler).not.toHaveBeenCalled()
|
||||||
})
|
|
||||||
|
|
||||||
expect(handleClickOutside).not.toHaveBeenCalled()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should work with custom events', () => {
|
it('should not call handler if target has data-ignore-outside-clicks attribute', () => {
|
||||||
const handleClickOutside = jest.fn()
|
const handler = jest.fn()
|
||||||
const TestComponentWithCustomEvent: React.FC = () => {
|
render(
|
||||||
const ref = useClickOutside(handleClickOutside, ['click'])
|
<>
|
||||||
return <div ref={ref as React.RefObject<HTMLDivElement>}>Test</div>
|
<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(() => {
|
it('should call handler when clicking outside if nodes is an empty array', () => {
|
||||||
fireEvent.click(document.body)
|
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') {
|
if (typeof initialValue === 'boolean') {
|
||||||
return initialValue
|
return initialValue
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
import { renderHook, act } from '@testing-library/react'
|
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', () => {
|
describe('@joi/hooks/useMediaQuery', () => {
|
||||||
const matchMediaMock = jest.fn()
|
const matchMediaMock = jest.fn()
|
||||||
@ -10,6 +13,39 @@ describe('@joi/hooks/useMediaQuery', () => {
|
|||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
matchMediaMock.mockClear()
|
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', () => {
|
it('should return initial value when getInitialValueInEffect is true', () => {
|
||||||
@ -87,4 +123,38 @@ describe('@joi/hooks/useMediaQuery', () => {
|
|||||||
|
|
||||||
expect(result.current).toBe(true)
|
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'
|
| 'android'
|
||||||
| 'linux'
|
| 'linux'
|
||||||
|
|
||||||
function getOS(): OS {
|
export function getOS(): OS {
|
||||||
if (typeof window === 'undefined') {
|
if (typeof window === 'undefined') {
|
||||||
return 'undetermined'
|
return 'undetermined'
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { renderHook } from '@testing-library/react'
|
import { renderHook } from '@testing-library/react'
|
||||||
import { useOs } from './index'
|
import { useOs, getOS } from './index'
|
||||||
|
import '@testing-library/jest-dom'
|
||||||
|
|
||||||
const platforms = {
|
const platforms = {
|
||||||
windows: [
|
windows: [
|
||||||
@ -21,10 +22,28 @@ const platforms = {
|
|||||||
} as const
|
} as const
|
||||||
|
|
||||||
describe('@joi/hooks/useOS', () => {
|
describe('@joi/hooks/useOS', () => {
|
||||||
|
const global = globalThis
|
||||||
|
const originalWindow = global.window
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
global.window = originalWindow
|
||||||
jest.clearAllMocks()
|
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]) => {
|
Object.entries(platforms).forEach(([os, userAgents]) => {
|
||||||
it.each(userAgents)(`should detect %s platform on ${os}`, (userAgent) => {
|
it.each(userAgents)(`should detect %s platform on ${os}`, (userAgent) => {
|
||||||
jest
|
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