mirror of
https://github.com/langgenius/dify.git
synced 2026-02-09 23:20:12 -05:00
test(web): add global zustand mock for tests (#31149)
This commit is contained in:
56
web/__mocks__/zustand.ts
Normal file
56
web/__mocks__/zustand.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import type * as ZustandExportedTypes from 'zustand'
|
||||||
|
import { act } from '@testing-library/react'
|
||||||
|
|
||||||
|
export * from 'zustand'
|
||||||
|
|
||||||
|
const { create: actualCreate, createStore: actualCreateStore }
|
||||||
|
// eslint-disable-next-line antfu/no-top-level-await
|
||||||
|
= await vi.importActual<typeof ZustandExportedTypes>('zustand')
|
||||||
|
|
||||||
|
export const storeResetFns = new Set<() => void>()
|
||||||
|
|
||||||
|
const createUncurried = <T>(
|
||||||
|
stateCreator: ZustandExportedTypes.StateCreator<T>,
|
||||||
|
) => {
|
||||||
|
const store = actualCreate(stateCreator)
|
||||||
|
const initialState = store.getInitialState()
|
||||||
|
storeResetFns.add(() => {
|
||||||
|
store.setState(initialState, true)
|
||||||
|
})
|
||||||
|
return store
|
||||||
|
}
|
||||||
|
|
||||||
|
export const create = (<T>(
|
||||||
|
stateCreator: ZustandExportedTypes.StateCreator<T>,
|
||||||
|
) => {
|
||||||
|
return typeof stateCreator === 'function'
|
||||||
|
? createUncurried(stateCreator)
|
||||||
|
: createUncurried
|
||||||
|
}) as typeof ZustandExportedTypes.create
|
||||||
|
|
||||||
|
const createStoreUncurried = <T>(
|
||||||
|
stateCreator: ZustandExportedTypes.StateCreator<T>,
|
||||||
|
) => {
|
||||||
|
const store = actualCreateStore(stateCreator)
|
||||||
|
const initialState = store.getInitialState()
|
||||||
|
storeResetFns.add(() => {
|
||||||
|
store.setState(initialState, true)
|
||||||
|
})
|
||||||
|
return store
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createStore = (<T>(
|
||||||
|
stateCreator: ZustandExportedTypes.StateCreator<T>,
|
||||||
|
) => {
|
||||||
|
return typeof stateCreator === 'function'
|
||||||
|
? createStoreUncurried(stateCreator)
|
||||||
|
: createStoreUncurried
|
||||||
|
}) as typeof ZustandExportedTypes.createStore
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
act(() => {
|
||||||
|
storeResetFns.forEach((resetFn) => {
|
||||||
|
resetFn()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -3,9 +3,7 @@ import type { App } from '@/types/app'
|
|||||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
import useAccessControlStore from '@/context/access-control-store'
|
import useAccessControlStore from '@/context/access-control-store'
|
||||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
|
||||||
import { AccessMode, SubjectType } from '@/models/access-control'
|
import { AccessMode, SubjectType } from '@/models/access-control'
|
||||||
import { defaultSystemFeatures } from '@/types/feature'
|
|
||||||
import Toast from '../../base/toast'
|
import Toast from '../../base/toast'
|
||||||
import AccessControlDialog from './access-control-dialog'
|
import AccessControlDialog from './access-control-dialog'
|
||||||
import AccessControlItem from './access-control-item'
|
import AccessControlItem from './access-control-item'
|
||||||
@@ -105,22 +103,6 @@ const memberSubject: Subject = {
|
|||||||
accountData: baseMember,
|
accountData: baseMember,
|
||||||
} as Subject
|
} as Subject
|
||||||
|
|
||||||
const resetAccessControlStore = () => {
|
|
||||||
useAccessControlStore.setState({
|
|
||||||
appId: '',
|
|
||||||
specificGroups: [],
|
|
||||||
specificMembers: [],
|
|
||||||
currentMenu: AccessMode.SPECIFIC_GROUPS_MEMBERS,
|
|
||||||
selectedGroupsForBreadcrumb: [],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const resetGlobalStore = () => {
|
|
||||||
useGlobalPublicStore.setState({
|
|
||||||
systemFeatures: defaultSystemFeatures,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
class MockIntersectionObserver {
|
class MockIntersectionObserver {
|
||||||
observe = vi.fn(() => undefined)
|
observe = vi.fn(() => undefined)
|
||||||
@@ -132,9 +114,6 @@ beforeAll(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks()
|
|
||||||
resetAccessControlStore()
|
|
||||||
resetGlobalStore()
|
|
||||||
mockMutateAsync.mockResolvedValue(undefined)
|
mockMutateAsync.mockResolvedValue(undefined)
|
||||||
mockUseUpdateAccessMode.mockReturnValue({
|
mockUseUpdateAccessMode.mockReturnValue({
|
||||||
isPending: false,
|
isPending: false,
|
||||||
|
|||||||
@@ -144,17 +144,6 @@ describe('constant.ts - Type Definitions', () => {
|
|||||||
|
|
||||||
// ==================== store.ts Tests ====================
|
// ==================== store.ts Tests ====================
|
||||||
describe('store.ts - Zustand Store', () => {
|
describe('store.ts - Zustand Store', () => {
|
||||||
beforeEach(() => {
|
|
||||||
// Reset store to initial state
|
|
||||||
const { setState } = useStore
|
|
||||||
setState({
|
|
||||||
tagList: [],
|
|
||||||
categoryList: [],
|
|
||||||
showTagManagementModal: false,
|
|
||||||
showCategoryManagementModal: false,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('Initial State', () => {
|
describe('Initial State', () => {
|
||||||
it('should have empty tagList initially', () => {
|
it('should have empty tagList initially', () => {
|
||||||
const { result } = renderHook(() => useStore(state => state.tagList))
|
const { result } = renderHook(() => useStore(state => state.tagList))
|
||||||
|
|||||||
@@ -134,13 +134,6 @@ describe('BUILTIN_TOOLS_ARRAY', () => {
|
|||||||
// Store Tests
|
// Store Tests
|
||||||
// ================================
|
// ================================
|
||||||
describe('useReadmePanelStore', () => {
|
describe('useReadmePanelStore', () => {
|
||||||
beforeEach(() => {
|
|
||||||
vi.clearAllMocks()
|
|
||||||
// Reset store state before each test
|
|
||||||
const { setCurrentPluginDetail } = useReadmePanelStore.getState()
|
|
||||||
setCurrentPluginDetail()
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('Initial State', () => {
|
describe('Initial State', () => {
|
||||||
it('should have undefined currentPluginDetail initially', () => {
|
it('should have undefined currentPluginDetail initially', () => {
|
||||||
const { currentPluginDetail } = useReadmePanelStore.getState()
|
const { currentPluginDetail } = useReadmePanelStore.getState()
|
||||||
@@ -228,12 +221,6 @@ describe('useReadmePanelStore', () => {
|
|||||||
// ReadmeEntrance Component Tests
|
// ReadmeEntrance Component Tests
|
||||||
// ================================
|
// ================================
|
||||||
describe('ReadmeEntrance', () => {
|
describe('ReadmeEntrance', () => {
|
||||||
beforeEach(() => {
|
|
||||||
vi.clearAllMocks()
|
|
||||||
// Reset store state
|
|
||||||
const { setCurrentPluginDetail } = useReadmePanelStore.getState()
|
|
||||||
setCurrentPluginDetail()
|
|
||||||
})
|
|
||||||
|
|
||||||
// ================================
|
// ================================
|
||||||
// Rendering Tests
|
// Rendering Tests
|
||||||
@@ -417,11 +404,6 @@ describe('ReadmeEntrance', () => {
|
|||||||
// ================================
|
// ================================
|
||||||
describe('ReadmePanel', () => {
|
describe('ReadmePanel', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks()
|
|
||||||
// Reset store state
|
|
||||||
const { setCurrentPluginDetail } = useReadmePanelStore.getState()
|
|
||||||
setCurrentPluginDetail()
|
|
||||||
// Reset mock
|
|
||||||
mockUsePluginReadme.mockReturnValue({
|
mockUsePluginReadme.mockReturnValue({
|
||||||
data: null,
|
data: null,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"target": "es2015",
|
"target": "es2022",
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
"lib": [
|
"lib": [
|
||||||
"dom",
|
"dom",
|
||||||
|
|||||||
@@ -85,6 +85,10 @@ afterEach(() => {
|
|||||||
// mock next/image to avoid width/height requirements for data URLs
|
// mock next/image to avoid width/height requirements for data URLs
|
||||||
vi.mock('next/image')
|
vi.mock('next/image')
|
||||||
|
|
||||||
|
// mock zustand - auto-resets all stores after each test
|
||||||
|
// Based on official Zustand testing guide: https://zustand.docs.pmnd.rs/guides/testing
|
||||||
|
vi.mock('zustand')
|
||||||
|
|
||||||
// mock react-i18next
|
// mock react-i18next
|
||||||
vi.mock('react-i18next', async () => {
|
vi.mock('react-i18next', async () => {
|
||||||
const actual = await vi.importActual<typeof import('react-i18next')>('react-i18next')
|
const actual = await vi.importActual<typeof import('react-i18next')>('react-i18next')
|
||||||
|
|||||||
Reference in New Issue
Block a user