mirror of
https://github.com/langgenius/dify.git
synced 2026-03-21 10:30:11 +08:00
172 lines
5.2 KiB
TypeScript
172 lines
5.2 KiB
TypeScript
import type { AppDetailResponse } from '@/models/app'
|
|
import { act, fireEvent, render, screen } from '@testing-library/react'
|
|
import { useRouter } from 'next/navigation'
|
|
import { vi } from 'vitest'
|
|
import { useAppContext } from '@/context/app-context'
|
|
import AppSelector from './index'
|
|
|
|
// Mock next/navigation
|
|
vi.mock('next/navigation', () => ({
|
|
useRouter: vi.fn(),
|
|
}))
|
|
|
|
// Mock app context
|
|
vi.mock('@/context/app-context', () => ({
|
|
useAppContext: vi.fn(),
|
|
}))
|
|
|
|
// Mock CreateAppDialog to avoid complex dependencies
|
|
vi.mock('@/app/components/app/create-app-dialog', () => ({
|
|
default: ({ show, onClose }: { show: boolean, onClose: () => void }) => show
|
|
? (
|
|
<div data-testid="create-app-dialog">
|
|
<button onClick={onClose}>Close</button>
|
|
</div>
|
|
)
|
|
: null,
|
|
}))
|
|
|
|
describe('AppSelector Component', () => {
|
|
const mockPush = vi.fn()
|
|
const mockAppItems = [
|
|
{ id: '1', name: 'App 1' },
|
|
{ id: '2', name: 'App 2' },
|
|
] as unknown as AppDetailResponse[]
|
|
const mockCurApp = mockAppItems[0]
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
vi.mocked(useRouter).mockReturnValue({
|
|
push: mockPush,
|
|
} as unknown as ReturnType<typeof useRouter>)
|
|
vi.mocked(useAppContext).mockReturnValue({
|
|
isCurrentWorkspaceEditor: true,
|
|
} as unknown as ReturnType<typeof useAppContext>)
|
|
})
|
|
|
|
describe('Rendering', () => {
|
|
it('should render current app name', () => {
|
|
render(<AppSelector appItems={mockAppItems} curApp={mockCurApp} />)
|
|
expect(screen.getByText('App 1')).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
describe('Interactions', () => {
|
|
it('should open menu and show app items', async () => {
|
|
render(<AppSelector appItems={mockAppItems} curApp={mockCurApp} />)
|
|
|
|
const button = screen.getByRole('button', { name: /App 1/i })
|
|
await act(async () => {
|
|
fireEvent.click(button)
|
|
})
|
|
|
|
expect(screen.getByText('App 2')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should navigate to configuration when an app is clicked and user is editor', async () => {
|
|
render(<AppSelector appItems={mockAppItems} curApp={mockCurApp} />)
|
|
|
|
const button = screen.getByRole('button', { name: /App 1/i })
|
|
await act(async () => {
|
|
fireEvent.click(button)
|
|
})
|
|
|
|
const app2Item = screen.getByText('App 2')
|
|
await act(async () => {
|
|
fireEvent.click(app2Item)
|
|
})
|
|
|
|
expect(mockPush).toHaveBeenCalledWith('/app/2/configuration')
|
|
})
|
|
|
|
it('should navigate to overview when an app is clicked and user is not editor', async () => {
|
|
vi.mocked(useAppContext).mockReturnValue({
|
|
isCurrentWorkspaceEditor: false,
|
|
} as unknown as ReturnType<typeof useAppContext>)
|
|
|
|
render(<AppSelector appItems={mockAppItems} curApp={mockCurApp} />)
|
|
|
|
const button = screen.getByRole('button', { name: /App 1/i })
|
|
await act(async () => {
|
|
fireEvent.click(button)
|
|
})
|
|
|
|
const app2Item = screen.getByText('App 2')
|
|
await act(async () => {
|
|
fireEvent.click(app2Item)
|
|
})
|
|
|
|
expect(mockPush).toHaveBeenCalledWith('/app/2/overview')
|
|
})
|
|
})
|
|
|
|
describe('New App Dialog', () => {
|
|
it('should show "New App" button for editor and open dialog', async () => {
|
|
render(<AppSelector appItems={mockAppItems} curApp={mockCurApp} />)
|
|
|
|
const button = screen.getByRole('button', { name: /App 1/i })
|
|
await act(async () => {
|
|
fireEvent.click(button)
|
|
})
|
|
|
|
const newAppBtn = screen.getByText('common.menus.newApp')
|
|
await act(async () => {
|
|
fireEvent.click(newAppBtn)
|
|
})
|
|
|
|
expect(screen.getByTestId('create-app-dialog')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should not show "New App" button for non-editor', async () => {
|
|
vi.mocked(useAppContext).mockReturnValue({
|
|
isCurrentWorkspaceEditor: false,
|
|
} as unknown as ReturnType<typeof useAppContext>)
|
|
|
|
render(<AppSelector appItems={mockAppItems} curApp={mockCurApp} />)
|
|
|
|
const button = screen.getByRole('button', { name: /App 1/i })
|
|
await act(async () => {
|
|
fireEvent.click(button)
|
|
})
|
|
|
|
expect(screen.queryByText('common.menus.newApp')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('should close dialog when onClose is called', async () => {
|
|
render(<AppSelector appItems={mockAppItems} curApp={mockCurApp} />)
|
|
|
|
const button = screen.getByRole('button', { name: /App 1/i })
|
|
await act(async () => {
|
|
fireEvent.click(button)
|
|
})
|
|
|
|
const newAppBtn = screen.getByText('common.menus.newApp')
|
|
await act(async () => {
|
|
fireEvent.click(newAppBtn)
|
|
})
|
|
|
|
const closeBtn = screen.getByText('Close')
|
|
await act(async () => {
|
|
fireEvent.click(closeBtn)
|
|
})
|
|
|
|
expect(screen.queryByTestId('create-app-dialog')).not.toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
describe('Edge Cases', () => {
|
|
it('should render nothing in menu if appItems is empty', async () => {
|
|
render(<AppSelector appItems={[]} curApp={mockCurApp} />)
|
|
|
|
const button = screen.getByRole('button', { name: /App 1/i })
|
|
await act(async () => {
|
|
fireEvent.click(button)
|
|
})
|
|
|
|
expect(screen.queryByText('App 2')).not.toBeInTheDocument()
|
|
// "New App" should still be there if editor
|
|
expect(screen.getByText('common.menus.newApp')).toBeInTheDocument()
|
|
})
|
|
})
|
|
})
|