mirror of
https://github.com/langgenius/dify.git
synced 2025-12-19 17:27:16 -05:00
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
682 lines
21 KiB
TypeScript
682 lines
21 KiB
TypeScript
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(() => ({})),
|
|
}))
|
|
jest.mock('@/context/web-app-context', () => ({
|
|
useWebAppStore: jest.fn(),
|
|
}))
|
|
jest.mock('@/service/access-control', () => ({
|
|
useGetUserCanAccessApp: jest.fn(),
|
|
}))
|
|
jest.mock('@/service/use-explore', () => ({
|
|
useGetInstalledAppAccessModeByAppId: jest.fn(),
|
|
useGetInstalledAppParams: jest.fn(),
|
|
useGetInstalledAppMeta: jest.fn(),
|
|
}))
|
|
|
|
import { useContext } from 'use-context-selector'
|
|
import InstalledApp from './index'
|
|
import { useWebAppStore } from '@/context/web-app-context'
|
|
import { useGetUserCanAccessApp } from '@/service/access-control'
|
|
import { useGetInstalledAppAccessModeByAppId, useGetInstalledAppMeta, useGetInstalledAppParams } from '@/service/use-explore'
|
|
import type { InstalledApp as InstalledAppType } from '@/models/explore'
|
|
|
|
/**
|
|
* Mock child components for unit testing
|
|
*
|
|
* RATIONALE FOR MOCKING:
|
|
* - TextGenerationApp: 648 lines, complex batch processing, task management, file uploads
|
|
* - ChatWithHistory: 576-line custom hook, complex conversation/history management, 30+ context values
|
|
*
|
|
* These components are too complex to test as real components. Using real components would:
|
|
* 1. Require mocking dozens of their dependencies (services, contexts, hooks)
|
|
* 2. Make tests fragile and coupled to child component implementation details
|
|
* 3. Violate the principle of testing one component in isolation
|
|
*
|
|
* For a container component like InstalledApp, its responsibility is to:
|
|
* - Correctly route to the appropriate child component based on app mode
|
|
* - Pass the correct props to child components
|
|
* - Handle loading/error states before rendering children
|
|
*
|
|
* The internal logic of ChatWithHistory and TextGenerationApp should be tested
|
|
* in their own dedicated test files.
|
|
*/
|
|
jest.mock('@/app/components/share/text-generation', () => ({
|
|
__esModule: true,
|
|
default: ({ isInstalledApp, installedAppInfo, isWorkflow }: {
|
|
isInstalledApp?: boolean
|
|
installedAppInfo?: InstalledAppType
|
|
isWorkflow?: boolean
|
|
}) => (
|
|
<div data-testid="text-generation-app">
|
|
Text Generation App
|
|
{isWorkflow && ' (Workflow)'}
|
|
{isInstalledApp && ` - ${installedAppInfo?.id}`}
|
|
</div>
|
|
),
|
|
}))
|
|
|
|
jest.mock('@/app/components/base/chat/chat-with-history', () => ({
|
|
__esModule: true,
|
|
default: ({ installedAppInfo, className }: {
|
|
installedAppInfo?: InstalledAppType
|
|
className?: string
|
|
}) => (
|
|
<div data-testid="chat-with-history" className={className}>
|
|
Chat With History - {installedAppInfo?.id}
|
|
</div>
|
|
),
|
|
}))
|
|
|
|
describe('InstalledApp', () => {
|
|
const mockUpdateAppInfo = jest.fn()
|
|
const mockUpdateWebAppAccessMode = jest.fn()
|
|
const mockUpdateAppParams = jest.fn()
|
|
const mockUpdateWebAppMeta = jest.fn()
|
|
const mockUpdateUserCanAccessApp = jest.fn()
|
|
|
|
const mockInstalledApp = {
|
|
id: 'installed-app-123',
|
|
app: {
|
|
id: 'app-123',
|
|
name: 'Test App',
|
|
mode: AppModeEnum.CHAT,
|
|
icon_type: 'emoji' as const,
|
|
icon: '🚀',
|
|
icon_background: '#FFFFFF',
|
|
icon_url: '',
|
|
description: 'Test description',
|
|
use_icon_as_answer_icon: false,
|
|
},
|
|
uninstallable: true,
|
|
is_pinned: false,
|
|
}
|
|
|
|
const mockAppParams = {
|
|
user_input_form: [],
|
|
file_upload: { image: { enabled: false, number_limits: 0, transfer_methods: [] } },
|
|
system_parameters: {},
|
|
}
|
|
|
|
const mockAppMeta = {
|
|
tool_icons: {},
|
|
}
|
|
|
|
const mockWebAppAccessMode = {
|
|
accessMode: AccessMode.PUBLIC,
|
|
}
|
|
|
|
const mockUserCanAccessApp = {
|
|
result: true,
|
|
}
|
|
|
|
beforeEach(() => {
|
|
jest.clearAllMocks()
|
|
|
|
// Mock useContext
|
|
;(useContext as jest.Mock).mockReturnValue({
|
|
installedApps: [mockInstalledApp],
|
|
isFetchingInstalledApps: false,
|
|
})
|
|
|
|
// Mock useWebAppStore
|
|
;(useWebAppStore as unknown as jest.Mock).mockImplementation((
|
|
selector: (state: {
|
|
updateAppInfo: jest.Mock
|
|
updateWebAppAccessMode: jest.Mock
|
|
updateAppParams: jest.Mock
|
|
updateWebAppMeta: jest.Mock
|
|
updateUserCanAccessApp: jest.Mock
|
|
}) => unknown,
|
|
) => {
|
|
const state = {
|
|
updateAppInfo: mockUpdateAppInfo,
|
|
updateWebAppAccessMode: mockUpdateWebAppAccessMode,
|
|
updateAppParams: mockUpdateAppParams,
|
|
updateWebAppMeta: mockUpdateWebAppMeta,
|
|
updateUserCanAccessApp: mockUpdateUserCanAccessApp,
|
|
}
|
|
return selector(state)
|
|
})
|
|
|
|
// Mock service hooks with default success states
|
|
;(useGetInstalledAppAccessModeByAppId as jest.Mock).mockReturnValue({
|
|
isFetching: false,
|
|
data: mockWebAppAccessMode,
|
|
error: null,
|
|
})
|
|
|
|
;(useGetInstalledAppParams as jest.Mock).mockReturnValue({
|
|
isFetching: false,
|
|
data: mockAppParams,
|
|
error: null,
|
|
})
|
|
|
|
;(useGetInstalledAppMeta as jest.Mock).mockReturnValue({
|
|
isFetching: false,
|
|
data: mockAppMeta,
|
|
error: null,
|
|
})
|
|
|
|
;(useGetUserCanAccessApp as jest.Mock).mockReturnValue({
|
|
data: mockUserCanAccessApp,
|
|
error: null,
|
|
})
|
|
})
|
|
|
|
describe('Rendering', () => {
|
|
it('should render without crashing', () => {
|
|
render(<InstalledApp id="installed-app-123" />)
|
|
expect(screen.getByText(/Chat With History/i)).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render loading state when fetching app params', () => {
|
|
;(useGetInstalledAppParams as jest.Mock).mockReturnValue({
|
|
isFetching: true,
|
|
data: null,
|
|
error: null,
|
|
})
|
|
|
|
const { container } = render(<InstalledApp id="installed-app-123" />)
|
|
const svg = container.querySelector('svg.spin-animation')
|
|
expect(svg).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render loading state when fetching app meta', () => {
|
|
;(useGetInstalledAppMeta as jest.Mock).mockReturnValue({
|
|
isFetching: true,
|
|
data: null,
|
|
error: null,
|
|
})
|
|
|
|
const { container } = render(<InstalledApp id="installed-app-123" />)
|
|
const svg = container.querySelector('svg.spin-animation')
|
|
expect(svg).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render loading state when fetching web app access mode', () => {
|
|
;(useGetInstalledAppAccessModeByAppId as jest.Mock).mockReturnValue({
|
|
isFetching: true,
|
|
data: null,
|
|
error: null,
|
|
})
|
|
|
|
const { container } = render(<InstalledApp id="installed-app-123" />)
|
|
const svg = container.querySelector('svg.spin-animation')
|
|
expect(svg).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render loading state when fetching installed apps', () => {
|
|
;(useContext as jest.Mock).mockReturnValue({
|
|
installedApps: [mockInstalledApp],
|
|
isFetchingInstalledApps: true,
|
|
})
|
|
|
|
const { container } = render(<InstalledApp id="installed-app-123" />)
|
|
const svg = container.querySelector('svg.spin-animation')
|
|
expect(svg).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render app not found (404) when installedApp does not exist', () => {
|
|
;(useContext as jest.Mock).mockReturnValue({
|
|
installedApps: [],
|
|
isFetchingInstalledApps: false,
|
|
})
|
|
|
|
render(<InstalledApp id="nonexistent-app" />)
|
|
expect(screen.getByText(/404/)).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
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({
|
|
isFetching: false,
|
|
data: null,
|
|
error,
|
|
})
|
|
|
|
render(<InstalledApp id="installed-app-123" />)
|
|
expect(screen.getByText(/Failed to load app params/)).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render error when app meta fails to load', () => {
|
|
const error = new Error('Failed to load app meta')
|
|
;(useGetInstalledAppMeta as jest.Mock).mockReturnValue({
|
|
isFetching: false,
|
|
data: null,
|
|
error,
|
|
})
|
|
|
|
render(<InstalledApp id="installed-app-123" />)
|
|
expect(screen.getByText(/Failed to load app meta/)).toBeInTheDocument()
|
|
})
|
|
|
|
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({
|
|
isFetching: false,
|
|
data: null,
|
|
error,
|
|
})
|
|
|
|
render(<InstalledApp id="installed-app-123" />)
|
|
expect(screen.getByText(/Failed to load access mode/)).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render error when user access check fails', () => {
|
|
const error = new Error('Failed to check user access')
|
|
;(useGetUserCanAccessApp as jest.Mock).mockReturnValue({
|
|
data: null,
|
|
error,
|
|
})
|
|
|
|
render(<InstalledApp id="installed-app-123" />)
|
|
expect(screen.getByText(/Failed to check user access/)).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render no permission (403) when user cannot access app', () => {
|
|
;(useGetUserCanAccessApp as jest.Mock).mockReturnValue({
|
|
data: { result: false },
|
|
error: null,
|
|
})
|
|
|
|
render(<InstalledApp id="installed-app-123" />)
|
|
expect(screen.getByText(/403/)).toBeInTheDocument()
|
|
expect(screen.getByText(/no permission/i)).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
describe('App Mode Rendering', () => {
|
|
it('should render ChatWithHistory for CHAT mode', () => {
|
|
render(<InstalledApp id="installed-app-123" />)
|
|
expect(screen.getByText(/Chat With History/i)).toBeInTheDocument()
|
|
expect(screen.queryByText(/Text Generation App/i)).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('should render ChatWithHistory for ADVANCED_CHAT mode', () => {
|
|
const advancedChatApp = {
|
|
...mockInstalledApp,
|
|
app: {
|
|
...mockInstalledApp.app,
|
|
mode: AppModeEnum.ADVANCED_CHAT,
|
|
},
|
|
}
|
|
;(useContext as jest.Mock).mockReturnValue({
|
|
installedApps: [advancedChatApp],
|
|
isFetchingInstalledApps: false,
|
|
})
|
|
|
|
render(<InstalledApp id="installed-app-123" />)
|
|
expect(screen.getByText(/Chat With History/i)).toBeInTheDocument()
|
|
expect(screen.queryByText(/Text Generation App/i)).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('should render ChatWithHistory for AGENT_CHAT mode', () => {
|
|
const agentChatApp = {
|
|
...mockInstalledApp,
|
|
app: {
|
|
...mockInstalledApp.app,
|
|
mode: AppModeEnum.AGENT_CHAT,
|
|
},
|
|
}
|
|
;(useContext as jest.Mock).mockReturnValue({
|
|
installedApps: [agentChatApp],
|
|
isFetchingInstalledApps: false,
|
|
})
|
|
|
|
render(<InstalledApp id="installed-app-123" />)
|
|
expect(screen.getByText(/Chat With History/i)).toBeInTheDocument()
|
|
expect(screen.queryByText(/Text Generation App/i)).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('should render TextGenerationApp for COMPLETION mode', () => {
|
|
const completionApp = {
|
|
...mockInstalledApp,
|
|
app: {
|
|
...mockInstalledApp.app,
|
|
mode: AppModeEnum.COMPLETION,
|
|
},
|
|
}
|
|
;(useContext as jest.Mock).mockReturnValue({
|
|
installedApps: [completionApp],
|
|
isFetchingInstalledApps: false,
|
|
})
|
|
|
|
render(<InstalledApp id="installed-app-123" />)
|
|
expect(screen.getByText(/Text Generation App/i)).toBeInTheDocument()
|
|
expect(screen.queryByText(/Workflow/)).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('should render TextGenerationApp with workflow flag for WORKFLOW mode', () => {
|
|
const workflowApp = {
|
|
...mockInstalledApp,
|
|
app: {
|
|
...mockInstalledApp.app,
|
|
mode: AppModeEnum.WORKFLOW,
|
|
},
|
|
}
|
|
;(useContext as jest.Mock).mockReturnValue({
|
|
installedApps: [workflowApp],
|
|
isFetchingInstalledApps: false,
|
|
})
|
|
|
|
render(<InstalledApp id="installed-app-123" />)
|
|
expect(screen.getByText(/Text Generation App/i)).toBeInTheDocument()
|
|
expect(screen.getByText(/Workflow/)).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
describe('Props', () => {
|
|
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({
|
|
installedApps: [app1, app2],
|
|
isFetchingInstalledApps: false,
|
|
})
|
|
|
|
render(<InstalledApp id="app-2" />)
|
|
expect(screen.getByText(/app-2/)).toBeInTheDocument()
|
|
})
|
|
|
|
it('should handle id that does not match any installed app', () => {
|
|
render(<InstalledApp id="nonexistent-id" />)
|
|
expect(screen.getByText(/404/)).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
describe('Effects', () => {
|
|
it('should update app info when installedApp is available', async () => {
|
|
render(<InstalledApp id="installed-app-123" />)
|
|
|
|
await waitFor(() => {
|
|
expect(mockUpdateAppInfo).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
app_id: 'installed-app-123',
|
|
site: expect.objectContaining({
|
|
title: 'Test App',
|
|
icon_type: 'emoji',
|
|
icon: '🚀',
|
|
icon_background: '#FFFFFF',
|
|
icon_url: '',
|
|
prompt_public: false,
|
|
copyright: '',
|
|
show_workflow_steps: true,
|
|
use_icon_as_answer_icon: false,
|
|
}),
|
|
plan: 'basic',
|
|
custom_config: null,
|
|
}),
|
|
)
|
|
})
|
|
})
|
|
|
|
it('should update app info to null when installedApp is not found', async () => {
|
|
;(useContext as jest.Mock).mockReturnValue({
|
|
installedApps: [],
|
|
isFetchingInstalledApps: false,
|
|
})
|
|
|
|
render(<InstalledApp id="nonexistent-app" />)
|
|
|
|
await waitFor(() => {
|
|
expect(mockUpdateAppInfo).toHaveBeenCalledWith(null)
|
|
})
|
|
})
|
|
|
|
it('should update app params when data is available', async () => {
|
|
render(<InstalledApp id="installed-app-123" />)
|
|
|
|
await waitFor(() => {
|
|
expect(mockUpdateAppParams).toHaveBeenCalledWith(mockAppParams)
|
|
})
|
|
})
|
|
|
|
it('should update app meta when data is available', async () => {
|
|
render(<InstalledApp id="installed-app-123" />)
|
|
|
|
await waitFor(() => {
|
|
expect(mockUpdateWebAppMeta).toHaveBeenCalledWith(mockAppMeta)
|
|
})
|
|
})
|
|
|
|
it('should update web app access mode when data is available', async () => {
|
|
render(<InstalledApp id="installed-app-123" />)
|
|
|
|
await waitFor(() => {
|
|
expect(mockUpdateWebAppAccessMode).toHaveBeenCalledWith(AccessMode.PUBLIC)
|
|
})
|
|
})
|
|
|
|
it('should update user can access app when data is available', async () => {
|
|
render(<InstalledApp id="installed-app-123" />)
|
|
|
|
await waitFor(() => {
|
|
expect(mockUpdateUserCanAccessApp).toHaveBeenCalledWith(true)
|
|
})
|
|
})
|
|
|
|
it('should update user can access app to false when result is false', async () => {
|
|
;(useGetUserCanAccessApp as jest.Mock).mockReturnValue({
|
|
data: { result: false },
|
|
error: null,
|
|
})
|
|
|
|
render(<InstalledApp id="installed-app-123" />)
|
|
|
|
await waitFor(() => {
|
|
expect(mockUpdateUserCanAccessApp).toHaveBeenCalledWith(false)
|
|
})
|
|
})
|
|
|
|
it('should update user can access app to false when data is null', async () => {
|
|
;(useGetUserCanAccessApp as jest.Mock).mockReturnValue({
|
|
data: null,
|
|
error: null,
|
|
})
|
|
|
|
render(<InstalledApp id="installed-app-123" />)
|
|
|
|
await waitFor(() => {
|
|
expect(mockUpdateUserCanAccessApp).toHaveBeenCalledWith(false)
|
|
})
|
|
})
|
|
|
|
it('should not update app params when data is null', async () => {
|
|
;(useGetInstalledAppParams as jest.Mock).mockReturnValue({
|
|
isFetching: false,
|
|
data: null,
|
|
error: null,
|
|
})
|
|
|
|
render(<InstalledApp id="installed-app-123" />)
|
|
|
|
await waitFor(() => {
|
|
expect(mockUpdateAppInfo).toHaveBeenCalled()
|
|
})
|
|
|
|
expect(mockUpdateAppParams).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('should not update app meta when data is null', async () => {
|
|
;(useGetInstalledAppMeta as jest.Mock).mockReturnValue({
|
|
isFetching: false,
|
|
data: null,
|
|
error: null,
|
|
})
|
|
|
|
render(<InstalledApp id="installed-app-123" />)
|
|
|
|
await waitFor(() => {
|
|
expect(mockUpdateAppInfo).toHaveBeenCalled()
|
|
})
|
|
|
|
expect(mockUpdateWebAppMeta).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('should not update access mode when data is null', async () => {
|
|
;(useGetInstalledAppAccessModeByAppId as jest.Mock).mockReturnValue({
|
|
isFetching: false,
|
|
data: null,
|
|
error: null,
|
|
})
|
|
|
|
render(<InstalledApp id="installed-app-123" />)
|
|
|
|
await waitFor(() => {
|
|
expect(mockUpdateAppInfo).toHaveBeenCalled()
|
|
})
|
|
|
|
expect(mockUpdateWebAppAccessMode).not.toHaveBeenCalled()
|
|
})
|
|
})
|
|
|
|
describe('Edge Cases', () => {
|
|
it('should handle empty installedApps array', () => {
|
|
;(useContext as jest.Mock).mockReturnValue({
|
|
installedApps: [],
|
|
isFetchingInstalledApps: false,
|
|
})
|
|
|
|
render(<InstalledApp id="installed-app-123" />)
|
|
expect(screen.getByText(/404/)).toBeInTheDocument()
|
|
})
|
|
|
|
it('should handle multiple installed apps and find the correct one', () => {
|
|
const otherApp = {
|
|
...mockInstalledApp,
|
|
id: 'other-app-id',
|
|
app: {
|
|
...mockInstalledApp.app,
|
|
name: 'Other App',
|
|
},
|
|
}
|
|
;(useContext as jest.Mock).mockReturnValue({
|
|
installedApps: [otherApp, mockInstalledApp],
|
|
isFetchingInstalledApps: false,
|
|
})
|
|
|
|
render(<InstalledApp id="installed-app-123" />)
|
|
// Should find and render the correct app
|
|
expect(screen.getByText(/Chat With History/i)).toBeInTheDocument()
|
|
expect(screen.getByText(/installed-app-123/)).toBeInTheDocument()
|
|
})
|
|
|
|
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({
|
|
installedApps: [app1, app2],
|
|
isFetchingInstalledApps: false,
|
|
})
|
|
|
|
const { rerender } = render(<InstalledApp id="app-1" />)
|
|
expect(screen.getByText(/app-1/)).toBeInTheDocument()
|
|
|
|
rerender(<InstalledApp id="app-2" />)
|
|
expect(screen.getByText(/app-2/)).toBeInTheDocument()
|
|
})
|
|
|
|
it('should call service hooks with correct appId', () => {
|
|
render(<InstalledApp id="installed-app-123" />)
|
|
|
|
expect(useGetInstalledAppAccessModeByAppId).toHaveBeenCalledWith('installed-app-123')
|
|
expect(useGetInstalledAppParams).toHaveBeenCalledWith('installed-app-123')
|
|
expect(useGetInstalledAppMeta).toHaveBeenCalledWith('installed-app-123')
|
|
expect(useGetUserCanAccessApp).toHaveBeenCalledWith({
|
|
appId: 'app-123',
|
|
isInstalledApp: true,
|
|
})
|
|
})
|
|
|
|
it('should call service hooks with null when installedApp is not found', () => {
|
|
;(useContext as jest.Mock).mockReturnValue({
|
|
installedApps: [],
|
|
isFetchingInstalledApps: false,
|
|
})
|
|
|
|
render(<InstalledApp id="nonexistent-app" />)
|
|
|
|
expect(useGetInstalledAppAccessModeByAppId).toHaveBeenCalledWith(null)
|
|
expect(useGetInstalledAppParams).toHaveBeenCalledWith(null)
|
|
expect(useGetInstalledAppMeta).toHaveBeenCalledWith(null)
|
|
expect(useGetUserCanAccessApp).toHaveBeenCalledWith({
|
|
appId: undefined,
|
|
isInstalledApp: true,
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('Render Priority', () => {
|
|
it('should show error before loading state', () => {
|
|
;(useGetInstalledAppParams as jest.Mock).mockReturnValue({
|
|
isFetching: true,
|
|
data: null,
|
|
error: new Error('Some error'),
|
|
})
|
|
|
|
render(<InstalledApp id="installed-app-123" />)
|
|
// Error should take precedence over loading
|
|
expect(screen.getByText(/Some error/)).toBeInTheDocument()
|
|
})
|
|
|
|
it('should show error before permission check', () => {
|
|
;(useGetInstalledAppParams as jest.Mock).mockReturnValue({
|
|
isFetching: false,
|
|
data: null,
|
|
error: new Error('Params error'),
|
|
})
|
|
;(useGetUserCanAccessApp as jest.Mock).mockReturnValue({
|
|
data: { result: false },
|
|
error: null,
|
|
})
|
|
|
|
render(<InstalledApp id="installed-app-123" />)
|
|
// Error should take precedence over permission
|
|
expect(screen.getByText(/Params error/)).toBeInTheDocument()
|
|
expect(screen.queryByText(/403/)).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('should show permission error before 404', () => {
|
|
;(useContext as jest.Mock).mockReturnValue({
|
|
installedApps: [],
|
|
isFetchingInstalledApps: false,
|
|
})
|
|
;(useGetUserCanAccessApp as jest.Mock).mockReturnValue({
|
|
data: { result: false },
|
|
error: null,
|
|
})
|
|
|
|
render(<InstalledApp id="nonexistent-app" />)
|
|
// Permission should take precedence over 404
|
|
expect(screen.getByText(/403/)).toBeInTheDocument()
|
|
expect(screen.queryByText(/404/)).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('should show loading before 404', () => {
|
|
;(useContext as jest.Mock).mockReturnValue({
|
|
installedApps: [],
|
|
isFetchingInstalledApps: false,
|
|
})
|
|
;(useGetInstalledAppParams as jest.Mock).mockReturnValue({
|
|
isFetching: true,
|
|
data: null,
|
|
error: null,
|
|
})
|
|
|
|
const { container } = render(<InstalledApp id="nonexistent-app" />)
|
|
// Loading should take precedence over 404
|
|
const svg = container.querySelector('svg.spin-animation')
|
|
expect(svg).toBeInTheDocument()
|
|
expect(screen.queryByText(/404/)).not.toBeInTheDocument()
|
|
})
|
|
})
|
|
})
|