mirror of
https://github.com/YFGaia/dify-plus.git
synced 2026-06-12 18:11:42 +08:00
refactor(web): migrate to Vitest and esm (#29974)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: yyh <yuanyouhuilyz@gmail.com>
This commit is contained in:
@@ -4,12 +4,7 @@ import AppCard, { type AppCardProps } from './index'
|
||||
import type { App } from '@/models/explore'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
|
||||
jest.mock('@/app/components/base/app-icon', () => ({
|
||||
__esModule: true,
|
||||
default: ({ children }: any) => <div data-testid="app-icon">{children}</div>,
|
||||
}))
|
||||
|
||||
jest.mock('../../app/type-selector', () => ({
|
||||
vi.mock('../../app/type-selector', () => ({
|
||||
AppTypeIcon: ({ type }: any) => <div data-testid="app-type-icon">{type}</div>,
|
||||
}))
|
||||
|
||||
@@ -42,7 +37,7 @@ const createApp = (overrides?: Partial<App>): App => ({
|
||||
})
|
||||
|
||||
describe('AppCard', () => {
|
||||
const onCreate = jest.fn()
|
||||
const onCreate = vi.fn()
|
||||
|
||||
const renderComponent = (props?: Partial<AppCardProps>) => {
|
||||
const mergedProps: AppCardProps = {
|
||||
@@ -56,7 +51,7 @@ describe('AppCard', () => {
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('should render app info with correct mode label when mode is CHAT', () => {
|
||||
|
||||
@@ -9,7 +9,7 @@ import type { CreateAppModalProps } from './index'
|
||||
|
||||
let mockTranslationOverrides: Record<string, string | undefined> = {}
|
||||
|
||||
jest.mock('react-i18next', () => ({
|
||||
vi.mock('react-i18next', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string, options?: Record<string, unknown>) => {
|
||||
const override = mockTranslationOverrides[key]
|
||||
@@ -23,22 +23,22 @@ jest.mock('react-i18next', () => ({
|
||||
},
|
||||
i18n: {
|
||||
language: 'en',
|
||||
changeLanguage: jest.fn(),
|
||||
changeLanguage: vi.fn(),
|
||||
},
|
||||
}),
|
||||
Trans: ({ children }: { children?: React.ReactNode }) => children,
|
||||
initReactI18next: {
|
||||
type: '3rdParty',
|
||||
init: jest.fn(),
|
||||
init: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
// Avoid heavy emoji dataset initialization during unit tests.
|
||||
jest.mock('emoji-mart', () => ({
|
||||
init: jest.fn(),
|
||||
SearchIndex: { search: jest.fn().mockResolvedValue([]) },
|
||||
vi.mock('emoji-mart', () => ({
|
||||
init: vi.fn(),
|
||||
SearchIndex: { search: vi.fn().mockResolvedValue([]) },
|
||||
}))
|
||||
jest.mock('@emoji-mart/data', () => ({
|
||||
vi.mock('@emoji-mart/data', () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
categories: [
|
||||
@@ -47,11 +47,11 @@ jest.mock('@emoji-mart/data', () => ({
|
||||
},
|
||||
}))
|
||||
|
||||
jest.mock('next/navigation', () => ({
|
||||
vi.mock('next/navigation', () => ({
|
||||
useParams: () => ({}),
|
||||
}))
|
||||
|
||||
jest.mock('@/context/app-context', () => ({
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useAppContext: () => ({
|
||||
userProfile: { email: 'test@example.com' },
|
||||
langGeniusVersionInfo: { current_version: '0.0.0' },
|
||||
@@ -73,7 +73,7 @@ let mockPlanType: Plan = Plan.team
|
||||
let mockUsagePlanInfo: UsagePlanInfo = createPlanInfo(1)
|
||||
let mockTotalPlanInfo: UsagePlanInfo = createPlanInfo(10)
|
||||
|
||||
jest.mock('@/context/provider-context', () => ({
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContext: () => {
|
||||
const withPlan = createMockPlan(mockPlanType)
|
||||
const withUsage = createMockPlanUsage(mockUsagePlanInfo, withPlan)
|
||||
@@ -85,8 +85,8 @@ jest.mock('@/context/provider-context', () => ({
|
||||
type ConfirmPayload = Parameters<CreateAppModalProps['onConfirm']>[0]
|
||||
|
||||
const setup = (overrides: Partial<CreateAppModalProps> = {}) => {
|
||||
const onConfirm = jest.fn<Promise<void>, [ConfirmPayload]>().mockResolvedValue(undefined)
|
||||
const onHide = jest.fn<void, []>()
|
||||
const onConfirm = vi.fn<(payload: ConfirmPayload) => Promise<void>>().mockResolvedValue(undefined)
|
||||
const onHide = vi.fn()
|
||||
|
||||
const props: CreateAppModalProps = {
|
||||
show: true,
|
||||
@@ -121,7 +121,7 @@ const getAppIconTrigger = (): HTMLElement => {
|
||||
|
||||
describe('CreateAppModal', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
vi.clearAllMocks()
|
||||
mockTranslationOverrides = {}
|
||||
mockEnableBilling = false
|
||||
mockPlanType = Plan.team
|
||||
@@ -261,11 +261,11 @@ describe('CreateAppModal', () => {
|
||||
// Shortcut handlers are important for power users and must respect gating rules.
|
||||
describe('Keyboard Shortcuts', () => {
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers()
|
||||
vi.useFakeTimers()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers()
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
test.each([
|
||||
@@ -276,7 +276,7 @@ describe('CreateAppModal', () => {
|
||||
|
||||
fireEvent.keyDown(window, { key: 'Enter', keyCode: 13, ...modifier })
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(300)
|
||||
vi.advanceTimersByTime(300)
|
||||
})
|
||||
|
||||
expect(onConfirm).toHaveBeenCalledTimes(1)
|
||||
@@ -288,7 +288,7 @@ describe('CreateAppModal', () => {
|
||||
|
||||
fireEvent.keyDown(window, { key: 'Enter', keyCode: 13, metaKey: true })
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(300)
|
||||
vi.advanceTimersByTime(300)
|
||||
})
|
||||
|
||||
expect(onConfirm).not.toHaveBeenCalled()
|
||||
@@ -305,7 +305,7 @@ describe('CreateAppModal', () => {
|
||||
|
||||
fireEvent.keyDown(window, { key: 'Enter', keyCode: 13, metaKey: true })
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(300)
|
||||
vi.advanceTimersByTime(300)
|
||||
})
|
||||
|
||||
expect(onConfirm).not.toHaveBeenCalled()
|
||||
@@ -322,7 +322,7 @@ describe('CreateAppModal', () => {
|
||||
|
||||
fireEvent.keyDown(window, { key: 'Enter', keyCode: 13, metaKey: true })
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(300)
|
||||
vi.advanceTimersByTime(300)
|
||||
})
|
||||
|
||||
expect(onConfirm).toHaveBeenCalledTimes(1)
|
||||
@@ -334,7 +334,7 @@ describe('CreateAppModal', () => {
|
||||
|
||||
fireEvent.keyDown(window, { key: 'Enter', keyCode: 13, metaKey: true })
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(300)
|
||||
vi.advanceTimersByTime(300)
|
||||
})
|
||||
|
||||
expect(onConfirm).not.toHaveBeenCalled()
|
||||
@@ -361,7 +361,7 @@ describe('CreateAppModal', () => {
|
||||
})
|
||||
|
||||
test('should update icon payload when selecting emoji and confirming', () => {
|
||||
jest.useFakeTimers()
|
||||
vi.useFakeTimers()
|
||||
try {
|
||||
const { onConfirm } = setup({
|
||||
appIconType: 'image',
|
||||
@@ -371,16 +371,19 @@ describe('CreateAppModal', () => {
|
||||
|
||||
fireEvent.click(getAppIconTrigger())
|
||||
|
||||
const emoji = document.querySelector('em-emoji[id="😀"]')
|
||||
if (!(emoji instanceof HTMLElement))
|
||||
throw new Error('Failed to locate emoji option in icon picker')
|
||||
fireEvent.click(emoji)
|
||||
// Find the emoji grid by locating the category label, then find the clickable emoji wrapper
|
||||
const categoryLabel = screen.getByText('people')
|
||||
const emojiGrid = categoryLabel.nextElementSibling
|
||||
const clickableEmojiWrapper = emojiGrid?.firstElementChild
|
||||
if (!(clickableEmojiWrapper instanceof HTMLElement))
|
||||
throw new Error('Failed to locate emoji wrapper')
|
||||
fireEvent.click(clickableEmojiWrapper)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'app.iconPicker.ok' }))
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.create' }))
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(300)
|
||||
vi.advanceTimersByTime(300)
|
||||
})
|
||||
|
||||
expect(onConfirm).toHaveBeenCalledTimes(1)
|
||||
@@ -392,47 +395,68 @@ describe('CreateAppModal', () => {
|
||||
})
|
||||
}
|
||||
finally {
|
||||
jest.useRealTimers()
|
||||
vi.useRealTimers()
|
||||
}
|
||||
})
|
||||
|
||||
test('should reset emoji icon to initial props when picker is cancelled', () => {
|
||||
setup({
|
||||
appIconType: 'emoji',
|
||||
appIcon: '🤖',
|
||||
appIconBackground: '#FFEAD5',
|
||||
})
|
||||
vi.useFakeTimers()
|
||||
try {
|
||||
const { onConfirm } = setup({
|
||||
appIconType: 'emoji',
|
||||
appIcon: '🤖',
|
||||
appIconBackground: '#FFEAD5',
|
||||
})
|
||||
|
||||
expect(document.querySelector('em-emoji[id="🤖"]')).toBeInTheDocument()
|
||||
// Open picker, select a new emoji, and confirm
|
||||
fireEvent.click(getAppIconTrigger())
|
||||
|
||||
fireEvent.click(getAppIconTrigger())
|
||||
// Find the emoji grid by locating the category label, then find the clickable emoji wrapper
|
||||
const categoryLabel = screen.getByText('people')
|
||||
const emojiGrid = categoryLabel.nextElementSibling
|
||||
const clickableEmojiWrapper = emojiGrid?.firstElementChild
|
||||
if (!(clickableEmojiWrapper instanceof HTMLElement))
|
||||
throw new Error('Failed to locate emoji wrapper')
|
||||
fireEvent.click(clickableEmojiWrapper)
|
||||
|
||||
const emoji = document.querySelector('em-emoji[id="😀"]')
|
||||
if (!(emoji instanceof HTMLElement))
|
||||
throw new Error('Failed to locate emoji option in icon picker')
|
||||
fireEvent.click(emoji)
|
||||
fireEvent.click(screen.getByRole('button', { name: 'app.iconPicker.ok' }))
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'app.iconPicker.ok' }))
|
||||
expect(screen.queryByRole('button', { name: 'app.iconPicker.cancel' })).not.toBeInTheDocument()
|
||||
|
||||
expect(screen.queryByRole('button', { name: 'app.iconPicker.cancel' })).not.toBeInTheDocument()
|
||||
expect(document.querySelector('em-emoji[id="😀"]')).toBeInTheDocument()
|
||||
// Open picker again and cancel - should reset to initial props
|
||||
fireEvent.click(getAppIconTrigger())
|
||||
fireEvent.click(screen.getByRole('button', { name: 'app.iconPicker.cancel' }))
|
||||
|
||||
fireEvent.click(getAppIconTrigger())
|
||||
fireEvent.click(screen.getByRole('button', { name: 'app.iconPicker.cancel' }))
|
||||
expect(screen.queryByRole('button', { name: 'app.iconPicker.cancel' })).not.toBeInTheDocument()
|
||||
|
||||
expect(screen.queryByRole('button', { name: 'app.iconPicker.cancel' })).not.toBeInTheDocument()
|
||||
expect(document.querySelector('em-emoji[id="🤖"]')).toBeInTheDocument()
|
||||
// Submit and verify the payload uses the original icon (cancel reverts to props)
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.create' }))
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(300)
|
||||
})
|
||||
|
||||
expect(onConfirm).toHaveBeenCalledTimes(1)
|
||||
const payload = onConfirm.mock.calls[0][0]
|
||||
expect(payload).toMatchObject({
|
||||
icon_type: 'emoji',
|
||||
icon: '🤖',
|
||||
icon_background: '#FFEAD5',
|
||||
})
|
||||
}
|
||||
finally {
|
||||
vi.useRealTimers()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// Submitting uses a debounced handler and builds a payload from current form state.
|
||||
describe('Submitting', () => {
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers()
|
||||
vi.useFakeTimers()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers()
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
test('should call onConfirm with emoji payload and hide when create is clicked', () => {
|
||||
@@ -446,7 +470,7 @@ describe('CreateAppModal', () => {
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.create' }))
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(300)
|
||||
vi.advanceTimersByTime(300)
|
||||
})
|
||||
|
||||
expect(onConfirm).toHaveBeenCalledTimes(1)
|
||||
@@ -470,7 +494,7 @@ describe('CreateAppModal', () => {
|
||||
fireEvent.change(screen.getByPlaceholderText('app.newApp.appDescriptionPlaceholder'), { target: { value: 'Updated description' } })
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.create' }))
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(300)
|
||||
vi.advanceTimersByTime(300)
|
||||
})
|
||||
|
||||
expect(onConfirm).toHaveBeenCalledTimes(1)
|
||||
@@ -487,7 +511,7 @@ describe('CreateAppModal', () => {
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.create' }))
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(300)
|
||||
vi.advanceTimersByTime(300)
|
||||
})
|
||||
|
||||
const payload = onConfirm.mock.calls[0][0]
|
||||
@@ -511,7 +535,7 @@ describe('CreateAppModal', () => {
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.save' }))
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(300)
|
||||
vi.advanceTimersByTime(300)
|
||||
})
|
||||
|
||||
const payload = onConfirm.mock.calls[0][0]
|
||||
@@ -526,7 +550,7 @@ describe('CreateAppModal', () => {
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.save' }))
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(300)
|
||||
vi.advanceTimersByTime(300)
|
||||
})
|
||||
|
||||
const payload = onConfirm.mock.calls[0][0]
|
||||
@@ -539,7 +563,7 @@ describe('CreateAppModal', () => {
|
||||
fireEvent.change(screen.getByRole('spinbutton'), { target: { value: 'abc' } })
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.save' }))
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(300)
|
||||
vi.advanceTimersByTime(300)
|
||||
})
|
||||
|
||||
const payload = onConfirm.mock.calls[0][0]
|
||||
@@ -553,12 +577,12 @@ describe('CreateAppModal', () => {
|
||||
fireEvent.change(screen.getByPlaceholderText('app.newApp.appNamePlaceholder'), { target: { value: ' ' } })
|
||||
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(300)
|
||||
vi.advanceTimersByTime(300)
|
||||
})
|
||||
|
||||
expect(screen.getByText('explore.appCustomize.nameRequired')).toBeInTheDocument()
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(6000)
|
||||
vi.advanceTimersByTime(6000)
|
||||
})
|
||||
expect(screen.queryByText('explore.appCustomize.nameRequired')).not.toBeInTheDocument()
|
||||
expect(onConfirm).not.toHaveBeenCalled()
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
import type { Mock } from 'vitest'
|
||||
import { render, screen, waitFor } from '@testing-library/react'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
import { AccessMode } from '@/models/access-control'
|
||||
|
||||
// Mock external dependencies BEFORE imports
|
||||
jest.mock('use-context-selector', () => ({
|
||||
useContext: jest.fn(),
|
||||
createContext: jest.fn(() => ({})),
|
||||
vi.mock('use-context-selector', () => ({
|
||||
useContext: vi.fn(),
|
||||
createContext: vi.fn(() => ({})),
|
||||
}))
|
||||
jest.mock('@/context/web-app-context', () => ({
|
||||
useWebAppStore: jest.fn(),
|
||||
vi.mock('@/context/web-app-context', () => ({
|
||||
useWebAppStore: vi.fn(),
|
||||
}))
|
||||
jest.mock('@/service/access-control', () => ({
|
||||
useGetUserCanAccessApp: jest.fn(),
|
||||
vi.mock('@/service/access-control', () => ({
|
||||
useGetUserCanAccessApp: vi.fn(),
|
||||
}))
|
||||
jest.mock('@/service/use-explore', () => ({
|
||||
useGetInstalledAppAccessModeByAppId: jest.fn(),
|
||||
useGetInstalledAppParams: jest.fn(),
|
||||
useGetInstalledAppMeta: jest.fn(),
|
||||
vi.mock('@/service/use-explore', () => ({
|
||||
useGetInstalledAppAccessModeByAppId: vi.fn(),
|
||||
useGetInstalledAppParams: vi.fn(),
|
||||
useGetInstalledAppMeta: vi.fn(),
|
||||
}))
|
||||
|
||||
import { useContext } from 'use-context-selector'
|
||||
@@ -46,7 +47,7 @@ import type { InstalledApp as InstalledAppType } from '@/models/explore'
|
||||
* The internal logic of ChatWithHistory and TextGenerationApp should be tested
|
||||
* in their own dedicated test files.
|
||||
*/
|
||||
jest.mock('@/app/components/share/text-generation', () => ({
|
||||
vi.mock('@/app/components/share/text-generation', () => ({
|
||||
__esModule: true,
|
||||
default: ({ isInstalledApp, installedAppInfo, isWorkflow }: {
|
||||
isInstalledApp?: boolean
|
||||
@@ -61,7 +62,7 @@ jest.mock('@/app/components/share/text-generation', () => ({
|
||||
),
|
||||
}))
|
||||
|
||||
jest.mock('@/app/components/base/chat/chat-with-history', () => ({
|
||||
vi.mock('@/app/components/base/chat/chat-with-history', () => ({
|
||||
__esModule: true,
|
||||
default: ({ installedAppInfo, className }: {
|
||||
installedAppInfo?: InstalledAppType
|
||||
@@ -74,11 +75,11 @@ jest.mock('@/app/components/base/chat/chat-with-history', () => ({
|
||||
}))
|
||||
|
||||
describe('InstalledApp', () => {
|
||||
const mockUpdateAppInfo = jest.fn()
|
||||
const mockUpdateWebAppAccessMode = jest.fn()
|
||||
const mockUpdateAppParams = jest.fn()
|
||||
const mockUpdateWebAppMeta = jest.fn()
|
||||
const mockUpdateUserCanAccessApp = jest.fn()
|
||||
const mockUpdateAppInfo = vi.fn()
|
||||
const mockUpdateWebAppAccessMode = vi.fn()
|
||||
const mockUpdateAppParams = vi.fn()
|
||||
const mockUpdateWebAppMeta = vi.fn()
|
||||
const mockUpdateUserCanAccessApp = vi.fn()
|
||||
|
||||
const mockInstalledApp = {
|
||||
id: 'installed-app-123',
|
||||
@@ -116,22 +117,22 @@ describe('InstalledApp', () => {
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
vi.clearAllMocks()
|
||||
|
||||
// Mock useContext
|
||||
;(useContext as jest.Mock).mockReturnValue({
|
||||
;(useContext as Mock).mockReturnValue({
|
||||
installedApps: [mockInstalledApp],
|
||||
isFetchingInstalledApps: false,
|
||||
})
|
||||
|
||||
// Mock useWebAppStore
|
||||
;(useWebAppStore as unknown as jest.Mock).mockImplementation((
|
||||
;(useWebAppStore as unknown as Mock).mockImplementation((
|
||||
selector: (state: {
|
||||
updateAppInfo: jest.Mock
|
||||
updateWebAppAccessMode: jest.Mock
|
||||
updateAppParams: jest.Mock
|
||||
updateWebAppMeta: jest.Mock
|
||||
updateUserCanAccessApp: jest.Mock
|
||||
updateAppInfo: Mock
|
||||
updateWebAppAccessMode: Mock
|
||||
updateAppParams: Mock
|
||||
updateWebAppMeta: Mock
|
||||
updateUserCanAccessApp: Mock
|
||||
}) => unknown,
|
||||
) => {
|
||||
const state = {
|
||||
@@ -145,25 +146,25 @@ describe('InstalledApp', () => {
|
||||
})
|
||||
|
||||
// Mock service hooks with default success states
|
||||
;(useGetInstalledAppAccessModeByAppId as jest.Mock).mockReturnValue({
|
||||
;(useGetInstalledAppAccessModeByAppId as Mock).mockReturnValue({
|
||||
isFetching: false,
|
||||
data: mockWebAppAccessMode,
|
||||
error: null,
|
||||
})
|
||||
|
||||
;(useGetInstalledAppParams as jest.Mock).mockReturnValue({
|
||||
;(useGetInstalledAppParams as Mock).mockReturnValue({
|
||||
isFetching: false,
|
||||
data: mockAppParams,
|
||||
error: null,
|
||||
})
|
||||
|
||||
;(useGetInstalledAppMeta as jest.Mock).mockReturnValue({
|
||||
;(useGetInstalledAppMeta as Mock).mockReturnValue({
|
||||
isFetching: false,
|
||||
data: mockAppMeta,
|
||||
error: null,
|
||||
})
|
||||
|
||||
;(useGetUserCanAccessApp as jest.Mock).mockReturnValue({
|
||||
;(useGetUserCanAccessApp as Mock).mockReturnValue({
|
||||
data: mockUserCanAccessApp,
|
||||
error: null,
|
||||
})
|
||||
@@ -176,7 +177,7 @@ describe('InstalledApp', () => {
|
||||
})
|
||||
|
||||
it('should render loading state when fetching app params', () => {
|
||||
;(useGetInstalledAppParams as jest.Mock).mockReturnValue({
|
||||
;(useGetInstalledAppParams as Mock).mockReturnValue({
|
||||
isFetching: true,
|
||||
data: null,
|
||||
error: null,
|
||||
@@ -188,7 +189,7 @@ describe('InstalledApp', () => {
|
||||
})
|
||||
|
||||
it('should render loading state when fetching app meta', () => {
|
||||
;(useGetInstalledAppMeta as jest.Mock).mockReturnValue({
|
||||
;(useGetInstalledAppMeta as Mock).mockReturnValue({
|
||||
isFetching: true,
|
||||
data: null,
|
||||
error: null,
|
||||
@@ -200,7 +201,7 @@ describe('InstalledApp', () => {
|
||||
})
|
||||
|
||||
it('should render loading state when fetching web app access mode', () => {
|
||||
;(useGetInstalledAppAccessModeByAppId as jest.Mock).mockReturnValue({
|
||||
;(useGetInstalledAppAccessModeByAppId as Mock).mockReturnValue({
|
||||
isFetching: true,
|
||||
data: null,
|
||||
error: null,
|
||||
@@ -212,7 +213,7 @@ describe('InstalledApp', () => {
|
||||
})
|
||||
|
||||
it('should render loading state when fetching installed apps', () => {
|
||||
;(useContext as jest.Mock).mockReturnValue({
|
||||
;(useContext as Mock).mockReturnValue({
|
||||
installedApps: [mockInstalledApp],
|
||||
isFetchingInstalledApps: true,
|
||||
})
|
||||
@@ -223,7 +224,7 @@ describe('InstalledApp', () => {
|
||||
})
|
||||
|
||||
it('should render app not found (404) when installedApp does not exist', () => {
|
||||
;(useContext as jest.Mock).mockReturnValue({
|
||||
;(useContext as Mock).mockReturnValue({
|
||||
installedApps: [],
|
||||
isFetchingInstalledApps: false,
|
||||
})
|
||||
@@ -236,7 +237,7 @@ describe('InstalledApp', () => {
|
||||
describe('Error States', () => {
|
||||
it('should render error when app params fails to load', () => {
|
||||
const error = new Error('Failed to load app params')
|
||||
;(useGetInstalledAppParams as jest.Mock).mockReturnValue({
|
||||
;(useGetInstalledAppParams as Mock).mockReturnValue({
|
||||
isFetching: false,
|
||||
data: null,
|
||||
error,
|
||||
@@ -248,7 +249,7 @@ describe('InstalledApp', () => {
|
||||
|
||||
it('should render error when app meta fails to load', () => {
|
||||
const error = new Error('Failed to load app meta')
|
||||
;(useGetInstalledAppMeta as jest.Mock).mockReturnValue({
|
||||
;(useGetInstalledAppMeta as Mock).mockReturnValue({
|
||||
isFetching: false,
|
||||
data: null,
|
||||
error,
|
||||
@@ -260,7 +261,7 @@ describe('InstalledApp', () => {
|
||||
|
||||
it('should render error when web app access mode fails to load', () => {
|
||||
const error = new Error('Failed to load access mode')
|
||||
;(useGetInstalledAppAccessModeByAppId as jest.Mock).mockReturnValue({
|
||||
;(useGetInstalledAppAccessModeByAppId as Mock).mockReturnValue({
|
||||
isFetching: false,
|
||||
data: null,
|
||||
error,
|
||||
@@ -272,7 +273,7 @@ describe('InstalledApp', () => {
|
||||
|
||||
it('should render error when user access check fails', () => {
|
||||
const error = new Error('Failed to check user access')
|
||||
;(useGetUserCanAccessApp as jest.Mock).mockReturnValue({
|
||||
;(useGetUserCanAccessApp as Mock).mockReturnValue({
|
||||
data: null,
|
||||
error,
|
||||
})
|
||||
@@ -282,7 +283,7 @@ describe('InstalledApp', () => {
|
||||
})
|
||||
|
||||
it('should render no permission (403) when user cannot access app', () => {
|
||||
;(useGetUserCanAccessApp as jest.Mock).mockReturnValue({
|
||||
;(useGetUserCanAccessApp as Mock).mockReturnValue({
|
||||
data: { result: false },
|
||||
error: null,
|
||||
})
|
||||
@@ -308,7 +309,7 @@ describe('InstalledApp', () => {
|
||||
mode: AppModeEnum.ADVANCED_CHAT,
|
||||
},
|
||||
}
|
||||
;(useContext as jest.Mock).mockReturnValue({
|
||||
;(useContext as Mock).mockReturnValue({
|
||||
installedApps: [advancedChatApp],
|
||||
isFetchingInstalledApps: false,
|
||||
})
|
||||
@@ -326,7 +327,7 @@ describe('InstalledApp', () => {
|
||||
mode: AppModeEnum.AGENT_CHAT,
|
||||
},
|
||||
}
|
||||
;(useContext as jest.Mock).mockReturnValue({
|
||||
;(useContext as Mock).mockReturnValue({
|
||||
installedApps: [agentChatApp],
|
||||
isFetchingInstalledApps: false,
|
||||
})
|
||||
@@ -344,7 +345,7 @@ describe('InstalledApp', () => {
|
||||
mode: AppModeEnum.COMPLETION,
|
||||
},
|
||||
}
|
||||
;(useContext as jest.Mock).mockReturnValue({
|
||||
;(useContext as Mock).mockReturnValue({
|
||||
installedApps: [completionApp],
|
||||
isFetchingInstalledApps: false,
|
||||
})
|
||||
@@ -362,7 +363,7 @@ describe('InstalledApp', () => {
|
||||
mode: AppModeEnum.WORKFLOW,
|
||||
},
|
||||
}
|
||||
;(useContext as jest.Mock).mockReturnValue({
|
||||
;(useContext as Mock).mockReturnValue({
|
||||
installedApps: [workflowApp],
|
||||
isFetchingInstalledApps: false,
|
||||
})
|
||||
@@ -377,7 +378,7 @@ describe('InstalledApp', () => {
|
||||
it('should use id prop to find installed app', () => {
|
||||
const app1 = { ...mockInstalledApp, id: 'app-1' }
|
||||
const app2 = { ...mockInstalledApp, id: 'app-2' }
|
||||
;(useContext as jest.Mock).mockReturnValue({
|
||||
;(useContext as Mock).mockReturnValue({
|
||||
installedApps: [app1, app2],
|
||||
isFetchingInstalledApps: false,
|
||||
})
|
||||
@@ -419,7 +420,7 @@ describe('InstalledApp', () => {
|
||||
})
|
||||
|
||||
it('should update app info to null when installedApp is not found', async () => {
|
||||
;(useContext as jest.Mock).mockReturnValue({
|
||||
;(useContext as Mock).mockReturnValue({
|
||||
installedApps: [],
|
||||
isFetchingInstalledApps: false,
|
||||
})
|
||||
@@ -464,7 +465,7 @@ describe('InstalledApp', () => {
|
||||
})
|
||||
|
||||
it('should update user can access app to false when result is false', async () => {
|
||||
;(useGetUserCanAccessApp as jest.Mock).mockReturnValue({
|
||||
;(useGetUserCanAccessApp as Mock).mockReturnValue({
|
||||
data: { result: false },
|
||||
error: null,
|
||||
})
|
||||
@@ -477,7 +478,7 @@ describe('InstalledApp', () => {
|
||||
})
|
||||
|
||||
it('should update user can access app to false when data is null', async () => {
|
||||
;(useGetUserCanAccessApp as jest.Mock).mockReturnValue({
|
||||
;(useGetUserCanAccessApp as Mock).mockReturnValue({
|
||||
data: null,
|
||||
error: null,
|
||||
})
|
||||
@@ -490,7 +491,7 @@ describe('InstalledApp', () => {
|
||||
})
|
||||
|
||||
it('should not update app params when data is null', async () => {
|
||||
;(useGetInstalledAppParams as jest.Mock).mockReturnValue({
|
||||
;(useGetInstalledAppParams as Mock).mockReturnValue({
|
||||
isFetching: false,
|
||||
data: null,
|
||||
error: null,
|
||||
@@ -506,7 +507,7 @@ describe('InstalledApp', () => {
|
||||
})
|
||||
|
||||
it('should not update app meta when data is null', async () => {
|
||||
;(useGetInstalledAppMeta as jest.Mock).mockReturnValue({
|
||||
;(useGetInstalledAppMeta as Mock).mockReturnValue({
|
||||
isFetching: false,
|
||||
data: null,
|
||||
error: null,
|
||||
@@ -522,7 +523,7 @@ describe('InstalledApp', () => {
|
||||
})
|
||||
|
||||
it('should not update access mode when data is null', async () => {
|
||||
;(useGetInstalledAppAccessModeByAppId as jest.Mock).mockReturnValue({
|
||||
;(useGetInstalledAppAccessModeByAppId as Mock).mockReturnValue({
|
||||
isFetching: false,
|
||||
data: null,
|
||||
error: null,
|
||||
@@ -540,7 +541,7 @@ describe('InstalledApp', () => {
|
||||
|
||||
describe('Edge Cases', () => {
|
||||
it('should handle empty installedApps array', () => {
|
||||
;(useContext as jest.Mock).mockReturnValue({
|
||||
;(useContext as Mock).mockReturnValue({
|
||||
installedApps: [],
|
||||
isFetchingInstalledApps: false,
|
||||
})
|
||||
@@ -558,7 +559,7 @@ describe('InstalledApp', () => {
|
||||
name: 'Other App',
|
||||
},
|
||||
}
|
||||
;(useContext as jest.Mock).mockReturnValue({
|
||||
;(useContext as Mock).mockReturnValue({
|
||||
installedApps: [otherApp, mockInstalledApp],
|
||||
isFetchingInstalledApps: false,
|
||||
})
|
||||
@@ -572,7 +573,7 @@ describe('InstalledApp', () => {
|
||||
it('should handle rapid id prop changes', async () => {
|
||||
const app1 = { ...mockInstalledApp, id: 'app-1' }
|
||||
const app2 = { ...mockInstalledApp, id: 'app-2' }
|
||||
;(useContext as jest.Mock).mockReturnValue({
|
||||
;(useContext as Mock).mockReturnValue({
|
||||
installedApps: [app1, app2],
|
||||
isFetchingInstalledApps: false,
|
||||
})
|
||||
@@ -597,7 +598,7 @@ describe('InstalledApp', () => {
|
||||
})
|
||||
|
||||
it('should call service hooks with null when installedApp is not found', () => {
|
||||
;(useContext as jest.Mock).mockReturnValue({
|
||||
;(useContext as Mock).mockReturnValue({
|
||||
installedApps: [],
|
||||
isFetchingInstalledApps: false,
|
||||
})
|
||||
@@ -616,7 +617,7 @@ describe('InstalledApp', () => {
|
||||
|
||||
describe('Render Priority', () => {
|
||||
it('should show error before loading state', () => {
|
||||
;(useGetInstalledAppParams as jest.Mock).mockReturnValue({
|
||||
;(useGetInstalledAppParams as Mock).mockReturnValue({
|
||||
isFetching: true,
|
||||
data: null,
|
||||
error: new Error('Some error'),
|
||||
@@ -628,12 +629,12 @@ describe('InstalledApp', () => {
|
||||
})
|
||||
|
||||
it('should show error before permission check', () => {
|
||||
;(useGetInstalledAppParams as jest.Mock).mockReturnValue({
|
||||
;(useGetInstalledAppParams as Mock).mockReturnValue({
|
||||
isFetching: false,
|
||||
data: null,
|
||||
error: new Error('Params error'),
|
||||
})
|
||||
;(useGetUserCanAccessApp as jest.Mock).mockReturnValue({
|
||||
;(useGetUserCanAccessApp as Mock).mockReturnValue({
|
||||
data: { result: false },
|
||||
error: null,
|
||||
})
|
||||
@@ -645,11 +646,11 @@ describe('InstalledApp', () => {
|
||||
})
|
||||
|
||||
it('should show permission error before 404', () => {
|
||||
;(useContext as jest.Mock).mockReturnValue({
|
||||
;(useContext as Mock).mockReturnValue({
|
||||
installedApps: [],
|
||||
isFetchingInstalledApps: false,
|
||||
})
|
||||
;(useGetUserCanAccessApp as jest.Mock).mockReturnValue({
|
||||
;(useGetUserCanAccessApp as Mock).mockReturnValue({
|
||||
data: { result: false },
|
||||
error: null,
|
||||
})
|
||||
@@ -661,11 +662,11 @@ describe('InstalledApp', () => {
|
||||
})
|
||||
|
||||
it('should show loading before 404', () => {
|
||||
;(useContext as jest.Mock).mockReturnValue({
|
||||
;(useContext as Mock).mockReturnValue({
|
||||
installedApps: [],
|
||||
isFetchingInstalledApps: false,
|
||||
})
|
||||
;(useGetInstalledAppParams as jest.Mock).mockReturnValue({
|
||||
;(useGetInstalledAppParams as Mock).mockReturnValue({
|
||||
isFetching: true,
|
||||
data: null,
|
||||
error: null,
|
||||
|
||||
Reference in New Issue
Block a user