mirror of
https://github.com/nowcommunity/nowdatabase.git
synced 2026-10-12 19:13:52 +03:00
jest fixes
This commit is contained in:
@@ -12,6 +12,7 @@ lerna-debug.log*
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
.jest-cache
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
|
||||
@@ -3,11 +3,16 @@ export default {
|
||||
'^.+\\.tsx?$': [
|
||||
'ts-jest',
|
||||
{
|
||||
tsconfig: '<rootDir>/tsconfig.json',
|
||||
isolatedModules: true,
|
||||
tsconfig: '<rootDir>/tsconfig.jest.json',
|
||||
},
|
||||
],
|
||||
},
|
||||
moduleNameMapper: {
|
||||
'^.+\\.css$': '<rootDir>/src/tests/mocks/styleMock.ts',
|
||||
'^lodash-es$': '<rootDir>/src/tests/mocks/lodashEs.ts',
|
||||
'^@/util/config$': '<rootDir>/src/tests/mocks/config.ts',
|
||||
'^\\.\\.?/util/config$': '<rootDir>/src/tests/mocks/config.ts',
|
||||
'^@/(.*)$': '<rootDir>/src/$1',
|
||||
'^@components/Table$': '<rootDir>/src/components/table',
|
||||
'^@components/(.*)$': '<rootDir>/src/components/$1',
|
||||
|
||||
@@ -11,7 +11,11 @@ import type { EditDataType } from '@/shared/types'
|
||||
|
||||
jest.mock('lodash-es', () => ({
|
||||
cloneDeep: <T,>(value: T) => structuredClone(value),
|
||||
isEqual: (a: unknown, b: unknown) => JSON.stringify(a) === JSON.stringify(b),
|
||||
isEqualWith: (a: unknown, b: unknown, customizer?: (left: unknown, right: unknown) => boolean | undefined) => {
|
||||
const customizedResult = customizer?.(a, b)
|
||||
if (customizedResult !== undefined) return customizedResult
|
||||
return JSON.stringify(a) === JSON.stringify(b)
|
||||
},
|
||||
}))
|
||||
|
||||
type TestData = { name: string; count: number }
|
||||
|
||||
@@ -1,14 +1,34 @@
|
||||
import { renderHook } from '@testing-library/react'
|
||||
import { ReactNode } from 'react'
|
||||
import { UnsavedChangesProvider } from '@/components/UnsavedChangesProvider'
|
||||
import { useUnsavedChangesPrompt } from '../../hooks/useUnsavedChangesPrompt'
|
||||
import { useBlocker } from 'react-router-dom'
|
||||
|
||||
jest.mock('react-router-dom', () => {
|
||||
const actual = jest.requireActual<typeof import('react-router-dom')>('react-router-dom')
|
||||
return {
|
||||
...actual,
|
||||
useBlocker: jest.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
const wrapper = ({ children }: { children: ReactNode }) => <UnsavedChangesProvider>{children}</UnsavedChangesProvider>
|
||||
|
||||
describe('useUnsavedChangesPrompt', () => {
|
||||
let addEventListenerSpy: jest.SpyInstance
|
||||
let removeEventListenerSpy: jest.SpyInstance
|
||||
const mockUseBlocker = useBlocker as jest.MockedFunction<typeof useBlocker>
|
||||
|
||||
beforeEach(() => {
|
||||
// Mock window.addEventListener and removeEventListener
|
||||
addEventListenerSpy = jest.spyOn(window, 'addEventListener')
|
||||
removeEventListenerSpy = jest.spyOn(window, 'removeEventListener')
|
||||
mockUseBlocker.mockReturnValue({
|
||||
state: 'unblocked',
|
||||
proceed: undefined,
|
||||
reset: undefined,
|
||||
location: undefined,
|
||||
} as ReturnType<typeof useBlocker>)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
@@ -18,19 +38,19 @@ describe('useUnsavedChangesPrompt', () => {
|
||||
})
|
||||
|
||||
it('should add beforeunload event listener when hasUnsavedChanges is true', () => {
|
||||
renderHook(() => useUnsavedChangesPrompt(true))
|
||||
renderHook(() => useUnsavedChangesPrompt(true), { wrapper })
|
||||
|
||||
expect(addEventListenerSpy).toHaveBeenCalledWith('beforeunload', expect.any(Function))
|
||||
})
|
||||
|
||||
it('should not add beforeunload event listener when hasUnsavedChanges is false', () => {
|
||||
renderHook(() => useUnsavedChangesPrompt(false))
|
||||
renderHook(() => useUnsavedChangesPrompt(false), { wrapper })
|
||||
|
||||
expect(addEventListenerSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should remove event listener on cleanup when hasUnsavedChanges was true', () => {
|
||||
const { unmount } = renderHook(() => useUnsavedChangesPrompt(true))
|
||||
const { unmount } = renderHook(() => useUnsavedChangesPrompt(true), { wrapper })
|
||||
|
||||
// Get the handler that was added
|
||||
const handler = (addEventListenerSpy.mock.calls[0] as unknown[])?.[1] as (event: BeforeUnloadEvent) => void
|
||||
@@ -43,6 +63,7 @@ describe('useUnsavedChangesPrompt', () => {
|
||||
it('should update event listener when hasUnsavedChanges changes from false to true', () => {
|
||||
const { rerender } = renderHook(({ hasUnsavedChanges }) => useUnsavedChangesPrompt(hasUnsavedChanges), {
|
||||
initialProps: { hasUnsavedChanges: false },
|
||||
wrapper,
|
||||
})
|
||||
|
||||
expect(addEventListenerSpy).not.toHaveBeenCalled()
|
||||
@@ -55,6 +76,7 @@ describe('useUnsavedChangesPrompt', () => {
|
||||
it('should remove event listener when hasUnsavedChanges changes from true to false', () => {
|
||||
const { rerender } = renderHook(({ hasUnsavedChanges }) => useUnsavedChangesPrompt(hasUnsavedChanges), {
|
||||
initialProps: { hasUnsavedChanges: true },
|
||||
wrapper,
|
||||
})
|
||||
|
||||
const handler = (addEventListenerSpy.mock.calls[0] as unknown[])?.[1] as (event: BeforeUnloadEvent) => void
|
||||
@@ -65,7 +87,7 @@ describe('useUnsavedChangesPrompt', () => {
|
||||
})
|
||||
|
||||
it('should call preventDefault on the event when hasUnsavedChanges is true', () => {
|
||||
renderHook(() => useUnsavedChangesPrompt(true))
|
||||
renderHook(() => useUnsavedChangesPrompt(true), { wrapper })
|
||||
|
||||
// Get the handler function that was registered
|
||||
const handler = (addEventListenerSpy.mock.calls[0] as unknown[])?.[1] as (event: BeforeUnloadEvent) => void
|
||||
@@ -81,11 +103,11 @@ describe('useUnsavedChangesPrompt', () => {
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
expect(mockEvent.preventDefault).toHaveBeenCalled()
|
||||
expect(mockEvent.returnValue).toBe('')
|
||||
expect(mockEvent.returnValue).toBe('You have unsaved changes. Do you want to leave this page without saving?')
|
||||
})
|
||||
|
||||
it('should set returnValue to empty string to trigger browser prompt', () => {
|
||||
renderHook(() => useUnsavedChangesPrompt(true))
|
||||
it('should set returnValue to the active prompt message', () => {
|
||||
renderHook(() => useUnsavedChangesPrompt(true), { wrapper })
|
||||
|
||||
const handler = (addEventListenerSpy.mock.calls[0] as unknown[])?.[1] as (event: BeforeUnloadEvent) => void
|
||||
|
||||
@@ -96,12 +118,13 @@ describe('useUnsavedChangesPrompt', () => {
|
||||
|
||||
handler(mockEvent)
|
||||
|
||||
expect(mockEvent.returnValue).toBe('')
|
||||
expect(mockEvent.returnValue).toBe('You have unsaved changes. Do you want to leave this page without saving?')
|
||||
})
|
||||
|
||||
it('should not add multiple event listeners when re-rendered with same hasUnsavedChanges value', () => {
|
||||
const { rerender } = renderHook(({ hasUnsavedChanges }) => useUnsavedChangesPrompt(hasUnsavedChanges), {
|
||||
initialProps: { hasUnsavedChanges: true },
|
||||
wrapper,
|
||||
})
|
||||
|
||||
expect(addEventListenerSpy).toHaveBeenCalledTimes(1)
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export const ENV = 'dev' as const
|
||||
export const BACKEND_URL = 'http://localhost'
|
||||
export const ENABLE_WRITE = true
|
||||
@@ -0,0 +1,39 @@
|
||||
const cloneValue = <T,>(value: T): T => {
|
||||
if (typeof structuredClone === 'function') {
|
||||
return structuredClone(value)
|
||||
}
|
||||
|
||||
return JSON.parse(JSON.stringify(value)) as T
|
||||
}
|
||||
|
||||
const isPlainObject = (value: unknown): value is Record<string, unknown> =>
|
||||
typeof value === 'object' && value !== null && !Array.isArray(value)
|
||||
|
||||
export const cloneDeep = cloneValue
|
||||
|
||||
export const isEqualWith = (
|
||||
value: unknown,
|
||||
other: unknown,
|
||||
customizer?: (value: unknown, other: unknown) => boolean | undefined
|
||||
): boolean => {
|
||||
const customizedResult = customizer?.(value, other)
|
||||
if (customizedResult !== undefined) return customizedResult
|
||||
|
||||
if (Object.is(value, other)) return true
|
||||
|
||||
if (Array.isArray(value) && Array.isArray(other)) {
|
||||
if (value.length !== other.length) return false
|
||||
return value.every((item, index) => isEqualWith(item, other[index], customizer))
|
||||
}
|
||||
|
||||
if (isPlainObject(value) && isPlainObject(other)) {
|
||||
const valueKeys = Object.keys(value)
|
||||
const otherKeys = Object.keys(other)
|
||||
|
||||
if (valueKeys.length !== otherKeys.length) return false
|
||||
|
||||
return valueKeys.every(key => isEqualWith(value[key], other[key], customizer))
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export default {}
|
||||
@@ -1,5 +1,7 @@
|
||||
// Polyfill for React Router's data router in Jest environment
|
||||
// See: https://github.com/remix-run/react-router/discussions/9851
|
||||
import '@testing-library/jest-dom'
|
||||
|
||||
if (typeof global.Request === 'undefined') {
|
||||
global.Request = class Request {
|
||||
constructor(
|
||||
@@ -17,3 +19,7 @@ if (typeof global.Response === 'undefined') {
|
||||
) {}
|
||||
} as typeof Response
|
||||
}
|
||||
|
||||
if (typeof global.structuredClone === 'undefined') {
|
||||
global.structuredClone = (<T,>(value: T) => JSON.parse(JSON.stringify(value)) as T) as typeof structuredClone
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "node",
|
||||
"isolatedModules": true,
|
||||
"noEmit": false,
|
||||
"allowImportingTsExtensions": false,
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"include": ["src", "tests", "jest.config.ts"]
|
||||
}
|
||||
Reference in New Issue
Block a user