mirror of
https://github.com/langgenius/dify.git
synced 2026-05-06 02:18:08 +08:00
Merge commit 'fb41b215' into sandboxed-agent-rebase
Made-with: Cursor # Conflicts: # .devcontainer/post_create_command.sh # api/commands.py # api/core/agent/cot_agent_runner.py # api/core/agent/fc_agent_runner.py # api/core/app/apps/workflow_app_runner.py # api/core/app/entities/queue_entities.py # api/core/app/entities/task_entities.py # api/core/workflow/workflow_entry.py # api/dify_graph/enums.py # api/dify_graph/graph/graph.py # api/dify_graph/graph_events/node.py # api/dify_graph/model_runtime/entities/message_entities.py # api/dify_graph/node_events/node.py # api/dify_graph/nodes/agent/agent_node.py # api/dify_graph/nodes/base/__init__.py # api/dify_graph/nodes/base/entities.py # api/dify_graph/nodes/base/node.py # api/dify_graph/nodes/llm/entities.py # api/dify_graph/nodes/llm/node.py # api/dify_graph/nodes/tool/tool_node.py # api/pyproject.toml # api/uv.lock # web/app/components/base/avatar/__tests__/index.spec.tsx # web/app/components/base/avatar/index.tsx # web/app/components/base/date-and-time-picker/time-picker/__tests__/index.spec.tsx # web/app/components/base/file-uploader/file-from-link-or-local/index.tsx # web/app/components/base/prompt-editor/index.tsx # web/app/components/datasets/metadata/edit-metadata-batch/modal.tsx # web/app/components/header/account-dropdown/index.spec.tsx # web/app/components/share/text-generation/index.tsx # web/app/components/workflow/block-selector/tool/action-item.tsx # web/app/components/workflow/block-selector/trigger-plugin/action-item.tsx # web/app/components/workflow/hooks/use-edges-interactions.ts # web/app/components/workflow/hooks/use-nodes-interactions.ts # web/app/components/workflow/index.tsx # web/app/components/workflow/nodes/_base/components/editor/code-editor/index.tsx # web/app/components/workflow/nodes/http/components/key-value/key-value-edit/index.tsx # web/app/components/workflow/nodes/human-input/components/delivery-method/recipient/email-item.tsx # web/app/components/workflow/nodes/loop/use-interactions.ts # web/contract/router.ts # web/env.ts # web/eslint-suppressions.json # web/package.json # web/pnpm-lock.yaml
This commit is contained in:
@ -3,9 +3,8 @@ import type { ImgHTMLAttributes } from 'react'
|
||||
import type { EmbeddedChatbotContextValue } from '../../context'
|
||||
import type { AppData } from '@/models/share'
|
||||
import type { SystemFeatures } from '@/types/feature'
|
||||
import { render, screen, waitFor } from '@testing-library/react'
|
||||
import { act, render, screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { vi } from 'vitest'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { InstallationScope, LicenseStatus } from '@/types/feature'
|
||||
import { useEmbeddedChatbotContext } from '../../context'
|
||||
@ -122,6 +121,18 @@ describe('EmbeddedChatbot Header', () => {
|
||||
Object.defineProperty(window, 'top', { value: window, configurable: true })
|
||||
})
|
||||
|
||||
const dispatchChatbotConfigMessage = async (origin: string, payload: { isToggledByButton: boolean, isDraggable: boolean }) => {
|
||||
await act(async () => {
|
||||
window.dispatchEvent(new MessageEvent('message', {
|
||||
origin,
|
||||
data: {
|
||||
type: 'dify-chatbot-config',
|
||||
payload,
|
||||
},
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
describe('Desktop Rendering', () => {
|
||||
it('should render desktop header with branding by default', async () => {
|
||||
render(<Header title="Test Chatbot" />)
|
||||
@ -166,7 +177,23 @@ describe('EmbeddedChatbot Header', () => {
|
||||
expect(img).toHaveAttribute('src', 'https://example.com/workspace.png')
|
||||
})
|
||||
|
||||
it('should render Dify logo by default when no branding or custom logo is provided', () => {
|
||||
it('should render Dify logo by default when branding enabled is true but no logo provided', () => {
|
||||
vi.mocked(useGlobalPublicStore).mockImplementation((selector: (s: GlobalPublicStoreMock) => unknown) => selector({
|
||||
systemFeatures: {
|
||||
...defaultSystemFeatures,
|
||||
branding: {
|
||||
...defaultSystemFeatures.branding,
|
||||
enabled: true,
|
||||
workspace_logo: '',
|
||||
},
|
||||
},
|
||||
setSystemFeatures: vi.fn(),
|
||||
}))
|
||||
render(<Header title="Test Chatbot" />)
|
||||
expect(screen.getByAltText('Dify logo')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render Dify logo when branding is disabled', () => {
|
||||
vi.mocked(useGlobalPublicStore).mockImplementation((selector: (s: GlobalPublicStoreMock) => unknown) => selector({
|
||||
systemFeatures: {
|
||||
...defaultSystemFeatures,
|
||||
@ -198,6 +225,20 @@ describe('EmbeddedChatbot Header', () => {
|
||||
expect(screen.queryByTestId('webapp-brand')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render divider only when currentConversationId is present', () => {
|
||||
vi.mocked(useEmbeddedChatbotContext).mockReturnValue({ ...defaultContext } as EmbeddedChatbotContextValue)
|
||||
const { unmount } = render(<Header title="Test Chatbot" />)
|
||||
expect(screen.getByTestId('divider')).toBeInTheDocument()
|
||||
unmount()
|
||||
|
||||
vi.mocked(useEmbeddedChatbotContext).mockReturnValue({
|
||||
...defaultContext,
|
||||
currentConversationId: '',
|
||||
} as EmbeddedChatbotContextValue)
|
||||
render(<Header title="Test Chatbot" />)
|
||||
expect(screen.queryByTestId('divider')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render reset button when allowResetChat is true and conversation exists', () => {
|
||||
render(<Header title="Test Chatbot" allowResetChat={true} />)
|
||||
|
||||
@ -268,6 +309,42 @@ describe('EmbeddedChatbot Header', () => {
|
||||
|
||||
expect(screen.getByTestId('mobile-reset-chat-button')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should NOT render mobile reset button when currentConversationId is missing', () => {
|
||||
vi.mocked(useEmbeddedChatbotContext).mockReturnValue({
|
||||
...defaultContext,
|
||||
currentConversationId: '',
|
||||
} as EmbeddedChatbotContextValue)
|
||||
render(<Header title="Mobile Chatbot" isMobile allowResetChat />)
|
||||
|
||||
expect(screen.queryByTestId('mobile-reset-chat-button')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render ViewFormDropdown in mobile when conditions are met', () => {
|
||||
vi.mocked(useEmbeddedChatbotContext).mockReturnValue({
|
||||
...defaultContext,
|
||||
inputsForms: [{ id: '1' }],
|
||||
} as EmbeddedChatbotContextValue)
|
||||
render(<Header title="Mobile Chatbot" isMobile />)
|
||||
expect(screen.getByTestId('view-form-dropdown')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle mobile expand button', async () => {
|
||||
const user = userEvent.setup()
|
||||
const mockPostMessage = setupIframe()
|
||||
render(<Header title="Mobile Chatbot" isMobile />)
|
||||
|
||||
await dispatchChatbotConfigMessage('https://parent.com', { isToggledByButton: true, isDraggable: false })
|
||||
|
||||
const expandBtn = await screen.findByTestId('mobile-expand-button')
|
||||
expect(expandBtn).toBeInTheDocument()
|
||||
|
||||
await user.click(expandBtn)
|
||||
expect(mockPostMessage).toHaveBeenCalledWith(
|
||||
{ type: 'dify-chatbot-expand-change' },
|
||||
'https://parent.com',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Iframe Communication', () => {
|
||||
@ -286,13 +363,7 @@ describe('EmbeddedChatbot Header', () => {
|
||||
const mockPostMessage = setupIframe()
|
||||
render(<Header title="Iframe" />)
|
||||
|
||||
window.dispatchEvent(new MessageEvent('message', {
|
||||
origin: 'https://parent.com',
|
||||
data: {
|
||||
type: 'dify-chatbot-config',
|
||||
payload: { isToggledByButton: true, isDraggable: false },
|
||||
},
|
||||
}))
|
||||
await dispatchChatbotConfigMessage('https://parent.com', { isToggledByButton: true, isDraggable: false })
|
||||
|
||||
const expandBtn = await screen.findByTestId('expand-button')
|
||||
expect(expandBtn).toBeInTheDocument()
|
||||
@ -310,13 +381,7 @@ describe('EmbeddedChatbot Header', () => {
|
||||
setupIframe()
|
||||
render(<Header title="Iframe" />)
|
||||
|
||||
window.dispatchEvent(new MessageEvent('message', {
|
||||
origin: 'https://parent.com',
|
||||
data: {
|
||||
type: 'dify-chatbot-config',
|
||||
payload: { isToggledByButton: true, isDraggable: true },
|
||||
},
|
||||
}))
|
||||
await dispatchChatbotConfigMessage('https://parent.com', { isToggledByButton: true, isDraggable: true })
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('expand-button')).not.toBeInTheDocument()
|
||||
@ -327,20 +392,43 @@ describe('EmbeddedChatbot Header', () => {
|
||||
setupIframe()
|
||||
render(<Header title="Iframe" />)
|
||||
|
||||
window.dispatchEvent(new MessageEvent('message', {
|
||||
origin: 'https://secure.com',
|
||||
data: { type: 'dify-chatbot-config', payload: { isToggledByButton: true, isDraggable: false } },
|
||||
}))
|
||||
await dispatchChatbotConfigMessage('https://secure.com', { isToggledByButton: true, isDraggable: false })
|
||||
|
||||
await screen.findByTestId('expand-button')
|
||||
|
||||
window.dispatchEvent(new MessageEvent('message', {
|
||||
origin: 'https://malicious.com',
|
||||
data: { type: 'dify-chatbot-config', payload: { isToggledByButton: false, isDraggable: false } },
|
||||
}))
|
||||
await dispatchChatbotConfigMessage('https://malicious.com', { isToggledByButton: false, isDraggable: false })
|
||||
|
||||
// Should still be visible (not hidden by the malicious message)
|
||||
expect(screen.getByTestId('expand-button')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should ignore non-config messages for origin locking', async () => {
|
||||
setupIframe()
|
||||
render(<Header title="Iframe" />)
|
||||
|
||||
await act(async () => {
|
||||
window.dispatchEvent(new MessageEvent('message', {
|
||||
origin: 'https://first.com',
|
||||
data: { type: 'other-type' },
|
||||
}))
|
||||
})
|
||||
|
||||
await dispatchChatbotConfigMessage('https://second.com', { isToggledByButton: true, isDraggable: false })
|
||||
|
||||
// Should lock to second.com
|
||||
const expandBtn = await screen.findByTestId('expand-button')
|
||||
expect(expandBtn).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should NOT handle toggle expand if showToggleExpandButton is false', async () => {
|
||||
const mockPostMessage = setupIframe()
|
||||
render(<Header title="Iframe" />)
|
||||
// Directly call handleToggleExpand would require more setup, but we can verify it doesn't trigger unexpectedly
|
||||
expect(mockPostMessage).not.toHaveBeenCalledWith(
|
||||
expect.objectContaining({ type: 'dify-chatbot-expand-change' }),
|
||||
expect.anything(),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Edge Cases', () => {
|
||||
|
||||
Reference in New Issue
Block a user