mirror of
https://github.com/langgenius/dify.git
synced 2026-05-02 16:38:04 +08:00
refactor(components): reorder class names for consistency in various plugin components and add unit tests for CardMoreInfo and other components
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
import type { FilterState } from '../filter-management'
|
||||
import type { FilterState } from '../../filter-management'
|
||||
import type { SystemFeatures } from '@/types/feature'
|
||||
import { act, fireEvent, render, screen } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
@ -6,7 +6,7 @@ import { defaultSystemFeatures, InstallationScope } from '@/types/feature'
|
||||
|
||||
// ==================== Imports (after mocks) ====================
|
||||
|
||||
import Empty from './index'
|
||||
import Empty from '../index'
|
||||
|
||||
// ==================== Mock Setup ====================
|
||||
|
||||
@ -15,7 +15,6 @@ const {
|
||||
mockSetActiveTab,
|
||||
mockUseInstalledPluginList,
|
||||
mockState,
|
||||
stableT,
|
||||
} = vi.hoisted(() => {
|
||||
const state = {
|
||||
filters: {
|
||||
@ -32,20 +31,16 @@ const {
|
||||
} as Partial<SystemFeatures>,
|
||||
pluginList: { plugins: [] as Array<{ id: string }> } as { plugins: Array<{ id: string }> } | undefined,
|
||||
}
|
||||
// Stable t function to prevent infinite re-renders
|
||||
// The component's useEffect and useMemo depend on t
|
||||
const t = (key: string) => key
|
||||
return {
|
||||
mockSetActiveTab: vi.fn(),
|
||||
mockUseInstalledPluginList: vi.fn(() => ({ data: state.pluginList })),
|
||||
mockState: state,
|
||||
stableT: t,
|
||||
}
|
||||
})
|
||||
|
||||
// Mock plugin page context
|
||||
vi.mock('../context', () => ({
|
||||
usePluginPageContext: (selector: (value: any) => any) => {
|
||||
vi.mock('../../context', () => ({
|
||||
usePluginPageContext: (selector: (value: Record<string, unknown>) => unknown) => {
|
||||
const contextValue = {
|
||||
filters: mockState.filters,
|
||||
setActiveTab: mockSetActiveTab,
|
||||
@ -56,7 +51,7 @@ vi.mock('../context', () => ({
|
||||
|
||||
// Mock global public store (Zustand store)
|
||||
vi.mock('@/context/global-public-context', () => ({
|
||||
useGlobalPublicStore: (selector: (state: any) => any) => {
|
||||
useGlobalPublicStore: (selector: (state: Record<string, unknown>) => unknown) => {
|
||||
return selector({
|
||||
systemFeatures: {
|
||||
...defaultSystemFeatures,
|
||||
@ -92,22 +87,10 @@ vi.mock('@/app/components/plugins/install-plugin/install-from-local-package', ()
|
||||
}))
|
||||
|
||||
// Mock Line component
|
||||
vi.mock('../../marketplace/empty/line', () => ({
|
||||
vi.mock('../../../marketplace/empty/line', () => ({
|
||||
default: ({ className }: { className?: string }) => <div data-testid="line-component" className={className} />,
|
||||
}))
|
||||
|
||||
// Override react-i18next with stable t function reference to prevent infinite re-renders
|
||||
// The component's useEffect and useMemo depend on t, so it MUST be stable
|
||||
vi.mock('react-i18next', () => ({
|
||||
useTranslation: () => ({
|
||||
t: stableT,
|
||||
i18n: {
|
||||
language: 'en',
|
||||
changeLanguage: vi.fn(),
|
||||
},
|
||||
}),
|
||||
}))
|
||||
|
||||
// ==================== Test Utilities ====================
|
||||
|
||||
const resetMockState = () => {
|
||||
@ -191,7 +174,7 @@ describe('Empty Component', () => {
|
||||
await flushEffects()
|
||||
|
||||
// Assert
|
||||
expect(screen.getByText('list.noInstalled')).toBeInTheDocument()
|
||||
expect(screen.getByText('plugin.list.noInstalled')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should display "notFound" text when filters are active with plugins', async () => {
|
||||
@ -202,19 +185,19 @@ describe('Empty Component', () => {
|
||||
setMockFilters({ categories: ['model'] })
|
||||
const { rerender } = render(<Empty />)
|
||||
await flushEffects()
|
||||
expect(screen.getByText('list.notFound')).toBeInTheDocument()
|
||||
expect(screen.getByText('plugin.list.notFound')).toBeInTheDocument()
|
||||
|
||||
// Test tags filter
|
||||
setMockFilters({ categories: [], tags: ['tag1'] })
|
||||
rerender(<Empty />)
|
||||
await flushEffects()
|
||||
expect(screen.getByText('list.notFound')).toBeInTheDocument()
|
||||
expect(screen.getByText('plugin.list.notFound')).toBeInTheDocument()
|
||||
|
||||
// Test searchQuery filter
|
||||
setMockFilters({ tags: [], searchQuery: 'test query' })
|
||||
rerender(<Empty />)
|
||||
await flushEffects()
|
||||
expect(screen.getByText('list.notFound')).toBeInTheDocument()
|
||||
expect(screen.getByText('plugin.list.notFound')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should prioritize "noInstalled" over "notFound" when no plugins exist', async () => {
|
||||
@ -227,7 +210,7 @@ describe('Empty Component', () => {
|
||||
await flushEffects()
|
||||
|
||||
// Assert
|
||||
expect(screen.getByText('list.noInstalled')).toBeInTheDocument()
|
||||
expect(screen.getByText('plugin.list.noInstalled')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -250,15 +233,15 @@ describe('Empty Component', () => {
|
||||
// Assert
|
||||
const buttons = screen.getAllByRole('button')
|
||||
expect(buttons).toHaveLength(3)
|
||||
expect(screen.getByText('source.marketplace')).toBeInTheDocument()
|
||||
expect(screen.getByText('source.github')).toBeInTheDocument()
|
||||
expect(screen.getByText('source.local')).toBeInTheDocument()
|
||||
expect(screen.getByText('plugin.source.marketplace')).toBeInTheDocument()
|
||||
expect(screen.getByText('plugin.source.github')).toBeInTheDocument()
|
||||
expect(screen.getByText('plugin.source.local')).toBeInTheDocument()
|
||||
|
||||
// Verify button order
|
||||
const buttonTexts = buttons.map(btn => btn.textContent)
|
||||
expect(buttonTexts[0]).toContain('source.marketplace')
|
||||
expect(buttonTexts[1]).toContain('source.github')
|
||||
expect(buttonTexts[2]).toContain('source.local')
|
||||
expect(buttonTexts[0]).toContain('plugin.source.marketplace')
|
||||
expect(buttonTexts[1]).toContain('plugin.source.github')
|
||||
expect(buttonTexts[2]).toContain('plugin.source.local')
|
||||
})
|
||||
|
||||
it('should render only marketplace method when restricted to marketplace only', async () => {
|
||||
@ -278,9 +261,9 @@ describe('Empty Component', () => {
|
||||
// Assert
|
||||
const buttons = screen.getAllByRole('button')
|
||||
expect(buttons).toHaveLength(1)
|
||||
expect(screen.getByText('source.marketplace')).toBeInTheDocument()
|
||||
expect(screen.queryByText('source.github')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('source.local')).not.toBeInTheDocument()
|
||||
expect(screen.getByText('plugin.source.marketplace')).toBeInTheDocument()
|
||||
expect(screen.queryByText('plugin.source.github')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('plugin.source.local')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render github and local methods when marketplace is disabled', async () => {
|
||||
@ -300,9 +283,9 @@ describe('Empty Component', () => {
|
||||
// Assert
|
||||
const buttons = screen.getAllByRole('button')
|
||||
expect(buttons).toHaveLength(2)
|
||||
expect(screen.queryByText('source.marketplace')).not.toBeInTheDocument()
|
||||
expect(screen.getByText('source.github')).toBeInTheDocument()
|
||||
expect(screen.getByText('source.local')).toBeInTheDocument()
|
||||
expect(screen.queryByText('plugin.source.marketplace')).not.toBeInTheDocument()
|
||||
expect(screen.getByText('plugin.source.github')).toBeInTheDocument()
|
||||
expect(screen.getByText('plugin.source.local')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render no methods when marketplace disabled and restricted', async () => {
|
||||
@ -333,7 +316,7 @@ describe('Empty Component', () => {
|
||||
await flushEffects()
|
||||
|
||||
// Act
|
||||
fireEvent.click(screen.getByText('source.marketplace'))
|
||||
fireEvent.click(screen.getByText('plugin.source.marketplace'))
|
||||
|
||||
// Assert
|
||||
expect(mockSetActiveTab).toHaveBeenCalledWith('discover')
|
||||
@ -348,7 +331,7 @@ describe('Empty Component', () => {
|
||||
expect(screen.queryByTestId('install-from-github-modal')).not.toBeInTheDocument()
|
||||
|
||||
// Act - open modal
|
||||
fireEvent.click(screen.getByText('source.github'))
|
||||
fireEvent.click(screen.getByText('plugin.source.github'))
|
||||
|
||||
// Assert - modal is open
|
||||
expect(screen.getByTestId('install-from-github-modal')).toBeInTheDocument()
|
||||
@ -368,7 +351,7 @@ describe('Empty Component', () => {
|
||||
const clickSpy = vi.spyOn(fileInput, 'click')
|
||||
|
||||
// Act
|
||||
fireEvent.click(screen.getByText('source.local'))
|
||||
fireEvent.click(screen.getByText('plugin.source.local'))
|
||||
|
||||
// Assert
|
||||
expect(clickSpy).toHaveBeenCalled()
|
||||
@ -422,13 +405,13 @@ describe('Empty Component', () => {
|
||||
await flushEffects()
|
||||
|
||||
// Act - Open, close, and reopen GitHub modal
|
||||
fireEvent.click(screen.getByText('source.github'))
|
||||
fireEvent.click(screen.getByText('plugin.source.github'))
|
||||
expect(screen.getByTestId('install-from-github-modal')).toBeInTheDocument()
|
||||
|
||||
fireEvent.click(screen.getByTestId('github-modal-close'))
|
||||
expect(screen.queryByTestId('install-from-github-modal')).not.toBeInTheDocument()
|
||||
|
||||
fireEvent.click(screen.getByText('source.github'))
|
||||
fireEvent.click(screen.getByText('plugin.source.github'))
|
||||
expect(screen.getByTestId('install-from-github-modal')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
@ -480,7 +463,7 @@ describe('Empty Component', () => {
|
||||
render(<Empty />)
|
||||
await flushEffects()
|
||||
expect(screen.getAllByRole('button')).toHaveLength(1)
|
||||
expect(screen.getByText('source.marketplace')).toBeInTheDocument()
|
||||
expect(screen.getByText('plugin.source.marketplace')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render correct text based on plugin list and filters', async () => {
|
||||
@ -490,7 +473,7 @@ describe('Empty Component', () => {
|
||||
|
||||
const { unmount: unmount1 } = render(<Empty />)
|
||||
await flushEffects()
|
||||
expect(screen.getByText('list.noInstalled')).toBeInTheDocument()
|
||||
expect(screen.getByText('plugin.list.noInstalled')).toBeInTheDocument()
|
||||
unmount1()
|
||||
|
||||
// Test 2: notFound when filters are active with plugins
|
||||
@ -499,7 +482,7 @@ describe('Empty Component', () => {
|
||||
|
||||
render(<Empty />)
|
||||
await flushEffects()
|
||||
expect(screen.getByText('list.notFound')).toBeInTheDocument()
|
||||
expect(screen.getByText('plugin.list.notFound')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -529,8 +512,8 @@ describe('Empty Component', () => {
|
||||
it('should be wrapped with React.memo and have displayName', () => {
|
||||
// Assert
|
||||
expect(Empty).toBeDefined()
|
||||
expect((Empty as any).$$typeof?.toString()).toContain('Symbol')
|
||||
expect((Empty as any).displayName || (Empty as any).type?.displayName).toBeDefined()
|
||||
expect((Empty as { $$typeof?: symbol }).$$typeof?.toString()).toContain('Symbol')
|
||||
expect((Empty as unknown as { displayName?: string, type?: { displayName?: string } }).displayName || (Empty as unknown as { type?: { displayName?: string } }).type?.displayName).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
@ -542,7 +525,7 @@ describe('Empty Component', () => {
|
||||
await flushEffects()
|
||||
|
||||
// Test GitHub modal onSuccess
|
||||
fireEvent.click(screen.getByText('source.github'))
|
||||
fireEvent.click(screen.getByText('plugin.source.github'))
|
||||
fireEvent.click(screen.getByTestId('github-modal-success'))
|
||||
expect(screen.getByTestId('install-from-github-modal')).toBeInTheDocument()
|
||||
|
||||
@ -570,12 +553,12 @@ describe('Empty Component', () => {
|
||||
expect(screen.queryByTestId('install-from-local-modal')).not.toBeInTheDocument()
|
||||
|
||||
// Open GitHub modal - only GitHub modal visible
|
||||
fireEvent.click(screen.getByText('source.github'))
|
||||
fireEvent.click(screen.getByText('plugin.source.github'))
|
||||
expect(screen.getByTestId('install-from-github-modal')).toBeInTheDocument()
|
||||
expect(screen.queryByTestId('install-from-local-modal')).not.toBeInTheDocument()
|
||||
|
||||
// Click local button - triggers file input, no modal yet (no file selected)
|
||||
fireEvent.click(screen.getByText('source.local'))
|
||||
fireEvent.click(screen.getByText('plugin.source.local'))
|
||||
// GitHub modal should still be visible, local modal requires file selection
|
||||
expect(screen.queryByTestId('install-from-local-modal')).not.toBeInTheDocument()
|
||||
})
|
||||
Reference in New Issue
Block a user