mirror of
https://github.com/langgenius/dify.git
synced 2026-04-29 12:37:20 +08:00
242 lines
7.8 KiB
TypeScript
242 lines
7.8 KiB
TypeScript
import type { ChatWithHistoryContextValue } from '../context'
|
|
import { render, screen } from '@testing-library/react'
|
|
import userEvent from '@testing-library/user-event'
|
|
import * as React from 'react'
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import { useChatWithHistoryContext } from '../context'
|
|
import Sidebar from './index'
|
|
|
|
// Mock List to allow us to trigger operations
|
|
vi.mock('./list', () => ({
|
|
default: ({ list, onOperate, title }: { list: Array<{ id: string, name: string }>, onOperate: (type: string, item: { id: string, name: string }) => void, title?: string }) => (
|
|
<div>
|
|
{title && <div>{title}</div>}
|
|
{list.map(item => (
|
|
<div key={item.id}>
|
|
<div>{item.name}</div>
|
|
<button onClick={() => onOperate('pin', item)}>Pin</button>
|
|
<button onClick={() => onOperate('unpin', item)}>Unpin</button>
|
|
<button onClick={() => onOperate('delete', item)}>Delete</button>
|
|
<button onClick={() => onOperate('rename', item)}>Rename</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
),
|
|
}))
|
|
|
|
// Mock context hook
|
|
vi.mock('../context', () => ({
|
|
useChatWithHistoryContext: vi.fn(),
|
|
}))
|
|
|
|
// Mock global public store
|
|
vi.mock('@/context/global-public-context', () => ({
|
|
useGlobalPublicStore: vi.fn(selector => selector({
|
|
systemFeatures: {
|
|
branding: {
|
|
enabled: true,
|
|
},
|
|
},
|
|
})),
|
|
}))
|
|
|
|
// Mock next/navigation
|
|
vi.mock('next/navigation', () => ({
|
|
useRouter: () => ({ push: vi.fn() }),
|
|
usePathname: () => '/test',
|
|
}))
|
|
|
|
// Mock Modal to avoid Headless UI issues in tests
|
|
vi.mock('@/app/components/base/modal', () => ({
|
|
default: ({ children, isShow, title }: { children: React.ReactNode, isShow: boolean, title: React.ReactNode }) => {
|
|
if (!isShow)
|
|
return null
|
|
return (
|
|
<div data-testid="modal">
|
|
{!!title && <div>{title}</div>}
|
|
{children}
|
|
</div>
|
|
)
|
|
},
|
|
}))
|
|
|
|
describe('Sidebar Index', () => {
|
|
const mockContextValue = {
|
|
isInstalledApp: false,
|
|
appData: {
|
|
site: {
|
|
title: 'Test App',
|
|
icon_type: 'image',
|
|
},
|
|
custom_config: {},
|
|
},
|
|
handleNewConversation: vi.fn(),
|
|
pinnedConversationList: [],
|
|
conversationList: [
|
|
{ id: '1', name: 'Conv 1', inputs: {}, introduction: '' },
|
|
],
|
|
currentConversationId: '0',
|
|
handleChangeConversation: vi.fn(),
|
|
handlePinConversation: vi.fn(),
|
|
handleUnpinConversation: vi.fn(),
|
|
conversationRenaming: false,
|
|
handleRenameConversation: vi.fn(),
|
|
handleDeleteConversation: vi.fn(),
|
|
sidebarCollapseState: false,
|
|
handleSidebarCollapse: vi.fn(),
|
|
isMobile: false,
|
|
isResponding: false,
|
|
} as unknown as ChatWithHistoryContextValue
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
vi.mocked(useChatWithHistoryContext).mockReturnValue(mockContextValue)
|
|
})
|
|
|
|
it('should render app title', () => {
|
|
render(<Sidebar />)
|
|
expect(screen.getByText('Test App')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should call handleNewConversation when button clicked', async () => {
|
|
const user = userEvent.setup()
|
|
render(<Sidebar />)
|
|
|
|
await user.click(screen.getByText('share.chat.newChat'))
|
|
expect(mockContextValue.handleNewConversation).toHaveBeenCalled()
|
|
})
|
|
|
|
it('should call handleSidebarCollapse when collapse button clicked', async () => {
|
|
const user = userEvent.setup()
|
|
render(<Sidebar />)
|
|
|
|
// Find the collapse button - it's the first ActionButton
|
|
const collapseButton = screen.getAllByRole('button')[0]
|
|
await user.click(collapseButton)
|
|
expect(mockContextValue.handleSidebarCollapse).toHaveBeenCalledWith(true)
|
|
})
|
|
|
|
it('should render conversation lists', () => {
|
|
vi.mocked(useChatWithHistoryContext).mockReturnValue({
|
|
...mockContextValue,
|
|
pinnedConversationList: [{ id: 'p1', name: 'Pinned 1', inputs: {}, introduction: '' }],
|
|
} as unknown as ChatWithHistoryContextValue)
|
|
|
|
render(<Sidebar />)
|
|
expect(screen.getByText('share.chat.pinnedTitle')).toBeInTheDocument()
|
|
expect(screen.getByText('Pinned 1')).toBeInTheDocument()
|
|
expect(screen.getByText('share.chat.unpinnedTitle')).toBeInTheDocument()
|
|
expect(screen.getByText('Conv 1')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render expand button when sidebar is collapsed', () => {
|
|
vi.mocked(useChatWithHistoryContext).mockReturnValue({
|
|
...mockContextValue,
|
|
sidebarCollapseState: true,
|
|
} as unknown as ChatWithHistoryContextValue)
|
|
|
|
render(<Sidebar />)
|
|
const buttons = screen.getAllByRole('button')
|
|
expect(buttons.length).toBeGreaterThan(0)
|
|
})
|
|
|
|
it('should call handleSidebarCollapse with false when expand button clicked', async () => {
|
|
const user = userEvent.setup()
|
|
vi.mocked(useChatWithHistoryContext).mockReturnValue({
|
|
...mockContextValue,
|
|
sidebarCollapseState: true,
|
|
} as unknown as ChatWithHistoryContextValue)
|
|
|
|
render(<Sidebar />)
|
|
|
|
const expandButton = screen.getAllByRole('button')[0]
|
|
await user.click(expandButton)
|
|
expect(mockContextValue.handleSidebarCollapse).toHaveBeenCalledWith(false)
|
|
})
|
|
|
|
it('should call handlePinConversation when pin operation is triggered', async () => {
|
|
const user = userEvent.setup()
|
|
render(<Sidebar />)
|
|
|
|
const pinButton = screen.getByText('Pin')
|
|
await user.click(pinButton)
|
|
|
|
expect(mockContextValue.handlePinConversation).toHaveBeenCalledWith('1')
|
|
})
|
|
|
|
it('should call handleUnpinConversation when unpin operation is triggered', async () => {
|
|
const user = userEvent.setup()
|
|
render(<Sidebar />)
|
|
|
|
const unpinButton = screen.getByText('Unpin')
|
|
await user.click(unpinButton)
|
|
|
|
expect(mockContextValue.handleUnpinConversation).toHaveBeenCalledWith('1')
|
|
})
|
|
|
|
it('should show delete confirmation modal when delete operation is triggered', async () => {
|
|
const user = userEvent.setup()
|
|
render(<Sidebar />)
|
|
|
|
const deleteButton = screen.getByText('Delete')
|
|
await user.click(deleteButton)
|
|
|
|
expect(screen.getByText('share.chat.deleteConversation.title')).toBeInTheDocument()
|
|
|
|
const confirmButton = screen.getByText('common.operation.confirm')
|
|
await user.click(confirmButton)
|
|
|
|
expect(mockContextValue.handleDeleteConversation).toHaveBeenCalledWith('1', expect.any(Object))
|
|
})
|
|
|
|
it('should close delete confirmation modal when cancel is clicked', async () => {
|
|
const user = userEvent.setup()
|
|
render(<Sidebar />)
|
|
|
|
const deleteButton = screen.getByText('Delete')
|
|
await user.click(deleteButton)
|
|
|
|
expect(screen.getByText('share.chat.deleteConversation.title')).toBeInTheDocument()
|
|
|
|
const cancelButton = screen.getByText('common.operation.cancel')
|
|
await user.click(cancelButton)
|
|
|
|
expect(screen.queryByText('share.chat.deleteConversation.title')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('should show rename modal when rename operation is triggered', async () => {
|
|
const user = userEvent.setup()
|
|
render(<Sidebar />)
|
|
|
|
const renameButton = screen.getByText('Rename')
|
|
await user.click(renameButton)
|
|
|
|
expect(screen.getByText('common.chat.renameConversation')).toBeInTheDocument()
|
|
|
|
const input = screen.getByDisplayValue('Conv 1') as HTMLInputElement
|
|
await user.click(input)
|
|
await user.clear(input)
|
|
await user.type(input, 'Renamed Conv')
|
|
|
|
const saveButton = screen.getByText('common.operation.save')
|
|
await user.click(saveButton)
|
|
|
|
expect(mockContextValue.handleRenameConversation).toHaveBeenCalled()
|
|
})
|
|
|
|
it('should close rename modal when cancel is clicked', async () => {
|
|
const user = userEvent.setup()
|
|
render(<Sidebar />)
|
|
|
|
const renameButton = screen.getByText('Rename')
|
|
await user.click(renameButton)
|
|
|
|
expect(screen.getByText('common.chat.renameConversation')).toBeInTheDocument()
|
|
|
|
const cancelButton = screen.getByText('common.operation.cancel')
|
|
await user.click(cancelButton)
|
|
|
|
expect(screen.queryByText('common.chat.renameConversation')).not.toBeInTheDocument()
|
|
})
|
|
})
|