mirror of
https://github.com/langgenius/dify.git
synced 2026-04-05 16:03:14 +08:00
fix test
This commit is contained in:
@@ -75,11 +75,10 @@ vi.mock('@/app/components/plugins/card/base/description', () => ({
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/plugins/card/base/org-info', () => ({
|
||||
default: ({ orgName, packageName }: { orgName: string, packageName: string }) => (
|
||||
default: ({ orgName, downloadCount }: { orgName: string, downloadCount?: number }) => (
|
||||
<div data-testid="org-info">
|
||||
{orgName}
|
||||
/
|
||||
{packageName}
|
||||
{typeof downloadCount === 'number' ? ` · ${downloadCount}` : null}
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
@@ -124,7 +123,7 @@ describe('Plugin Card Rendering Integration', () => {
|
||||
|
||||
expect(screen.getByTestId('card-icon')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('title')).toHaveTextContent('Google Search')
|
||||
expect(screen.getByTestId('org-info')).toHaveTextContent('langgenius/google-search')
|
||||
expect(screen.getByTestId('org-info')).toHaveTextContent('langgenius')
|
||||
expect(screen.getByTestId('description')).toHaveTextContent('Search the web using Google')
|
||||
})
|
||||
|
||||
|
||||
@@ -181,7 +181,7 @@ describe('Card', () => {
|
||||
render(<Card payload={plugin} />)
|
||||
|
||||
expect(screen.getByText('my-org')).toBeInTheDocument()
|
||||
expect(screen.getByText('my-plugin')).toBeInTheDocument()
|
||||
expect(screen.queryByText('my-plugin')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render plugin icon', () => {
|
||||
@@ -596,7 +596,7 @@ describe('Card', () => {
|
||||
|
||||
render(<Card payload={plugin} />)
|
||||
|
||||
expect(screen.getByText('plugin-with-special-chars!@#$%')).toBeInTheDocument()
|
||||
expect(screen.getByText('org<script>alert(1)</script>')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle very long title', () => {
|
||||
|
||||
@@ -2,6 +2,12 @@ import { render, screen } from '@testing-library/react'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import DownloadCount from '../download-count'
|
||||
|
||||
vi.mock('#i18n', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string) => key === 'marketplace.installs' ? 'installs' : key,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/utils/format', () => ({
|
||||
formatNumber: (n: number) => {
|
||||
if (n >= 1000)
|
||||
@@ -13,16 +19,16 @@ vi.mock('@/utils/format', () => ({
|
||||
describe('DownloadCount', () => {
|
||||
it('renders formatted download count', () => {
|
||||
render(<DownloadCount downloadCount={1500} />)
|
||||
expect(screen.getByText('1.5k')).toBeInTheDocument()
|
||||
expect(screen.getByText('1.5k installs')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders small numbers directly', () => {
|
||||
render(<DownloadCount downloadCount={42} />)
|
||||
expect(screen.getByText('42')).toBeInTheDocument()
|
||||
expect(screen.getByText('42 installs')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders zero download count', () => {
|
||||
render(<DownloadCount downloadCount={0} />)
|
||||
expect(screen.getByText('0')).toBeInTheDocument()
|
||||
expect(screen.getByText('0 installs')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -15,6 +15,22 @@ import {
|
||||
} from '../atoms'
|
||||
import { DEFAULT_PLUGIN_SORT } from '../constants'
|
||||
|
||||
const { mockRouterPush, mockNavigation } = vi.hoisted(() => ({
|
||||
mockRouterPush: vi.fn(),
|
||||
mockNavigation: {
|
||||
pathname: '/plugins',
|
||||
params: {} as Record<string, string | undefined>,
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('next/navigation', () => ({
|
||||
useRouter: () => ({
|
||||
push: mockRouterPush,
|
||||
}),
|
||||
usePathname: () => mockNavigation.pathname,
|
||||
useParams: () => mockNavigation.params,
|
||||
}))
|
||||
|
||||
const createWrapper = (searchParams = '') => {
|
||||
const { wrapper: NuqsWrapper } = createNuqsTestWrapper({ searchParams })
|
||||
const wrapper = ({ children }: { children: ReactNode }) => (
|
||||
@@ -30,6 +46,8 @@ const createWrapper = (searchParams = '') => {
|
||||
describe('Marketplace sort atoms', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockNavigation.pathname = '/plugins'
|
||||
mockNavigation.params = {}
|
||||
})
|
||||
|
||||
it('should return default sort value from useMarketplaceSort', () => {
|
||||
@@ -76,6 +94,8 @@ describe('Marketplace sort atoms', () => {
|
||||
describe('useSearchText', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockNavigation.pathname = '/plugins'
|
||||
mockNavigation.params = {}
|
||||
})
|
||||
|
||||
it('should return empty string as default', () => {
|
||||
@@ -108,6 +128,8 @@ describe('useSearchText', () => {
|
||||
describe('useActivePluginCategory', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockNavigation.pathname = '/plugins'
|
||||
mockNavigation.params = {}
|
||||
})
|
||||
|
||||
it('should return "all" as default category', () => {
|
||||
@@ -128,6 +150,8 @@ describe('useActivePluginCategory', () => {
|
||||
describe('useFilterPluginTags', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockNavigation.pathname = '/plugins'
|
||||
mockNavigation.params = {}
|
||||
})
|
||||
|
||||
it('should return empty array as default', () => {
|
||||
@@ -148,6 +172,8 @@ describe('useFilterPluginTags', () => {
|
||||
describe('useMarketplaceSearchMode', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockNavigation.pathname = '/plugins'
|
||||
mockNavigation.params = {}
|
||||
})
|
||||
|
||||
it('should return false when no search text, no tags, and category has collections (all)', () => {
|
||||
@@ -161,7 +187,7 @@ describe('useMarketplaceSearchMode', () => {
|
||||
const { wrapper } = createWrapper('?q=test&category=all')
|
||||
const { result } = renderHook(() => useMarketplaceSearchMode(), { wrapper })
|
||||
|
||||
expect(result.current).toBe(true)
|
||||
expect(result.current).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should return true when tags are present', () => {
|
||||
@@ -189,6 +215,8 @@ describe('useMarketplaceSearchMode', () => {
|
||||
describe('useMarketplaceMoreClick', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockNavigation.pathname = '/plugins'
|
||||
mockNavigation.params = {}
|
||||
})
|
||||
|
||||
it('should return a callback function', () => {
|
||||
|
||||
@@ -42,8 +42,10 @@ const mockCollectionPlugins = vi.fn()
|
||||
|
||||
vi.mock('@/service/client', () => ({
|
||||
marketplaceClient: {
|
||||
collections: (...args: unknown[]) => mockCollections(...args),
|
||||
collectionPlugins: (...args: unknown[]) => mockCollectionPlugins(...args),
|
||||
plugins: {
|
||||
collections: (...args: unknown[]) => mockCollections(...args),
|
||||
collectionPlugins: (...args: unknown[]) => mockCollectionPlugins(...args),
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
|
||||
@@ -2,6 +2,12 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { render } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
vi.mock('next/headers', () => ({
|
||||
headers: async () => ({
|
||||
get: (name: string) => name === 'sec-fetch-dest' ? 'document' : null,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/config', () => ({
|
||||
API_PREFIX: '/api',
|
||||
APP_VERSION: '1.0.0',
|
||||
@@ -15,15 +21,24 @@ vi.mock('@/utils/var', () => ({
|
||||
|
||||
const mockCollections = vi.fn()
|
||||
const mockCollectionPlugins = vi.fn()
|
||||
const mockSearchAdvanced = vi.fn()
|
||||
|
||||
vi.mock('@/service/client', () => ({
|
||||
marketplaceClient: {
|
||||
collections: (...args: unknown[]) => mockCollections(...args),
|
||||
collectionPlugins: (...args: unknown[]) => mockCollectionPlugins(...args),
|
||||
plugins: {
|
||||
collections: (...args: unknown[]) => mockCollections(...args),
|
||||
collectionPlugins: (...args: unknown[]) => mockCollectionPlugins(...args),
|
||||
searchAdvanced: (...args: unknown[]) => mockSearchAdvanced(...args),
|
||||
},
|
||||
},
|
||||
marketplaceQuery: {
|
||||
collections: {
|
||||
queryKey: (params: unknown) => ['marketplace', 'collections', params],
|
||||
plugins: {
|
||||
collections: {
|
||||
queryKey: (params: unknown) => ['marketplace', 'plugins', 'collections', params],
|
||||
},
|
||||
searchAdvanced: {
|
||||
queryKey: (params: unknown) => ['marketplace', 'plugins', 'searchAdvanced', params],
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
@@ -46,6 +61,9 @@ describe('HydrateQueryClient', () => {
|
||||
mockCollectionPlugins.mockResolvedValue({
|
||||
data: { plugins: [] },
|
||||
})
|
||||
mockSearchAdvanced.mockResolvedValue({
|
||||
data: { plugins: [], total: 0 },
|
||||
})
|
||||
})
|
||||
|
||||
it('should render children within HydrationBoundary', async () => {
|
||||
@@ -81,6 +99,7 @@ describe('HydrateQueryClient', () => {
|
||||
|
||||
await HydrateQueryClient({
|
||||
searchParams: Promise.resolve({ category: 'all' }),
|
||||
isMarketplacePlatform: true,
|
||||
children: <div>Child</div>,
|
||||
})
|
||||
|
||||
@@ -92,31 +111,36 @@ describe('HydrateQueryClient', () => {
|
||||
|
||||
await HydrateQueryClient({
|
||||
searchParams: Promise.resolve({ category: 'tool' }),
|
||||
isMarketplacePlatform: true,
|
||||
children: <div>Child</div>,
|
||||
})
|
||||
|
||||
expect(mockCollections).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not prefetch when category does not have collections (model)', async () => {
|
||||
it('should prefetch search results when category does not have collections (model)', async () => {
|
||||
const { HydrateQueryClient } = await import('../hydration-server')
|
||||
|
||||
await HydrateQueryClient({
|
||||
searchParams: Promise.resolve({ category: 'model' }),
|
||||
isMarketplacePlatform: true,
|
||||
children: <div>Child</div>,
|
||||
})
|
||||
|
||||
expect(mockCollections).not.toHaveBeenCalled()
|
||||
expect(mockCollections).toHaveBeenCalled()
|
||||
expect(mockSearchAdvanced).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not prefetch when category does not have collections (bundle)', async () => {
|
||||
it('should prefetch search results when category does not have collections (bundle)', async () => {
|
||||
const { HydrateQueryClient } = await import('../hydration-server')
|
||||
|
||||
await HydrateQueryClient({
|
||||
searchParams: Promise.resolve({ category: 'bundle' }),
|
||||
isMarketplacePlatform: true,
|
||||
children: <div>Child</div>,
|
||||
})
|
||||
|
||||
expect(mockCollections).not.toHaveBeenCalled()
|
||||
expect(mockCollections).toHaveBeenCalled()
|
||||
expect(mockSearchAdvanced).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -5,6 +5,22 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { createNuqsTestWrapper } from '@/test/nuqs-testing'
|
||||
import { PluginCategorySwitch } from '../category-switch/plugin'
|
||||
|
||||
const { mockRouterPush, mockNavigation } = vi.hoisted(() => ({
|
||||
mockRouterPush: vi.fn(),
|
||||
mockNavigation: {
|
||||
pathname: '/plugins',
|
||||
params: {} as Record<string, string | undefined>,
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('next/navigation', () => ({
|
||||
useRouter: () => ({
|
||||
push: mockRouterPush,
|
||||
}),
|
||||
usePathname: () => mockNavigation.pathname,
|
||||
useParams: () => mockNavigation.params,
|
||||
}))
|
||||
|
||||
vi.mock('#i18n', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string) => {
|
||||
@@ -38,6 +54,8 @@ const createWrapper = (searchParams = '') => {
|
||||
describe('PluginCategorySwitch', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockNavigation.pathname = '/plugins'
|
||||
mockNavigation.params = {}
|
||||
})
|
||||
|
||||
it('should render all category options', () => {
|
||||
|
||||
@@ -20,16 +20,20 @@ const mockSearchAdvanced = vi.fn()
|
||||
|
||||
vi.mock('@/service/client', () => ({
|
||||
marketplaceClient: {
|
||||
collections: (...args: unknown[]) => mockCollections(...args),
|
||||
collectionPlugins: (...args: unknown[]) => mockCollectionPlugins(...args),
|
||||
searchAdvanced: (...args: unknown[]) => mockSearchAdvanced(...args),
|
||||
plugins: {
|
||||
collections: (...args: unknown[]) => mockCollections(...args),
|
||||
collectionPlugins: (...args: unknown[]) => mockCollectionPlugins(...args),
|
||||
searchAdvanced: (...args: unknown[]) => mockSearchAdvanced(...args),
|
||||
},
|
||||
},
|
||||
marketplaceQuery: {
|
||||
collections: {
|
||||
queryKey: (params: unknown) => ['marketplace', 'collections', params],
|
||||
},
|
||||
searchAdvanced: {
|
||||
queryKey: (params: unknown) => ['marketplace', 'searchAdvanced', params],
|
||||
plugins: {
|
||||
collections: {
|
||||
queryKey: (params: unknown) => ['marketplace', 'plugins', 'collections', params],
|
||||
},
|
||||
searchAdvanced: {
|
||||
queryKey: (params: unknown) => ['marketplace', 'plugins', 'searchAdvanced', params],
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
@@ -5,6 +5,10 @@ import { Provider as JotaiProvider } from 'jotai'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { createNuqsTestWrapper } from '@/test/nuqs-testing'
|
||||
|
||||
vi.mock('ahooks', () => ({
|
||||
useDebounce: <T,>(value: T) => value,
|
||||
}))
|
||||
|
||||
vi.mock('@/config', () => ({
|
||||
API_PREFIX: '/api',
|
||||
APP_VERSION: '1.0.0',
|
||||
@@ -19,19 +23,38 @@ vi.mock('@/utils/var', () => ({
|
||||
const mockCollections = vi.fn()
|
||||
const mockCollectionPlugins = vi.fn()
|
||||
const mockSearchAdvanced = vi.fn()
|
||||
const { mockRouterPush, mockNavigation } = vi.hoisted(() => ({
|
||||
mockRouterPush: vi.fn(),
|
||||
mockNavigation: {
|
||||
pathname: '/plugins',
|
||||
params: {} as Record<string, string | undefined>,
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('next/navigation', () => ({
|
||||
useRouter: () => ({
|
||||
push: mockRouterPush,
|
||||
}),
|
||||
usePathname: () => mockNavigation.pathname,
|
||||
useParams: () => mockNavigation.params,
|
||||
}))
|
||||
|
||||
vi.mock('@/service/client', () => ({
|
||||
marketplaceClient: {
|
||||
collections: (...args: unknown[]) => mockCollections(...args),
|
||||
collectionPlugins: (...args: unknown[]) => mockCollectionPlugins(...args),
|
||||
searchAdvanced: (...args: unknown[]) => mockSearchAdvanced(...args),
|
||||
plugins: {
|
||||
collections: (...args: unknown[]) => mockCollections(...args),
|
||||
collectionPlugins: (...args: unknown[]) => mockCollectionPlugins(...args),
|
||||
searchAdvanced: (...args: unknown[]) => mockSearchAdvanced(...args),
|
||||
},
|
||||
},
|
||||
marketplaceQuery: {
|
||||
collections: {
|
||||
queryKey: (params: unknown) => ['marketplace', 'collections', params],
|
||||
},
|
||||
searchAdvanced: {
|
||||
queryKey: (params: unknown) => ['marketplace', 'searchAdvanced', params],
|
||||
plugins: {
|
||||
collections: {
|
||||
queryKey: (params: unknown) => ['marketplace', 'plugins', 'collections', params],
|
||||
},
|
||||
searchAdvanced: {
|
||||
queryKey: (params: unknown) => ['marketplace', 'plugins', 'searchAdvanced', params],
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
@@ -58,6 +81,8 @@ const createWrapper = (searchParams = '') => {
|
||||
describe('usePluginsMarketplaceData', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockNavigation.pathname = '/plugins'
|
||||
mockNavigation.params = {}
|
||||
|
||||
mockCollections.mockResolvedValue({
|
||||
data: {
|
||||
|
||||
@@ -109,7 +109,7 @@ describe('getPluginLinkInMarketplace', () => {
|
||||
const { getPluginLinkInMarketplace } = await import('../utils')
|
||||
const plugin = createMockPlugin({ org: 'test-org', name: 'test-plugin', type: 'plugin' })
|
||||
const link = getPluginLinkInMarketplace(plugin)
|
||||
expect(link).toBe('https://marketplace.dify.ai/plugins/test-org/test-plugin')
|
||||
expect(link).toBe('https://marketplace.dify.ai/plugin/test-org/test-plugin')
|
||||
})
|
||||
|
||||
it('should return correct link for bundle', async () => {
|
||||
@@ -125,7 +125,7 @@ describe('getPluginDetailLinkInMarketplace', () => {
|
||||
const { getPluginDetailLinkInMarketplace } = await import('../utils')
|
||||
const plugin = createMockPlugin({ org: 'test-org', name: 'test-plugin', type: 'plugin' })
|
||||
const link = getPluginDetailLinkInMarketplace(plugin)
|
||||
expect(link).toBe('/plugins/test-org/test-plugin')
|
||||
expect(link).toBe('/plugin/test-org/test-plugin')
|
||||
})
|
||||
|
||||
it('should return correct detail link for bundle', async () => {
|
||||
@@ -149,7 +149,7 @@ describe('getPluginCondition', () => {
|
||||
|
||||
it('should return category condition for agent', async () => {
|
||||
const { getPluginCondition } = await import('../utils')
|
||||
expect(getPluginCondition(PluginCategoryEnum.agent)).toBe('category=agent')
|
||||
expect(getPluginCondition(PluginCategoryEnum.agent)).toBe('category=agent-strategy')
|
||||
})
|
||||
|
||||
it('should return category condition for datasource', async () => {
|
||||
|
||||
@@ -2,648 +2,100 @@ import { render, screen } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { Description } from '../index'
|
||||
|
||||
// ================================
|
||||
// Mock external dependencies
|
||||
// ================================
|
||||
|
||||
// Track mock locale for testing
|
||||
let mockDefaultLocale = 'en-US'
|
||||
|
||||
// Mock translations with realistic values
|
||||
const pluginTranslations: Record<string, string> = {
|
||||
'marketplace.empower': 'Empower your AI development',
|
||||
'marketplace.discover': 'Discover',
|
||||
'marketplace.difyMarketplace': 'Dify Marketplace',
|
||||
'marketplace.and': 'and',
|
||||
'category.models': 'Models',
|
||||
'category.tools': 'Tools',
|
||||
'category.datasources': 'Data Sources',
|
||||
'category.triggers': 'Triggers',
|
||||
'category.agents': 'Agent Strategies',
|
||||
'category.extensions': 'Extensions',
|
||||
'category.bundles': 'Bundles',
|
||||
}
|
||||
|
||||
const commonTranslations: Record<string, string> = {
|
||||
'operation.in': 'in',
|
||||
}
|
||||
|
||||
// Mock i18n hooks
|
||||
vi.mock('#i18n', () => ({
|
||||
useLocale: vi.fn(() => mockDefaultLocale),
|
||||
useTranslation: vi.fn((ns: string) => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string) => {
|
||||
if (ns === 'plugin')
|
||||
return pluginTranslations[key] || key
|
||||
if (ns === 'common')
|
||||
return commonTranslations[key] || key
|
||||
return key
|
||||
const translations: Record<string, string> = {
|
||||
'marketplace.pluginsHeroTitle': 'Build with plugins',
|
||||
'marketplace.pluginsHeroSubtitle': 'Discover and install marketplace plugins.',
|
||||
'marketplace.templatesHeroTitle': 'Build with templates',
|
||||
'marketplace.templatesHeroSubtitle': 'Explore reusable templates.',
|
||||
}
|
||||
return translations[key] || key
|
||||
},
|
||||
})),
|
||||
}),
|
||||
}))
|
||||
|
||||
// ================================
|
||||
// Description Component Tests
|
||||
// ================================
|
||||
let mockCreationType = 'plugins'
|
||||
|
||||
vi.mock('../../atoms', () => ({
|
||||
useCreationType: () => mockCreationType,
|
||||
}))
|
||||
|
||||
vi.mock('../../search-params', () => ({
|
||||
CREATION_TYPE: {
|
||||
plugins: 'plugins',
|
||||
templates: 'templates',
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('../../category-switch', () => ({
|
||||
PluginCategorySwitch: ({ variant }: { variant?: string }) => <div data-testid="plugin-category-switch">{variant}</div>,
|
||||
TemplateCategorySwitch: ({ variant }: { variant?: string }) => <div data-testid="template-category-switch">{variant}</div>,
|
||||
}))
|
||||
|
||||
vi.mock('motion/react', () => ({
|
||||
motion: {
|
||||
div: ({ children, ...props }: React.HTMLAttributes<HTMLDivElement>) => <div {...props}>{children}</div>,
|
||||
},
|
||||
useMotionValue: (value: number) => ({ set: vi.fn(), get: () => value }),
|
||||
useSpring: (value: unknown) => value,
|
||||
useTransform: (...args: unknown[]) => {
|
||||
const values = args[0]
|
||||
if (Array.isArray(values))
|
||||
return 0
|
||||
return values
|
||||
},
|
||||
}))
|
||||
|
||||
class ResizeObserverMock {
|
||||
observe() {}
|
||||
disconnect() {}
|
||||
}
|
||||
|
||||
describe('Description', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockDefaultLocale = 'en-US'
|
||||
mockCreationType = 'plugins'
|
||||
vi.stubGlobal('ResizeObserver', ResizeObserverMock)
|
||||
vi.stubGlobal('requestAnimationFrame', (cb: FrameRequestCallback) => {
|
||||
cb(0)
|
||||
return 1
|
||||
})
|
||||
vi.stubGlobal('cancelAnimationFrame', vi.fn())
|
||||
})
|
||||
|
||||
// ================================
|
||||
// Rendering Tests
|
||||
// ================================
|
||||
describe('Rendering', () => {
|
||||
it('should render without crashing', () => {
|
||||
const { container } = render(<Description />)
|
||||
|
||||
expect(container.firstChild).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render h1 heading with empower text', () => {
|
||||
it('should render plugin hero content by default', () => {
|
||||
render(<Description />)
|
||||
|
||||
const heading = screen.getByRole('heading', { level: 1 })
|
||||
expect(heading).toBeInTheDocument()
|
||||
expect(heading).toHaveTextContent('Empower your AI development')
|
||||
expect(screen.getByRole('heading', { level: 1 })).toHaveTextContent('Build with plugins')
|
||||
expect(screen.getByRole('heading', { level: 2 })).toHaveTextContent('Discover and install marketplace plugins.')
|
||||
expect(screen.getByTestId('plugin-category-switch')).toHaveTextContent('hero')
|
||||
})
|
||||
|
||||
it('should render h2 subheading', () => {
|
||||
it('should render template hero content when creationType is templates', () => {
|
||||
mockCreationType = 'templates'
|
||||
|
||||
render(<Description />)
|
||||
|
||||
const subheading = screen.getByRole('heading', { level: 2 })
|
||||
expect(subheading).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should apply correct CSS classes to h1', () => {
|
||||
render(<Description />)
|
||||
|
||||
const heading = screen.getByRole('heading', { level: 1 })
|
||||
expect(heading).toHaveClass('title-4xl-semi-bold')
|
||||
expect(heading).toHaveClass('mb-2')
|
||||
expect(heading).toHaveClass('text-center')
|
||||
expect(heading).toHaveClass('text-text-primary')
|
||||
})
|
||||
|
||||
it('should apply correct CSS classes to h2', () => {
|
||||
render(<Description />)
|
||||
|
||||
const subheading = screen.getByRole('heading', { level: 2 })
|
||||
expect(subheading).toHaveClass('body-md-regular')
|
||||
expect(subheading).toHaveClass('text-center')
|
||||
expect(subheading).toHaveClass('text-text-tertiary')
|
||||
expect(screen.getByRole('heading', { level: 1 })).toHaveTextContent('Build with templates')
|
||||
expect(screen.getByRole('heading', { level: 2 })).toHaveTextContent('Explore reusable templates.')
|
||||
expect(screen.getByTestId('template-category-switch')).toHaveTextContent('hero')
|
||||
})
|
||||
})
|
||||
|
||||
// ================================
|
||||
// Non-Chinese Locale Rendering Tests
|
||||
// ================================
|
||||
describe('Non-Chinese Locale Rendering', () => {
|
||||
beforeEach(() => {
|
||||
mockDefaultLocale = 'en-US'
|
||||
describe('Props', () => {
|
||||
it('should render marketplace nav content when provided', () => {
|
||||
render(<Description marketplaceNav={<div data-testid="marketplace-nav">Nav</div>} />)
|
||||
|
||||
expect(screen.getByTestId('marketplace-nav')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render discover text for en-US locale', () => {
|
||||
render(<Description />)
|
||||
it('should apply custom className to the sticky wrapper', () => {
|
||||
const { container } = render(<Description className="custom-hero-class" />)
|
||||
|
||||
expect(screen.getByText(/Discover/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render all category names', () => {
|
||||
render(<Description />)
|
||||
|
||||
expect(screen.getByText('Models')).toBeInTheDocument()
|
||||
expect(screen.getByText('Tools')).toBeInTheDocument()
|
||||
expect(screen.getByText('Data Sources')).toBeInTheDocument()
|
||||
expect(screen.getByText('Triggers')).toBeInTheDocument()
|
||||
expect(screen.getByText('Agent Strategies')).toBeInTheDocument()
|
||||
expect(screen.getByText('Extensions')).toBeInTheDocument()
|
||||
expect(screen.getByText('Bundles')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render "and" conjunction text', () => {
|
||||
render(<Description />)
|
||||
|
||||
const subheading = screen.getByRole('heading', { level: 2 })
|
||||
expect(subheading.textContent).toContain('and')
|
||||
})
|
||||
|
||||
it('should render "in" preposition at the end for non-Chinese locales', () => {
|
||||
render(<Description />)
|
||||
|
||||
expect(screen.getByText('in')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render Dify Marketplace text at the end for non-Chinese locales', () => {
|
||||
render(<Description />)
|
||||
|
||||
const subheading = screen.getByRole('heading', { level: 2 })
|
||||
expect(subheading.textContent).toContain('Dify Marketplace')
|
||||
})
|
||||
|
||||
it('should render category spans with styled underline effect', () => {
|
||||
const { container } = render(<Description />)
|
||||
|
||||
const styledSpans = container.querySelectorAll('.body-md-medium.relative.z-\\[1\\]')
|
||||
// 7 category spans (models, tools, datasources, triggers, agents, extensions, bundles)
|
||||
expect(styledSpans.length).toBe(7)
|
||||
})
|
||||
|
||||
it('should apply text-text-secondary class to category spans', () => {
|
||||
const { container } = render(<Description />)
|
||||
|
||||
const styledSpans = container.querySelectorAll('.text-text-secondary')
|
||||
expect(styledSpans.length).toBeGreaterThanOrEqual(7)
|
||||
})
|
||||
})
|
||||
|
||||
// ================================
|
||||
// Chinese (zh-Hans) Locale Rendering Tests
|
||||
// ================================
|
||||
describe('Chinese (zh-Hans) Locale Rendering', () => {
|
||||
beforeEach(() => {
|
||||
mockDefaultLocale = 'zh-Hans'
|
||||
})
|
||||
|
||||
it('should render "in" text at the beginning for zh-Hans locale', () => {
|
||||
render(<Description />)
|
||||
|
||||
// In zh-Hans mode, "in" appears at the beginning
|
||||
const inElements = screen.getAllByText('in')
|
||||
expect(inElements.length).toBeGreaterThanOrEqual(1)
|
||||
})
|
||||
|
||||
it('should render Dify Marketplace text for zh-Hans locale', () => {
|
||||
render(<Description />)
|
||||
|
||||
const subheading = screen.getByRole('heading', { level: 2 })
|
||||
expect(subheading.textContent).toContain('Dify Marketplace')
|
||||
})
|
||||
|
||||
it('should render discover text for zh-Hans locale', () => {
|
||||
render(<Description />)
|
||||
|
||||
expect(screen.getByText(/Discover/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render all categories for zh-Hans locale', () => {
|
||||
render(<Description />)
|
||||
|
||||
expect(screen.getByText('Models')).toBeInTheDocument()
|
||||
expect(screen.getByText('Tools')).toBeInTheDocument()
|
||||
expect(screen.getByText('Data Sources')).toBeInTheDocument()
|
||||
expect(screen.getByText('Triggers')).toBeInTheDocument()
|
||||
expect(screen.getByText('Agent Strategies')).toBeInTheDocument()
|
||||
expect(screen.getByText('Extensions')).toBeInTheDocument()
|
||||
expect(screen.getByText('Bundles')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render both zh-Hans specific elements and shared elements', () => {
|
||||
render(<Description />)
|
||||
|
||||
// zh-Hans has specific element order: "in" -> Dify Marketplace -> Discover
|
||||
// then the same category list with "and" -> Bundles
|
||||
const subheading = screen.getByRole('heading', { level: 2 })
|
||||
expect(subheading.textContent).toContain('and')
|
||||
})
|
||||
})
|
||||
|
||||
// ================================
|
||||
// Locale Variations Tests
|
||||
// ================================
|
||||
describe('Locale Variations', () => {
|
||||
it('should use en-US locale by default', () => {
|
||||
mockDefaultLocale = 'en-US'
|
||||
render(<Description />)
|
||||
|
||||
expect(screen.getByText('Empower your AI development')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle ja-JP locale as non-Chinese', () => {
|
||||
mockDefaultLocale = 'ja-JP'
|
||||
render(<Description />)
|
||||
|
||||
// Should render in non-Chinese format (discover first, then "in Dify Marketplace" at end)
|
||||
const subheading = screen.getByRole('heading', { level: 2 })
|
||||
expect(subheading.textContent).toContain('Dify Marketplace')
|
||||
})
|
||||
|
||||
it('should handle ko-KR locale as non-Chinese', () => {
|
||||
mockDefaultLocale = 'ko-KR'
|
||||
render(<Description />)
|
||||
|
||||
// Should render in non-Chinese format
|
||||
expect(screen.getByText('Empower your AI development')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle de-DE locale as non-Chinese', () => {
|
||||
mockDefaultLocale = 'de-DE'
|
||||
render(<Description />)
|
||||
|
||||
expect(screen.getByText('Empower your AI development')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle fr-FR locale as non-Chinese', () => {
|
||||
mockDefaultLocale = 'fr-FR'
|
||||
render(<Description />)
|
||||
|
||||
expect(screen.getByText('Empower your AI development')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle pt-BR locale as non-Chinese', () => {
|
||||
mockDefaultLocale = 'pt-BR'
|
||||
render(<Description />)
|
||||
|
||||
expect(screen.getByText('Empower your AI development')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle es-ES locale as non-Chinese', () => {
|
||||
mockDefaultLocale = 'es-ES'
|
||||
render(<Description />)
|
||||
|
||||
expect(screen.getByText('Empower your AI development')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// ================================
|
||||
// Conditional Rendering Tests
|
||||
// ================================
|
||||
describe('Conditional Rendering', () => {
|
||||
it('should render zh-Hans specific content when locale is zh-Hans', () => {
|
||||
mockDefaultLocale = 'zh-Hans'
|
||||
const { container } = render(<Description />)
|
||||
|
||||
// zh-Hans has additional span with mr-1 before "in" text at the start
|
||||
const mrSpan = container.querySelector('span.mr-1')
|
||||
expect(mrSpan).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render non-Chinese specific content when locale is not zh-Hans', () => {
|
||||
mockDefaultLocale = 'en-US'
|
||||
render(<Description />)
|
||||
|
||||
// Non-Chinese has "in" and "Dify Marketplace" at the end
|
||||
const subheading = screen.getByRole('heading', { level: 2 })
|
||||
expect(subheading.textContent).toContain('Dify Marketplace')
|
||||
})
|
||||
|
||||
it('should not render zh-Hans intro content for non-Chinese locales', () => {
|
||||
mockDefaultLocale = 'en-US'
|
||||
render(<Description />)
|
||||
|
||||
// For en-US, the order should be Discover ... in Dify Marketplace
|
||||
// The "in" text should only appear once at the end
|
||||
const subheading = screen.getByRole('heading', { level: 2 })
|
||||
const content = subheading.textContent || ''
|
||||
|
||||
// "in" should appear after "Bundles" and before "Dify Marketplace"
|
||||
const bundlesIndex = content.indexOf('Bundles')
|
||||
const inIndex = content.indexOf('in')
|
||||
const marketplaceIndex = content.indexOf('Dify Marketplace')
|
||||
|
||||
expect(bundlesIndex).toBeLessThan(inIndex)
|
||||
expect(inIndex).toBeLessThan(marketplaceIndex)
|
||||
})
|
||||
|
||||
it('should render zh-Hans with proper word order', () => {
|
||||
mockDefaultLocale = 'zh-Hans'
|
||||
render(<Description />)
|
||||
|
||||
const subheading = screen.getByRole('heading', { level: 2 })
|
||||
const content = subheading.textContent || ''
|
||||
|
||||
// zh-Hans order: in -> Dify Marketplace -> Discover -> categories
|
||||
const inIndex = content.indexOf('in')
|
||||
const marketplaceIndex = content.indexOf('Dify Marketplace')
|
||||
const discoverIndex = content.indexOf('Discover')
|
||||
|
||||
expect(inIndex).toBeLessThan(marketplaceIndex)
|
||||
expect(marketplaceIndex).toBeLessThan(discoverIndex)
|
||||
})
|
||||
})
|
||||
|
||||
// ================================
|
||||
// Category Styling Tests
|
||||
// ================================
|
||||
describe('Category Styling', () => {
|
||||
it('should apply underline effect with after pseudo-element styling', () => {
|
||||
const { container } = render(<Description />)
|
||||
|
||||
const categorySpan = container.querySelector('.after\\:absolute')
|
||||
expect(categorySpan).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should apply correct after pseudo-element classes', () => {
|
||||
const { container } = render(<Description />)
|
||||
|
||||
// Check for the specific after pseudo-element classes
|
||||
const categorySpans = container.querySelectorAll('.after\\:bottom-\\[1\\.5px\\]')
|
||||
expect(categorySpans.length).toBe(7)
|
||||
})
|
||||
|
||||
it('should apply full width to after element', () => {
|
||||
const { container } = render(<Description />)
|
||||
|
||||
const categorySpans = container.querySelectorAll('.after\\:w-full')
|
||||
expect(categorySpans.length).toBe(7)
|
||||
})
|
||||
|
||||
it('should apply correct height to after element', () => {
|
||||
const { container } = render(<Description />)
|
||||
|
||||
const categorySpans = container.querySelectorAll('.after\\:h-2')
|
||||
expect(categorySpans.length).toBe(7)
|
||||
})
|
||||
|
||||
it('should apply bg-text-text-selected to after element', () => {
|
||||
const { container } = render(<Description />)
|
||||
|
||||
const categorySpans = container.querySelectorAll('.after\\:bg-text-text-selected')
|
||||
expect(categorySpans.length).toBe(7)
|
||||
})
|
||||
|
||||
it('should have z-index 1 on category spans', () => {
|
||||
const { container } = render(<Description />)
|
||||
|
||||
const categorySpans = container.querySelectorAll('.z-\\[1\\]')
|
||||
expect(categorySpans.length).toBe(7)
|
||||
})
|
||||
|
||||
it('should apply left margin to category spans', () => {
|
||||
const { container } = render(<Description />)
|
||||
|
||||
const categorySpans = container.querySelectorAll('.ml-1')
|
||||
expect(categorySpans.length).toBeGreaterThanOrEqual(7)
|
||||
})
|
||||
|
||||
it('should apply both left and right margin to specific spans', () => {
|
||||
const { container } = render(<Description />)
|
||||
|
||||
// Extensions and Bundles spans have both ml-1 and mr-1
|
||||
const extensionsBundlesSpans = container.querySelectorAll('.ml-1.mr-1')
|
||||
expect(extensionsBundlesSpans.length).toBe(2)
|
||||
})
|
||||
})
|
||||
|
||||
// ================================
|
||||
// Edge Cases Tests
|
||||
// ================================
|
||||
describe('Edge Cases', () => {
|
||||
it('should render fragment as root element', () => {
|
||||
const { container } = render(<Description />)
|
||||
|
||||
// Fragment renders h1 and h2 as direct children
|
||||
expect(container.querySelector('h1')).toBeInTheDocument()
|
||||
expect(container.querySelector('h2')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle zh-Hant as non-Chinese simplified', () => {
|
||||
mockDefaultLocale = 'zh-Hant'
|
||||
render(<Description />)
|
||||
|
||||
// zh-Hant is different from zh-Hans, should use non-Chinese format
|
||||
const subheading = screen.getByRole('heading', { level: 2 })
|
||||
const content = subheading.textContent || ''
|
||||
|
||||
// Check that "Dify Marketplace" appears at the end (non-Chinese format)
|
||||
const discoverIndex = content.indexOf('Discover')
|
||||
const marketplaceIndex = content.indexOf('Dify Marketplace')
|
||||
|
||||
// For non-Chinese locales, Discover should come before Dify Marketplace
|
||||
expect(discoverIndex).toBeLessThan(marketplaceIndex)
|
||||
})
|
||||
})
|
||||
|
||||
// ================================
|
||||
// Content Structure Tests
|
||||
// ================================
|
||||
describe('Content Structure', () => {
|
||||
it('should have comma separators between categories', () => {
|
||||
render(<Description />)
|
||||
|
||||
const subheading = screen.getByRole('heading', { level: 2 })
|
||||
const content = subheading.textContent || ''
|
||||
|
||||
// Commas should exist between categories
|
||||
expect(content).toMatch(/Models[^\n\r,\u2028\u2029]*,.*Tools[^\n\r,\u2028\u2029]*,.*Data Sources[^\n\r,\u2028\u2029]*,.*Triggers[^\n\r,\u2028\u2029]*,.*Agent Strategies[^\n\r,\u2028\u2029]*,.*Extensions/)
|
||||
})
|
||||
|
||||
it('should have "and" before last category (Bundles)', () => {
|
||||
render(<Description />)
|
||||
|
||||
const subheading = screen.getByRole('heading', { level: 2 })
|
||||
const content = subheading.textContent || ''
|
||||
|
||||
// "and" should appear before Bundles
|
||||
const andIndex = content.indexOf('and')
|
||||
const bundlesIndex = content.indexOf('Bundles')
|
||||
|
||||
expect(andIndex).toBeLessThan(bundlesIndex)
|
||||
})
|
||||
|
||||
it('should render all text elements in correct order for en-US', () => {
|
||||
mockDefaultLocale = 'en-US'
|
||||
render(<Description />)
|
||||
|
||||
const subheading = screen.getByRole('heading', { level: 2 })
|
||||
const content = subheading.textContent || ''
|
||||
|
||||
const expectedOrder = [
|
||||
'Discover',
|
||||
'Models',
|
||||
'Tools',
|
||||
'Data Sources',
|
||||
'Triggers',
|
||||
'Agent Strategies',
|
||||
'Extensions',
|
||||
'and',
|
||||
'Bundles',
|
||||
'in',
|
||||
'Dify Marketplace',
|
||||
]
|
||||
|
||||
let lastIndex = -1
|
||||
for (const text of expectedOrder) {
|
||||
const currentIndex = content.indexOf(text)
|
||||
expect(currentIndex).toBeGreaterThan(lastIndex)
|
||||
lastIndex = currentIndex
|
||||
}
|
||||
})
|
||||
|
||||
it('should render all text elements in correct order for zh-Hans', () => {
|
||||
mockDefaultLocale = 'zh-Hans'
|
||||
render(<Description />)
|
||||
|
||||
const subheading = screen.getByRole('heading', { level: 2 })
|
||||
const content = subheading.textContent || ''
|
||||
|
||||
// zh-Hans order: in -> Dify Marketplace -> Discover -> categories -> and -> Bundles
|
||||
const inIndex = content.indexOf('in')
|
||||
const marketplaceIndex = content.indexOf('Dify Marketplace')
|
||||
const discoverIndex = content.indexOf('Discover')
|
||||
const modelsIndex = content.indexOf('Models')
|
||||
|
||||
expect(inIndex).toBeLessThan(marketplaceIndex)
|
||||
expect(marketplaceIndex).toBeLessThan(discoverIndex)
|
||||
expect(discoverIndex).toBeLessThan(modelsIndex)
|
||||
})
|
||||
})
|
||||
|
||||
// ================================
|
||||
// Layout Tests
|
||||
// ================================
|
||||
describe('Layout', () => {
|
||||
it('should have shrink-0 on h1 heading', () => {
|
||||
render(<Description />)
|
||||
|
||||
const heading = screen.getByRole('heading', { level: 1 })
|
||||
expect(heading).toHaveClass('shrink-0')
|
||||
})
|
||||
|
||||
it('should have shrink-0 on h2 subheading', () => {
|
||||
render(<Description />)
|
||||
|
||||
const subheading = screen.getByRole('heading', { level: 2 })
|
||||
expect(subheading).toHaveClass('shrink-0')
|
||||
})
|
||||
|
||||
it('should have flex layout on h2', () => {
|
||||
render(<Description />)
|
||||
|
||||
const subheading = screen.getByRole('heading', { level: 2 })
|
||||
expect(subheading).toHaveClass('flex')
|
||||
})
|
||||
|
||||
it('should have items-center on h2', () => {
|
||||
render(<Description />)
|
||||
|
||||
const subheading = screen.getByRole('heading', { level: 2 })
|
||||
expect(subheading).toHaveClass('items-center')
|
||||
})
|
||||
|
||||
it('should have justify-center on h2', () => {
|
||||
render(<Description />)
|
||||
|
||||
const subheading = screen.getByRole('heading', { level: 2 })
|
||||
expect(subheading).toHaveClass('justify-center')
|
||||
})
|
||||
})
|
||||
|
||||
// ================================
|
||||
// Accessibility Tests
|
||||
// ================================
|
||||
describe('Accessibility', () => {
|
||||
it('should have proper heading hierarchy', () => {
|
||||
render(<Description />)
|
||||
|
||||
const h1 = screen.getByRole('heading', { level: 1 })
|
||||
const h2 = screen.getByRole('heading', { level: 2 })
|
||||
|
||||
expect(h1).toBeInTheDocument()
|
||||
expect(h2).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should have readable text content', () => {
|
||||
render(<Description />)
|
||||
|
||||
const h1 = screen.getByRole('heading', { level: 1 })
|
||||
expect(h1.textContent).not.toBe('')
|
||||
})
|
||||
|
||||
it('should have visible h1 heading', () => {
|
||||
render(<Description />)
|
||||
|
||||
const heading = screen.getByRole('heading', { level: 1 })
|
||||
expect(heading).toBeVisible()
|
||||
})
|
||||
|
||||
it('should have visible h2 heading', () => {
|
||||
render(<Description />)
|
||||
|
||||
const subheading = screen.getByRole('heading', { level: 2 })
|
||||
expect(subheading).toBeVisible()
|
||||
expect(container.querySelector('.custom-hero-class')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// ================================
|
||||
// Integration Tests
|
||||
// ================================
|
||||
describe('Description Integration', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockDefaultLocale = 'en-US'
|
||||
})
|
||||
|
||||
it('should render complete component structure', () => {
|
||||
const { container } = render(<Description />)
|
||||
|
||||
// Main headings
|
||||
expect(container.querySelector('h1')).toBeInTheDocument()
|
||||
expect(container.querySelector('h2')).toBeInTheDocument()
|
||||
|
||||
// All category spans
|
||||
const categorySpans = container.querySelectorAll('.body-md-medium')
|
||||
expect(categorySpans.length).toBe(7)
|
||||
})
|
||||
|
||||
it('should render complete zh-Hans structure', () => {
|
||||
mockDefaultLocale = 'zh-Hans'
|
||||
const { container } = render(<Description />)
|
||||
|
||||
// Main headings
|
||||
expect(container.querySelector('h1')).toBeInTheDocument()
|
||||
expect(container.querySelector('h2')).toBeInTheDocument()
|
||||
|
||||
// All category spans
|
||||
const categorySpans = container.querySelectorAll('.body-md-medium')
|
||||
expect(categorySpans.length).toBe(7)
|
||||
})
|
||||
|
||||
it('should correctly differentiate between zh-Hans and en-US layouts', () => {
|
||||
// Render en-US
|
||||
mockDefaultLocale = 'en-US'
|
||||
const { container: enContainer, unmount: unmountEn } = render(<Description />)
|
||||
const enContent = enContainer.querySelector('h2')?.textContent || ''
|
||||
unmountEn()
|
||||
|
||||
// Render zh-Hans
|
||||
mockDefaultLocale = 'zh-Hans'
|
||||
const { container: zhContainer } = render(<Description />)
|
||||
const zhContent = zhContainer.querySelector('h2')?.textContent || ''
|
||||
|
||||
// Both should have all categories
|
||||
expect(enContent).toContain('Models')
|
||||
expect(zhContent).toContain('Models')
|
||||
|
||||
// But order should differ
|
||||
const enMarketplaceIndex = enContent.indexOf('Dify Marketplace')
|
||||
const enDiscoverIndex = enContent.indexOf('Discover')
|
||||
const zhMarketplaceIndex = zhContent.indexOf('Dify Marketplace')
|
||||
const zhDiscoverIndex = zhContent.indexOf('Discover')
|
||||
|
||||
// en-US: Discover comes before Dify Marketplace
|
||||
expect(enDiscoverIndex).toBeLessThan(enMarketplaceIndex)
|
||||
|
||||
// zh-Hans: Dify Marketplace comes before Discover
|
||||
expect(zhMarketplaceIndex).toBeLessThan(zhDiscoverIndex)
|
||||
})
|
||||
|
||||
it('should maintain consistent styling across locales', () => {
|
||||
// Render en-US
|
||||
mockDefaultLocale = 'en-US'
|
||||
const { container: enContainer, unmount: unmountEn } = render(<Description />)
|
||||
const enCategoryCount = enContainer.querySelectorAll('.body-md-medium').length
|
||||
unmountEn()
|
||||
|
||||
// Render zh-Hans
|
||||
mockDefaultLocale = 'zh-Hans'
|
||||
const { container: zhContainer } = render(<Description />)
|
||||
const zhCategoryCount = zhContainer.querySelectorAll('.body-md-medium').length
|
||||
|
||||
// Both should have same number of styled category spans
|
||||
expect(enCategoryCount).toBe(zhCategoryCount)
|
||||
expect(enCategoryCount).toBe(7)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -43,6 +43,7 @@ const { mockMarketplaceData, mockMoreClick } = vi.hoisted(() => {
|
||||
mockMoreClick: vi.fn(),
|
||||
}
|
||||
})
|
||||
let mockSearchMode = false
|
||||
|
||||
vi.mock('../../state', () => ({
|
||||
useMarketplaceData: () => mockMarketplaceData,
|
||||
@@ -51,7 +52,11 @@ vi.mock('../../state', () => ({
|
||||
|
||||
vi.mock('../../atoms', () => ({
|
||||
useMarketplaceMoreClick: () => mockMoreClick,
|
||||
useMarketplaceSearchMode: () => false,
|
||||
useMarketplaceSearchMode: () => mockSearchMode,
|
||||
useCreationType: () => 'plugins',
|
||||
useFilterPluginTags: () => [[]],
|
||||
useActivePluginCategory: () => ['all'],
|
||||
useActiveTemplateCategory: () => ['all'],
|
||||
}))
|
||||
|
||||
vi.mock('@/context/i18n', () => ({
|
||||
@@ -148,7 +153,7 @@ vi.mock('../../sort-dropdown', () => ({
|
||||
}))
|
||||
|
||||
// Mock Empty component
|
||||
vi.mock('../empty', () => ({
|
||||
vi.mock('../../empty', () => ({
|
||||
default: ({ className, text }: { className?: string, text?: string }) => (
|
||||
<div data-testid="empty-component" className={className}>
|
||||
{text || 'No plugins found'}
|
||||
@@ -234,6 +239,7 @@ describe('List', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockSearchMode = false
|
||||
})
|
||||
|
||||
// ================================
|
||||
@@ -872,6 +878,7 @@ describe('ListWithCollection', () => {
|
||||
describe('ListWrapper', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockSearchMode = false
|
||||
// Reset mock data
|
||||
mockMarketplaceData.plugins = undefined
|
||||
mockMarketplaceData.pluginsTotal = 0
|
||||
@@ -917,6 +924,7 @@ describe('ListWrapper', () => {
|
||||
})
|
||||
|
||||
it('should render template empty state with flex content wrapper when templates are empty', () => {
|
||||
mockSearchMode = true
|
||||
delete (mockMarketplaceData as Record<string, unknown>).pluginCollections
|
||||
delete (mockMarketplaceData as Record<string, unknown>).pluginCollectionPluginsMap
|
||||
;(mockMarketplaceData as Record<string, unknown>).templateCollections = []
|
||||
@@ -931,6 +939,7 @@ describe('ListWrapper', () => {
|
||||
})
|
||||
|
||||
it('should keep plugin empty text when plugins are empty', () => {
|
||||
mockSearchMode = true
|
||||
mockMarketplaceData.plugins = []
|
||||
mockMarketplaceData.pluginsTotal = 0
|
||||
mockMarketplaceData.pluginCollections = []
|
||||
@@ -947,16 +956,18 @@ describe('ListWrapper', () => {
|
||||
// Plugins Header Tests
|
||||
// ================================
|
||||
describe('Plugins Header', () => {
|
||||
it('should render plugins result count when plugins are present', () => {
|
||||
it('should render list top info when search mode is enabled', () => {
|
||||
mockSearchMode = true
|
||||
mockMarketplaceData.plugins = createMockPluginList(5)
|
||||
mockMarketplaceData.pluginsTotal = 5
|
||||
|
||||
render(<ListWrapper />)
|
||||
|
||||
expect(screen.getByText('5 plugins found')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('sort-dropdown')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render SortDropdown when plugins are present', () => {
|
||||
mockSearchMode = true
|
||||
mockMarketplaceData.plugins = createMockPluginList(1)
|
||||
|
||||
render(<ListWrapper />)
|
||||
@@ -1039,25 +1050,28 @@ describe('ListWrapper', () => {
|
||||
// ================================
|
||||
describe('Edge Cases', () => {
|
||||
it('should handle empty plugins array', () => {
|
||||
mockSearchMode = true
|
||||
mockMarketplaceData.plugins = []
|
||||
mockMarketplaceData.pluginsTotal = 0
|
||||
|
||||
render(<ListWrapper />)
|
||||
|
||||
expect(screen.getByText('0 plugins found')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('empty-component')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle large pluginsTotal', () => {
|
||||
it('should handle many plugin results', () => {
|
||||
mockSearchMode = true
|
||||
mockMarketplaceData.plugins = createMockPluginList(10)
|
||||
mockMarketplaceData.pluginsTotal = 10000
|
||||
|
||||
render(<ListWrapper />)
|
||||
|
||||
expect(screen.getByText('10000 plugins found')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('card-plugin-0')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('card-plugin-9')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle both loading and has plugins', () => {
|
||||
mockSearchMode = true
|
||||
mockMarketplaceData.isLoading = true
|
||||
mockMarketplaceData.page = 2
|
||||
mockMarketplaceData.plugins = createMockPluginList(5)
|
||||
@@ -1065,9 +1079,7 @@ describe('ListWrapper', () => {
|
||||
|
||||
render(<ListWrapper />)
|
||||
|
||||
// Should show plugins header and list
|
||||
expect(screen.getByText('50 plugins found')).toBeInTheDocument()
|
||||
// Should not show loading because page > 1
|
||||
expect(screen.getByTestId('card-plugin-0')).toBeInTheDocument()
|
||||
expect(screen.queryByTestId('loading-component')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@@ -1405,6 +1417,7 @@ describe('Combined Workflows', () => {
|
||||
})
|
||||
|
||||
it('should transition from collections to search results', async () => {
|
||||
mockSearchMode = true
|
||||
mockMarketplaceData.pluginCollections = createMockCollectionList(1)
|
||||
mockMarketplaceData.pluginCollectionPluginsMap = {
|
||||
'collection-0': createMockPluginList(1),
|
||||
@@ -1421,20 +1434,21 @@ describe('Combined Workflows', () => {
|
||||
rerender(<ListWrapper />)
|
||||
|
||||
expect(screen.queryByText('Collection 0')).not.toBeInTheDocument()
|
||||
expect(screen.getByText('5 plugins found')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('card-plugin-0')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle empty search results', () => {
|
||||
mockSearchMode = true
|
||||
mockMarketplaceData.plugins = []
|
||||
mockMarketplaceData.pluginsTotal = 0
|
||||
|
||||
render(<ListWrapper />)
|
||||
|
||||
expect(screen.getByTestId('empty-component')).toBeInTheDocument()
|
||||
expect(screen.getByText('0 plugins found')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should support pagination (page > 1)', () => {
|
||||
mockSearchMode = true
|
||||
mockMarketplaceData.plugins = createMockPluginList(40)
|
||||
mockMarketplaceData.pluginsTotal = 80
|
||||
mockMarketplaceData.isLoading = true
|
||||
@@ -1442,9 +1456,7 @@ describe('Combined Workflows', () => {
|
||||
|
||||
render(<ListWrapper />)
|
||||
|
||||
// Should show existing results while loading more
|
||||
expect(screen.getByText('80 plugins found')).toBeInTheDocument()
|
||||
// Should not show loading spinner for pagination
|
||||
expect(screen.getByTestId('card-plugin-0')).toBeInTheDocument()
|
||||
expect(screen.queryByTestId('loading-component')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -21,6 +21,11 @@ const { mockMarketplaceData } = vi.hoisted(() => ({
|
||||
|
||||
vi.mock('../state', () => ({
|
||||
useMarketplaceData: () => mockMarketplaceData,
|
||||
isPluginsData: (data: typeof mockMarketplaceData) => data.creationType === 'plugins',
|
||||
}))
|
||||
|
||||
vi.mock('../atoms', () => ({
|
||||
useMarketplaceSearchMode: () => false,
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/loading', () => ({
|
||||
|
||||
@@ -44,9 +44,8 @@ vi.mock('next/link', () => ({
|
||||
),
|
||||
}))
|
||||
|
||||
// Mock next-themes
|
||||
vi.mock('next-themes', () => ({
|
||||
useTheme: () => ({ theme: 'light' }),
|
||||
vi.mock('@/hooks/use-theme', () => ({
|
||||
default: () => ({ theme: 'light' }),
|
||||
}))
|
||||
|
||||
// Mock marketplace utils
|
||||
@@ -66,16 +65,20 @@ vi.mock('@/app/components/plugins/card/base/corner-mark', () => ({
|
||||
default: ({ text }: { text: string }) => <span data-testid="corner-mark">{text}</span>,
|
||||
}))
|
||||
|
||||
// Mock marketplace utils (getTemplateIconUrl)
|
||||
vi.mock('../utils', () => ({
|
||||
getTemplateIconUrl: (template: { id: string, icon?: string, icon_file_key?: string }): string => {
|
||||
if (template.icon?.startsWith('http'))
|
||||
return template.icon
|
||||
if (template.icon_file_key)
|
||||
return `https://marketplace.dify.ai/api/v1/templates/${template.id}/icon`
|
||||
return ''
|
||||
},
|
||||
}))
|
||||
// Mock marketplace utils (getTemplateIconUrl) while keeping other exports intact.
|
||||
vi.mock('../utils', async () => {
|
||||
const actual = await vi.importActual<typeof import('../utils')>('../utils')
|
||||
return {
|
||||
...actual,
|
||||
getTemplateIconUrl: (template: { id: string, icon?: string, icon_file_key?: string }): string => {
|
||||
if (template.icon?.startsWith('http'))
|
||||
return template.icon
|
||||
if (template.icon_file_key)
|
||||
return `https://marketplace.dify.ai/api/v1/templates/${template.id}/icon`
|
||||
return ''
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
// ================================
|
||||
// Test Data Factories
|
||||
|
||||
@@ -41,10 +41,18 @@ vi.mock('ahooks', () => ({
|
||||
useDebounce: (value: string) => value,
|
||||
}))
|
||||
|
||||
const mockRouterPush = vi.fn()
|
||||
vi.mock('next/navigation', () => ({
|
||||
useRouter: () => ({
|
||||
push: mockRouterPush,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('jotai', async () => {
|
||||
const actual = await vi.importActual<typeof import('jotai')>('jotai')
|
||||
return {
|
||||
...actual,
|
||||
useAtomValue: () => false,
|
||||
useSetAtom: () => vi.fn(),
|
||||
}
|
||||
})
|
||||
@@ -61,6 +69,7 @@ vi.mock('@/hooks/use-i18n', () => ({
|
||||
const {
|
||||
mockSearchText,
|
||||
mockHandleSearchTextChange,
|
||||
mockSetSearchTab,
|
||||
mockFilterPluginTags,
|
||||
mockHandleFilterPluginTagsChange,
|
||||
mockActivePluginCategory,
|
||||
@@ -69,6 +78,7 @@ const {
|
||||
return {
|
||||
mockSearchText: '',
|
||||
mockHandleSearchTextChange: vi.fn(),
|
||||
mockSetSearchTab: vi.fn(),
|
||||
mockFilterPluginTags: [] as string[],
|
||||
mockHandleFilterPluginTagsChange: vi.fn(),
|
||||
mockActivePluginCategory: 'all',
|
||||
@@ -87,8 +97,9 @@ vi.mock('../../atoms', () => ({
|
||||
useMarketplaceTemplateSortValue: () => ({ sortBy: 'usage_count', sortOrder: 'DESC' }),
|
||||
useActiveSort: () => [mockSortValue, vi.fn()],
|
||||
useActiveSortValue: () => mockSortValue,
|
||||
useCreationType: () => ['plugins', vi.fn()],
|
||||
useSearchTab: () => ['', vi.fn()],
|
||||
useCreationType: () => 'plugins',
|
||||
useSearchTab: () => ['', mockSetSearchTab],
|
||||
isMarketplacePlatformAtom: {},
|
||||
searchModeAtom: {},
|
||||
}))
|
||||
|
||||
@@ -135,7 +146,7 @@ vi.mock('@/app/components/plugins/hooks', () => ({
|
||||
}))
|
||||
|
||||
let mockDropdownPlugins: Plugin[] = []
|
||||
vi.mock('../query', () => ({
|
||||
vi.mock('../../query', () => ({
|
||||
useMarketplaceUnifiedSearch: () => ({
|
||||
data: {
|
||||
plugins: { items: mockDropdownPlugins, total: mockDropdownPlugins.length },
|
||||
@@ -233,6 +244,7 @@ describe('SearchBox', () => {
|
||||
vi.clearAllMocks()
|
||||
mockPortalOpenState = false
|
||||
mockDropdownPlugins = []
|
||||
mockRouterPush.mockReset()
|
||||
})
|
||||
|
||||
// ================================
|
||||
@@ -630,6 +642,7 @@ describe('SearchBoxWrapper', () => {
|
||||
vi.clearAllMocks()
|
||||
mockPortalOpenState = false
|
||||
mockDropdownPlugins = []
|
||||
mockRouterPush.mockReset()
|
||||
})
|
||||
|
||||
describe('Rendering', () => {
|
||||
@@ -639,17 +652,10 @@ describe('SearchBoxWrapper', () => {
|
||||
expect(screen.getByRole('textbox')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render in marketplace mode', () => {
|
||||
const { container } = render(<SearchBoxWrapper />)
|
||||
it('should apply custom wrapper class to the input wrapper', () => {
|
||||
const { container } = render(<SearchBoxWrapper wrapperClassName="custom-wrapper" />)
|
||||
|
||||
expect(container.querySelector('.rounded-xl')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should apply correct wrapper classes', () => {
|
||||
const { container } = render(<SearchBoxWrapper />)
|
||||
|
||||
// Check for z-[11] class from wrapper
|
||||
expect(container.querySelector('.z-\\[11\\]')).toBeInTheDocument()
|
||||
expect(container.querySelector('.custom-wrapper')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -671,6 +677,7 @@ describe('SearchBoxWrapper', () => {
|
||||
fireEvent.keyDown(input, { key: 'Enter' })
|
||||
|
||||
expect(mockHandleSearchTextChange).toHaveBeenCalledWith('new search')
|
||||
expect(mockSetSearchTab).toHaveBeenCalledWith('all')
|
||||
})
|
||||
|
||||
it('should clear committed search when input is emptied and blurred', () => {
|
||||
@@ -702,7 +709,7 @@ describe('SearchBoxWrapper', () => {
|
||||
it('should use translation for placeholder', () => {
|
||||
render(<SearchBoxWrapper />)
|
||||
|
||||
expect(screen.getByPlaceholderText('Search plugins')).toBeInTheDocument()
|
||||
expect(screen.getByPlaceholderText('searchInMarketplace')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
import { fireEvent, render, screen, within } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import SortDropdown from '../index'
|
||||
|
||||
// ================================
|
||||
// Mock external dependencies only
|
||||
// ================================
|
||||
|
||||
// Mock i18n translation hook
|
||||
const mockTranslation = vi.fn((key: string, options?: { ns?: string }) => {
|
||||
// Build full key with namespace prefix if provided
|
||||
const fullKey = options?.ns ? `${options.ns}.${key}` : key
|
||||
const translations: Record<string, string> = {
|
||||
'plugin.marketplace.sortBy': 'Sort by',
|
||||
@@ -27,73 +20,36 @@ vi.mock('#i18n', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
// Mock marketplace atoms with controllable values
|
||||
let mockSort: { sortBy: string, sortOrder: string } = { sortBy: 'install_count', sortOrder: 'DESC' }
|
||||
const mockHandleSortChange = vi.fn()
|
||||
let mockCreationType = 'plugins'
|
||||
|
||||
vi.mock('../atoms', () => ({
|
||||
vi.mock('../../atoms', () => ({
|
||||
useActiveSort: () => [mockSort, mockHandleSortChange],
|
||||
useCreationType: () => [mockCreationType, vi.fn()],
|
||||
useMarketplaceSort: () => [mockSort, mockHandleSortChange],
|
||||
useCreationType: () => mockCreationType,
|
||||
}))
|
||||
|
||||
vi.mock('../search-params', () => ({
|
||||
vi.mock('../../search-params', () => ({
|
||||
CREATION_TYPE: { plugins: 'plugins', templates: 'templates' },
|
||||
}))
|
||||
|
||||
// Mock portal component with controllable open state
|
||||
let mockPortalOpenState = false
|
||||
|
||||
vi.mock('@/app/components/base/portal-to-follow-elem', () => ({
|
||||
PortalToFollowElem: ({ children, open, onOpenChange: _onOpenChange }: {
|
||||
children: React.ReactNode
|
||||
open: boolean
|
||||
onOpenChange: (open: boolean) => void
|
||||
}) => {
|
||||
PortalToFollowElem: ({ children, open }: { children: React.ReactNode, open: boolean }) => {
|
||||
mockPortalOpenState = open
|
||||
return (
|
||||
<div data-testid="portal-wrapper" data-open={open}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
return <div data-testid="portal-wrapper" data-open={String(open)}>{children}</div>
|
||||
},
|
||||
PortalToFollowElemTrigger: ({ children, onClick }: {
|
||||
children: React.ReactNode
|
||||
onClick: () => void
|
||||
}) => (
|
||||
<div data-testid="portal-trigger" onClick={onClick}>
|
||||
{children}
|
||||
</div>
|
||||
PortalToFollowElemTrigger: ({ children, onClick }: { children: React.ReactNode, onClick: () => void }) => (
|
||||
<div data-testid="portal-trigger" onClick={onClick}>{children}</div>
|
||||
),
|
||||
PortalToFollowElemContent: ({ children }: { children: React.ReactNode }) => {
|
||||
// Match actual behavior: only render when portal is open
|
||||
if (!mockPortalOpenState)
|
||||
return null
|
||||
return <div data-testid="portal-content">{children}</div>
|
||||
},
|
||||
}))
|
||||
|
||||
// ================================
|
||||
// Test Factory Functions
|
||||
// ================================
|
||||
|
||||
type SortOption = {
|
||||
value: string
|
||||
order: string
|
||||
text: string
|
||||
}
|
||||
|
||||
const createSortOptions = (): SortOption[] => [
|
||||
{ value: 'install_count', order: 'DESC', text: 'Most Popular' },
|
||||
{ value: 'version_updated_at', order: 'DESC', text: 'Recently Updated' },
|
||||
{ value: 'created_at', order: 'DESC', text: 'Newly Released' },
|
||||
{ value: 'created_at', order: 'ASC', text: 'First Released' },
|
||||
]
|
||||
|
||||
// ================================
|
||||
// SortDropdown Component Tests
|
||||
// ================================
|
||||
describe('SortDropdown', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
@@ -102,149 +58,40 @@ describe('SortDropdown', () => {
|
||||
mockPortalOpenState = false
|
||||
})
|
||||
|
||||
// ================================
|
||||
// Rendering Tests
|
||||
// ================================
|
||||
describe('Rendering', () => {
|
||||
it('should render without crashing', () => {
|
||||
render(<SortDropdown />)
|
||||
|
||||
expect(screen.getByTestId('portal-wrapper')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render sort by label', () => {
|
||||
it('should render selected plugin sort label by default', () => {
|
||||
render(<SortDropdown />)
|
||||
|
||||
expect(screen.getByText('Sort by')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render selected option text', () => {
|
||||
render(<SortDropdown />)
|
||||
|
||||
expect(screen.getByText('Most Popular')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('portal-wrapper')).toHaveAttribute('data-open', 'false')
|
||||
})
|
||||
|
||||
it('should render arrow down icon', () => {
|
||||
const { container } = render(<SortDropdown />)
|
||||
it('should render template sort label when creationType is templates', () => {
|
||||
mockCreationType = 'templates'
|
||||
mockSort = { sortBy: 'updated_at', sortOrder: 'DESC' }
|
||||
|
||||
const arrowIcon = container.querySelector('.h-4.w-4.text-text-tertiary')
|
||||
expect(arrowIcon).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render trigger element with correct styles', () => {
|
||||
const { container } = render(<SortDropdown />)
|
||||
|
||||
const trigger = container.querySelector('.cursor-pointer')
|
||||
expect(trigger).toBeInTheDocument()
|
||||
expect(trigger).toHaveClass('h-8', 'rounded-lg', 'bg-state-base-hover-alt')
|
||||
})
|
||||
|
||||
it('should not render dropdown content when closed', () => {
|
||||
render(<SortDropdown />)
|
||||
|
||||
expect(screen.queryByTestId('portal-content')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// ================================
|
||||
// State Management Tests
|
||||
// ================================
|
||||
describe('State Management', () => {
|
||||
it('should initialize with closed state', () => {
|
||||
render(<SortDropdown />)
|
||||
|
||||
const wrapper = screen.getByTestId('portal-wrapper')
|
||||
expect(wrapper).toHaveAttribute('data-open', 'false')
|
||||
})
|
||||
|
||||
it('should display correct selected option for install_count DESC', () => {
|
||||
mockSort = { sortBy: 'install_count', sortOrder: 'DESC' }
|
||||
render(<SortDropdown />)
|
||||
|
||||
expect(screen.getByText('Most Popular')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should display correct selected option for version_updated_at DESC', () => {
|
||||
mockSort = { sortBy: 'version_updated_at', sortOrder: 'DESC' }
|
||||
render(<SortDropdown />)
|
||||
|
||||
expect(screen.getByText('Recently Updated')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should display correct selected option for created_at DESC', () => {
|
||||
mockSort = { sortBy: 'created_at', sortOrder: 'DESC' }
|
||||
render(<SortDropdown />)
|
||||
|
||||
expect(screen.getByText('Newly Released')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should display correct selected option for created_at ASC', () => {
|
||||
mockSort = { sortBy: 'created_at', sortOrder: 'ASC' }
|
||||
render(<SortDropdown />)
|
||||
|
||||
expect(screen.getByText('First Released')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should toggle open state when trigger clicked', () => {
|
||||
render(<SortDropdown />)
|
||||
|
||||
const trigger = screen.getByTestId('portal-trigger')
|
||||
fireEvent.click(trigger)
|
||||
|
||||
// After click, portal content should be visible
|
||||
expect(screen.getByTestId('portal-content')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should close dropdown when trigger clicked again', () => {
|
||||
render(<SortDropdown />)
|
||||
|
||||
const trigger = screen.getByTestId('portal-trigger')
|
||||
|
||||
// Open
|
||||
fireEvent.click(trigger)
|
||||
expect(screen.getByTestId('portal-content')).toBeInTheDocument()
|
||||
|
||||
// Close
|
||||
fireEvent.click(trigger)
|
||||
expect(screen.queryByTestId('portal-content')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// ================================
|
||||
// User Interactions Tests
|
||||
// ================================
|
||||
describe('User Interactions', () => {
|
||||
it('should open dropdown on trigger click', () => {
|
||||
describe('Interactions', () => {
|
||||
it('should open the dropdown when trigger is clicked', () => {
|
||||
render(<SortDropdown />)
|
||||
|
||||
const trigger = screen.getByTestId('portal-trigger')
|
||||
fireEvent.click(trigger)
|
||||
fireEvent.click(screen.getByTestId('portal-trigger'))
|
||||
|
||||
expect(screen.getByTestId('portal-content')).toBeInTheDocument()
|
||||
expect(within(screen.getByTestId('portal-content')).getByText('Recently Updated')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render all sort options when open', () => {
|
||||
it('should call handleSortChange with plugin sort option values', () => {
|
||||
render(<SortDropdown />)
|
||||
|
||||
// Open dropdown
|
||||
fireEvent.click(screen.getByTestId('portal-trigger'))
|
||||
|
||||
const content = screen.getByTestId('portal-content')
|
||||
expect(within(content).getByText('Most Popular')).toBeInTheDocument()
|
||||
expect(within(content).getByText('Recently Updated')).toBeInTheDocument()
|
||||
expect(within(content).getByText('Newly Released')).toBeInTheDocument()
|
||||
expect(within(content).getByText('First Released')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should call handleSortChange when option clicked', () => {
|
||||
render(<SortDropdown />)
|
||||
|
||||
// Open dropdown
|
||||
fireEvent.click(screen.getByTestId('portal-trigger'))
|
||||
|
||||
// Click on "Recently Updated"
|
||||
const content = screen.getByTestId('portal-content')
|
||||
fireEvent.click(within(content).getByText('Recently Updated'))
|
||||
fireEvent.click(within(screen.getByTestId('portal-content')).getByText('Recently Updated'))
|
||||
|
||||
expect(mockHandleSortChange).toHaveBeenCalledWith({
|
||||
sortBy: 'version_updated_at',
|
||||
@@ -252,455 +99,28 @@ describe('SortDropdown', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should call handleSortChange with correct params for Most Popular', () => {
|
||||
mockSort = { sortBy: 'created_at', sortOrder: 'DESC' }
|
||||
it('should call handleSortChange with template sort option values', () => {
|
||||
mockCreationType = 'templates'
|
||||
|
||||
render(<SortDropdown />)
|
||||
|
||||
fireEvent.click(screen.getByTestId('portal-trigger'))
|
||||
|
||||
const content = screen.getByTestId('portal-content')
|
||||
fireEvent.click(within(content).getByText('Most Popular'))
|
||||
fireEvent.click(within(screen.getByTestId('portal-content')).getByText('Most Popular'))
|
||||
|
||||
expect(mockHandleSortChange).toHaveBeenCalledWith({
|
||||
sortBy: 'install_count',
|
||||
sortBy: 'usage_count',
|
||||
sortOrder: 'DESC',
|
||||
})
|
||||
})
|
||||
|
||||
it('should call handleSortChange with correct params for Newly Released', () => {
|
||||
render(<SortDropdown />)
|
||||
|
||||
fireEvent.click(screen.getByTestId('portal-trigger'))
|
||||
|
||||
const content = screen.getByTestId('portal-content')
|
||||
fireEvent.click(within(content).getByText('Newly Released'))
|
||||
|
||||
expect(mockHandleSortChange).toHaveBeenCalledWith({
|
||||
sortBy: 'created_at',
|
||||
sortOrder: 'DESC',
|
||||
})
|
||||
})
|
||||
|
||||
it('should call handleSortChange with correct params for First Released', () => {
|
||||
render(<SortDropdown />)
|
||||
|
||||
fireEvent.click(screen.getByTestId('portal-trigger'))
|
||||
|
||||
const content = screen.getByTestId('portal-content')
|
||||
fireEvent.click(within(content).getByText('First Released'))
|
||||
|
||||
expect(mockHandleSortChange).toHaveBeenCalledWith({
|
||||
sortBy: 'created_at',
|
||||
sortOrder: 'ASC',
|
||||
})
|
||||
})
|
||||
|
||||
it('should allow selecting currently selected option', () => {
|
||||
mockSort = { sortBy: 'install_count', sortOrder: 'DESC' }
|
||||
render(<SortDropdown />)
|
||||
|
||||
fireEvent.click(screen.getByTestId('portal-trigger'))
|
||||
|
||||
const content = screen.getByTestId('portal-content')
|
||||
fireEvent.click(within(content).getByText('Most Popular'))
|
||||
|
||||
expect(mockHandleSortChange).toHaveBeenCalledWith({
|
||||
sortBy: 'install_count',
|
||||
sortOrder: 'DESC',
|
||||
})
|
||||
})
|
||||
|
||||
it('should support userEvent for trigger click', async () => {
|
||||
const user = userEvent.setup()
|
||||
render(<SortDropdown />)
|
||||
|
||||
const trigger = screen.getByTestId('portal-trigger')
|
||||
await user.click(trigger)
|
||||
|
||||
expect(screen.getByTestId('portal-content')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// ================================
|
||||
// Check Icon Tests
|
||||
// ================================
|
||||
describe('Check Icon', () => {
|
||||
it('should show check icon for selected option', () => {
|
||||
mockSort = { sortBy: 'install_count', sortOrder: 'DESC' }
|
||||
const { container } = render(<SortDropdown />)
|
||||
|
||||
// Open dropdown
|
||||
fireEvent.click(screen.getByTestId('portal-trigger'))
|
||||
|
||||
// Check icon should be present in the dropdown
|
||||
const checkIcon = container.querySelector('.text-text-accent')
|
||||
expect(checkIcon).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show check icon only for matching sortBy AND sortOrder', () => {
|
||||
mockSort = { sortBy: 'created_at', sortOrder: 'DESC' }
|
||||
render(<SortDropdown />)
|
||||
|
||||
fireEvent.click(screen.getByTestId('portal-trigger'))
|
||||
|
||||
const content = screen.getByTestId('portal-content')
|
||||
const options = content.querySelectorAll('.cursor-pointer')
|
||||
|
||||
// "Newly Released" (created_at DESC) should have check icon
|
||||
// "First Released" (created_at ASC) should NOT have check icon
|
||||
expect(options.length).toBe(4)
|
||||
})
|
||||
|
||||
it('should not show check icon for different sortOrder with same sortBy', () => {
|
||||
mockSort = { sortBy: 'created_at', sortOrder: 'DESC' }
|
||||
const { container } = render(<SortDropdown />)
|
||||
|
||||
fireEvent.click(screen.getByTestId('portal-trigger'))
|
||||
|
||||
// Only one check icon should be visible (for Newly Released, not First Released)
|
||||
const checkIcons = container.querySelectorAll('.text-text-accent')
|
||||
expect(checkIcons.length).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
// ================================
|
||||
// Dropdown Options Structure Tests
|
||||
// ================================
|
||||
describe('Dropdown Options Structure', () => {
|
||||
const sortOptions = createSortOptions()
|
||||
|
||||
it('should render 4 sort options', () => {
|
||||
render(<SortDropdown />)
|
||||
|
||||
fireEvent.click(screen.getByTestId('portal-trigger'))
|
||||
|
||||
const content = screen.getByTestId('portal-content')
|
||||
const options = content.querySelectorAll('.cursor-pointer')
|
||||
expect(options.length).toBe(4)
|
||||
})
|
||||
|
||||
it.each(sortOptions)('should render option: $text', ({ text }) => {
|
||||
render(<SortDropdown />)
|
||||
|
||||
fireEvent.click(screen.getByTestId('portal-trigger'))
|
||||
|
||||
const content = screen.getByTestId('portal-content')
|
||||
expect(within(content).getByText(text)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render options with unique keys', () => {
|
||||
render(<SortDropdown />)
|
||||
|
||||
fireEvent.click(screen.getByTestId('portal-trigger'))
|
||||
|
||||
const content = screen.getByTestId('portal-content')
|
||||
const options = content.querySelectorAll('.cursor-pointer')
|
||||
|
||||
// All options should be rendered (no key conflicts)
|
||||
expect(options.length).toBe(4)
|
||||
})
|
||||
|
||||
it('should render dropdown container with correct styles', () => {
|
||||
render(<SortDropdown />)
|
||||
|
||||
fireEvent.click(screen.getByTestId('portal-trigger'))
|
||||
|
||||
const content = screen.getByTestId('portal-content')
|
||||
const container = content.firstChild as HTMLElement
|
||||
expect(container).toHaveClass('rounded-xl', 'shadow-lg')
|
||||
})
|
||||
|
||||
it('should render option items with hover styles', () => {
|
||||
render(<SortDropdown />)
|
||||
|
||||
fireEvent.click(screen.getByTestId('portal-trigger'))
|
||||
|
||||
const content = screen.getByTestId('portal-content')
|
||||
const option = content.querySelector('.cursor-pointer')
|
||||
expect(option).toHaveClass('hover:bg-components-panel-on-panel-item-bg-hover')
|
||||
})
|
||||
})
|
||||
|
||||
// ================================
|
||||
// Edge Cases Tests
|
||||
// ================================
|
||||
describe('Edge Cases', () => {
|
||||
// The component falls back to the first option (Most Popular) when sort values are invalid
|
||||
|
||||
it('should fallback to default option when sortBy is unknown', () => {
|
||||
mockSort = { sortBy: 'unknown_field', sortOrder: 'DESC' }
|
||||
|
||||
render(<SortDropdown />)
|
||||
|
||||
// Should fallback to first option "Most Popular"
|
||||
expect(screen.getByText('Most Popular')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should fallback to default option when sortBy is empty', () => {
|
||||
mockSort = { sortBy: '', sortOrder: 'DESC' }
|
||||
describe('Selection', () => {
|
||||
it('should fall back to the first option when the sort does not match any option', () => {
|
||||
mockSort = { sortBy: 'unknown', sortOrder: 'DESC' }
|
||||
|
||||
render(<SortDropdown />)
|
||||
|
||||
expect(screen.getByText('Most Popular')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should fallback to default option when sortOrder is unknown', () => {
|
||||
mockSort = { sortBy: 'install_count', sortOrder: 'UNKNOWN' }
|
||||
|
||||
render(<SortDropdown />)
|
||||
|
||||
expect(screen.getByText('Most Popular')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render correctly when handleSortChange is a no-op', () => {
|
||||
mockHandleSortChange.mockImplementation(() => {})
|
||||
render(<SortDropdown />)
|
||||
|
||||
fireEvent.click(screen.getByTestId('portal-trigger'))
|
||||
|
||||
const content = screen.getByTestId('portal-content')
|
||||
fireEvent.click(within(content).getByText('Recently Updated'))
|
||||
|
||||
expect(mockHandleSortChange).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should handle rapid toggle clicks', () => {
|
||||
render(<SortDropdown />)
|
||||
|
||||
const trigger = screen.getByTestId('portal-trigger')
|
||||
|
||||
// Rapid clicks
|
||||
fireEvent.click(trigger)
|
||||
fireEvent.click(trigger)
|
||||
fireEvent.click(trigger)
|
||||
|
||||
// Final state should be open (odd number of clicks)
|
||||
expect(screen.getByTestId('portal-content')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle multiple option selections', () => {
|
||||
render(<SortDropdown />)
|
||||
|
||||
fireEvent.click(screen.getByTestId('portal-trigger'))
|
||||
|
||||
const content = screen.getByTestId('portal-content')
|
||||
|
||||
// Click multiple options
|
||||
fireEvent.click(within(content).getByText('Recently Updated'))
|
||||
fireEvent.click(within(content).getByText('Newly Released'))
|
||||
fireEvent.click(within(content).getByText('First Released'))
|
||||
|
||||
expect(mockHandleSortChange).toHaveBeenCalledTimes(3)
|
||||
})
|
||||
})
|
||||
|
||||
// ================================
|
||||
// Context Integration Tests
|
||||
// ================================
|
||||
describe('Context Integration', () => {
|
||||
it('should read sort value from context', () => {
|
||||
mockSort = { sortBy: 'version_updated_at', sortOrder: 'DESC' }
|
||||
render(<SortDropdown />)
|
||||
|
||||
expect(screen.getByText('Recently Updated')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should call context handleSortChange on selection', () => {
|
||||
render(<SortDropdown />)
|
||||
|
||||
fireEvent.click(screen.getByTestId('portal-trigger'))
|
||||
|
||||
const content = screen.getByTestId('portal-content')
|
||||
fireEvent.click(within(content).getByText('First Released'))
|
||||
|
||||
expect(mockHandleSortChange).toHaveBeenCalledWith({
|
||||
sortBy: 'created_at',
|
||||
sortOrder: 'ASC',
|
||||
})
|
||||
})
|
||||
|
||||
it('should update display when context sort changes', () => {
|
||||
const { rerender } = render(<SortDropdown />)
|
||||
|
||||
expect(screen.getByText('Most Popular')).toBeInTheDocument()
|
||||
|
||||
// Simulate context change
|
||||
mockSort = { sortBy: 'created_at', sortOrder: 'ASC' }
|
||||
rerender(<SortDropdown />)
|
||||
|
||||
expect(screen.getByText('First Released')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should use selector pattern correctly', () => {
|
||||
render(<SortDropdown />)
|
||||
|
||||
// Component should have called useMarketplaceContext with selector functions
|
||||
expect(screen.getByTestId('portal-wrapper')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// ================================
|
||||
// Accessibility Tests
|
||||
// ================================
|
||||
describe('Accessibility', () => {
|
||||
it('should have cursor pointer on trigger', () => {
|
||||
const { container } = render(<SortDropdown />)
|
||||
|
||||
const trigger = container.querySelector('.cursor-pointer')
|
||||
expect(trigger).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should have cursor pointer on options', () => {
|
||||
render(<SortDropdown />)
|
||||
|
||||
fireEvent.click(screen.getByTestId('portal-trigger'))
|
||||
|
||||
const content = screen.getByTestId('portal-content')
|
||||
const options = content.querySelectorAll('.cursor-pointer')
|
||||
expect(options.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('should have visible focus indicators via hover styles', () => {
|
||||
render(<SortDropdown />)
|
||||
|
||||
fireEvent.click(screen.getByTestId('portal-trigger'))
|
||||
|
||||
const content = screen.getByTestId('portal-content')
|
||||
const option = content.querySelector('.hover\\:bg-components-panel-on-panel-item-bg-hover')
|
||||
expect(option).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// ================================
|
||||
// Translation Tests
|
||||
// ================================
|
||||
describe('Translations', () => {
|
||||
it('should call translation for sortBy label', () => {
|
||||
render(<SortDropdown />)
|
||||
|
||||
expect(mockTranslation).toHaveBeenCalledWith('marketplace.sortBy', { ns: 'plugin' })
|
||||
})
|
||||
|
||||
it('should call translation for all sort options', () => {
|
||||
render(<SortDropdown />)
|
||||
|
||||
expect(mockTranslation).toHaveBeenCalledWith('marketplace.sortOption.mostPopular', { ns: 'plugin' })
|
||||
expect(mockTranslation).toHaveBeenCalledWith('marketplace.sortOption.recentlyUpdated', { ns: 'plugin' })
|
||||
expect(mockTranslation).toHaveBeenCalledWith('marketplace.sortOption.newlyReleased', { ns: 'plugin' })
|
||||
expect(mockTranslation).toHaveBeenCalledWith('marketplace.sortOption.firstReleased', { ns: 'plugin' })
|
||||
})
|
||||
})
|
||||
|
||||
// ================================
|
||||
// Portal Component Integration Tests
|
||||
// ================================
|
||||
describe('Portal Component Integration', () => {
|
||||
it('should pass open state to PortalToFollowElem', () => {
|
||||
render(<SortDropdown />)
|
||||
|
||||
const wrapper = screen.getByTestId('portal-wrapper')
|
||||
expect(wrapper).toHaveAttribute('data-open', 'false')
|
||||
|
||||
fireEvent.click(screen.getByTestId('portal-trigger'))
|
||||
|
||||
expect(wrapper).toHaveAttribute('data-open', 'true')
|
||||
})
|
||||
|
||||
it('should render trigger content inside PortalToFollowElemTrigger', () => {
|
||||
render(<SortDropdown />)
|
||||
|
||||
const trigger = screen.getByTestId('portal-trigger')
|
||||
expect(within(trigger).getByText('Sort by')).toBeInTheDocument()
|
||||
expect(within(trigger).getByText('Most Popular')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render options inside PortalToFollowElemContent', () => {
|
||||
render(<SortDropdown />)
|
||||
|
||||
fireEvent.click(screen.getByTestId('portal-trigger'))
|
||||
|
||||
const content = screen.getByTestId('portal-content')
|
||||
expect(within(content).getByText('Most Popular')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// ================================
|
||||
// Visual Style Tests
|
||||
// ================================
|
||||
describe('Visual Styles', () => {
|
||||
it('should apply correct trigger container styles', () => {
|
||||
const { container } = render(<SortDropdown />)
|
||||
|
||||
const triggerDiv = container.querySelector('.flex.h-8.cursor-pointer.items-center.rounded-lg')
|
||||
expect(triggerDiv).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should apply secondary text color to sort by label', () => {
|
||||
const { container } = render(<SortDropdown />)
|
||||
|
||||
const label = container.querySelector('.text-text-secondary')
|
||||
expect(label).toBeInTheDocument()
|
||||
expect(label?.textContent).toBe('Sort by')
|
||||
})
|
||||
|
||||
it('should apply primary text color to selected option', () => {
|
||||
const { container } = render(<SortDropdown />)
|
||||
|
||||
const selected = container.querySelector('.text-text-primary.system-sm-medium')
|
||||
expect(selected).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should apply tertiary text color to arrow icon', () => {
|
||||
const { container } = render(<SortDropdown />)
|
||||
|
||||
const arrow = container.querySelector('.text-text-tertiary')
|
||||
expect(arrow).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should apply accent text color to check icon when option selected', () => {
|
||||
mockSort = { sortBy: 'install_count', sortOrder: 'DESC' }
|
||||
const { container } = render(<SortDropdown />)
|
||||
|
||||
fireEvent.click(screen.getByTestId('portal-trigger'))
|
||||
|
||||
const checkIcon = container.querySelector('.text-text-accent')
|
||||
expect(checkIcon).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should apply blur backdrop to dropdown container', () => {
|
||||
render(<SortDropdown />)
|
||||
|
||||
fireEvent.click(screen.getByTestId('portal-trigger'))
|
||||
|
||||
const content = screen.getByTestId('portal-content')
|
||||
const container = content.querySelector('.backdrop-blur-sm')
|
||||
expect(container).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// ================================
|
||||
// All Sort Options Click Tests
|
||||
// ================================
|
||||
describe('All Sort Options Click Handlers', () => {
|
||||
const testCases = [
|
||||
{ text: 'Most Popular', sortBy: 'install_count', sortOrder: 'DESC' },
|
||||
{ text: 'Recently Updated', sortBy: 'version_updated_at', sortOrder: 'DESC' },
|
||||
{ text: 'Newly Released', sortBy: 'created_at', sortOrder: 'DESC' },
|
||||
{ text: 'First Released', sortBy: 'created_at', sortOrder: 'ASC' },
|
||||
]
|
||||
|
||||
it.each(testCases)(
|
||||
'should call handleSortChange with { sortBy: "$sortBy", sortOrder: "$sortOrder" } when clicking "$text"',
|
||||
({ text, sortBy, sortOrder }) => {
|
||||
render(<SortDropdown />)
|
||||
|
||||
fireEvent.click(screen.getByTestId('portal-trigger'))
|
||||
|
||||
const content = screen.getByTestId('portal-content')
|
||||
fireEvent.click(within(content).getByText(text))
|
||||
|
||||
expect(mockHandleSortChange).toHaveBeenCalledWith({ sortBy, sortOrder })
|
||||
},
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -610,7 +610,7 @@ describe('PluginMutationModal', () => {
|
||||
render(<PluginMutationModal {...props} />)
|
||||
|
||||
expect(screen.getByText('my-organization')).toBeInTheDocument()
|
||||
expect(screen.getByText('my-plugin-name')).toBeInTheDocument()
|
||||
expect(screen.queryByText('my-plugin-name')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should display plugin category', () => {
|
||||
@@ -750,7 +750,7 @@ describe('PluginMutationModal', () => {
|
||||
|
||||
render(<PluginMutationModal {...props} />)
|
||||
|
||||
expect(screen.getByText('plugin-with-special<chars>!@#$%')).toBeInTheDocument()
|
||||
expect(screen.getByText('org<script>test</script>')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle very long title', () => {
|
||||
|
||||
@@ -104,6 +104,10 @@ vi.mock('../install-plugin-dropdown', () => ({
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock('../../marketplace/search-box/search-box-wrapper', () => ({
|
||||
default: () => <div data-testid="search-box-wrapper">SearchBoxWrapper</div>,
|
||||
}))
|
||||
|
||||
vi.mock('../../install-plugin/install-from-local-package', () => ({
|
||||
default: ({ onClose }: { onClose: () => void }) => (
|
||||
<div data-testid="install-local-modal">
|
||||
@@ -180,11 +184,7 @@ describe('PluginPage Component', () => {
|
||||
vi.mocked(useQueryState).mockReturnValue(['discover', vi.fn()])
|
||||
|
||||
render(<PluginPageWithContext {...createDefaultProps()} />)
|
||||
// The marketplace content should be visible when enable_marketplace is true and on discover tab
|
||||
const container = document.getElementById('marketplace-container')
|
||||
expect(container).toBeInTheDocument()
|
||||
// Check that marketplace-specific links are shown
|
||||
expect(screen.getByText(/requestAPlugin/i)).toBeInTheDocument()
|
||||
expect(screen.getByTestId('marketplace-content')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render TabSlider', () => {
|
||||
@@ -225,9 +225,7 @@ describe('PluginPage Component', () => {
|
||||
vi.mocked(useQueryState).mockReturnValue(['discover', vi.fn()])
|
||||
|
||||
render(<PluginPageWithContext {...createDefaultProps()} />)
|
||||
// Check for marketplace-specific buttons
|
||||
expect(screen.getByText(/requestAPlugin/i)).toBeInTheDocument()
|
||||
expect(screen.getByText(/publishPlugins/i)).toBeInTheDocument()
|
||||
expect(screen.getByText(/requestSubmit/i)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not show marketplace links when on plugins tab', () => {
|
||||
@@ -548,12 +546,11 @@ describe('PluginPage Component', () => {
|
||||
|
||||
const { rerender } = render(<PluginPageWithContext {...createDefaultProps()} />)
|
||||
|
||||
// Should show marketplace links when on discover tab
|
||||
expect(screen.getByText(/requestAPlugin/i)).toBeInTheDocument()
|
||||
expect(screen.getByTestId('marketplace-content')).toBeInTheDocument()
|
||||
|
||||
// Rerender with same props
|
||||
rerender(<PluginPageWithContext {...createDefaultProps()} />)
|
||||
expect(screen.getByText(/requestAPlugin/i)).toBeInTheDocument()
|
||||
expect(screen.getByTestId('marketplace-content')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should recognize plugin type tabs as marketplace', () => {
|
||||
@@ -562,9 +559,8 @@ describe('PluginPage Component', () => {
|
||||
|
||||
render(<PluginPageWithContext {...createDefaultProps()} />)
|
||||
|
||||
// Should show marketplace links when on a plugin type tab
|
||||
expect(screen.getByText(/requestAPlugin/i)).toBeInTheDocument()
|
||||
expect(screen.getByText(/publishPlugins/i)).toBeInTheDocument()
|
||||
expect(screen.getByTestId('marketplace-content')).toBeInTheDocument()
|
||||
expect(screen.getByText(/requestSubmit/i)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render marketplace content when isExploringMarketplace and enable_marketplace are true', () => {
|
||||
@@ -572,11 +568,7 @@ describe('PluginPage Component', () => {
|
||||
|
||||
render(<PluginPageWithContext {...createDefaultProps()} />)
|
||||
|
||||
// The marketplace prop content should be rendered
|
||||
// Since we mock the marketplace as a div, check it's not hidden
|
||||
const container = document.getElementById('marketplace-container')
|
||||
expect(container).toBeInTheDocument()
|
||||
expect(container).toHaveClass('bg-background-body')
|
||||
expect(screen.getByTestId('marketplace-content')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -616,6 +608,7 @@ describe('PluginPage Component', () => {
|
||||
|
||||
render(<PluginPageWithContext plugins={null} marketplace={null} />)
|
||||
expect(document.getElementById('marketplace-container')).toBeInTheDocument()
|
||||
expect(screen.queryByTestId('marketplace-content')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle rapid tab switches', async () => {
|
||||
@@ -638,8 +631,8 @@ describe('PluginPage Component', () => {
|
||||
|
||||
render(<PluginPageWithContext {...createDefaultProps()} />)
|
||||
|
||||
// Component should still render but without marketplace content when disabled
|
||||
expect(document.getElementById('marketplace-container')).toBeInTheDocument()
|
||||
expect(screen.queryByTestId('marketplace-content')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle file with empty name', async () => {
|
||||
@@ -731,7 +724,7 @@ describe('PluginPage Component', () => {
|
||||
render(<PluginPageWithContext {...createDefaultProps()} />)
|
||||
const container = document.getElementById('marketplace-container')
|
||||
|
||||
expect(container).toHaveClass('bg-background-body')
|
||||
expect(container).toHaveClass('bg-components-panel-bg')
|
||||
})
|
||||
|
||||
it('should have scrollbar-gutter stable style', () => {
|
||||
@@ -1027,11 +1020,10 @@ describe('PluginPage Integration', () => {
|
||||
|
||||
const { rerender } = render(<PluginPageWithContext {...createDefaultProps()} />)
|
||||
|
||||
// With enable_marketplace: true (default mock), marketplace links should show
|
||||
expect(screen.getByText(/requestAPlugin/i)).toBeInTheDocument()
|
||||
expect(screen.getByTestId('marketplace-content')).toBeInTheDocument()
|
||||
|
||||
// Rerender to verify consistent behavior
|
||||
rerender(<PluginPageWithContext {...createDefaultProps()} />)
|
||||
expect(screen.getByText(/publishPlugins/i)).toBeInTheDocument()
|
||||
expect(screen.getByTestId('marketplace-content')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user