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:
CodingOnStar
2026-02-11 11:15:11 +08:00
parent c36de51771
commit 0e36aa9c67
182 changed files with 2006 additions and 1844 deletions

View File

@ -1,59 +1,10 @@
import { renderHook } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { PLUGIN_PAGE_TABS_MAP, useCategories, usePluginPageTabs, useTags } from './hooks'
// Create mock translation function
const mockT = vi.fn((key: string, _options?: Record<string, string>) => {
const translations: Record<string, string> = {
'tags.agent': 'Agent',
'tags.rag': 'RAG',
'tags.search': 'Search',
'tags.image': 'Image',
'tags.videos': 'Videos',
'tags.weather': 'Weather',
'tags.finance': 'Finance',
'tags.design': 'Design',
'tags.travel': 'Travel',
'tags.social': 'Social',
'tags.news': 'News',
'tags.medical': 'Medical',
'tags.productivity': 'Productivity',
'tags.education': 'Education',
'tags.business': 'Business',
'tags.entertainment': 'Entertainment',
'tags.utilities': 'Utilities',
'tags.other': 'Other',
'category.models': 'Models',
'category.tools': 'Tools',
'category.datasources': 'Datasources',
'category.agents': 'Agents',
'category.extensions': 'Extensions',
'category.bundles': 'Bundles',
'category.triggers': 'Triggers',
'categorySingle.model': 'Model',
'categorySingle.tool': 'Tool',
'categorySingle.datasource': 'Datasource',
'categorySingle.agent': 'Agent',
'categorySingle.extension': 'Extension',
'categorySingle.bundle': 'Bundle',
'categorySingle.trigger': 'Trigger',
'menus.plugins': 'Plugins',
'menus.exploreMarketplace': 'Explore Marketplace',
}
return translations[key] || key
})
// Mock react-i18next
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: mockT,
}),
}))
import { PLUGIN_PAGE_TABS_MAP, useCategories, usePluginPageTabs, useTags } from '../hooks'
describe('useTags', () => {
beforeEach(() => {
vi.clearAllMocks()
mockT.mockClear()
})
describe('Rendering', () => {
@ -65,13 +16,12 @@ describe('useTags', () => {
expect(result.current.tags.length).toBeGreaterThan(0)
})
it('should call translation function for each tag', () => {
renderHook(() => useTags())
it('should return tags with translated labels', () => {
const { result } = renderHook(() => useTags())
// Verify t() was called for tag translations
expect(mockT).toHaveBeenCalled()
const tagCalls = mockT.mock.calls.filter(call => call[0].startsWith('tags.'))
expect(tagCalls.length).toBeGreaterThan(0)
result.current.tags.forEach((tag) => {
expect(tag.label).toBe(`pluginTags.tags.${tag.name}`)
})
})
it('should return tags with name and label properties', () => {
@ -99,7 +49,7 @@ describe('useTags', () => {
expect(result.current.tagsMap.agent).toBeDefined()
expect(result.current.tagsMap.agent.name).toBe('agent')
expect(result.current.tagsMap.agent.label).toBe('Agent')
expect(result.current.tagsMap.agent.label).toBe('pluginTags.tags.agent')
})
it('should contain all tags from tags array', () => {
@ -116,9 +66,8 @@ describe('useTags', () => {
it('should return label for existing tag', () => {
const { result } = renderHook(() => useTags())
// Test existing tags - this covers the branch where tagsMap[name] exists
expect(result.current.getTagLabel('agent')).toBe('Agent')
expect(result.current.getTagLabel('search')).toBe('Search')
expect(result.current.getTagLabel('agent')).toBe('pluginTags.tags.agent')
expect(result.current.getTagLabel('search')).toBe('pluginTags.tags.search')
})
it('should return name for non-existing tag', () => {
@ -132,11 +81,9 @@ describe('useTags', () => {
it('should cover both branches of getTagLabel conditional', () => {
const { result } = renderHook(() => useTags())
// Branch 1: tag exists in tagsMap - returns label
const existingTagResult = result.current.getTagLabel('rag')
expect(existingTagResult).toBe('RAG')
expect(existingTagResult).toBe('pluginTags.tags.rag')
// Branch 2: tag does not exist in tagsMap - returns name itself
const nonExistingTagResult = result.current.getTagLabel('unknown-tag-xyz')
expect(nonExistingTagResult).toBe('unknown-tag-xyz')
})
@ -150,23 +97,22 @@ describe('useTags', () => {
it('should return correct labels for all predefined tags', () => {
const { result } = renderHook(() => useTags())
// Test all predefined tags
expect(result.current.getTagLabel('rag')).toBe('RAG')
expect(result.current.getTagLabel('image')).toBe('Image')
expect(result.current.getTagLabel('videos')).toBe('Videos')
expect(result.current.getTagLabel('weather')).toBe('Weather')
expect(result.current.getTagLabel('finance')).toBe('Finance')
expect(result.current.getTagLabel('design')).toBe('Design')
expect(result.current.getTagLabel('travel')).toBe('Travel')
expect(result.current.getTagLabel('social')).toBe('Social')
expect(result.current.getTagLabel('news')).toBe('News')
expect(result.current.getTagLabel('medical')).toBe('Medical')
expect(result.current.getTagLabel('productivity')).toBe('Productivity')
expect(result.current.getTagLabel('education')).toBe('Education')
expect(result.current.getTagLabel('business')).toBe('Business')
expect(result.current.getTagLabel('entertainment')).toBe('Entertainment')
expect(result.current.getTagLabel('utilities')).toBe('Utilities')
expect(result.current.getTagLabel('other')).toBe('Other')
expect(result.current.getTagLabel('rag')).toBe('pluginTags.tags.rag')
expect(result.current.getTagLabel('image')).toBe('pluginTags.tags.image')
expect(result.current.getTagLabel('videos')).toBe('pluginTags.tags.videos')
expect(result.current.getTagLabel('weather')).toBe('pluginTags.tags.weather')
expect(result.current.getTagLabel('finance')).toBe('pluginTags.tags.finance')
expect(result.current.getTagLabel('design')).toBe('pluginTags.tags.design')
expect(result.current.getTagLabel('travel')).toBe('pluginTags.tags.travel')
expect(result.current.getTagLabel('social')).toBe('pluginTags.tags.social')
expect(result.current.getTagLabel('news')).toBe('pluginTags.tags.news')
expect(result.current.getTagLabel('medical')).toBe('pluginTags.tags.medical')
expect(result.current.getTagLabel('productivity')).toBe('pluginTags.tags.productivity')
expect(result.current.getTagLabel('education')).toBe('pluginTags.tags.education')
expect(result.current.getTagLabel('business')).toBe('pluginTags.tags.business')
expect(result.current.getTagLabel('entertainment')).toBe('pluginTags.tags.entertainment')
expect(result.current.getTagLabel('utilities')).toBe('pluginTags.tags.utilities')
expect(result.current.getTagLabel('other')).toBe('pluginTags.tags.other')
})
it('should handle empty string tag name', () => {
@ -255,27 +201,27 @@ describe('useCategories', () => {
it('should use plural labels when isSingle is false', () => {
const { result } = renderHook(() => useCategories(false))
expect(result.current.categoriesMap.tool.label).toBe('Tools')
expect(result.current.categoriesMap.tool.label).toBe('plugin.category.tools')
})
it('should use plural labels when isSingle is undefined', () => {
const { result } = renderHook(() => useCategories())
expect(result.current.categoriesMap.tool.label).toBe('Tools')
expect(result.current.categoriesMap.tool.label).toBe('plugin.category.tools')
})
it('should use singular labels when isSingle is true', () => {
const { result } = renderHook(() => useCategories(true))
expect(result.current.categoriesMap.tool.label).toBe('Tool')
expect(result.current.categoriesMap.tool.label).toBe('plugin.categorySingle.tool')
})
it('should handle agent category specially', () => {
const { result: resultPlural } = renderHook(() => useCategories(false))
const { result: resultSingle } = renderHook(() => useCategories(true))
expect(resultPlural.current.categoriesMap['agent-strategy'].label).toBe('Agents')
expect(resultSingle.current.categoriesMap['agent-strategy'].label).toBe('Agent')
expect(resultPlural.current.categoriesMap['agent-strategy'].label).toBe('plugin.category.agents')
expect(resultSingle.current.categoriesMap['agent-strategy'].label).toBe('plugin.categorySingle.agent')
})
})
@ -298,7 +244,6 @@ describe('useCategories', () => {
describe('usePluginPageTabs', () => {
beforeEach(() => {
vi.clearAllMocks()
mockT.mockClear()
})
describe('Rendering', () => {
@ -326,12 +271,11 @@ describe('usePluginPageTabs', () => {
})
})
it('should call translation function for tab texts', () => {
renderHook(() => usePluginPageTabs())
it('should return tabs with translated texts', () => {
const { result } = renderHook(() => usePluginPageTabs())
// Verify t() was called for menu translations
expect(mockT).toHaveBeenCalledWith('menus.plugins', { ns: 'common' })
expect(mockT).toHaveBeenCalledWith('menus.exploreMarketplace', { ns: 'common' })
expect(result.current[0].text).toBe('common.menus.plugins')
expect(result.current[1].text).toBe('common.menus.exploreMarketplace')
})
})
@ -342,7 +286,7 @@ describe('usePluginPageTabs', () => {
const pluginsTab = result.current.find(tab => tab.value === PLUGIN_PAGE_TABS_MAP.plugins)
expect(pluginsTab).toBeDefined()
expect(pluginsTab?.value).toBe('plugins')
expect(pluginsTab?.text).toBe('Plugins')
expect(pluginsTab?.text).toBe('common.menus.plugins')
})
it('should have marketplace tab with correct value', () => {
@ -351,7 +295,7 @@ describe('usePluginPageTabs', () => {
const marketplaceTab = result.current.find(tab => tab.value === PLUGIN_PAGE_TABS_MAP.marketplace)
expect(marketplaceTab).toBeDefined()
expect(marketplaceTab?.value).toBe('discover')
expect(marketplaceTab?.text).toBe('Explore Marketplace')
expect(marketplaceTab?.text).toBe('common.menus.exploreMarketplace')
})
})
@ -360,14 +304,14 @@ describe('usePluginPageTabs', () => {
const { result } = renderHook(() => usePluginPageTabs())
expect(result.current[0].value).toBe('plugins')
expect(result.current[0].text).toBe('Plugins')
expect(result.current[0].text).toBe('common.menus.plugins')
})
it('should return marketplace tab as second tab', () => {
const { result } = renderHook(() => usePluginPageTabs())
expect(result.current[1].value).toBe('discover')
expect(result.current[1].text).toBe('Explore Marketplace')
expect(result.current[1].text).toBe('common.menus.exploreMarketplace')
})
})

View File

@ -1,7 +1,7 @@
import type { TagKey } from './constants'
import type { TagKey } from '../constants'
import { describe, expect, it } from 'vitest'
import { PluginCategoryEnum } from './types'
import { getValidCategoryKeys, getValidTagKeys } from './utils'
import { PluginCategoryEnum } from '../types'
import { getValidCategoryKeys, getValidTagKeys } from '../utils'
describe('plugins/utils', () => {
describe('getValidTagKeys', () => {

View File

@ -1,32 +1,6 @@
import { cleanup, render, screen } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import DeprecationNotice from './deprecation-notice'
vi.mock('#i18n', () => ({
useTranslation: () => ({
t: (key: string, opts?: Record<string, unknown>) => {
const map: Record<string, string> = {
'detailPanel.deprecation.noReason': 'This plugin has been deprecated.',
'detailPanel.deprecation.reason.businessAdjustments': 'business adjustments',
'detailPanel.deprecation.reason.ownershipTransferred': 'ownership transferred',
'detailPanel.deprecation.reason.noMaintainer': 'no maintainer',
}
if (key === 'detailPanel.deprecation.onlyReason')
return `Deprecated due to ${opts?.deprecatedReason}`
return map[key] || key
},
}),
}))
vi.mock('react-i18next', () => ({
Trans: ({ values }: { values: Record<string, string> }) => (
<span data-testid="trans">{`Deprecated: ${values?.deprecatedReason}${values?.alternativePluginId}`}</span>
),
}))
vi.mock('@remixicon/react', () => ({
RiAlertFill: () => <span data-testid="alert-icon" />,
}))
import DeprecationNotice from '../deprecation-notice'
vi.mock('next/link', () => ({
default: ({ children, href }: { children: React.ReactNode, href: string }) => (
@ -64,8 +38,7 @@ describe('DeprecationNotice', () => {
alternativePluginURL=""
/>,
)
expect(screen.getByTestId('alert-icon')).toBeInTheDocument()
expect(screen.getByText('This plugin has been deprecated.')).toBeInTheDocument()
expect(screen.getByText('plugin.detailPanel.deprecation.noReason')).toBeInTheDocument()
})
it('renders with valid reason and alternative plugin', () => {
@ -77,7 +50,7 @@ describe('DeprecationNotice', () => {
alternativePluginURL="/plugins/better-plugin"
/>,
)
expect(screen.getByTestId('trans')).toBeInTheDocument()
expect(screen.getByText('detailPanel.deprecation.fullMessage')).toBeInTheDocument()
})
it('renders only reason without alternative plugin', () => {
@ -89,7 +62,7 @@ describe('DeprecationNotice', () => {
alternativePluginURL=""
/>,
)
expect(screen.getByText('Deprecated due to no maintainer')).toBeInTheDocument()
expect(screen.getByText(/plugin\.detailPanel\.deprecation\.onlyReason/)).toBeInTheDocument()
})
it('renders no-reason message for invalid reason', () => {
@ -101,7 +74,7 @@ describe('DeprecationNotice', () => {
alternativePluginURL=""
/>,
)
expect(screen.getByText('This plugin has been deprecated.')).toBeInTheDocument()
expect(screen.getByText('plugin.detailPanel.deprecation.noReason')).toBeInTheDocument()
})
it('applies custom className', () => {

View File

@ -1,28 +1,12 @@
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import KeyValueItem from './key-value-item'
import KeyValueItem from '../key-value-item'
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => {
const map: Record<string, string> = {
'operation.copy': 'Copy',
'operation.copied': 'Copied',
}
return map[key] || key
},
}),
}))
vi.mock('@remixicon/react', () => ({
RiClipboardLine: () => <span data-testid="clipboard-icon" />,
}))
vi.mock('../../base/icons/src/vender/line/files', () => ({
vi.mock('../../../base/icons/src/vender/line/files', () => ({
CopyCheck: () => <span data-testid="copy-check-icon" />,
}))
vi.mock('../../base/tooltip', () => ({
vi.mock('../../../base/tooltip', () => ({
default: ({ children, popupContent }: { children: React.ReactNode, popupContent: string }) => (
<div data-testid="tooltip" data-content={popupContent}>{children}</div>
),
@ -34,7 +18,6 @@ vi.mock('@/app/components/base/action-button', () => ({
),
}))
// Mock copy-to-clipboard
const mockCopy = vi.fn()
vi.mock('copy-to-clipboard', () => ({
default: (...args: unknown[]) => mockCopy(...args),
@ -69,13 +52,8 @@ describe('KeyValueItem', () => {
expect(mockCopy).toHaveBeenCalledWith('sk-secret')
})
it('shows clipboard icon initially', () => {
render(<KeyValueItem label="ID" value="123" />)
expect(screen.getByTestId('clipboard-icon')).toBeInTheDocument()
})
it('renders copy tooltip', () => {
render(<KeyValueItem label="ID" value="123" />)
expect(screen.getByTestId('tooltip')).toHaveAttribute('data-content', 'Copy')
expect(screen.getByTestId('tooltip')).toHaveAttribute('data-content', 'common.operation.copy')
})
})

View File

@ -1,7 +1,7 @@
import { render, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { Theme } from '@/types/app'
import IconWithTooltip from './icon-with-tooltip'
import IconWithTooltip from '../icon-with-tooltip'
// Mock Tooltip component
vi.mock('@/app/components/base/tooltip', () => ({

View File

@ -2,7 +2,7 @@ import type { ComponentProps } from 'react'
import { render, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { Theme } from '@/types/app'
import Partner from './partner'
import Partner from '../partner'
// Mock useTheme hook
const mockUseTheme = vi.fn()
@ -11,9 +11,9 @@ vi.mock('@/hooks/use-theme', () => ({
}))
// Mock IconWithTooltip to directly test Partner's behavior
type IconWithTooltipProps = ComponentProps<typeof import('./icon-with-tooltip').default>
type IconWithTooltipProps = ComponentProps<typeof import('../icon-with-tooltip').default>
const mockIconWithTooltip = vi.fn()
vi.mock('./icon-with-tooltip', () => ({
vi.mock('../icon-with-tooltip', () => ({
default: (props: IconWithTooltipProps) => {
mockIconWithTooltip(props)
const { theme, BadgeIconLight, BadgeIconDark, className, popupContent } = props

View File

@ -14,7 +14,7 @@ vi.mock('@/hooks/use-theme', () => ({
default: () => ({ theme: 'light' }),
}))
vi.mock('./icon-with-tooltip', () => ({
vi.mock('../icon-with-tooltip', () => ({
default: ({ popupContent, BadgeIconLight, BadgeIconDark, theme }: {
popupContent: string
BadgeIconLight: React.FC
@ -29,11 +29,11 @@ vi.mock('./icon-with-tooltip', () => ({
}))
describe('Verified', () => {
let Verified: (typeof import('./verified'))['default']
let Verified: (typeof import('../verified'))['default']
beforeEach(async () => {
vi.clearAllMocks()
const mod = await import('./verified')
const mod = await import('../verified')
Verified = mod.default
})

View File

@ -1,8 +1,8 @@
import { render, screen } from '@testing-library/react'
import { describe, expect, it, vi } from 'vitest'
import CardMoreInfo from './card-more-info'
import CardMoreInfo from '../card-more-info'
vi.mock('./base/download-count', () => ({
vi.mock('../base/download-count', () => ({
default: ({ downloadCount }: { downloadCount: number }) => (
<span data-testid="download-count">{downloadCount}</span>
),

View File

@ -1,9 +1,9 @@
import type { Plugin } from '../types'
import type { Plugin } from '../../types'
import { render, screen } from '@testing-library/react'
import * as React from 'react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { PluginCategoryEnum } from '../types'
import Card from './index'
import { PluginCategoryEnum } from '../../types'
import Card from '../index'
let mockTheme = 'light'
vi.mock('@/hooks/use-theme', () => ({
@ -30,7 +30,7 @@ const mockCategoriesMap: Record<string, { label: string }> = {
'bundle': { label: 'Bundle' },
}
vi.mock('../hooks', () => ({
vi.mock('../../hooks', () => ({
useCategories: () => ({
categoriesMap: mockCategoriesMap,
}),
@ -73,19 +73,19 @@ vi.mock('@/app/components/base/icons/src/vender/other', () => ({
),
}))
vi.mock('../../base/icons/src/vender/plugin', () => ({
vi.mock('../../../base/icons/src/vender/plugin', () => ({
LeftCorner: ({ className }: { className?: string }) => (
<div data-testid="left-corner" className={className}>LeftCorner</div>
),
}))
vi.mock('../base/badges/partner', () => ({
vi.mock('../../base/badges/partner', () => ({
default: ({ className, text }: { className?: string, text?: string }) => (
<div data-testid="partner-badge" className={className} title={text}>Partner</div>
),
}))
vi.mock('../base/badges/verified', () => ({
vi.mock('../../base/badges/verified', () => ({
default: ({ className, text }: { className?: string, text?: string }) => (
<div data-testid="verified-badge" className={className} title={text}>Verified</div>
),
@ -104,26 +104,6 @@ vi.mock('@/app/components/base/skeleton', () => ({
),
}))
// Mock Remix icons
vi.mock('@remixicon/react', () => ({
RiCheckLine: ({ className }: { className?: string }) => (
<span data-testid="ri-check-line" className={className}></span>
),
RiCloseLine: ({ className }: { className?: string }) => (
<span data-testid="ri-close-line" className={className}></span>
),
RiInstallLine: ({ className }: { className?: string }) => (
<span data-testid="ri-install-line" className={className}></span>
),
RiAlertFill: ({ className }: { className?: string }) => (
<span data-testid="ri-alert-fill" className={className}></span>
),
}))
// ================================
// Test Data Factories
// ================================
const createMockPlugin = (overrides?: Partial<Plugin>): Plugin => ({
type: 'plugin',
org: 'test-org',
@ -149,9 +129,6 @@ const createMockPlugin = (overrides?: Partial<Plugin>): Plugin => ({
...overrides,
})
// ================================
// Card Component Tests (index.tsx)
// ================================
describe('Card', () => {
beforeEach(() => {
vi.clearAllMocks()
@ -293,18 +270,16 @@ describe('Card', () => {
it('should pass installed prop to Icon component', () => {
const plugin = createMockPlugin()
render(<Card payload={plugin} installed={true} />)
const { container } = render(<Card payload={plugin} installed={true} />)
// Check for the check icon that appears when installed
expect(screen.getByTestId('ri-check-line')).toBeInTheDocument()
expect(container.querySelector('.bg-state-success-solid')).toBeInTheDocument()
})
it('should pass installFailed prop to Icon component', () => {
const plugin = createMockPlugin()
render(<Card payload={plugin} installFailed={true} />)
const { container } = render(<Card payload={plugin} installFailed={true} />)
// Check for the close icon that appears when install failed
expect(screen.getByTestId('ri-close-line')).toBeInTheDocument()
expect(container.querySelector('.bg-state-destructive-solid')).toBeInTheDocument()
})
it('should render footer when provided', () => {
@ -462,17 +437,17 @@ describe('Card', () => {
it('should render warning when limitedInstall is true', () => {
const plugin = createMockPlugin()
render(<Card payload={plugin} limitedInstall={true} />)
const { container } = render(<Card payload={plugin} limitedInstall={true} />)
expect(screen.getByTestId('ri-alert-fill')).toBeInTheDocument()
expect(container.querySelector('.text-text-warning-secondary')).toBeInTheDocument()
})
it('should not render warning by default', () => {
const plugin = createMockPlugin()
render(<Card payload={plugin} />)
const { container } = render(<Card payload={plugin} />)
expect(screen.queryByTestId('ri-alert-fill')).not.toBeInTheDocument()
expect(container.querySelector('.text-text-warning-secondary')).not.toBeInTheDocument()
})
it('should apply limited padding when limitedInstall is true', () => {
@ -612,14 +587,3 @@ describe('Card', () => {
})
})
})
// Child component tests (CardMoreInfo, Icon, CornerMark, Description, DownloadCount,
// OrgInfo, Placeholder, Title) have been moved to their respective dedicated spec files:
// - card-more-info.spec.tsx
// - base/card-icon.spec.tsx
// - base/corner-mark.spec.tsx
// - base/description.spec.tsx
// - base/download-count.spec.tsx
// - base/org-info.spec.tsx
// - base/placeholder.spec.tsx
// - base/title.spec.tsx

View File

@ -1,11 +1,6 @@
import { render, screen } from '@testing-library/react'
import { describe, expect, it, vi } from 'vitest'
import Icon from './card-icon'
vi.mock('@remixicon/react', () => ({
RiCheckLine: () => <span data-testid="check-icon" />,
RiCloseLine: () => <span data-testid="close-icon" />,
}))
import Icon from '../card-icon'
vi.mock('@/app/components/base/app-icon', () => ({
default: ({ icon, background }: { icon: string, background: string }) => (
@ -36,19 +31,19 @@ describe('Icon', () => {
})
it('shows check icon when installed', () => {
render(<Icon src="icon.png" installed />)
expect(screen.getByTestId('check-icon')).toBeInTheDocument()
const { container } = render(<Icon src="icon.png" installed />)
expect(container.querySelector('.bg-state-success-solid')).toBeInTheDocument()
})
it('shows close icon when installFailed', () => {
render(<Icon src="icon.png" installFailed />)
expect(screen.getByTestId('close-icon')).toBeInTheDocument()
const { container } = render(<Icon src="icon.png" installFailed />)
expect(container.querySelector('.bg-state-destructive-solid')).toBeInTheDocument()
})
it('does not show status icons by default', () => {
render(<Icon src="icon.png" />)
expect(screen.queryByTestId('check-icon')).not.toBeInTheDocument()
expect(screen.queryByTestId('close-icon')).not.toBeInTheDocument()
const { container } = render(<Icon src="icon.png" />)
expect(container.querySelector('.bg-state-success-solid')).not.toBeInTheDocument()
expect(container.querySelector('.bg-state-destructive-solid')).not.toBeInTheDocument()
})
it('applies custom className', () => {

View File

@ -1,8 +1,8 @@
import { render, screen } from '@testing-library/react'
import { describe, expect, it, vi } from 'vitest'
import CornerMark from './corner-mark'
import CornerMark from '../corner-mark'
vi.mock('../../../base/icons/src/vender/plugin', () => ({
vi.mock('../../../../base/icons/src/vender/plugin', () => ({
LeftCorner: ({ className }: { className: string }) => <svg data-testid="left-corner" className={className} />,
}))

View File

@ -1,6 +1,6 @@
import { render, screen } from '@testing-library/react'
import { describe, expect, it } from 'vitest'
import Description from './description'
import Description from '../description'
describe('Description', () => {
it('renders description text', () => {

View File

@ -1,10 +1,6 @@
import { render, screen } from '@testing-library/react'
import { describe, expect, it, vi } from 'vitest'
import DownloadCount from './download-count'
vi.mock('@remixicon/react', () => ({
RiInstallLine: () => <span data-testid="install-icon" />,
}))
import DownloadCount from '../download-count'
vi.mock('@/utils/format', () => ({
formatNumber: (n: number) => {
@ -25,11 +21,6 @@ describe('DownloadCount', () => {
expect(screen.getByText('42')).toBeInTheDocument()
})
it('renders install icon', () => {
render(<DownloadCount downloadCount={100} />)
expect(screen.getByTestId('install-icon')).toBeInTheDocument()
})
it('renders zero download count', () => {
render(<DownloadCount downloadCount={0} />)
expect(screen.getByText('0')).toBeInTheDocument()

View File

@ -1,6 +1,6 @@
import { render, screen } from '@testing-library/react'
import { describe, expect, it } from 'vitest'
import OrgInfo from './org-info'
import OrgInfo from '../org-info'
describe('OrgInfo', () => {
it('renders package name', () => {

View File

@ -2,11 +2,11 @@ import { render, screen } from '@testing-library/react'
import * as React from 'react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
vi.mock('./title', () => ({
vi.mock('../title', () => ({
default: ({ title }: { title: string }) => <span data-testid="title">{title}</span>,
}))
vi.mock('../../../base/icons/src/vender/other', () => ({
vi.mock('../../../../base/icons/src/vender/other', () => ({
Group: ({ className }: { className: string }) => <span data-testid="group-icon" className={className} />,
}))
@ -15,11 +15,11 @@ vi.mock('@/utils/classnames', () => ({
}))
describe('Placeholder', () => {
let Placeholder: (typeof import('./placeholder'))['default']
let Placeholder: (typeof import('../placeholder'))['default']
beforeEach(async () => {
vi.clearAllMocks()
const mod = await import('./placeholder')
const mod = await import('../placeholder')
Placeholder = mod.default
})
@ -49,11 +49,11 @@ describe('Placeholder', () => {
})
describe('LoadingPlaceholder', () => {
let LoadingPlaceholder: (typeof import('./placeholder'))['LoadingPlaceholder']
let LoadingPlaceholder: (typeof import('../placeholder'))['LoadingPlaceholder']
beforeEach(async () => {
vi.clearAllMocks()
const mod = await import('./placeholder')
const mod = await import('../placeholder')
LoadingPlaceholder = mod.LoadingPlaceholder
})

View File

@ -1,6 +1,6 @@
import { render, screen } from '@testing-library/react'
import { describe, expect, it } from 'vitest'
import Title from './title'
import Title from '../title'
describe('Title', () => {
it('renders the title text', () => {

View File

@ -1,6 +1,6 @@
import { renderHook } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { useGitHubReleases, useGitHubUpload } from './hooks'
import { useGitHubReleases, useGitHubUpload } from '../hooks'
const mockNotify = vi.fn()
vi.mock('@/app/components/base/toast', () => ({

View File

@ -1,12 +1,12 @@
import type { PluginDeclaration, PluginManifestInMarket } from '../types'
import type { PluginDeclaration, PluginManifestInMarket } from '../../types'
import { describe, expect, it, vi } from 'vitest'
import { PluginCategoryEnum } from '../types'
import { PluginCategoryEnum } from '../../types'
import {
convertRepoToUrl,
parseGitHubUrl,
pluginManifestInMarketToPluginProps,
pluginManifestToCardPluginProps,
} from './utils'
} from '../utils'
// Mock es-toolkit/compat
vi.mock('es-toolkit/compat', () => ({

View File

@ -1,6 +1,6 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { TaskStatus } from '../../types'
import checkTaskStatus from './check-task-status'
import { TaskStatus } from '../../../types'
import checkTaskStatus from '../check-task-status'
const mockCheckTaskStatus = vi.fn()
vi.mock('@/service/plugins', () => ({

View File

@ -2,42 +2,36 @@ import { fireEvent, render, screen } from '@testing-library/react'
import * as React from 'react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}))
vi.mock('../../card', () => ({
vi.mock('../../../card', () => ({
default: ({ installed, installFailed, titleLeft }: { installed: boolean, installFailed: boolean, titleLeft?: React.ReactNode }) => (
<div data-testid="card" data-installed={installed} data-failed={installFailed}>{titleLeft}</div>
),
}))
vi.mock('../utils', () => ({
vi.mock('../../utils', () => ({
pluginManifestInMarketToPluginProps: (p: unknown) => p,
pluginManifestToCardPluginProps: (p: unknown) => p,
}))
describe('Installed', () => {
let Installed: (typeof import('./installed'))['default']
let Installed: (typeof import('../installed'))['default']
beforeEach(async () => {
vi.clearAllMocks()
const mod = await import('./installed')
const mod = await import('../installed')
Installed = mod.default
})
it('should render success message when not failed', () => {
render(<Installed isFailed={false} onCancel={vi.fn()} />)
expect(screen.getByText('installModal.installedSuccessfullyDesc')).toBeInTheDocument()
expect(screen.getByText('plugin.installModal.installedSuccessfullyDesc')).toBeInTheDocument()
})
it('should render failure message when failed', () => {
render(<Installed isFailed={true} onCancel={vi.fn()} />)
expect(screen.getByText('installModal.installFailedDesc')).toBeInTheDocument()
expect(screen.getByText('plugin.installModal.installFailedDesc')).toBeInTheDocument()
})
it('should render custom error message when provided', () => {
@ -68,7 +62,7 @@ describe('Installed', () => {
const mockOnCancel = vi.fn()
render(<Installed isFailed={false} onCancel={mockOnCancel} />)
fireEvent.click(screen.getByText('operation.close'))
fireEvent.click(screen.getByText('common.operation.close'))
expect(mockOnCancel).toHaveBeenCalled()
})

View File

@ -2,21 +2,11 @@ import { render, screen } from '@testing-library/react'
import * as React from 'react'
import { describe, expect, it, vi } from 'vitest'
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}))
vi.mock('@remixicon/react', () => ({
RiCloseLine: () => <span data-testid="icon-close" />,
}))
vi.mock('@/app/components/plugins/card/base/placeholder', () => ({
LoadingPlaceholder: () => <div data-testid="loading-placeholder" />,
}))
vi.mock('../../../base/icons/src/vender/other', () => ({
vi.mock('../../../../base/icons/src/vender/other', () => ({
Group: ({ className }: { className: string }) => <span data-testid="group-icon" className={className} />,
}))
@ -25,15 +15,15 @@ describe('LoadingError', () => {
beforeEach(async () => {
vi.clearAllMocks()
const mod = await import('./loading-error')
const mod = await import('../loading-error')
LoadingError = mod.default
})
it('should render error message', () => {
render(<LoadingError />)
expect(screen.getByText('installModal.pluginLoadError')).toBeInTheDocument()
expect(screen.getByText('installModal.pluginLoadErrorDesc')).toBeInTheDocument()
expect(screen.getByText('plugin.installModal.pluginLoadError')).toBeInTheDocument()
expect(screen.getByText('plugin.installModal.pluginLoadErrorDesc')).toBeInTheDocument()
})
it('should render disabled checkbox', () => {
@ -45,7 +35,6 @@ describe('LoadingError', () => {
it('should render error icon with close indicator', () => {
render(<LoadingError />)
expect(screen.getByTestId('icon-close')).toBeInTheDocument()
expect(screen.getByTestId('group-icon')).toBeInTheDocument()
})

View File

@ -2,7 +2,7 @@ import { render, screen } from '@testing-library/react'
import * as React from 'react'
import { describe, expect, it, vi } from 'vitest'
vi.mock('../../card/base/placeholder', () => ({
vi.mock('../../../card/base/placeholder', () => ({
default: () => <div data-testid="placeholder" />,
}))
@ -11,7 +11,7 @@ describe('Loading', () => {
beforeEach(async () => {
vi.clearAllMocks()
const mod = await import('./loading')
const mod = await import('../loading')
Loading = mod.default
})

View File

@ -3,11 +3,11 @@ import * as React from 'react'
import { describe, expect, it, vi } from 'vitest'
describe('Version', () => {
let Version: (typeof import('./version'))['default']
let Version: (typeof import('../version'))['default']
beforeEach(async () => {
vi.clearAllMocks()
const mod = await import('./version')
const mod = await import('../version')
Version = mod.default
})

View File

@ -1,6 +1,6 @@
import { renderHook } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import useCheckInstalled from './use-check-installed'
import useCheckInstalled from '../use-check-installed'
const mockPlugins = [
{

View File

@ -1,12 +1,12 @@
import { act, renderHook } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import useHideLogic from './use-hide-logic'
import useHideLogic from '../use-hide-logic'
const mockFoldAnimInto = vi.fn()
const mockClearCountDown = vi.fn()
const mockCountDownFoldIntoAnim = vi.fn()
vi.mock('./use-fold-anim-into', () => ({
vi.mock('../use-fold-anim-into', () => ({
default: () => ({
modalClassName: 'test-modal-class',
foldIntoAnim: mockFoldAnimInto,

View File

@ -1,7 +1,7 @@
import { renderHook } from '@testing-library/react'
import { describe, expect, it, vi } from 'vitest'
import { InstallationScope } from '@/types/feature'
import { pluginInstallLimit } from './use-install-plugin-limit'
import { pluginInstallLimit } from '../use-install-plugin-limit'
const mockSystemFeatures = {
plugin_installation_permission: {
@ -139,7 +139,7 @@ describe('pluginInstallLimit', () => {
describe('usePluginInstallLimit', () => {
it('should return canInstall from pluginInstallLimit using global store', async () => {
const { default: usePluginInstallLimit } = await import('./use-install-plugin-limit')
const { default: usePluginInstallLimit } = await import('../use-install-plugin-limit')
const plugin = { from: 'marketplace' as const, verification: { authorized_category: 'langgenius' } }
const { result } = renderHook(() => usePluginInstallLimit(plugin as never))

View File

@ -1,6 +1,6 @@
import { renderHook } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { PluginCategoryEnum } from '../../types'
import { PluginCategoryEnum } from '../../../types'
// Mock invalidation / refresh functions
const mockInvalidateInstalledPluginList = vi.fn()
@ -61,7 +61,7 @@ vi.mock('@/service/use-triggers', () => ({
useInvalidateAllTriggerPlugins: () => mockInvalidateAllTriggerPlugins,
}))
const { default: useRefreshPluginList } = await import('./use-refresh-plugin-list')
const { default: useRefreshPluginList } = await import('../use-refresh-plugin-list')
describe('useRefreshPluginList', () => {
beforeEach(() => {

View File

@ -1,14 +1,14 @@
import type { Dependency, GitHubItemAndMarketPlaceDependency, InstallStatus, PackageDependency, Plugin, PluginDeclaration, VersionProps } from '../../types'
import type { Dependency, GitHubItemAndMarketPlaceDependency, InstallStatus, PackageDependency, Plugin, PluginDeclaration, VersionProps } from '../../../types'
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { InstallStep, PluginCategoryEnum } from '../../types'
import InstallBundle, { InstallType } from './index'
import GithubItem from './item/github-item'
import LoadedItem from './item/loaded-item'
import MarketplaceItem from './item/marketplace-item'
import PackageItem from './item/package-item'
import ReadyToInstall from './ready-to-install'
import Installed from './steps/installed'
import { InstallStep, PluginCategoryEnum } from '../../../types'
import InstallBundle, { InstallType } from '../index'
import GithubItem from '../item/github-item'
import LoadedItem from '../item/loaded-item'
import MarketplaceItem from '../item/marketplace-item'
import PackageItem from '../item/package-item'
import ReadyToInstall from '../ready-to-install'
import Installed from '../steps/installed'
// Factory functions for test data
const createMockPlugin = (overrides: Partial<Plugin> = {}): Plugin => ({
@ -143,19 +143,19 @@ let mockHideLogicState = {
setIsInstalling: vi.fn(),
handleStartToInstall: vi.fn(),
}
vi.mock('../hooks/use-hide-logic', () => ({
vi.mock('../../hooks/use-hide-logic', () => ({
default: () => mockHideLogicState,
}))
// Mock useGetIcon hook
vi.mock('../base/use-get-icon', () => ({
vi.mock('../../base/use-get-icon', () => ({
default: () => ({
getIconUrl: (icon: string) => icon || 'default-icon.png',
}),
}))
// Mock usePluginInstallLimit hook
vi.mock('../hooks/use-install-plugin-limit', () => ({
vi.mock('../../hooks/use-install-plugin-limit', () => ({
default: () => ({ canInstall: true }),
pluginInstallLimit: () => ({ canInstall: true }),
}))
@ -190,22 +190,22 @@ vi.mock('@/app/components/plugins/plugin-page/use-reference-setting', () => ({
}))
// Mock checkTaskStatus
vi.mock('../base/check-task-status', () => ({
vi.mock('../../base/check-task-status', () => ({
default: () => ({ check: vi.fn(), stop: vi.fn() }),
}))
// Mock useRefreshPluginList
vi.mock('../hooks/use-refresh-plugin-list', () => ({
vi.mock('../../hooks/use-refresh-plugin-list', () => ({
default: () => ({ refreshPluginList: vi.fn() }),
}))
// Mock useCheckInstalled
vi.mock('../hooks/use-check-installed', () => ({
vi.mock('../../hooks/use-check-installed', () => ({
default: () => ({ installedInfo: {} }),
}))
// Mock ReadyToInstall child component to test InstallBundle in isolation
vi.mock('./ready-to-install', () => ({
vi.mock('../ready-to-install', () => ({
default: ({
step,
onStepChange,

View File

@ -1,9 +1,9 @@
import type { Dependency, GitHubItemAndMarketPlaceDependency, PackageDependency, Plugin, VersionInfo } from '../../../types'
import type { Dependency, GitHubItemAndMarketPlaceDependency, PackageDependency, Plugin, VersionInfo } from '../../../../types'
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
import * as React from 'react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { PluginCategoryEnum } from '../../../types'
import InstallMulti from './install-multi'
import { PluginCategoryEnum } from '../../../../types'
import InstallMulti from '../install-multi'
// ==================== Mock Setup ====================
@ -62,12 +62,12 @@ vi.mock('@/context/global-public-context', () => ({
}))
// Mock pluginInstallLimit
vi.mock('../../hooks/use-install-plugin-limit', () => ({
vi.mock('../../../hooks/use-install-plugin-limit', () => ({
pluginInstallLimit: () => ({ canInstall: true }),
}))
// Mock child components
vi.mock('../item/github-item', () => ({
vi.mock('../../item/github-item', () => ({
default: vi.fn().mockImplementation(({
checked,
onCheckedChange,
@ -120,7 +120,7 @@ vi.mock('../item/github-item', () => ({
}),
}))
vi.mock('../item/marketplace-item', () => ({
vi.mock('../../item/marketplace-item', () => ({
default: vi.fn().mockImplementation(({
checked,
onCheckedChange,
@ -142,7 +142,7 @@ vi.mock('../item/marketplace-item', () => ({
)),
}))
vi.mock('../item/package-item', () => ({
vi.mock('../../item/package-item', () => ({
default: vi.fn().mockImplementation(({
checked,
onCheckedChange,
@ -163,7 +163,7 @@ vi.mock('../item/package-item', () => ({
)),
}))
vi.mock('../../base/loading-error', () => ({
vi.mock('../../../base/loading-error', () => ({
default: () => <div data-testid="loading-error">Loading Error</div>,
}))

View File

@ -1,8 +1,8 @@
import type { Dependency, InstallStatusResponse, PackageDependency } from '../../../types'
import type { Dependency, InstallStatusResponse, PackageDependency } from '../../../../types'
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { PluginCategoryEnum, TaskStatus } from '../../../types'
import Install from './install'
import { PluginCategoryEnum, TaskStatus } from '../../../../types'
import Install from '../install'
// ==================== Mock Setup ====================
@ -42,7 +42,7 @@ vi.mock('@/service/use-plugins', () => ({
// Mock checkTaskStatus
const mockCheck = vi.fn()
const mockStop = vi.fn()
vi.mock('../../base/check-task-status', () => ({
vi.mock('../../../base/check-task-status', () => ({
default: () => ({
check: mockCheck,
stop: mockStop,
@ -51,7 +51,7 @@ vi.mock('../../base/check-task-status', () => ({
// Mock useRefreshPluginList
const mockRefreshPluginList = vi.fn()
vi.mock('../../hooks/use-refresh-plugin-list', () => ({
vi.mock('../../../hooks/use-refresh-plugin-list', () => ({
default: () => ({
refreshPluginList: mockRefreshPluginList,
}),
@ -69,7 +69,7 @@ vi.mock('@/app/components/plugins/plugin-page/use-reference-setting', () => ({
}))
// Mock InstallMulti component with forwardRef support
vi.mock('./install-multi', async () => {
vi.mock('../install-multi', async () => {
const React = await import('react')
const createPlugin = (index: number) => ({
@ -838,7 +838,7 @@ describe('Install Component', () => {
// ==================== Memoization Test ====================
describe('Memoization', () => {
it('should be memoized', async () => {
const InstallModule = await import('./install')
const InstallModule = await import('../install')
// memo returns an object with $$typeof
expect(typeof InstallModule.default).toBe('object')
})

View File

@ -1,9 +1,9 @@
import type { GitHubRepoReleaseResponse, PluginDeclaration, PluginManifestInMarket, UpdateFromGitHubPayload } from '../../types'
import type { GitHubRepoReleaseResponse, PluginDeclaration, PluginManifestInMarket, UpdateFromGitHubPayload } from '../../../types'
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { PluginCategoryEnum } from '../../types'
import { convertRepoToUrl, parseGitHubUrl, pluginManifestInMarketToPluginProps, pluginManifestToCardPluginProps } from '../utils'
import InstallFromGitHub from './index'
import { PluginCategoryEnum } from '../../../types'
import { convertRepoToUrl, parseGitHubUrl, pluginManifestInMarketToPluginProps, pluginManifestToCardPluginProps } from '../../utils'
import InstallFromGitHub from '../index'
// Factory functions for test data (defined before mocks that use them)
const createMockManifest = (overrides: Partial<PluginDeclaration> = {}): PluginDeclaration => ({
@ -69,12 +69,12 @@ vi.mock('@/app/components/plugins/install-plugin/base/use-get-icon', () => ({
}))
const mockFetchReleases = vi.fn()
vi.mock('../hooks', () => ({
vi.mock('../../hooks', () => ({
useGitHubReleases: () => ({ fetchReleases: mockFetchReleases }),
}))
const mockRefreshPluginList = vi.fn()
vi.mock('../hooks/use-refresh-plugin-list', () => ({
vi.mock('../../hooks/use-refresh-plugin-list', () => ({
default: () => ({ refreshPluginList: mockRefreshPluginList }),
}))
@ -84,12 +84,12 @@ let mockHideLogicState = {
setIsInstalling: vi.fn(),
handleStartToInstall: vi.fn(),
}
vi.mock('../hooks/use-hide-logic', () => ({
vi.mock('../../hooks/use-hide-logic', () => ({
default: () => mockHideLogicState,
}))
// Mock child components
vi.mock('./steps/setURL', () => ({
vi.mock('../steps/setURL', () => ({
default: ({ repoUrl, onChange, onNext, onCancel }: {
repoUrl: string
onChange: (value: string) => void
@ -108,7 +108,7 @@ vi.mock('./steps/setURL', () => ({
),
}))
vi.mock('./steps/selectPackage', () => ({
vi.mock('../steps/selectPackage', () => ({
default: ({
repoUrl,
selectedVersion,
@ -170,7 +170,7 @@ vi.mock('./steps/selectPackage', () => ({
),
}))
vi.mock('./steps/loaded', () => ({
vi.mock('../steps/loaded', () => ({
default: ({
uniqueIdentifier,
payload,
@ -208,7 +208,7 @@ vi.mock('./steps/loaded', () => ({
),
}))
vi.mock('../base/installed', () => ({
vi.mock('../../base/installed', () => ({
default: ({ payload, isFailed, errMsg, onCancel }: {
payload: PluginDeclaration | null
isFailed: boolean

View File

@ -1,8 +1,8 @@
import type { Plugin, PluginDeclaration, UpdateFromGitHubPayload } from '../../../types'
import type { Plugin, PluginDeclaration, UpdateFromGitHubPayload } from '../../../../types'
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { PluginCategoryEnum, TaskStatus } from '../../../types'
import Loaded from './loaded'
import { PluginCategoryEnum, TaskStatus } from '../../../../types'
import Loaded from '../loaded'
// Mock dependencies
const mockUseCheckInstalled = vi.fn()
@ -23,12 +23,12 @@ vi.mock('@/service/use-plugins', () => ({
}))
const mockCheck = vi.fn()
vi.mock('../../base/check-task-status', () => ({
vi.mock('../../../base/check-task-status', () => ({
default: () => ({ check: mockCheck }),
}))
// Mock Card component
vi.mock('../../../card', () => ({
vi.mock('../../../../card', () => ({
default: ({ payload, titleLeft }: { payload: Plugin, titleLeft?: React.ReactNode }) => (
<div data-testid="plugin-card">
<span data-testid="card-name">{payload.name}</span>
@ -38,7 +38,7 @@ vi.mock('../../../card', () => ({
}))
// Mock Version component
vi.mock('../../base/version', () => ({
vi.mock('../../../base/version', () => ({
default: ({ hasInstalled, installedVersion, toInstallVersion }: {
hasInstalled: boolean
installedVersion?: string

View File

@ -1,13 +1,13 @@
import type { PluginDeclaration, UpdateFromGitHubPayload } from '../../../types'
import type { PluginDeclaration, UpdateFromGitHubPayload } from '../../../../types'
import type { Item } from '@/app/components/base/select'
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { PluginCategoryEnum } from '../../../types'
import SelectPackage from './selectPackage'
import { PluginCategoryEnum } from '../../../../types'
import SelectPackage from '../selectPackage'
// Mock the useGitHubUpload hook
const mockHandleUpload = vi.fn()
vi.mock('../../hooks', () => ({
vi.mock('../../../hooks', () => ({
useGitHubUpload: () => ({ handleUpload: mockHandleUpload }),
}))

View File

@ -1,6 +1,6 @@
import { fireEvent, render, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import SetURL from './setURL'
import SetURL from '../setURL'
describe('SetURL', () => {
const defaultProps = {

View File

@ -1,8 +1,8 @@
import type { Dependency, PluginDeclaration } from '../../types'
import type { Dependency, PluginDeclaration } from '../../../types'
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { InstallStep, PluginCategoryEnum } from '../../types'
import InstallFromLocalPackage from './index'
import { InstallStep, PluginCategoryEnum } from '../../../types'
import InstallFromLocalPackage from '../index'
// Factory functions for test data
const createMockManifest = (overrides: Partial<PluginDeclaration> = {}): PluginDeclaration => ({
@ -64,7 +64,7 @@ let mockHideLogicState = {
setIsInstalling: vi.fn(),
handleStartToInstall: vi.fn(),
}
vi.mock('../hooks/use-hide-logic', () => ({
vi.mock('../../hooks/use-hide-logic', () => ({
default: () => mockHideLogicState,
}))
@ -73,7 +73,7 @@ let uploadingOnPackageUploaded: ((result: { uniqueIdentifier: string, manifest:
let uploadingOnBundleUploaded: ((result: Dependency[]) => void) | null = null
let _uploadingOnFailed: ((errorMsg: string) => void) | null = null
vi.mock('./steps/uploading', () => ({
vi.mock('../steps/uploading', () => ({
default: ({
isBundle,
file,
@ -127,7 +127,7 @@ let _packageStepChangeCallback: ((step: InstallStep) => void) | null = null
let _packageSetIsInstallingCallback: ((isInstalling: boolean) => void) | null = null
let _packageOnErrorCallback: ((errorMsg: string) => void) | null = null
vi.mock('./ready-to-install', () => ({
vi.mock('../ready-to-install', () => ({
default: ({
step,
onStepChange,
@ -192,7 +192,7 @@ vi.mock('./ready-to-install', () => ({
let _bundleStepChangeCallback: ((step: InstallStep) => void) | null = null
let _bundleSetIsInstallingCallback: ((isInstalling: boolean) => void) | null = null
vi.mock('../install-bundle/ready-to-install', () => ({
vi.mock('../../install-bundle/ready-to-install', () => ({
default: ({
step,
onStepChange,

View File

@ -1,8 +1,8 @@
import type { PluginDeclaration } from '../../types'
import type { PluginDeclaration } from '../../../types'
import { fireEvent, render, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { InstallStep, PluginCategoryEnum } from '../../types'
import ReadyToInstall from './ready-to-install'
import { InstallStep, PluginCategoryEnum } from '../../../types'
import ReadyToInstall from '../ready-to-install'
// Factory function for test data
const createMockManifest = (overrides: Partial<PluginDeclaration> = {}): PluginDeclaration => ({
@ -29,7 +29,7 @@ const createMockManifest = (overrides: Partial<PluginDeclaration> = {}): PluginD
// Mock external dependencies
const mockRefreshPluginList = vi.fn()
vi.mock('../hooks/use-refresh-plugin-list', () => ({
vi.mock('../../hooks/use-refresh-plugin-list', () => ({
default: () => ({
refreshPluginList: mockRefreshPluginList,
}),
@ -41,7 +41,7 @@ let _installOnFailed: ((message?: string) => void) | null = null
let _installOnCancel: (() => void) | null = null
let _installOnStartToInstall: (() => void) | null = null
vi.mock('./steps/install', () => ({
vi.mock('../steps/install', () => ({
default: ({
uniqueIdentifier,
payload,
@ -87,7 +87,7 @@ vi.mock('./steps/install', () => ({
}))
// Mock Installed component
vi.mock('../base/installed', () => ({
vi.mock('../../base/installed', () => ({
default: ({
payload,
isFailed,

View File

@ -1,8 +1,8 @@
import type { PluginDeclaration } from '../../../types'
import type { PluginDeclaration } from '../../../../types'
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { PluginCategoryEnum, TaskStatus } from '../../../types'
import Install from './install'
import { PluginCategoryEnum, TaskStatus } from '../../../../types'
import Install from '../install'
// Factory function for test data
const createMockManifest = (overrides: Partial<PluginDeclaration> = {}): PluginDeclaration => ({
@ -50,7 +50,7 @@ vi.mock('@/service/plugins', () => ({
const mockCheck = vi.fn()
const mockStop = vi.fn()
vi.mock('../../base/check-task-status', () => ({
vi.mock('../../../base/check-task-status', () => ({
default: () => ({
check: mockCheck,
stop: mockStop,
@ -64,22 +64,7 @@ vi.mock('@/context/app-context', () => ({
}),
}))
vi.mock('react-i18next', async (importOriginal) => {
const actual = await importOriginal<typeof import('react-i18next')>()
const { createReactI18nextMock } = await import('@/test/i18n-mock')
return {
...actual,
...createReactI18nextMock(),
Trans: ({ i18nKey, components }: { i18nKey: string, components?: Record<string, React.ReactNode> }) => (
<span data-testid="trans">
{i18nKey}
{components?.trustSource}
</span>
),
}
})
vi.mock('../../../card', () => ({
vi.mock('../../../../card', () => ({
default: ({ payload, titleLeft }: {
payload: Record<string, unknown>
titleLeft?: React.ReactNode
@ -91,7 +76,7 @@ vi.mock('../../../card', () => ({
),
}))
vi.mock('../../base/version', () => ({
vi.mock('../../../base/version', () => ({
default: ({ hasInstalled, installedVersion, toInstallVersion }: {
hasInstalled: boolean
installedVersion?: string
@ -105,7 +90,7 @@ vi.mock('../../base/version', () => ({
),
}))
vi.mock('../../utils', () => ({
vi.mock('../../../utils', () => ({
pluginManifestToCardPluginProps: (manifest: PluginDeclaration) => ({
name: manifest.name,
author: manifest.author,
@ -148,7 +133,7 @@ describe('Install', () => {
it('should render trust source message', () => {
render(<Install {...defaultProps} />)
expect(screen.getByTestId('trans')).toBeInTheDocument()
expect(screen.getByText('installModal.fromTrustSource')).toBeInTheDocument()
})
it('should render plugin card', () => {

View File

@ -1,9 +1,9 @@
import type { Dependency, PluginDeclaration } from '../../../types'
import type { Dependency, PluginDeclaration } from '../../../../types'
import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { PluginCategoryEnum } from '../../../types'
import Uploading from './uploading'
import { PluginCategoryEnum } from '../../../../types'
import Uploading from '../uploading'
// Factory function for test data
const createMockManifest = (overrides: Partial<PluginDeclaration> = {}): PluginDeclaration => ({
@ -48,7 +48,7 @@ vi.mock('@/service/plugins', () => ({
uploadFile: (...args: unknown[]) => mockUploadFile(...args),
}))
vi.mock('../../../card', () => ({
vi.mock('../../../../card', () => ({
default: ({ payload, isLoading, loadingFileName }: {
payload: { name: string }
isLoading?: boolean

View File

@ -1,8 +1,8 @@
import type { Dependency, Plugin, PluginManifestInMarket } from '../../types'
import type { Dependency, Plugin, PluginManifestInMarket } from '../../../types'
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { InstallStep, PluginCategoryEnum } from '../../types'
import InstallFromMarketplace from './index'
import { InstallStep, PluginCategoryEnum } from '../../../types'
import InstallFromMarketplace from '../index'
// Factory functions for test data
// Use type casting to avoid strict locale requirements in tests
@ -69,7 +69,7 @@ const createMockDependencies = (): Dependency[] => [
// Mock external dependencies
const mockRefreshPluginList = vi.fn()
vi.mock('../hooks/use-refresh-plugin-list', () => ({
vi.mock('../../hooks/use-refresh-plugin-list', () => ({
default: () => ({ refreshPluginList: mockRefreshPluginList }),
}))
@ -79,12 +79,12 @@ let mockHideLogicState = {
setIsInstalling: vi.fn(),
handleStartToInstall: vi.fn(),
}
vi.mock('../hooks/use-hide-logic', () => ({
vi.mock('../../hooks/use-hide-logic', () => ({
default: () => mockHideLogicState,
}))
// Mock child components
vi.mock('./steps/install', () => ({
vi.mock('../steps/install', () => ({
default: ({
uniqueIdentifier,
payload,
@ -113,7 +113,7 @@ vi.mock('./steps/install', () => ({
),
}))
vi.mock('../install-bundle/ready-to-install', () => ({
vi.mock('../../install-bundle/ready-to-install', () => ({
default: ({
step,
onStepChange,
@ -145,7 +145,7 @@ vi.mock('../install-bundle/ready-to-install', () => ({
),
}))
vi.mock('../base/installed', () => ({
vi.mock('../../base/installed', () => ({
default: ({
payload,
isMarketPayload,

View File

@ -1,9 +1,9 @@
import type { Plugin, PluginManifestInMarket } from '../../../types'
import type { Plugin, PluginManifestInMarket } from '../../../../types'
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import { act } from 'react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { PluginCategoryEnum, TaskStatus } from '../../../types'
import Install from './install'
import { PluginCategoryEnum, TaskStatus } from '../../../../types'
import Install from '../install'
// Factory functions for test data
const createMockManifest = (overrides: Partial<PluginManifestInMarket> = {}): PluginManifestInMarket => ({
@ -64,7 +64,7 @@ let mockLangGeniusVersionInfo = { current_version: '1.0.0' }
// Mock useCheckInstalled
vi.mock('@/app/components/plugins/install-plugin/hooks/use-check-installed', () => ({
default: ({ pluginIds }: { pluginIds: string[], enabled: boolean }) => ({
default: ({ pluginIds: _pluginIds }: { pluginIds: string[], enabled: boolean }) => ({
installedInfo: mockInstalledInfo,
isLoading: mockIsLoading,
error: null,
@ -88,7 +88,7 @@ vi.mock('@/service/use-plugins', () => ({
}))
// Mock checkTaskStatus
vi.mock('../../base/check-task-status', () => ({
vi.mock('../../../base/check-task-status', () => ({
default: () => ({
check: mockCheckTaskStatus,
stop: mockStopTaskStatus,
@ -103,20 +103,20 @@ vi.mock('@/context/app-context', () => ({
}))
// Mock useInstallPluginLimit
vi.mock('../../hooks/use-install-plugin-limit', () => ({
vi.mock('../../../hooks/use-install-plugin-limit', () => ({
default: () => ({ canInstall: mockCanInstall }),
}))
// Mock Card component
vi.mock('../../../card', () => ({
default: ({ payload, titleLeft, className, limitedInstall }: {
payload: any
vi.mock('../../../../card', () => ({
default: ({ payload, titleLeft, className: _className, limitedInstall }: {
payload: Record<string, unknown>
titleLeft?: React.ReactNode
className?: string
limitedInstall?: boolean
}) => (
<div data-testid="plugin-card">
<span data-testid="card-payload-name">{payload?.name}</span>
<span data-testid="card-payload-name">{String(payload?.name ?? '')}</span>
<span data-testid="card-limited-install">{limitedInstall ? 'true' : 'false'}</span>
{!!titleLeft && <div data-testid="card-title-left">{titleLeft}</div>}
</div>
@ -124,7 +124,7 @@ vi.mock('../../../card', () => ({
}))
// Mock Version component
vi.mock('../../base/version', () => ({
vi.mock('../../../base/version', () => ({
default: ({ hasInstalled, installedVersion, toInstallVersion }: {
hasInstalled: boolean
installedVersion?: string
@ -139,7 +139,7 @@ vi.mock('../../base/version', () => ({
}))
// Mock utils
vi.mock('../../utils', () => ({
vi.mock('../../../utils', () => ({
pluginManifestInMarketToPluginProps: (payload: PluginManifestInMarket) => ({
name: payload.name,
icon: payload.icon,
@ -255,7 +255,7 @@ describe('Install Component (steps/install.tsx)', () => {
})
it('should fallback to latest_version when version is undefined', () => {
const manifest = createMockManifest({ version: undefined as any, latest_version: '3.0.0' })
const manifest = createMockManifest({ version: undefined as unknown as string, latest_version: '3.0.0' })
render(<Install {...defaultProps} payload={manifest} />)
expect(screen.getByTestId('to-install-version')).toHaveTextContent('3.0.0')
@ -701,7 +701,7 @@ describe('Install Component (steps/install.tsx)', () => {
})
it('should handle null current_version in langGeniusVersionInfo', () => {
mockLangGeniusVersionInfo = { current_version: null as any }
mockLangGeniusVersionInfo = { current_version: null as unknown as string }
mockPluginDeclaration = {
manifest: { meta: { minimum_dify_version: '1.0.0' } },
}

View File

@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest'
import { PluginCategoryEnum } from '@/app/components/plugins/types'
import { DEFAULT_SORT, PLUGIN_CATEGORY_WITH_COLLECTIONS, PLUGIN_TYPE_SEARCH_MAP, SCROLL_BOTTOM_THRESHOLD } from './constants'
import { DEFAULT_SORT, PLUGIN_CATEGORY_WITH_COLLECTIONS, PLUGIN_TYPE_SEARCH_MAP, SCROLL_BOTTOM_THRESHOLD } from '../constants'
describe('DEFAULT_SORT', () => {
it('should have correct default sort values', () => {

View File

@ -0,0 +1,601 @@
import { render, renderHook } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
// ================================
// Mock External Dependencies
// ================================
vi.mock('@/i18n-config/i18next-config', () => ({
default: {
getFixedT: () => (key: string) => key,
},
}))
const mockSetUrlFilters = vi.fn()
vi.mock('@/hooks/use-query-params', () => ({
useMarketplaceFilters: () => [
{ q: '', tags: [], category: '' },
mockSetUrlFilters,
],
}))
vi.mock('@/service/use-plugins', () => ({
useInstalledPluginList: () => ({
data: { plugins: [] },
isSuccess: true,
}),
}))
const mockFetchNextPage = vi.fn()
const mockHasNextPage = false
let mockInfiniteQueryData: { pages: Array<{ plugins: unknown[], total: number, page: number, page_size: number }> } | undefined
let capturedInfiniteQueryFn: ((ctx: { pageParam: number, signal: AbortSignal }) => Promise<unknown>) | null = null
let capturedQueryFn: ((ctx: { signal: AbortSignal }) => Promise<unknown>) | null = null
let capturedGetNextPageParam: ((lastPage: { page: number, page_size: number, total: number }) => number | undefined) | null = null
vi.mock('@tanstack/react-query', () => ({
useQuery: vi.fn(({ queryFn, enabled }: { queryFn: (ctx: { signal: AbortSignal }) => Promise<unknown>, enabled: boolean }) => {
capturedQueryFn = queryFn
if (queryFn) {
const controller = new AbortController()
queryFn({ signal: controller.signal }).catch(() => {})
}
return {
data: enabled ? { marketplaceCollections: [], marketplaceCollectionPluginsMap: {} } : undefined,
isFetching: false,
isPending: false,
isSuccess: enabled,
}
}),
useInfiniteQuery: vi.fn(({ queryFn, getNextPageParam }: {
queryFn: (ctx: { pageParam: number, signal: AbortSignal }) => Promise<unknown>
getNextPageParam: (lastPage: { page: number, page_size: number, total: number }) => number | undefined
enabled: boolean
}) => {
capturedInfiniteQueryFn = queryFn
capturedGetNextPageParam = getNextPageParam
if (queryFn) {
const controller = new AbortController()
queryFn({ pageParam: 1, signal: controller.signal }).catch(() => {})
}
if (getNextPageParam) {
getNextPageParam({ page: 1, page_size: 40, total: 100 })
getNextPageParam({ page: 3, page_size: 40, total: 100 })
}
return {
data: mockInfiniteQueryData,
isPending: false,
isFetching: false,
isFetchingNextPage: false,
hasNextPage: mockHasNextPage,
fetchNextPage: mockFetchNextPage,
}
}),
useQueryClient: vi.fn(() => ({
removeQueries: vi.fn(),
})),
}))
vi.mock('ahooks', () => ({
useDebounceFn: (fn: (...args: unknown[]) => void) => ({
run: fn,
cancel: vi.fn(),
}),
}))
let mockPostMarketplaceShouldFail = false
const mockPostMarketplaceResponse = {
data: {
plugins: [
{ type: 'plugin', org: 'test', name: 'plugin1', tags: [] },
{ type: 'plugin', org: 'test', name: 'plugin2', tags: [] },
],
bundles: [] as Array<{ type: string, org: string, name: string, tags: unknown[] }>,
total: 2,
},
}
vi.mock('@/service/base', () => ({
postMarketplace: vi.fn(() => {
if (mockPostMarketplaceShouldFail)
return Promise.reject(new Error('Mock API error'))
return Promise.resolve(mockPostMarketplaceResponse)
}),
}))
vi.mock('@/config', () => ({
API_PREFIX: '/api',
APP_VERSION: '1.0.0',
IS_MARKETPLACE: false,
MARKETPLACE_API_PREFIX: 'https://marketplace.dify.ai/api/v1',
}))
vi.mock('@/utils/var', () => ({
getMarketplaceUrl: (path: string) => `https://marketplace.dify.ai${path}`,
}))
vi.mock('@/service/client', () => ({
marketplaceClient: {
collections: vi.fn(async () => ({
data: {
collections: [
{
name: 'collection-1',
label: { 'en-US': 'Collection 1' },
description: { 'en-US': 'Desc' },
rule: '',
created_at: '2024-01-01',
updated_at: '2024-01-01',
searchable: true,
search_params: { query: '', sort_by: 'install_count', sort_order: 'DESC' },
},
],
},
})),
collectionPlugins: vi.fn(async () => ({
data: {
plugins: [
{ type: 'plugin', org: 'test', name: 'plugin1', tags: [] },
],
},
})),
searchAdvanced: vi.fn(async () => ({
data: {
plugins: [
{ type: 'plugin', org: 'test', name: 'plugin1', tags: [] },
],
total: 1,
},
})),
},
}))
// ================================
// useMarketplaceCollectionsAndPlugins Tests
// ================================
describe('useMarketplaceCollectionsAndPlugins', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('should return initial state correctly', async () => {
const { useMarketplaceCollectionsAndPlugins } = await import('../hooks')
const { result } = renderHook(() => useMarketplaceCollectionsAndPlugins())
expect(result.current.isLoading).toBe(false)
expect(result.current.isSuccess).toBe(false)
expect(result.current.queryMarketplaceCollectionsAndPlugins).toBeDefined()
expect(result.current.setMarketplaceCollections).toBeDefined()
expect(result.current.setMarketplaceCollectionPluginsMap).toBeDefined()
})
it('should provide queryMarketplaceCollectionsAndPlugins function', async () => {
const { useMarketplaceCollectionsAndPlugins } = await import('../hooks')
const { result } = renderHook(() => useMarketplaceCollectionsAndPlugins())
expect(typeof result.current.queryMarketplaceCollectionsAndPlugins).toBe('function')
})
it('should provide setMarketplaceCollections function', async () => {
const { useMarketplaceCollectionsAndPlugins } = await import('../hooks')
const { result } = renderHook(() => useMarketplaceCollectionsAndPlugins())
expect(typeof result.current.setMarketplaceCollections).toBe('function')
})
it('should provide setMarketplaceCollectionPluginsMap function', async () => {
const { useMarketplaceCollectionsAndPlugins } = await import('../hooks')
const { result } = renderHook(() => useMarketplaceCollectionsAndPlugins())
expect(typeof result.current.setMarketplaceCollectionPluginsMap).toBe('function')
})
it('should return marketplaceCollections from data or override', async () => {
const { useMarketplaceCollectionsAndPlugins } = await import('../hooks')
const { result } = renderHook(() => useMarketplaceCollectionsAndPlugins())
expect(result.current.marketplaceCollections).toBeUndefined()
})
it('should return marketplaceCollectionPluginsMap from data or override', async () => {
const { useMarketplaceCollectionsAndPlugins } = await import('../hooks')
const { result } = renderHook(() => useMarketplaceCollectionsAndPlugins())
expect(result.current.marketplaceCollectionPluginsMap).toBeUndefined()
})
})
// ================================
// useMarketplacePluginsByCollectionId Tests
// ================================
describe('useMarketplacePluginsByCollectionId', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('should return initial state when collectionId is undefined', async () => {
const { useMarketplacePluginsByCollectionId } = await import('../hooks')
const { result } = renderHook(() => useMarketplacePluginsByCollectionId(undefined))
expect(result.current.plugins).toEqual([])
expect(result.current.isLoading).toBe(false)
expect(result.current.isSuccess).toBe(false)
})
it('should return isLoading false when collectionId is provided and query completes', async () => {
const { useMarketplacePluginsByCollectionId } = await import('../hooks')
const { result } = renderHook(() => useMarketplacePluginsByCollectionId('test-collection'))
expect(result.current.isLoading).toBe(false)
})
it('should accept query parameter', async () => {
const { useMarketplacePluginsByCollectionId } = await import('../hooks')
const { result } = renderHook(() =>
useMarketplacePluginsByCollectionId('test-collection', {
category: 'tool',
type: 'plugin',
}))
expect(result.current.plugins).toBeDefined()
})
it('should return plugins property from hook', async () => {
const { useMarketplacePluginsByCollectionId } = await import('../hooks')
const { result } = renderHook(() => useMarketplacePluginsByCollectionId('collection-1'))
expect(result.current.plugins).toBeDefined()
})
})
// ================================
// useMarketplacePlugins Tests
// ================================
describe('useMarketplacePlugins', () => {
beforeEach(() => {
vi.clearAllMocks()
mockInfiniteQueryData = undefined
})
it('should return initial state correctly', async () => {
const { useMarketplacePlugins } = await import('../hooks')
const { result } = renderHook(() => useMarketplacePlugins())
expect(result.current.plugins).toBeUndefined()
expect(result.current.total).toBeUndefined()
expect(result.current.isLoading).toBe(false)
expect(result.current.isFetchingNextPage).toBe(false)
expect(result.current.hasNextPage).toBe(false)
expect(result.current.page).toBe(0)
})
it('should provide queryPlugins function', async () => {
const { useMarketplacePlugins } = await import('../hooks')
const { result } = renderHook(() => useMarketplacePlugins())
expect(typeof result.current.queryPlugins).toBe('function')
})
it('should provide queryPluginsWithDebounced function', async () => {
const { useMarketplacePlugins } = await import('../hooks')
const { result } = renderHook(() => useMarketplacePlugins())
expect(typeof result.current.queryPluginsWithDebounced).toBe('function')
})
it('should provide cancelQueryPluginsWithDebounced function', async () => {
const { useMarketplacePlugins } = await import('../hooks')
const { result } = renderHook(() => useMarketplacePlugins())
expect(typeof result.current.cancelQueryPluginsWithDebounced).toBe('function')
})
it('should provide resetPlugins function', async () => {
const { useMarketplacePlugins } = await import('../hooks')
const { result } = renderHook(() => useMarketplacePlugins())
expect(typeof result.current.resetPlugins).toBe('function')
})
it('should provide fetchNextPage function', async () => {
const { useMarketplacePlugins } = await import('../hooks')
const { result } = renderHook(() => useMarketplacePlugins())
expect(typeof result.current.fetchNextPage).toBe('function')
})
it('should handle queryPlugins call without errors', async () => {
const { useMarketplacePlugins } = await import('../hooks')
const { result } = renderHook(() => useMarketplacePlugins())
expect(() => {
result.current.queryPlugins({
query: 'test',
sort_by: 'install_count',
sort_order: 'DESC',
category: 'tool',
page_size: 20,
})
}).not.toThrow()
})
it('should handle queryPlugins with bundle type', async () => {
const { useMarketplacePlugins } = await import('../hooks')
const { result } = renderHook(() => useMarketplacePlugins())
expect(() => {
result.current.queryPlugins({
query: 'test',
type: 'bundle',
page_size: 40,
})
}).not.toThrow()
})
it('should handle resetPlugins call', async () => {
const { useMarketplacePlugins } = await import('../hooks')
const { result } = renderHook(() => useMarketplacePlugins())
expect(() => {
result.current.resetPlugins()
}).not.toThrow()
})
it('should handle queryPluginsWithDebounced call', async () => {
const { useMarketplacePlugins } = await import('../hooks')
const { result } = renderHook(() => useMarketplacePlugins())
expect(() => {
result.current.queryPluginsWithDebounced({
query: 'debounced search',
category: 'all',
})
}).not.toThrow()
})
it('should handle cancelQueryPluginsWithDebounced call', async () => {
const { useMarketplacePlugins } = await import('../hooks')
const { result } = renderHook(() => useMarketplacePlugins())
expect(() => {
result.current.cancelQueryPluginsWithDebounced()
}).not.toThrow()
})
it('should return correct page number', async () => {
const { useMarketplacePlugins } = await import('../hooks')
const { result } = renderHook(() => useMarketplacePlugins())
expect(result.current.page).toBe(0)
})
it('should handle queryPlugins with tags', async () => {
const { useMarketplacePlugins } = await import('../hooks')
const { result } = renderHook(() => useMarketplacePlugins())
expect(() => {
result.current.queryPlugins({
query: 'test',
tags: ['search', 'image'],
exclude: ['excluded-plugin'],
})
}).not.toThrow()
})
})
// ================================
// Hooks queryFn Coverage Tests
// ================================
describe('Hooks queryFn Coverage', () => {
beforeEach(() => {
vi.clearAllMocks()
mockInfiniteQueryData = undefined
mockPostMarketplaceShouldFail = false
capturedInfiniteQueryFn = null
capturedQueryFn = null
})
it('should cover queryFn with pages data', async () => {
mockInfiniteQueryData = {
pages: [
{ plugins: [{ name: 'plugin1' }], total: 10, page: 1, page_size: 40 },
],
}
const { useMarketplacePlugins } = await import('../hooks')
const { result } = renderHook(() => useMarketplacePlugins())
result.current.queryPlugins({
query: 'test',
category: 'tool',
})
expect(result.current).toBeDefined()
})
it('should expose page and total from infinite query data', async () => {
mockInfiniteQueryData = {
pages: [
{ plugins: [{ name: 'plugin1' }, { name: 'plugin2' }], total: 20, page: 1, page_size: 40 },
{ plugins: [{ name: 'plugin3' }], total: 20, page: 2, page_size: 40 },
],
}
const { useMarketplacePlugins } = await import('../hooks')
const { result } = renderHook(() => useMarketplacePlugins())
result.current.queryPlugins({ query: 'search' })
expect(result.current.page).toBe(2)
})
it('should return undefined total when no query is set', async () => {
const { useMarketplacePlugins } = await import('../hooks')
const { result } = renderHook(() => useMarketplacePlugins())
expect(result.current.total).toBeUndefined()
})
it('should directly test queryFn execution', async () => {
const { useMarketplacePlugins } = await import('../hooks')
const { result } = renderHook(() => useMarketplacePlugins())
result.current.queryPlugins({
query: 'direct test',
category: 'tool',
sort_by: 'install_count',
sort_order: 'DESC',
page_size: 40,
})
if (capturedInfiniteQueryFn) {
const controller = new AbortController()
const response = await capturedInfiniteQueryFn({ pageParam: 1, signal: controller.signal })
expect(response).toBeDefined()
}
})
it('should test queryFn with bundle type', async () => {
const { useMarketplacePlugins } = await import('../hooks')
const { result } = renderHook(() => useMarketplacePlugins())
result.current.queryPlugins({
type: 'bundle',
query: 'bundle test',
})
if (capturedInfiniteQueryFn) {
const controller = new AbortController()
const response = await capturedInfiniteQueryFn({ pageParam: 2, signal: controller.signal })
expect(response).toBeDefined()
}
})
it('should test queryFn error handling', async () => {
mockPostMarketplaceShouldFail = true
const { useMarketplacePlugins } = await import('../hooks')
const { result } = renderHook(() => useMarketplacePlugins())
result.current.queryPlugins({ query: 'test that will fail' })
if (capturedInfiniteQueryFn) {
const controller = new AbortController()
const response = await capturedInfiniteQueryFn({ pageParam: 1, signal: controller.signal })
expect(response).toBeDefined()
expect(response).toHaveProperty('plugins')
}
mockPostMarketplaceShouldFail = false
})
it('should test useMarketplaceCollectionsAndPlugins queryFn', async () => {
const { useMarketplaceCollectionsAndPlugins } = await import('../hooks')
const { result } = renderHook(() => useMarketplaceCollectionsAndPlugins())
result.current.queryMarketplaceCollectionsAndPlugins({
condition: 'category=tool',
})
if (capturedQueryFn) {
const controller = new AbortController()
const response = await capturedQueryFn({ signal: controller.signal })
expect(response).toBeDefined()
}
})
it('should test getNextPageParam directly', async () => {
const { useMarketplacePlugins } = await import('../hooks')
renderHook(() => useMarketplacePlugins())
if (capturedGetNextPageParam) {
const nextPage = capturedGetNextPageParam({ page: 1, page_size: 40, total: 100 })
expect(nextPage).toBe(2)
const noMorePages = capturedGetNextPageParam({ page: 3, page_size: 40, total: 100 })
expect(noMorePages).toBeUndefined()
const atBoundary = capturedGetNextPageParam({ page: 2, page_size: 50, total: 100 })
expect(atBoundary).toBeUndefined()
}
})
})
// ================================
// useMarketplaceContainerScroll Tests
// ================================
describe('useMarketplaceContainerScroll', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('should attach scroll event listener to container', async () => {
const mockCallback = vi.fn()
const mockContainer = document.createElement('div')
mockContainer.id = 'marketplace-container'
document.body.appendChild(mockContainer)
const addEventListenerSpy = vi.spyOn(mockContainer, 'addEventListener')
const { useMarketplaceContainerScroll } = await import('../hooks')
const TestComponent = () => {
useMarketplaceContainerScroll(mockCallback)
return null
}
render(<TestComponent />)
expect(addEventListenerSpy).toHaveBeenCalledWith('scroll', expect.any(Function))
document.body.removeChild(mockContainer)
})
it('should call callback when scrolled to bottom', async () => {
const mockCallback = vi.fn()
const mockContainer = document.createElement('div')
mockContainer.id = 'scroll-test-container-hooks'
document.body.appendChild(mockContainer)
Object.defineProperty(mockContainer, 'scrollTop', { value: 900, writable: true })
Object.defineProperty(mockContainer, 'scrollHeight', { value: 1000, writable: true })
Object.defineProperty(mockContainer, 'clientHeight', { value: 100, writable: true })
const { useMarketplaceContainerScroll } = await import('../hooks')
const TestComponent = () => {
useMarketplaceContainerScroll(mockCallback, 'scroll-test-container-hooks')
return null
}
render(<TestComponent />)
const scrollEvent = new Event('scroll')
Object.defineProperty(scrollEvent, 'target', { value: mockContainer })
mockContainer.dispatchEvent(scrollEvent)
expect(mockCallback).toHaveBeenCalled()
document.body.removeChild(mockContainer)
})
it('should not call callback when scrollTop is 0', async () => {
const mockCallback = vi.fn()
const mockContainer = document.createElement('div')
mockContainer.id = 'scroll-test-container-hooks-2'
document.body.appendChild(mockContainer)
Object.defineProperty(mockContainer, 'scrollTop', { value: 0, writable: true })
Object.defineProperty(mockContainer, 'scrollHeight', { value: 1000, writable: true })
Object.defineProperty(mockContainer, 'clientHeight', { value: 100, writable: true })
const { useMarketplaceContainerScroll } = await import('../hooks')
const TestComponent = () => {
useMarketplaceContainerScroll(mockCallback, 'scroll-test-container-hooks-2')
return null
}
render(<TestComponent />)
const scrollEvent = new Event('scroll')
Object.defineProperty(scrollEvent, 'target', { value: mockContainer })
mockContainer.dispatchEvent(scrollEvent)
expect(mockCallback).not.toHaveBeenCalled()
document.body.removeChild(mockContainer)
})
it('should remove event listener on unmount', async () => {
const mockCallback = vi.fn()
const mockContainer = document.createElement('div')
mockContainer.id = 'scroll-unmount-container-hooks'
document.body.appendChild(mockContainer)
const removeEventListenerSpy = vi.spyOn(mockContainer, 'removeEventListener')
const { useMarketplaceContainerScroll } = await import('../hooks')
const TestComponent = () => {
useMarketplaceContainerScroll(mockCallback, 'scroll-unmount-container-hooks')
return null
}
const { unmount } = render(<TestComponent />)
unmount()
expect(removeEventListenerSpy).toHaveBeenCalledWith('scroll', expect.any(Function))
document.body.removeChild(mockContainer)
})
})

View File

@ -1,7 +1,7 @@
import type { Plugin } from '@/app/components/plugins/types'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { PluginCategoryEnum } from '@/app/components/plugins/types'
import { PLUGIN_TYPE_SEARCH_MAP } from './constants'
import { PLUGIN_TYPE_SEARCH_MAP } from '../constants'
// Mock config
vi.mock('@/config', () => ({
@ -57,14 +57,14 @@ const createMockPlugin = (overrides?: Partial<Plugin>): Plugin => ({
describe('getPluginIconInMarketplace', () => {
it('should return correct icon URL for regular plugin', async () => {
const { getPluginIconInMarketplace } = await import('./utils')
const { getPluginIconInMarketplace } = await import('../utils')
const plugin = createMockPlugin({ org: 'test-org', name: 'test-plugin', type: 'plugin' })
const iconUrl = getPluginIconInMarketplace(plugin)
expect(iconUrl).toBe('https://marketplace.dify.ai/api/v1/plugins/test-org/test-plugin/icon')
})
it('should return correct icon URL for bundle', async () => {
const { getPluginIconInMarketplace } = await import('./utils')
const { getPluginIconInMarketplace } = await import('../utils')
const bundle = createMockPlugin({ org: 'test-org', name: 'test-bundle', type: 'bundle' })
const iconUrl = getPluginIconInMarketplace(bundle)
expect(iconUrl).toBe('https://marketplace.dify.ai/api/v1/bundles/test-org/test-bundle/icon')
@ -73,7 +73,7 @@ describe('getPluginIconInMarketplace', () => {
describe('getFormattedPlugin', () => {
it('should format plugin with icon URL', async () => {
const { getFormattedPlugin } = await import('./utils')
const { getFormattedPlugin } = await import('../utils')
const rawPlugin = {
type: 'plugin',
org: 'test-org',
@ -86,7 +86,7 @@ describe('getFormattedPlugin', () => {
})
it('should format bundle with additional properties', async () => {
const { getFormattedPlugin } = await import('./utils')
const { getFormattedPlugin } = await import('../utils')
const rawBundle = {
type: 'bundle',
org: 'test-org',
@ -104,14 +104,14 @@ describe('getFormattedPlugin', () => {
describe('getPluginLinkInMarketplace', () => {
it('should return correct link for regular plugin', async () => {
const { getPluginLinkInMarketplace } = await import('./utils')
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')
})
it('should return correct link for bundle', async () => {
const { getPluginLinkInMarketplace } = await import('./utils')
const { getPluginLinkInMarketplace } = await import('../utils')
const bundle = createMockPlugin({ org: 'test-org', name: 'test-bundle', type: 'bundle' })
const link = getPluginLinkInMarketplace(bundle)
expect(link).toBe('https://marketplace.dify.ai/bundles/test-org/test-bundle')
@ -120,14 +120,14 @@ describe('getPluginLinkInMarketplace', () => {
describe('getPluginDetailLinkInMarketplace', () => {
it('should return correct detail link for regular plugin', async () => {
const { getPluginDetailLinkInMarketplace } = await import('./utils')
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')
})
it('should return correct detail link for bundle', async () => {
const { getPluginDetailLinkInMarketplace } = await import('./utils')
const { getPluginDetailLinkInMarketplace } = await import('../utils')
const bundle = createMockPlugin({ org: 'test-org', name: 'test-bundle', type: 'bundle' })
const link = getPluginDetailLinkInMarketplace(bundle)
expect(link).toBe('/bundles/test-org/test-bundle')
@ -136,64 +136,64 @@ describe('getPluginDetailLinkInMarketplace', () => {
describe('getMarketplaceListCondition', () => {
it('should return category condition for tool', async () => {
const { getMarketplaceListCondition } = await import('./utils')
const { getMarketplaceListCondition } = await import('../utils')
expect(getMarketplaceListCondition(PluginCategoryEnum.tool)).toBe('category=tool')
})
it('should return category condition for model', async () => {
const { getMarketplaceListCondition } = await import('./utils')
const { getMarketplaceListCondition } = await import('../utils')
expect(getMarketplaceListCondition(PluginCategoryEnum.model)).toBe('category=model')
})
it('should return category condition for agent', async () => {
const { getMarketplaceListCondition } = await import('./utils')
const { getMarketplaceListCondition } = await import('../utils')
expect(getMarketplaceListCondition(PluginCategoryEnum.agent)).toBe('category=agent-strategy')
})
it('should return category condition for datasource', async () => {
const { getMarketplaceListCondition } = await import('./utils')
const { getMarketplaceListCondition } = await import('../utils')
expect(getMarketplaceListCondition(PluginCategoryEnum.datasource)).toBe('category=datasource')
})
it('should return category condition for trigger', async () => {
const { getMarketplaceListCondition } = await import('./utils')
const { getMarketplaceListCondition } = await import('../utils')
expect(getMarketplaceListCondition(PluginCategoryEnum.trigger)).toBe('category=trigger')
})
it('should return endpoint category for extension', async () => {
const { getMarketplaceListCondition } = await import('./utils')
const { getMarketplaceListCondition } = await import('../utils')
expect(getMarketplaceListCondition(PluginCategoryEnum.extension)).toBe('category=endpoint')
})
it('should return type condition for bundle', async () => {
const { getMarketplaceListCondition } = await import('./utils')
const { getMarketplaceListCondition } = await import('../utils')
expect(getMarketplaceListCondition('bundle')).toBe('type=bundle')
})
it('should return empty string for all', async () => {
const { getMarketplaceListCondition } = await import('./utils')
const { getMarketplaceListCondition } = await import('../utils')
expect(getMarketplaceListCondition('all')).toBe('')
})
it('should return empty string for unknown type', async () => {
const { getMarketplaceListCondition } = await import('./utils')
const { getMarketplaceListCondition } = await import('../utils')
expect(getMarketplaceListCondition('unknown')).toBe('')
})
})
describe('getMarketplaceListFilterType', () => {
it('should return undefined for all', async () => {
const { getMarketplaceListFilterType } = await import('./utils')
const { getMarketplaceListFilterType } = await import('../utils')
expect(getMarketplaceListFilterType(PLUGIN_TYPE_SEARCH_MAP.all)).toBeUndefined()
})
it('should return bundle for bundle', async () => {
const { getMarketplaceListFilterType } = await import('./utils')
const { getMarketplaceListFilterType } = await import('../utils')
expect(getMarketplaceListFilterType(PLUGIN_TYPE_SEARCH_MAP.bundle)).toBe('bundle')
})
it('should return plugin for other categories', async () => {
const { getMarketplaceListFilterType } = await import('./utils')
const { getMarketplaceListFilterType } = await import('../utils')
expect(getMarketplaceListFilterType(PLUGIN_TYPE_SEARCH_MAP.tool)).toBe('plugin')
expect(getMarketplaceListFilterType(PLUGIN_TYPE_SEARCH_MAP.model)).toBe('plugin')
expect(getMarketplaceListFilterType(PLUGIN_TYPE_SEARCH_MAP.agent)).toBe('plugin')
@ -214,7 +214,7 @@ describe('getMarketplacePluginsByCollectionId', () => {
data: { plugins: mockPlugins },
})
const { getMarketplacePluginsByCollectionId } = await import('./utils')
const { getMarketplacePluginsByCollectionId } = await import('../utils')
const result = await getMarketplacePluginsByCollectionId('test-collection', {
category: 'tool',
exclude: ['excluded-plugin'],
@ -228,7 +228,7 @@ describe('getMarketplacePluginsByCollectionId', () => {
it('should handle fetch error and return empty array', async () => {
mockCollectionPlugins.mockRejectedValueOnce(new Error('Network error'))
const { getMarketplacePluginsByCollectionId } = await import('./utils')
const { getMarketplacePluginsByCollectionId } = await import('../utils')
const result = await getMarketplacePluginsByCollectionId('test-collection')
expect(result).toEqual([])
@ -241,7 +241,7 @@ describe('getMarketplacePluginsByCollectionId', () => {
})
const controller = new AbortController()
const { getMarketplacePluginsByCollectionId } = await import('./utils')
const { getMarketplacePluginsByCollectionId } = await import('../utils')
await getMarketplacePluginsByCollectionId('test-collection', {}, { signal: controller.signal })
expect(mockCollectionPlugins).toHaveBeenCalled()
@ -264,7 +264,7 @@ describe('getMarketplaceCollectionsAndPlugins', () => {
mockCollections.mockResolvedValueOnce({ data: { collections: mockCollectionData } })
mockCollectionPlugins.mockResolvedValue({ data: { plugins: mockPluginData } })
const { getMarketplaceCollectionsAndPlugins } = await import('./utils')
const { getMarketplaceCollectionsAndPlugins } = await import('../utils')
const result = await getMarketplaceCollectionsAndPlugins({
condition: 'category=tool',
type: 'plugin',
@ -277,7 +277,7 @@ describe('getMarketplaceCollectionsAndPlugins', () => {
it('should handle fetch error and return empty data', async () => {
mockCollections.mockRejectedValueOnce(new Error('Network error'))
const { getMarketplaceCollectionsAndPlugins } = await import('./utils')
const { getMarketplaceCollectionsAndPlugins } = await import('../utils')
const result = await getMarketplaceCollectionsAndPlugins()
expect(result.marketplaceCollections).toEqual([])
@ -287,7 +287,7 @@ describe('getMarketplaceCollectionsAndPlugins', () => {
it('should append condition and type to URL when provided', async () => {
mockCollections.mockResolvedValueOnce({ data: { collections: [] } })
const { getMarketplaceCollectionsAndPlugins } = await import('./utils')
const { getMarketplaceCollectionsAndPlugins } = await import('../utils')
await getMarketplaceCollectionsAndPlugins({
condition: 'category=tool',
type: 'bundle',
@ -301,12 +301,12 @@ describe('getMarketplaceCollectionsAndPlugins', () => {
describe('getCollectionsParams', () => {
it('should return empty object for all category', async () => {
const { getCollectionsParams } = await import('./utils')
const { getCollectionsParams } = await import('../utils')
expect(getCollectionsParams(PLUGIN_TYPE_SEARCH_MAP.all)).toEqual({})
})
it('should return category, condition, and type for tool category', async () => {
const { getCollectionsParams } = await import('./utils')
const { getCollectionsParams } = await import('../utils')
const result = getCollectionsParams(PLUGIN_TYPE_SEARCH_MAP.tool)
expect(result).toEqual({
category: PluginCategoryEnum.tool,

View File

@ -1,6 +1,6 @@
import { render, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import Description from './index'
import Description from '../index'
// ================================
// Mock external dependencies

View File

@ -1,7 +1,7 @@
import { render, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import Empty from './index'
import Line from './line'
import Empty from '../index'
import Line from '../line'
// ================================
// Mock external dependencies only

View File

@ -1,10 +1,6 @@
import { render, renderHook } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
// ================================
// Mock External Dependencies
// ================================
vi.mock('@/i18n-config/i18next-config', () => ({
default: {
getFixedT: () => (key: string) => key,

View File

@ -1,17 +1,16 @@
import type { MarketplaceCollection, SearchParamsFromCollection } from '../types'
import type { MarketplaceCollection, SearchParamsFromCollection } from '../../types'
import type { Plugin } from '@/app/components/plugins/types'
import { fireEvent, render, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { PluginCategoryEnum } from '@/app/components/plugins/types'
import List from './index'
import ListWithCollection from './list-with-collection'
import ListWrapper from './list-wrapper'
import List from '../index'
import ListWithCollection from '../list-with-collection'
import ListWrapper from '../list-wrapper'
// ================================
// Mock External Dependencies Only
// ================================
// Mock i18n translation hook
vi.mock('#i18n', () => ({
useTranslation: () => ({
t: (key: string, options?: { ns?: string, num?: number }) => {
@ -30,7 +29,6 @@ vi.mock('#i18n', () => ({
useLocale: () => 'en-US',
}))
// Mock marketplace state hooks with controllable values
const { mockMarketplaceData, mockMoreClick } = vi.hoisted(() => {
return {
mockMarketplaceData: {
@ -45,27 +43,18 @@ const { mockMarketplaceData, mockMoreClick } = vi.hoisted(() => {
}
})
vi.mock('../state', () => ({
vi.mock('../../state', () => ({
useMarketplaceData: () => mockMarketplaceData,
}))
vi.mock('../atoms', () => ({
vi.mock('../../atoms', () => ({
useMarketplaceMoreClick: () => mockMoreClick,
}))
// Mock useLocale context
vi.mock('@/context/i18n', () => ({
useLocale: () => 'en-US',
}))
// Mock next-themes
vi.mock('next-themes', () => ({
useTheme: () => ({
theme: 'light',
}),
}))
// Mock useTags hook
const mockTags = [
{ name: 'search', label: 'Search' },
{ name: 'image', label: 'Image' },
@ -85,7 +74,6 @@ vi.mock('@/app/components/plugins/hooks', () => ({
}),
}))
// Mock ahooks useBoolean with controllable state
let mockUseBooleanValue = false
const mockSetTrue = vi.fn(() => {
mockUseBooleanValue = true
@ -107,20 +95,17 @@ vi.mock('ahooks', () => ({
},
}))
// Mock i18n-config/language
vi.mock('@/i18n-config/language', () => ({
getLanguage: (locale: string) => locale || 'en-US',
}))
// Mock marketplace utils
vi.mock('../utils', () => ({
vi.mock('../../utils', () => ({
getPluginLinkInMarketplace: (plugin: Plugin, _params?: Record<string, string | undefined>) =>
`/plugins/${plugin.org}/${plugin.name}`,
getPluginDetailLinkInMarketplace: (plugin: Plugin) =>
`/plugins/${plugin.org}/${plugin.name}`,
}))
// Mock Card component
vi.mock('@/app/components/plugins/card', () => ({
default: ({ payload, footer }: { payload: Plugin, footer?: React.ReactNode }) => (
<div data-testid={`card-${payload.name}`}>
@ -131,7 +116,6 @@ vi.mock('@/app/components/plugins/card', () => ({
),
}))
// Mock CardMoreInfo component
vi.mock('@/app/components/plugins/card/card-more-info', () => ({
default: ({ downloadCount, tags }: { downloadCount: number, tags: string[] }) => (
<div data-testid="card-more-info">
@ -141,7 +125,6 @@ vi.mock('@/app/components/plugins/card/card-more-info', () => ({
),
}))
// Mock InstallFromMarketplace component
vi.mock('@/app/components/plugins/install-plugin/install-from-marketplace', () => ({
default: ({ onClose }: { onClose: () => void }) => (
<div data-testid="install-from-marketplace">
@ -150,15 +133,13 @@ vi.mock('@/app/components/plugins/install-plugin/install-from-marketplace', () =
),
}))
// Mock SortDropdown component
vi.mock('../sort-dropdown', () => ({
vi.mock('../../sort-dropdown', () => ({
default: () => (
<div data-testid="sort-dropdown">Sort</div>
),
}))
// Mock Empty component
vi.mock('../empty', () => ({
vi.mock('../../empty', () => ({
default: ({ className }: { className?: string }) => (
<div data-testid="empty-component" className={className}>
No plugins found
@ -166,7 +147,6 @@ vi.mock('../empty', () => ({
),
}))
// Mock Loading component
vi.mock('@/app/components/base/loading', () => ({
default: () => <div data-testid="loading-component">Loading...</div>,
}))

View File

@ -1,10 +1,10 @@
import type { Tag } from '@/app/components/plugins/hooks'
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import SearchBox from './index'
import SearchBoxWrapper from './search-box-wrapper'
import MarketplaceTrigger from './trigger/marketplace'
import ToolSelectorTrigger from './trigger/tool-selector'
import SearchBox from '../index'
import SearchBoxWrapper from '../search-box-wrapper'
import MarketplaceTrigger from '../trigger/marketplace'
import ToolSelectorTrigger from '../trigger/tool-selector'
// ================================
// Mock external dependencies only
@ -36,7 +36,7 @@ const { mockSearchPluginText, mockHandleSearchPluginTextChange, mockFilterPlugin
}
})
vi.mock('../atoms', () => ({
vi.mock('../../atoms', () => ({
useSearchPluginText: () => [mockSearchPluginText, mockHandleSearchPluginTextChange],
useFilterPluginTags: () => [mockFilterPluginTags, mockHandleFilterPluginTagsChange],
}))

View File

@ -1,7 +1,7 @@
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'
import SortDropdown from '../index'
// ================================
// Mock external dependencies only
@ -31,7 +31,7 @@ vi.mock('#i18n', () => ({
let mockSort: { sortBy: string, sortOrder: string } = { sortBy: 'install_count', sortOrder: 'DESC' }
const mockHandleSortChange = vi.fn()
vi.mock('../atoms', () => ({
vi.mock('../../atoms', () => ({
useMarketplaceSort: () => [mockSort, mockHandleSortChange],
}))
@ -39,7 +39,7 @@ vi.mock('../atoms', () => ({
let mockPortalOpenState = false
vi.mock('@/app/components/base/portal-to-follow-elem', () => ({
PortalToFollowElem: ({ children, open, onOpenChange }: {
PortalToFollowElem: ({ children, open, onOpenChange: _onOpenChange }: {
children: React.ReactNode
open: boolean
onOpenChange: (open: boolean) => void

View File

@ -1,22 +1,6 @@
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import AuthorizedInDataSourceNode from './authorized-in-data-source-node'
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => {
const map: Record<string, string> = {
'auth.authorization': '1 Authorization',
'auth.authorizations': 'Multiple Authorizations',
}
return map[key] || key
},
}),
}))
vi.mock('@remixicon/react', () => ({
RiEqualizer2Line: () => <span data-testid="equalizer-icon" />,
}))
import AuthorizedInDataSourceNode from '../authorized-in-data-source-node'
vi.mock('@/app/components/header/indicator', () => ({
default: ({ color }: { color: string }) => <span data-testid="indicator" data-color={color} />,
@ -40,12 +24,12 @@ describe('AuthorizedInDataSourceNode', () => {
it('renders singular text for 1 authorization', () => {
render(<AuthorizedInDataSourceNode authorizationsNum={1} onJumpToDataSourcePage={mockOnJump} />)
expect(screen.getByText('1 Authorization')).toBeInTheDocument()
expect(screen.getByText('plugin.auth.authorization')).toBeInTheDocument()
})
it('renders plural text for multiple authorizations', () => {
render(<AuthorizedInDataSourceNode authorizationsNum={3} onJumpToDataSourcePage={mockOnJump} />)
expect(screen.getByText('Multiple Authorizations')).toBeInTheDocument()
expect(screen.getByText('plugin.auth.authorizations')).toBeInTheDocument()
})
it('calls onJumpToDataSourcePage when button is clicked', () => {
@ -54,8 +38,8 @@ describe('AuthorizedInDataSourceNode', () => {
expect(mockOnJump).toHaveBeenCalledTimes(1)
})
it('renders equalizer icon', () => {
it('renders settings button', () => {
render(<AuthorizedInDataSourceNode authorizationsNum={1} onJumpToDataSourcePage={mockOnJump} />)
expect(screen.getByTestId('equalizer-icon')).toBeInTheDocument()
expect(screen.getByRole('button')).toBeInTheDocument()
})
})

View File

@ -1,9 +1,9 @@
import type { ReactNode } from 'react'
import type { Credential, PluginPayload } from './types'
import type { Credential, PluginPayload } from '../types'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { fireEvent, render, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { AuthCategory, CredentialTypeEnum } from './types'
import { AuthCategory, CredentialTypeEnum } from '../types'
// ==================== Mock Setup ====================
@ -109,7 +109,7 @@ describe('AuthorizedInNode Component', () => {
})
it('should render with workspace default when no credentialId', async () => {
const AuthorizedInNode = (await import('./authorized-in-node')).default
const AuthorizedInNode = (await import('../authorized-in-node')).default
const pluginPayload = createPluginPayload()
render(
<AuthorizedInNode pluginPayload={pluginPayload} onAuthorizationItemClick={vi.fn()} />,
@ -119,7 +119,7 @@ describe('AuthorizedInNode Component', () => {
})
it('should render credential name when credentialId matches', async () => {
const AuthorizedInNode = (await import('./authorized-in-node')).default
const AuthorizedInNode = (await import('../authorized-in-node')).default
const credential = createCredential({ id: 'selected-id', name: 'My Credential' })
mockGetPluginCredentialInfo.mockReturnValue({
credentials: [credential],
@ -135,7 +135,7 @@ describe('AuthorizedInNode Component', () => {
})
it('should show auth removed when credentialId not found', async () => {
const AuthorizedInNode = (await import('./authorized-in-node')).default
const AuthorizedInNode = (await import('../authorized-in-node')).default
mockGetPluginCredentialInfo.mockReturnValue({
credentials: [createCredential()],
supported_credential_types: [CredentialTypeEnum.API_KEY],
@ -150,7 +150,7 @@ describe('AuthorizedInNode Component', () => {
})
it('should show unavailable when credential is not allowed', async () => {
const AuthorizedInNode = (await import('./authorized-in-node')).default
const AuthorizedInNode = (await import('../authorized-in-node')).default
const credential = createCredential({
id: 'unavailable-id',
not_allowed_to_use: true,
@ -171,7 +171,7 @@ describe('AuthorizedInNode Component', () => {
})
it('should show unavailable when default credential is not allowed', async () => {
const AuthorizedInNode = (await import('./authorized-in-node')).default
const AuthorizedInNode = (await import('../authorized-in-node')).default
const credential = createCredential({
is_default: true,
not_allowed_to_use: true,
@ -191,7 +191,7 @@ describe('AuthorizedInNode Component', () => {
})
it('should call onAuthorizationItemClick when clicking', async () => {
const AuthorizedInNode = (await import('./authorized-in-node')).default
const AuthorizedInNode = (await import('../authorized-in-node')).default
const onAuthorizationItemClick = vi.fn()
const pluginPayload = createPluginPayload()
render(
@ -204,7 +204,7 @@ describe('AuthorizedInNode Component', () => {
})
it('should be memoized', async () => {
const AuthorizedInNodeModule = await import('./authorized-in-node')
const AuthorizedInNodeModule = await import('../authorized-in-node')
expect(typeof AuthorizedInNodeModule.default).toBe('object')
})
})

View File

@ -1,8 +1,8 @@
import type { ReactNode } from 'react'
import type { Credential, PluginPayload } from './types'
import type { Credential, PluginPayload } from '../types'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { describe, expect, it, vi } from 'vitest'
import { AuthCategory, CredentialTypeEnum } from './types'
import { AuthCategory, CredentialTypeEnum } from '../types'
const mockGetPluginCredentialInfo = vi.fn()
const mockDeletePluginCredential = vi.fn()
@ -136,7 +136,7 @@ const _createCredentialList = (count: number, overrides: Partial<Credential>[] =
describe('Index Exports', () => {
it('should export all required components and hooks', async () => {
const exports = await import('./index')
const exports = await import('../index')
expect(exports.AddApiKeyButton).toBeDefined()
expect(exports.AddOAuthButton).toBeDefined()
@ -151,7 +151,7 @@ describe('Index Exports', () => {
}, 15000)
it('should export AuthCategory enum', async () => {
const exports = await import('./index')
const exports = await import('../index')
expect(exports.AuthCategory).toBeDefined()
expect(exports.AuthCategory.tool).toBe('tool')
@ -161,7 +161,7 @@ describe('Index Exports', () => {
}, 15000)
it('should export CredentialTypeEnum', async () => {
const exports = await import('./index')
const exports = await import('../index')
expect(exports.CredentialTypeEnum).toBeDefined()
expect(exports.CredentialTypeEnum.OAUTH2).toBe('oauth2')

View File

@ -1,9 +1,9 @@
import type { ReactNode } from 'react'
import type { Credential, PluginPayload } from './types'
import type { Credential, PluginPayload } from '../types'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { fireEvent, render, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { AuthCategory, CredentialTypeEnum } from './types'
import { AuthCategory, CredentialTypeEnum } from '../types'
// ==================== Mock Setup ====================
@ -109,7 +109,7 @@ describe('PluginAuthInAgent Component', () => {
})
it('should render Authorize when not authorized', async () => {
const PluginAuthInAgent = (await import('./plugin-auth-in-agent')).default
const PluginAuthInAgent = (await import('../plugin-auth-in-agent')).default
mockGetPluginCredentialInfo.mockReturnValue({
credentials: [],
supported_credential_types: [CredentialTypeEnum.API_KEY],
@ -124,7 +124,7 @@ describe('PluginAuthInAgent Component', () => {
})
it('should render Authorized with workspace default when authorized', async () => {
const PluginAuthInAgent = (await import('./plugin-auth-in-agent')).default
const PluginAuthInAgent = (await import('../plugin-auth-in-agent')).default
const pluginPayload = createPluginPayload()
render(
<PluginAuthInAgent pluginPayload={pluginPayload} />,
@ -135,7 +135,7 @@ describe('PluginAuthInAgent Component', () => {
})
it('should show credential name when credentialId is provided', async () => {
const PluginAuthInAgent = (await import('./plugin-auth-in-agent')).default
const PluginAuthInAgent = (await import('../plugin-auth-in-agent')).default
const credential = createCredential({ id: 'selected-id', name: 'Selected Credential' })
mockGetPluginCredentialInfo.mockReturnValue({
credentials: [credential],
@ -151,7 +151,7 @@ describe('PluginAuthInAgent Component', () => {
})
it('should show auth removed when credential not found', async () => {
const PluginAuthInAgent = (await import('./plugin-auth-in-agent')).default
const PluginAuthInAgent = (await import('../plugin-auth-in-agent')).default
mockGetPluginCredentialInfo.mockReturnValue({
credentials: [createCredential()],
supported_credential_types: [CredentialTypeEnum.API_KEY],
@ -166,7 +166,7 @@ describe('PluginAuthInAgent Component', () => {
})
it('should show unavailable when credential is not allowed to use', async () => {
const PluginAuthInAgent = (await import('./plugin-auth-in-agent')).default
const PluginAuthInAgent = (await import('../plugin-auth-in-agent')).default
const credential = createCredential({
id: 'unavailable-id',
name: 'Unavailable Credential',
@ -188,7 +188,7 @@ describe('PluginAuthInAgent Component', () => {
})
it('should call onAuthorizationItemClick when item is clicked', async () => {
const PluginAuthInAgent = (await import('./plugin-auth-in-agent')).default
const PluginAuthInAgent = (await import('../plugin-auth-in-agent')).default
const onAuthorizationItemClick = vi.fn()
const pluginPayload = createPluginPayload()
render(
@ -201,7 +201,7 @@ describe('PluginAuthInAgent Component', () => {
})
it('should trigger handleAuthorizationItemClick and close popup when item is clicked', async () => {
const PluginAuthInAgent = (await import('./plugin-auth-in-agent')).default
const PluginAuthInAgent = (await import('../plugin-auth-in-agent')).default
const onAuthorizationItemClick = vi.fn()
const credential = createCredential({ id: 'test-cred-id', name: 'Test Credential' })
mockGetPluginCredentialInfo.mockReturnValue({
@ -223,7 +223,7 @@ describe('PluginAuthInAgent Component', () => {
})
it('should call onAuthorizationItemClick with credential id when specific credential is clicked', async () => {
const PluginAuthInAgent = (await import('./plugin-auth-in-agent')).default
const PluginAuthInAgent = (await import('../plugin-auth-in-agent')).default
const onAuthorizationItemClick = vi.fn()
const credential = createCredential({
id: 'specific-cred-id',
@ -249,7 +249,7 @@ describe('PluginAuthInAgent Component', () => {
})
it('should be memoized', async () => {
const PluginAuthInAgentModule = await import('./plugin-auth-in-agent')
const PluginAuthInAgentModule = await import('../plugin-auth-in-agent')
expect(typeof PluginAuthInAgentModule.default).toBe('object')
})
})

View File

@ -1,21 +1,6 @@
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import PluginAuthInDataSourceNode from './plugin-auth-in-datasource-node'
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => {
const map: Record<string, string> = {
'integrations.connect': 'Connect',
}
return map[key] || key
},
}),
}))
vi.mock('@remixicon/react', () => ({
RiAddLine: () => <span data-testid="add-icon" />,
}))
import PluginAuthInDataSourceNode from '../plugin-auth-in-datasource-node'
describe('PluginAuthInDataSourceNode', () => {
const mockOnJump = vi.fn()
@ -30,18 +15,17 @@ describe('PluginAuthInDataSourceNode', () => {
it('renders connect button when not authorized', () => {
render(<PluginAuthInDataSourceNode onJumpToDataSourcePage={mockOnJump} />)
expect(screen.getByText('Connect')).toBeInTheDocument()
expect(screen.getByTestId('add-icon')).toBeInTheDocument()
expect(screen.getByText('common.integrations.connect')).toBeInTheDocument()
})
it('renders connect button', () => {
render(<PluginAuthInDataSourceNode onJumpToDataSourcePage={mockOnJump} />)
expect(screen.getByRole('button', { name: /Connect/ })).toBeInTheDocument()
expect(screen.getByRole('button', { name: /common\.integrations\.connect/ })).toBeInTheDocument()
})
it('calls onJumpToDataSourcePage when connect button is clicked', () => {
render(<PluginAuthInDataSourceNode onJumpToDataSourcePage={mockOnJump} />)
fireEvent.click(screen.getByRole('button', { name: /Connect/ }))
fireEvent.click(screen.getByRole('button', { name: /common\.integrations\.connect/ }))
expect(mockOnJump).toHaveBeenCalledTimes(1)
})
@ -51,7 +35,7 @@ describe('PluginAuthInDataSourceNode', () => {
<div data-testid="child-content">Data Source Connected</div>
</PluginAuthInDataSourceNode>,
)
expect(screen.queryByText('Connect')).not.toBeInTheDocument()
expect(screen.queryByText('common.integrations.connect')).not.toBeInTheDocument()
expect(screen.getByTestId('child-content')).toBeInTheDocument()
})
@ -61,7 +45,7 @@ describe('PluginAuthInDataSourceNode', () => {
<div data-testid="child-content">Data Source Connected</div>
</PluginAuthInDataSourceNode>,
)
expect(screen.getByText('Connect')).toBeInTheDocument()
expect(screen.getByText('common.integrations.connect')).toBeInTheDocument()
expect(screen.queryByTestId('child-content')).not.toBeInTheDocument()
})
})

View File

@ -1,14 +1,14 @@
import { cleanup, render, screen } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import PluginAuth from './plugin-auth'
import { AuthCategory } from './types'
import PluginAuth from '../plugin-auth'
import { AuthCategory } from '../types'
const mockUsePluginAuth = vi.fn()
vi.mock('./hooks/use-plugin-auth', () => ({
vi.mock('../hooks/use-plugin-auth', () => ({
usePluginAuth: (...args: unknown[]) => mockUsePluginAuth(...args),
}))
vi.mock('./authorize', () => ({
vi.mock('../authorize', () => ({
default: ({ pluginPayload }: { pluginPayload: { provider: string } }) => (
<div data-testid="authorize">
Authorize:
@ -17,7 +17,7 @@ vi.mock('./authorize', () => ({
),
}))
vi.mock('./authorized', () => ({
vi.mock('../authorized', () => ({
default: ({ pluginPayload }: { pluginPayload: { provider: string } }) => (
<div data-testid="authorized">
Authorized:

View File

@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest'
import { transformFormSchemasSecretInput } from './utils'
import { transformFormSchemasSecretInput } from '../utils'
describe('plugin-auth/utils', () => {
describe('transformFormSchemasSecretInput', () => {

View File

@ -1,10 +1,10 @@
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { AuthCategory } from '../types'
import AddApiKeyButton from './add-api-key-button'
import { AuthCategory } from '../../types'
import AddApiKeyButton from '../add-api-key-button'
let _mockModalOpen = false
vi.mock('./api-key-modal', () => ({
vi.mock('../api-key-modal', () => ({
default: ({ onClose, onUpdate }: { onClose: () => void, onUpdate?: () => void }) => {
_mockModalOpen = true
return (

View File

@ -1,17 +1,11 @@
import { fireEvent, render, screen } from '@testing-library/react'
import * as React from 'react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { AuthCategory } from '../types'
import { AuthCategory } from '../../types'
const mockGetPluginOAuthUrl = vi.fn().mockResolvedValue({ authorization_url: 'https://auth.example.com' })
const mockOpenOAuthPopup = vi.fn()
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}))
vi.mock('@/hooks/use-i18n', () => ({
useRenderI18nObject: () => (obj: Record<string, string> | string) => typeof obj === 'string' ? obj : obj.en_US || '',
}))
@ -20,7 +14,7 @@ vi.mock('@/hooks/use-oauth', () => ({
openOAuthPopup: (...args: unknown[]) => mockOpenOAuthPopup(...args),
}))
vi.mock('../hooks/use-credential', () => ({
vi.mock('../../hooks/use-credential', () => ({
useGetPluginOAuthUrlHook: () => ({
mutateAsync: mockGetPluginOAuthUrl,
}),
@ -36,7 +30,7 @@ vi.mock('../hooks/use-credential', () => ({
}),
}))
vi.mock('./oauth-client-settings', () => ({
vi.mock('../oauth-client-settings', () => ({
default: ({ onClose }: { onClose: () => void }) => (
<div data-testid="oauth-settings-modal">
<button data-testid="oauth-settings-close" onClick={onClose}>Close</button>
@ -52,23 +46,17 @@ vi.mock('@/utils/classnames', () => ({
cn: (...args: unknown[]) => args.filter(Boolean).join(' '),
}))
vi.mock('@remixicon/react', () => ({
RiClipboardLine: () => <span data-testid="icon-clipboard" />,
RiEqualizer2Line: () => <span data-testid="icon-equalizer" />,
RiInformation2Fill: () => <span data-testid="icon-info" />,
}))
const basePayload = {
category: AuthCategory.tool,
provider: 'test-provider',
}
describe('AddOAuthButton', () => {
let AddOAuthButton: (typeof import('./add-oauth-button'))['default']
let AddOAuthButton: (typeof import('../add-oauth-button'))['default']
beforeEach(async () => {
vi.clearAllMocks()
const mod = await import('./add-oauth-button')
const mod = await import('../add-oauth-button')
AddOAuthButton = mod.default
})

View File

@ -1,27 +1,21 @@
import type { ApiKeyModalProps } from './api-key-modal'
import type { ApiKeyModalProps } from '../api-key-modal'
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import * as React from 'react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { AuthCategory } from '../types'
import { AuthCategory } from '../../types'
const mockNotify = vi.fn()
const mockAddPluginCredential = vi.fn().mockResolvedValue({})
const mockUpdatePluginCredential = vi.fn().mockResolvedValue({})
const mockFormValues = { isCheckValidated: true, values: { __name__: 'My Key', api_key: 'sk-123' } }
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}))
vi.mock('@/app/components/base/toast', () => ({
useToastContext: () => ({
notify: mockNotify,
}),
}))
vi.mock('../hooks/use-credential', () => ({
vi.mock('../../hooks/use-credential', () => ({
useAddPluginCredentialHook: () => ({
mutateAsync: mockAddPluginCredential,
}),
@ -36,11 +30,11 @@ vi.mock('../hooks/use-credential', () => ({
}),
}))
vi.mock('../../readme-panel/entrance', () => ({
vi.mock('../../../readme-panel/entrance', () => ({
ReadmeEntrance: () => <div data-testid="readme-entrance" />,
}))
vi.mock('../../readme-panel/store', () => ({
vi.mock('../../../readme-panel/store', () => ({
ReadmeShowType: { modal: 'modal' },
}))
@ -93,14 +87,14 @@ describe('ApiKeyModal', () => {
beforeEach(async () => {
vi.clearAllMocks()
const mod = await import('./api-key-modal')
const mod = await import('../api-key-modal')
ApiKeyModal = mod.default
})
it('should render modal with correct title', () => {
render(<ApiKeyModal pluginPayload={basePayload} />)
expect(screen.getByTestId('modal-title')).toHaveTextContent('auth.useApiAuth')
expect(screen.getByTestId('modal-title')).toHaveTextContent('plugin.auth.useApiAuth')
})
it('should render auth form when data is loaded', () => {

View File

@ -1,10 +1,10 @@
import type { ReactNode } from 'react'
import type { PluginPayload } from '../types'
import type { PluginPayload } from '../../types'
import type { FormSchema } from '@/app/components/base/form/types'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { AuthCategory } from '../types'
import { AuthCategory } from '../../types'
// Create a wrapper with QueryClientProvider
const createTestQueryClient = () =>
@ -36,7 +36,7 @@ const mockAddPluginCredential = vi.fn()
const mockUpdatePluginCredential = vi.fn()
const mockGetPluginCredentialSchema = vi.fn()
vi.mock('../hooks/use-credential', () => ({
vi.mock('../../hooks/use-credential', () => ({
useGetPluginOAuthUrlHook: () => ({
mutateAsync: mockGetPluginOAuthUrl,
}),
@ -117,12 +117,12 @@ const createFormSchema = (overrides: Partial<FormSchema> = {}): FormSchema => ({
// ==================== AddApiKeyButton Tests ====================
describe('AddApiKeyButton', () => {
let AddApiKeyButton: typeof import('./add-api-key-button').default
let AddApiKeyButton: typeof import('../add-api-key-button').default
beforeEach(async () => {
vi.clearAllMocks()
mockGetPluginCredentialSchema.mockReturnValue([])
const importedAddApiKeyButton = await import('./add-api-key-button')
const importedAddApiKeyButton = await import('../add-api-key-button')
AddApiKeyButton = importedAddApiKeyButton.default
})
@ -327,7 +327,7 @@ describe('AddApiKeyButton', () => {
describe('Memoization', () => {
it('should be a memoized component', async () => {
const AddApiKeyButtonDefault = (await import('./add-api-key-button')).default
const AddApiKeyButtonDefault = (await import('../add-api-key-button')).default
expect(typeof AddApiKeyButtonDefault).toBe('object')
})
})
@ -335,7 +335,7 @@ describe('AddApiKeyButton', () => {
// ==================== AddOAuthButton Tests ====================
describe('AddOAuthButton', () => {
let AddOAuthButton: typeof import('./add-oauth-button').default
let AddOAuthButton: typeof import('../add-oauth-button').default
beforeEach(async () => {
vi.clearAllMocks()
@ -347,7 +347,7 @@ describe('AddOAuthButton', () => {
redirect_uri: 'https://example.com/callback',
})
mockGetPluginOAuthUrl.mockResolvedValue({ authorization_url: 'https://oauth.example.com/auth' })
const importedAddOAuthButton = await import('./add-oauth-button')
const importedAddOAuthButton = await import('../add-oauth-button')
AddOAuthButton = importedAddOAuthButton.default
})
@ -856,7 +856,7 @@ describe('AddOAuthButton', () => {
// ==================== ApiKeyModal Tests ====================
describe('ApiKeyModal', () => {
let ApiKeyModal: typeof import('./api-key-modal').default
let ApiKeyModal: typeof import('../api-key-modal').default
beforeEach(async () => {
vi.clearAllMocks()
@ -870,7 +870,7 @@ describe('ApiKeyModal', () => {
isCheckValidated: false,
values: {},
})
const importedApiKeyModal = await import('./api-key-modal')
const importedApiKeyModal = await import('../api-key-modal')
ApiKeyModal = importedApiKeyModal.default
})
@ -1272,13 +1272,13 @@ describe('ApiKeyModal', () => {
// ==================== OAuthClientSettings Tests ====================
describe('OAuthClientSettings', () => {
let OAuthClientSettings: typeof import('./oauth-client-settings').default
let OAuthClientSettings: typeof import('../oauth-client-settings').default
beforeEach(async () => {
vi.clearAllMocks()
mockSetPluginOAuthCustomClient.mockResolvedValue({})
mockDeletePluginOAuthCustomClient.mockResolvedValue({})
const importedOAuthClientSettings = await import('./oauth-client-settings')
const importedOAuthClientSettings = await import('../oauth-client-settings')
OAuthClientSettings = importedOAuthClientSettings.default
})
@ -2193,7 +2193,7 @@ describe('OAuthClientSettings', () => {
describe('Memoization', () => {
it('should be a memoized component', async () => {
const OAuthClientSettingsDefault = (await import('./oauth-client-settings')).default
const OAuthClientSettingsDefault = (await import('../oauth-client-settings')).default
expect(typeof OAuthClientSettingsDefault).toBe('object')
})
})
@ -2216,7 +2216,7 @@ describe('Authorize Components Integration', () => {
describe('AddApiKeyButton -> ApiKeyModal Flow', () => {
it('should open ApiKeyModal when AddApiKeyButton is clicked', async () => {
const AddApiKeyButton = (await import('./add-api-key-button')).default
const AddApiKeyButton = (await import('../add-api-key-button')).default
const pluginPayload = createPluginPayload()
render(<AddApiKeyButton pluginPayload={pluginPayload} />, { wrapper: createWrapper() })
@ -2231,7 +2231,7 @@ describe('Authorize Components Integration', () => {
describe('AddOAuthButton -> OAuthClientSettings Flow', () => {
it('should open OAuthClientSettings when setup button is clicked', async () => {
const AddOAuthButton = (await import('./add-oauth-button')).default
const AddOAuthButton = (await import('../add-oauth-button')).default
const pluginPayload = createPluginPayload()
mockGetPluginOAuthClientSchema.mockReturnValue({
schema: [createFormSchema({ name: 'client_id', label: 'Client ID' })],

View File

@ -1,10 +1,10 @@
import type { ReactNode } from 'react'
import type { PluginPayload } from '../types'
import type { PluginPayload } from '../../types'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { render, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { AuthCategory } from '../types'
import Authorize from './index'
import { AuthCategory } from '../../types'
import Authorize from '../index'
// Create a wrapper with QueryClientProvider for real component testing
const createTestQueryClient = () =>
@ -29,7 +29,7 @@ const createWrapper = () => {
// Mock API hooks - only mock network-related hooks
const mockGetPluginOAuthClientSchema = vi.fn()
vi.mock('../hooks/use-credential', () => ({
vi.mock('../../hooks/use-credential', () => ({
useGetPluginOAuthUrlHook: () => ({
mutateAsync: vi.fn().mockResolvedValue({ authorization_url: '' }),
}),
@ -568,7 +568,7 @@ describe('Authorize', () => {
// ==================== Component Memoization ====================
describe('Component Memoization', () => {
it('should be a memoized component (exported with memo)', async () => {
const AuthorizeDefault = (await import('./index')).default
const AuthorizeDefault = (await import('../index')).default
expect(AuthorizeDefault).toBeDefined()
// memo wrapped components are React elements with $$typeof
expect(typeof AuthorizeDefault).toBe('object')

View File

@ -1,7 +1,7 @@
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import * as React from 'react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { AuthCategory } from '../types'
import { AuthCategory } from '../../types'
const mockNotify = vi.fn()
const mockSetPluginOAuthCustomClient = vi.fn().mockResolvedValue({})
@ -9,19 +9,13 @@ const mockDeletePluginOAuthCustomClient = vi.fn().mockResolvedValue({})
const mockInvalidPluginOAuthClientSchema = vi.fn()
const mockFormValues = { isCheckValidated: true, values: { __oauth_client__: 'custom', client_id: 'test-id' } }
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}))
vi.mock('@/app/components/base/toast', () => ({
useToastContext: () => ({
notify: mockNotify,
}),
}))
vi.mock('../hooks/use-credential', () => ({
vi.mock('../../hooks/use-credential', () => ({
useSetPluginOAuthCustomClientHook: () => ({
mutateAsync: mockSetPluginOAuthCustomClient,
}),
@ -31,11 +25,11 @@ vi.mock('../hooks/use-credential', () => ({
useInvalidPluginOAuthClientSchemaHook: () => mockInvalidPluginOAuthClientSchema,
}))
vi.mock('../../readme-panel/entrance', () => ({
vi.mock('../../../readme-panel/entrance', () => ({
ReadmeEntrance: () => <div data-testid="readme-entrance" />,
}))
vi.mock('../../readme-panel/store', () => ({
vi.mock('../../../readme-panel/store', () => ({
ReadmeShowType: { modal: 'modal' },
}))
@ -89,11 +83,11 @@ const defaultSchemas = [
] as never
describe('OAuthClientSettings', () => {
let OAuthClientSettings: (typeof import('./oauth-client-settings'))['default']
let OAuthClientSettings: (typeof import('../oauth-client-settings'))['default']
beforeEach(async () => {
vi.clearAllMocks()
const mod = await import('./oauth-client-settings')
const mod = await import('../oauth-client-settings')
OAuthClientSettings = mod.default
})
@ -105,7 +99,7 @@ describe('OAuthClientSettings', () => {
/>,
)
expect(screen.getByTestId('modal-title')).toHaveTextContent('auth.oauthClientSettings')
expect(screen.getByTestId('modal-title')).toHaveTextContent('plugin.auth.oauthClientSettings')
})
it('should render auth form', () => {

View File

@ -1,10 +1,10 @@
import type { ReactNode } from 'react'
import type { Credential, PluginPayload } from '../types'
import type { Credential, PluginPayload } from '../../types'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { AuthCategory, CredentialTypeEnum } from '../types'
import Authorized from './index'
import { AuthCategory, CredentialTypeEnum } from '../../types'
import Authorized from '../index'
// ==================== Mock Setup ====================
@ -13,7 +13,7 @@ const mockDeletePluginCredential = vi.fn()
const mockSetPluginDefaultCredential = vi.fn()
const mockUpdatePluginCredential = vi.fn()
vi.mock('../hooks/use-credential', () => ({
vi.mock('../../hooks/use-credential', () => ({
useDeletePluginCredentialHook: () => ({
mutateAsync: mockDeletePluginCredential,
}),
@ -1620,7 +1620,7 @@ describe('Authorized Component', () => {
// ==================== Memoization Test ====================
describe('Memoization', () => {
it('should be memoized', async () => {
const AuthorizedModule = await import('./index')
const AuthorizedModule = await import('../index')
// memo returns an object with $$typeof
expect(typeof AuthorizedModule.default).toBe('object')
})

View File

@ -1,8 +1,8 @@
import type { Credential } from '../types'
import type { Credential } from '../../types'
import { fireEvent, render, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { CredentialTypeEnum } from '../types'
import Item from './item'
import { CredentialTypeEnum } from '../../types'
import Item from '../item'
// ==================== Test Utilities ====================
@ -829,7 +829,7 @@ describe('Item Component', () => {
// ==================== Memoization Test ====================
describe('Memoization', () => {
it('should be memoized', async () => {
const ItemModule = await import('./item')
const ItemModule = await import('../item')
// memo returns an object with $$typeof
expect(typeof ItemModule.default).toBe('object')
})

View File

@ -1,6 +1,6 @@
import { renderHook } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { AuthCategory, CredentialTypeEnum } from '../types'
import { AuthCategory, CredentialTypeEnum } from '../../types'
import {
useAddPluginCredentialHook,
useDeletePluginCredentialHook,
@ -14,7 +14,7 @@ import {
useSetPluginDefaultCredentialHook,
useSetPluginOAuthCustomClientHook,
useUpdatePluginCredentialHook,
} from './use-credential'
} from '../use-credential'
// Mock service hooks
const mockUseGetPluginCredentialInfo = vi.fn().mockReturnValue({ data: null, isLoading: false })

View File

@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest'
import { AuthCategory, CredentialTypeEnum } from '../types'
import { useGetApi } from './use-get-api'
import { AuthCategory, CredentialTypeEnum } from '../../types'
import { useGetApi } from '../use-get-api'
describe('useGetApi', () => {
const provider = 'test-provider'

View File

@ -3,27 +3,21 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { act, renderHook } from '@testing-library/react'
import * as React from 'react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { usePluginAuthAction } from '../hooks/use-plugin-auth-action'
import { AuthCategory } from '../types'
import { usePluginAuthAction } from '../../hooks/use-plugin-auth-action'
import { AuthCategory } from '../../types'
const mockDeletePluginCredential = vi.fn().mockResolvedValue({})
const mockSetPluginDefaultCredential = vi.fn().mockResolvedValue({})
const mockUpdatePluginCredential = vi.fn().mockResolvedValue({})
const mockNotify = vi.fn()
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}))
vi.mock('@/app/components/base/toast', () => ({
useToastContext: () => ({
notify: mockNotify,
}),
}))
vi.mock('../hooks/use-credential', () => ({
vi.mock('../../hooks/use-credential', () => ({
useDeletePluginCredentialHook: () => ({
mutateAsync: mockDeletePluginCredential,
}),

View File

@ -1,7 +1,7 @@
import { renderHook } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { AuthCategory, CredentialTypeEnum } from '../types'
import { usePluginAuth } from './use-plugin-auth'
import { AuthCategory, CredentialTypeEnum } from '../../types'
import { usePluginAuth } from '../use-plugin-auth'
// Mock dependencies
const mockCredentials = [
@ -17,7 +17,7 @@ const mockCredentialInfo = vi.fn().mockReturnValue({
const mockInvalidate = vi.fn()
vi.mock('./use-credential', () => ({
vi.mock('../use-credential', () => ({
useGetPluginCredentialInfoHook: (_payload: unknown, enable?: boolean) => ({
data: enable ? mockCredentialInfo() : undefined,
isLoading: false,

View File

@ -1,18 +1,7 @@
import type { PluginDetail } from '@/app/components/plugins/types'
import { render, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import ActionList from './action-list'
// Mock dependencies
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string, options?: Record<string, unknown>) => {
if (options?.num !== undefined)
return `${options.num} ${options.action || 'actions'}`
return key
},
}),
}))
import ActionList from '../action-list'
const mockToolData = [
{ name: 'tool-1', label: { en_US: 'Tool 1' } },
@ -82,7 +71,7 @@ describe('ActionList', () => {
const detail = createPluginDetail()
render(<ActionList detail={detail} />)
expect(screen.getByText('2 actions')).toBeInTheDocument()
expect(screen.getByText('plugin.detailPanel.actionNum:{"num":2,"action":"actions"}')).toBeInTheDocument()
expect(screen.getAllByTestId('tool-item')).toHaveLength(2)
})
@ -124,7 +113,7 @@ describe('ActionList', () => {
// The provider key is constructed from plugin_id and tool identity name
// When they match the mock, it renders
expect(screen.getByText('2 actions')).toBeInTheDocument()
expect(screen.getByText('plugin.detailPanel.actionNum:{"num":2,"action":"actions"}')).toBeInTheDocument()
})
})
})

View File

@ -1,17 +1,7 @@
import type { PluginDetail, StrategyDetail } from '@/app/components/plugins/types'
import { render, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import AgentStrategyList from './agent-strategy-list'
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string, options?: Record<string, unknown>) => {
if (options?.num !== undefined)
return `${options.num} ${options.strategy || 'strategies'}`
return key
},
}),
}))
import AgentStrategyList from '../agent-strategy-list'
const mockStrategies = [
{
@ -91,7 +81,7 @@ describe('AgentStrategyList', () => {
it('should render strategy items when data is available', () => {
render(<AgentStrategyList detail={createPluginDetail()} />)
expect(screen.getByText('1 strategy')).toBeInTheDocument()
expect(screen.getByText('plugin.detailPanel.strategyNum:{"num":1,"strategy":"strategy"}')).toBeInTheDocument()
expect(screen.getByTestId('strategy-item')).toBeInTheDocument()
})
@ -114,7 +104,7 @@ describe('AgentStrategyList', () => {
}
render(<AgentStrategyList detail={createPluginDetail()} />)
expect(screen.getByText('2 strategies')).toBeInTheDocument()
expect(screen.getByText('plugin.detailPanel.strategyNum:{"num":2,"strategy":"strategies"}')).toBeInTheDocument()
expect(screen.getAllByTestId('strategy-item')).toHaveLength(2)
})
})

View File

@ -1,17 +1,7 @@
import type { PluginDetail } from '@/app/components/plugins/types'
import { render, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import DatasourceActionList from './datasource-action-list'
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string, options?: Record<string, unknown>) => {
if (options?.num !== undefined)
return `${options.num} ${options.action || 'actions'}`
return key
},
}),
}))
import DatasourceActionList from '../datasource-action-list'
const mockDataSourceList = [
{ plugin_id: 'test-plugin', name: 'Data Source 1' },
@ -72,7 +62,7 @@ describe('DatasourceActionList', () => {
render(<DatasourceActionList detail={createPluginDetail()} />)
// The component always shows "0 action" because data is hardcoded as empty array
expect(screen.getByText('0 action')).toBeInTheDocument()
expect(screen.getByText('plugin.detailPanel.actionNum:{"num":0,"action":"action"}')).toBeInTheDocument()
})
it('should return null when no provider found', () => {
@ -98,7 +88,7 @@ describe('DatasourceActionList', () => {
render(<DatasourceActionList detail={detail} />)
expect(screen.getByText('0 action')).toBeInTheDocument()
expect(screen.getByText('plugin.detailPanel.actionNum:{"num":0,"action":"action"}')).toBeInTheDocument()
})
})
})

View File

@ -1,10 +1,10 @@
import type { PluginDetail } from '../types'
import type { PluginDetail } from '../../types'
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import * as amplitude from '@/app/components/base/amplitude'
import Toast from '@/app/components/base/toast'
import { PluginSource } from '../types'
import DetailHeader from './detail-header'
import { PluginSource } from '../../types'
import DetailHeader from '../detail-header'
const {
mockSetShowUpdatePluginModal,
@ -24,12 +24,6 @@ const {
}
})
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}))
vi.mock('ahooks', async () => {
const React = await import('react')
return {
@ -90,7 +84,7 @@ vi.mock('@/service/use-tools', () => ({
useInvalidateAllToolProviders: () => mockInvalidateAllToolProviders,
}))
vi.mock('../install-plugin/hooks', () => ({
vi.mock('../../install-plugin/hooks', () => ({
useGitHubReleases: () => ({
checkForUpdates: mockCheckForUpdates,
fetchReleases: mockFetchReleases,
@ -106,13 +100,13 @@ let mockAutoUpgradeInfo: {
upgrade_time_of_day: number
} | null = null
vi.mock('../plugin-page/use-reference-setting', () => ({
vi.mock('../../plugin-page/use-reference-setting', () => ({
default: () => ({
referenceSetting: mockAutoUpgradeInfo ? { auto_upgrade: mockAutoUpgradeInfo } : null,
}),
}))
vi.mock('../reference-setting-modal/auto-update-setting/types', () => ({
vi.mock('../../reference-setting-modal/auto-update-setting/types', () => ({
AUTO_UPDATE_MODE: {
update_all: 'update_all',
partial: 'partial',
@ -120,7 +114,7 @@ vi.mock('../reference-setting-modal/auto-update-setting/types', () => ({
},
}))
vi.mock('../reference-setting-modal/auto-update-setting/utils', () => ({
vi.mock('../../reference-setting-modal/auto-update-setting/utils', () => ({
convertUTCDaySecondsToLocalSeconds: (seconds: number) => seconds,
timeOfDayToDayjs: () => ({ format: () => '10:00 AM' }),
}))
@ -137,32 +131,32 @@ vi.mock('@/utils/var', () => ({
getMarketplaceUrl: (path: string) => `https://marketplace.example.com${path}`,
}))
vi.mock('../card/base/card-icon', () => ({
vi.mock('../../card/base/card-icon', () => ({
default: ({ src }: { src: string }) => <div data-testid="card-icon" data-src={src} />,
}))
vi.mock('../card/base/description', () => ({
vi.mock('../../card/base/description', () => ({
default: ({ text }: { text: string }) => <div data-testid="description">{text}</div>,
}))
vi.mock('../card/base/org-info', () => ({
vi.mock('../../card/base/org-info', () => ({
default: ({ orgName }: { orgName: string }) => <div data-testid="org-info">{orgName}</div>,
}))
vi.mock('../card/base/title', () => ({
vi.mock('../../card/base/title', () => ({
default: ({ title }: { title: string }) => <div data-testid="title">{title}</div>,
}))
vi.mock('../base/badges/verified', () => ({
vi.mock('../../base/badges/verified', () => ({
default: () => <span data-testid="verified-badge" />,
}))
vi.mock('../base/deprecation-notice', () => ({
vi.mock('../../base/deprecation-notice', () => ({
default: () => <div data-testid="deprecation-notice" />,
}))
// Enhanced operation-dropdown mock
vi.mock('./operation-dropdown', () => ({
vi.mock('../operation-dropdown', () => ({
default: ({ onInfo, onCheckVersion, onRemove }: { onInfo: () => void, onCheckVersion: () => void, onRemove: () => void }) => (
<div data-testid="operation-dropdown">
<button data-testid="info-btn" onClick={onInfo}>Info</button>
@ -173,7 +167,7 @@ vi.mock('./operation-dropdown', () => ({
}))
// Enhanced update modal mock
vi.mock('../update-plugin/from-market-place', () => ({
vi.mock('../../update-plugin/from-market-place', () => ({
default: ({ onSave, onCancel }: { onSave: () => void, onCancel: () => void }) => {
return (
<div data-testid="update-modal">
@ -185,7 +179,7 @@ vi.mock('../update-plugin/from-market-place', () => ({
}))
// Enhanced version picker mock
vi.mock('../update-plugin/plugin-version-picker', () => ({
vi.mock('../../update-plugin/plugin-version-picker', () => ({
default: ({ trigger, onSelect, onShowChange }: { trigger: React.ReactNode, onSelect: (state: { version: string, unique_identifier: string, isDowngrade?: boolean }) => void, onShowChange: (show: boolean) => void }) => (
<div data-testid="version-picker">
{trigger}
@ -211,7 +205,7 @@ vi.mock('../update-plugin/plugin-version-picker', () => ({
),
}))
vi.mock('../plugin-page/plugin-info', () => ({
vi.mock('../../plugin-page/plugin-info', () => ({
default: ({ onHide }: { onHide: () => void }) => (
<div data-testid="plugin-info">
<button data-testid="plugin-info-close" onClick={onHide}>Close</button>
@ -219,7 +213,7 @@ vi.mock('../plugin-page/plugin-info', () => ({
),
}))
vi.mock('../plugin-auth', () => ({
vi.mock('../../plugin-auth', () => ({
AuthCategory: { tool: 'tool' },
PluginAuth: () => <div data-testid="plugin-auth" />,
}))
@ -369,7 +363,7 @@ describe('DetailHeader', () => {
})
render(<DetailHeader detail={detail} onUpdate={mockOnUpdate} onHide={mockOnHide} />)
expect(screen.getByText('detailPanel.operation.update')).toBeInTheDocument()
expect(screen.getByText('plugin.detailPanel.operation.update')).toBeInTheDocument()
})
it('should show update button for GitHub source', () => {
@ -379,7 +373,7 @@ describe('DetailHeader', () => {
})
render(<DetailHeader detail={detail} onUpdate={mockOnUpdate} onHide={mockOnHide} />)
expect(screen.getByText('detailPanel.operation.update')).toBeInTheDocument()
expect(screen.getByText('plugin.detailPanel.operation.update')).toBeInTheDocument()
})
})
@ -556,7 +550,7 @@ describe('DetailHeader', () => {
})
render(<DetailHeader detail={detail} onUpdate={mockOnUpdate} onHide={mockOnHide} />)
const updateBtn = screen.getByText('detailPanel.operation.update')
const updateBtn = screen.getByText('plugin.detailPanel.operation.update')
fireEvent.click(updateBtn)
expect(updateBtn).toBeInTheDocument()
@ -589,7 +583,7 @@ describe('DetailHeader', () => {
})
render(<DetailHeader detail={detail} onUpdate={mockOnUpdate} onHide={mockOnHide} />)
fireEvent.click(screen.getByText('detailPanel.operation.update'))
fireEvent.click(screen.getByText('plugin.detailPanel.operation.update'))
await waitFor(() => {
expect(mockFetchReleases).toHaveBeenCalledWith('owner', 'repo')
@ -605,7 +599,7 @@ describe('DetailHeader', () => {
})
render(<DetailHeader detail={detail} onUpdate={mockOnUpdate} onHide={mockOnHide} />)
fireEvent.click(screen.getByText('detailPanel.operation.update'))
fireEvent.click(screen.getByText('plugin.detailPanel.operation.update'))
await waitFor(() => {
expect(mockFetchReleases).toHaveBeenCalled()
@ -619,7 +613,7 @@ describe('DetailHeader', () => {
})
render(<DetailHeader detail={detail} onUpdate={mockOnUpdate} onHide={mockOnHide} />)
fireEvent.click(screen.getByText('detailPanel.operation.update'))
fireEvent.click(screen.getByText('plugin.detailPanel.operation.update'))
await waitFor(() => {
expect(mockSetShowUpdatePluginModal).toHaveBeenCalled()
@ -638,7 +632,7 @@ describe('DetailHeader', () => {
})
render(<DetailHeader detail={detail} onUpdate={mockOnUpdate} onHide={mockOnHide} />)
fireEvent.click(screen.getByText('detailPanel.operation.update'))
fireEvent.click(screen.getByText('plugin.detailPanel.operation.update'))
await waitFor(() => {
expect(mockOnUpdate).toHaveBeenCalled()
@ -916,7 +910,7 @@ describe('DetailHeader', () => {
})
render(<DetailHeader detail={detail} onUpdate={mockOnUpdate} onHide={mockOnHide} />)
fireEvent.click(screen.getByText('detailPanel.operation.update'))
fireEvent.click(screen.getByText('plugin.detailPanel.operation.update'))
await waitFor(() => {
expect(screen.getByTestId('update-modal')).toBeInTheDocument()
@ -930,7 +924,7 @@ describe('DetailHeader', () => {
})
render(<DetailHeader detail={detail} onUpdate={mockOnUpdate} onHide={mockOnHide} />)
fireEvent.click(screen.getByText('detailPanel.operation.update'))
fireEvent.click(screen.getByText('plugin.detailPanel.operation.update'))
await waitFor(() => {
expect(screen.getByTestId('update-modal')).toBeInTheDocument()
})
@ -949,7 +943,7 @@ describe('DetailHeader', () => {
})
render(<DetailHeader detail={detail} onUpdate={mockOnUpdate} onHide={mockOnHide} />)
fireEvent.click(screen.getByText('detailPanel.operation.update'))
fireEvent.click(screen.getByText('plugin.detailPanel.operation.update'))
await waitFor(() => {
expect(screen.getByTestId('update-modal')).toBeInTheDocument()
})

View File

@ -1,14 +1,8 @@
import type { EndpointListItem, PluginDetail } from '../types'
import type { EndpointListItem, PluginDetail } from '../../types'
import { act, fireEvent, render, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import Toast from '@/app/components/base/toast'
import EndpointCard from './endpoint-card'
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}))
import EndpointCard from '../endpoint-card'
vi.mock('copy-to-clipboard', () => ({
default: vi.fn(),
@ -76,7 +70,7 @@ vi.mock('@/app/components/tools/utils/to-form-schema', () => ({
addDefaultValue: (value: unknown) => value,
}))
vi.mock('./endpoint-modal', () => ({
vi.mock('../endpoint-modal', () => ({
default: ({ onCancel, onSaved }: { onCancel: () => void, onSaved: (state: unknown) => void }) => (
<div data-testid="endpoint-modal">
<button data-testid="modal-cancel" onClick={onCancel}>Cancel</button>
@ -163,7 +157,7 @@ describe('EndpointCard', () => {
it('should show active status when enabled', () => {
render(<EndpointCard pluginDetail={mockPluginDetail} data={mockEndpointData} handleChange={mockHandleChange} />)
expect(screen.getByText('detailPanel.serviceOk')).toBeInTheDocument()
expect(screen.getByText('plugin.detailPanel.serviceOk')).toBeInTheDocument()
expect(screen.getByTestId('indicator')).toHaveAttribute('data-color', 'green')
})
@ -171,7 +165,7 @@ describe('EndpointCard', () => {
const disabledData = { ...mockEndpointData, enabled: false }
render(<EndpointCard pluginDetail={mockPluginDetail} data={disabledData} handleChange={mockHandleChange} />)
expect(screen.getByText('detailPanel.disabled')).toBeInTheDocument()
expect(screen.getByText('plugin.detailPanel.disabled')).toBeInTheDocument()
expect(screen.getByTestId('indicator')).toHaveAttribute('data-color', 'gray')
})
})
@ -182,7 +176,7 @@ describe('EndpointCard', () => {
fireEvent.click(screen.getByRole('switch'))
expect(screen.getByText('detailPanel.endpointDisableTip')).toBeInTheDocument()
expect(screen.getByText('plugin.detailPanel.endpointDisableTip')).toBeInTheDocument()
})
it('should call disableEndpoint when confirm disable', () => {
@ -190,7 +184,7 @@ describe('EndpointCard', () => {
fireEvent.click(screen.getByRole('switch'))
// Click confirm button in the Confirm dialog
fireEvent.click(screen.getByRole('button', { name: 'operation.confirm' }))
fireEvent.click(screen.getByRole('button', { name: 'common.operation.confirm' }))
expect(mockDisableEndpoint).toHaveBeenCalledWith('ep-1')
})
@ -205,7 +199,7 @@ describe('EndpointCard', () => {
if (deleteButton)
fireEvent.click(deleteButton)
expect(screen.getByText('detailPanel.endpointDeleteTip')).toBeInTheDocument()
expect(screen.getByText('plugin.detailPanel.endpointDeleteTip')).toBeInTheDocument()
})
it('should call deleteEndpoint when confirm delete', () => {
@ -216,7 +210,7 @@ describe('EndpointCard', () => {
expect(deleteButton).toBeDefined()
if (deleteButton)
fireEvent.click(deleteButton)
fireEvent.click(screen.getByRole('button', { name: 'operation.confirm' }))
fireEvent.click(screen.getByRole('button', { name: 'common.operation.confirm' }))
expect(mockDeleteEndpoint).toHaveBeenCalledWith('ep-1')
})
@ -290,12 +284,12 @@ describe('EndpointCard', () => {
render(<EndpointCard pluginDetail={mockPluginDetail} data={mockEndpointData} handleChange={mockHandleChange} />)
fireEvent.click(screen.getByRole('switch'))
expect(screen.getByText('detailPanel.endpointDisableTip')).toBeInTheDocument()
expect(screen.getByText('plugin.detailPanel.endpointDisableTip')).toBeInTheDocument()
fireEvent.click(screen.getByRole('button', { name: 'operation.cancel' }))
fireEvent.click(screen.getByRole('button', { name: 'common.operation.cancel' }))
// Confirm should be hidden
expect(screen.queryByText('detailPanel.endpointDisableTip')).not.toBeInTheDocument()
expect(screen.queryByText('plugin.detailPanel.endpointDisableTip')).not.toBeInTheDocument()
})
it('should hide delete confirm when cancel clicked', () => {
@ -306,11 +300,11 @@ describe('EndpointCard', () => {
expect(deleteButton).toBeDefined()
if (deleteButton)
fireEvent.click(deleteButton)
expect(screen.getByText('detailPanel.endpointDeleteTip')).toBeInTheDocument()
expect(screen.getByText('plugin.detailPanel.endpointDeleteTip')).toBeInTheDocument()
fireEvent.click(screen.getByRole('button', { name: 'operation.cancel' }))
fireEvent.click(screen.getByRole('button', { name: 'common.operation.cancel' }))
expect(screen.queryByText('detailPanel.endpointDeleteTip')).not.toBeInTheDocument()
expect(screen.queryByText('plugin.detailPanel.endpointDeleteTip')).not.toBeInTheDocument()
})
it('should hide edit modal when cancel clicked', () => {
@ -344,7 +338,7 @@ describe('EndpointCard', () => {
render(<EndpointCard pluginDetail={mockPluginDetail} data={mockEndpointData} handleChange={mockHandleChange} />)
fireEvent.click(screen.getByRole('switch'))
fireEvent.click(screen.getByRole('button', { name: 'operation.confirm' }))
fireEvent.click(screen.getByRole('button', { name: 'common.operation.confirm' }))
expect(mockDisableEndpoint).toHaveBeenCalled()
})
@ -357,7 +351,7 @@ describe('EndpointCard', () => {
const deleteButton = allButtons.find(btn => btn.classList.contains('text-text-tertiary'))
if (deleteButton)
fireEvent.click(deleteButton)
fireEvent.click(screen.getByRole('button', { name: 'operation.confirm' }))
fireEvent.click(screen.getByRole('button', { name: 'common.operation.confirm' }))
expect(mockDeleteEndpoint).toHaveBeenCalled()
})

View File

@ -1,13 +1,7 @@
import type { PluginDetail } from '@/app/components/plugins/types'
import { fireEvent, render, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import EndpointList from './endpoint-list'
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}))
import EndpointList from '../endpoint-list'
vi.mock('@/context/i18n', () => ({
useDocLink: () => (path: string) => `https://docs.example.com${path}`,
@ -41,13 +35,13 @@ vi.mock('@/app/components/tools/utils/to-form-schema', () => ({
toolCredentialToFormSchemas: (schemas: unknown[]) => schemas,
}))
vi.mock('./endpoint-card', () => ({
vi.mock('../endpoint-card', () => ({
default: ({ data }: { data: { name: string } }) => (
<div data-testid="endpoint-card">{data.name}</div>
),
}))
vi.mock('./endpoint-modal', () => ({
vi.mock('../endpoint-modal', () => ({
default: ({ onCancel, onSaved }: { onCancel: () => void, onSaved: (state: unknown) => void }) => (
<div data-testid="endpoint-modal">
<button data-testid="modal-cancel" onClick={onCancel}>Cancel</button>
@ -91,7 +85,7 @@ describe('EndpointList', () => {
it('should render endpoint list', () => {
render(<EndpointList detail={createPluginDetail()} />)
expect(screen.getByText('detailPanel.endpoints')).toBeInTheDocument()
expect(screen.getByText('plugin.detailPanel.endpoints')).toBeInTheDocument()
})
it('should render endpoint cards', () => {
@ -112,7 +106,7 @@ describe('EndpointList', () => {
mockEndpointListData = { endpoints: [] }
render(<EndpointList detail={createPluginDetail()} />)
expect(screen.getByText('detailPanel.endpointsEmpty')).toBeInTheDocument()
expect(screen.getByText('plugin.detailPanel.endpointsEmpty')).toBeInTheDocument()
})
it('should render add button', () => {
@ -165,7 +159,7 @@ describe('EndpointList', () => {
render(<EndpointList detail={detail} />)
// Verify the component renders correctly
expect(screen.getByText('detailPanel.endpoints')).toBeInTheDocument()
expect(screen.getByText('plugin.detailPanel.endpoints')).toBeInTheDocument()
})
})

View File

@ -1,19 +1,9 @@
import type { FormSchema } from '../../base/form/types'
import type { PluginDetail } from '../types'
import type { FormSchema } from '../../../base/form/types'
import type { PluginDetail } from '../../types'
import { fireEvent, render, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import Toast from '@/app/components/base/toast'
import EndpointModal from './endpoint-modal'
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string, opts?: Record<string, unknown>) => {
if (opts?.field)
return `${key}: ${opts.field}`
return key
},
}),
}))
import EndpointModal from '../endpoint-modal'
vi.mock('@/hooks/use-i18n', () => ({
useRenderI18nObject: () => (obj: Record<string, string> | string) =>
@ -45,7 +35,7 @@ vi.mock('@/app/components/header/account-setting/model-provider-page/model-modal
},
}))
vi.mock('../readme-panel/entrance', () => ({
vi.mock('../../readme-panel/entrance', () => ({
ReadmeEntrance: () => <div data-testid="readme-entrance" />,
}))
@ -110,8 +100,8 @@ describe('EndpointModal', () => {
/>,
)
expect(screen.getByText('detailPanel.endpointModalTitle')).toBeInTheDocument()
expect(screen.getByText('detailPanel.endpointModalDesc')).toBeInTheDocument()
expect(screen.getByText('plugin.detailPanel.endpointModalTitle')).toBeInTheDocument()
expect(screen.getByText('plugin.detailPanel.endpointModalDesc')).toBeInTheDocument()
})
it('should render form with fieldMoreInfo url link', () => {
@ -125,8 +115,7 @@ describe('EndpointModal', () => {
)
expect(screen.getByTestId('field-more-info')).toBeInTheDocument()
// Should render the "howToGet" link when url exists
expect(screen.getByText('howToGet')).toBeInTheDocument()
expect(screen.getByText('tools.howToGet')).toBeInTheDocument()
})
it('should render readme entrance', () => {
@ -154,7 +143,7 @@ describe('EndpointModal', () => {
/>,
)
fireEvent.click(screen.getByRole('button', { name: 'operation.cancel' }))
fireEvent.click(screen.getByRole('button', { name: 'common.operation.cancel' }))
expect(mockOnCancel).toHaveBeenCalledTimes(1)
})
@ -260,7 +249,7 @@ describe('EndpointModal', () => {
/>,
)
fireEvent.click(screen.getByRole('button', { name: 'operation.save' }))
fireEvent.click(screen.getByRole('button', { name: 'common.operation.save' }))
expect(mockToastNotify).toHaveBeenCalledWith({
type: 'error',
@ -283,7 +272,7 @@ describe('EndpointModal', () => {
/>,
)
fireEvent.click(screen.getByRole('button', { name: 'operation.save' }))
fireEvent.click(screen.getByRole('button', { name: 'common.operation.save' }))
expect(mockToastNotify).toHaveBeenCalledWith({
type: 'error',
@ -302,7 +291,7 @@ describe('EndpointModal', () => {
/>,
)
fireEvent.click(screen.getByRole('button', { name: 'operation.save' }))
fireEvent.click(screen.getByRole('button', { name: 'common.operation.save' }))
expect(mockOnSaved).toHaveBeenCalledWith({ name: 'Valid Name' })
})
@ -321,7 +310,7 @@ describe('EndpointModal', () => {
/>,
)
fireEvent.click(screen.getByRole('button', { name: 'operation.save' }))
fireEvent.click(screen.getByRole('button', { name: 'common.operation.save' }))
expect(mockToastNotify).not.toHaveBeenCalled()
expect(mockOnSaved).toHaveBeenCalled()
@ -344,7 +333,7 @@ describe('EndpointModal', () => {
/>,
)
fireEvent.click(screen.getByRole('button', { name: 'operation.save' }))
fireEvent.click(screen.getByRole('button', { name: 'common.operation.save' }))
expect(mockOnSaved).toHaveBeenCalledWith({ enabled: true })
})
@ -364,7 +353,7 @@ describe('EndpointModal', () => {
/>,
)
fireEvent.click(screen.getByRole('button', { name: 'operation.save' }))
fireEvent.click(screen.getByRole('button', { name: 'common.operation.save' }))
expect(mockOnSaved).toHaveBeenCalledWith({ enabled: true })
})
@ -384,7 +373,7 @@ describe('EndpointModal', () => {
/>,
)
fireEvent.click(screen.getByRole('button', { name: 'operation.save' }))
fireEvent.click(screen.getByRole('button', { name: 'common.operation.save' }))
expect(mockOnSaved).toHaveBeenCalledWith({ enabled: true })
})
@ -404,7 +393,7 @@ describe('EndpointModal', () => {
/>,
)
fireEvent.click(screen.getByRole('button', { name: 'operation.save' }))
fireEvent.click(screen.getByRole('button', { name: 'common.operation.save' }))
expect(mockOnSaved).toHaveBeenCalledWith({ enabled: false })
})
@ -424,7 +413,7 @@ describe('EndpointModal', () => {
/>,
)
fireEvent.click(screen.getByRole('button', { name: 'operation.save' }))
fireEvent.click(screen.getByRole('button', { name: 'common.operation.save' }))
expect(mockOnSaved).toHaveBeenCalledWith({ enabled: true })
})
@ -444,7 +433,7 @@ describe('EndpointModal', () => {
/>,
)
fireEvent.click(screen.getByRole('button', { name: 'operation.save' }))
fireEvent.click(screen.getByRole('button', { name: 'common.operation.save' }))
expect(mockOnSaved).toHaveBeenCalledWith({ enabled: false })
})
@ -464,7 +453,7 @@ describe('EndpointModal', () => {
/>,
)
fireEvent.click(screen.getByRole('button', { name: 'operation.save' }))
fireEvent.click(screen.getByRole('button', { name: 'common.operation.save' }))
expect(mockOnSaved).toHaveBeenCalledWith({ enabled: true })
})
@ -484,7 +473,7 @@ describe('EndpointModal', () => {
/>,
)
fireEvent.click(screen.getByRole('button', { name: 'operation.save' }))
fireEvent.click(screen.getByRole('button', { name: 'common.operation.save' }))
expect(mockOnSaved).toHaveBeenCalledWith({ enabled: false })
})
@ -504,7 +493,7 @@ describe('EndpointModal', () => {
/>,
)
fireEvent.click(screen.getByRole('button', { name: 'operation.save' }))
fireEvent.click(screen.getByRole('button', { name: 'common.operation.save' }))
expect(mockOnSaved).toHaveBeenCalledWith({ text: 'hello' })
})

View File

@ -2,11 +2,11 @@ import type { PluginDeclaration, PluginDetail } from '@/app/components/plugins/t
import { act, fireEvent, render, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { PluginCategoryEnum, PluginSource } from '@/app/components/plugins/types'
import PluginDetailPanel from './index'
import PluginDetailPanel from '../index'
// Mock store
const mockSetDetail = vi.fn()
vi.mock('./store', () => ({
vi.mock('../store', () => ({
usePluginStore: () => ({
setDetail: mockSetDetail,
}),
@ -14,7 +14,7 @@ vi.mock('./store', () => ({
// Mock DetailHeader
const mockDetailHeaderOnUpdate = vi.fn()
vi.mock('./detail-header', () => ({
vi.mock('../detail-header', () => ({
default: ({ detail, onUpdate, onHide }: {
detail: PluginDetail
onUpdate: (isDelete?: boolean) => void
@ -49,7 +49,7 @@ vi.mock('./detail-header', () => ({
}))
// Mock ActionList
vi.mock('./action-list', () => ({
vi.mock('../action-list', () => ({
default: ({ detail }: { detail: PluginDetail }) => (
<div data-testid="action-list">
<span data-testid="action-list-plugin-id">{detail.plugin_id}</span>
@ -58,7 +58,7 @@ vi.mock('./action-list', () => ({
}))
// Mock AgentStrategyList
vi.mock('./agent-strategy-list', () => ({
vi.mock('../agent-strategy-list', () => ({
default: ({ detail }: { detail: PluginDetail }) => (
<div data-testid="agent-strategy-list">
<span data-testid="strategy-list-plugin-id">{detail.plugin_id}</span>
@ -67,7 +67,7 @@ vi.mock('./agent-strategy-list', () => ({
}))
// Mock EndpointList
vi.mock('./endpoint-list', () => ({
vi.mock('../endpoint-list', () => ({
default: ({ detail }: { detail: PluginDetail }) => (
<div data-testid="endpoint-list">
<span data-testid="endpoint-list-plugin-id">{detail.plugin_id}</span>
@ -76,7 +76,7 @@ vi.mock('./endpoint-list', () => ({
}))
// Mock ModelList
vi.mock('./model-list', () => ({
vi.mock('../model-list', () => ({
default: ({ detail }: { detail: PluginDetail }) => (
<div data-testid="model-list">
<span data-testid="model-list-plugin-id">{detail.plugin_id}</span>
@ -85,7 +85,7 @@ vi.mock('./model-list', () => ({
}))
// Mock DatasourceActionList
vi.mock('./datasource-action-list', () => ({
vi.mock('../datasource-action-list', () => ({
default: ({ detail }: { detail: PluginDetail }) => (
<div data-testid="datasource-action-list">
<span data-testid="datasource-list-plugin-id">{detail.plugin_id}</span>
@ -94,7 +94,7 @@ vi.mock('./datasource-action-list', () => ({
}))
// Mock SubscriptionList
vi.mock('./subscription-list', () => ({
vi.mock('../subscription-list', () => ({
SubscriptionList: ({ pluginDetail }: { pluginDetail: PluginDetail }) => (
<div data-testid="subscription-list">
<span data-testid="subscription-list-plugin-id">{pluginDetail.plugin_id}</span>
@ -103,14 +103,14 @@ vi.mock('./subscription-list', () => ({
}))
// Mock TriggerEventsList
vi.mock('./trigger/event-list', () => ({
vi.mock('../trigger/event-list', () => ({
TriggerEventsList: () => (
<div data-testid="trigger-events-list">Events List</div>
),
}))
// Mock ReadmeEntrance
vi.mock('../readme-panel/entrance', () => ({
vi.mock('../../readme-panel/entrance', () => ({
ReadmeEntrance: ({ pluginDetail, className }: { pluginDetail: PluginDetail, className?: string }) => (
<div data-testid="readme-entrance" className={className}>
<span data-testid="readme-plugin-id">{pluginDetail.plugin_id}</span>

View File

@ -1,17 +1,7 @@
import type { PluginDetail } from '@/app/components/plugins/types'
import { render, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import ModelList from './model-list'
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string, options?: Record<string, unknown>) => {
if (options?.num !== undefined)
return `${options.num} models`
return key
},
}),
}))
import ModelList from '../model-list'
const mockModels = [
{ model: 'gpt-4', provider: 'openai' },
@ -72,7 +62,7 @@ describe('ModelList', () => {
it('should render model list when data is available', () => {
render(<ModelList detail={createPluginDetail()} />)
expect(screen.getByText('2 models')).toBeInTheDocument()
expect(screen.getByText('plugin.detailPanel.modelNum:{"num":2}')).toBeInTheDocument()
})
it('should render model icons and names', () => {
@ -96,7 +86,7 @@ describe('ModelList', () => {
mockModelListResponse = { data: [] }
render(<ModelList detail={createPluginDetail()} />)
expect(screen.getByText('0 models')).toBeInTheDocument()
expect(screen.getByText('plugin.detailPanel.modelNum:{"num":0}')).toBeInTheDocument()
expect(screen.queryByTestId('model-icon')).not.toBeInTheDocument()
})
})

View File

@ -1,14 +1,7 @@
import { fireEvent, render, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { PluginSource } from '../types'
import OperationDropdown from './operation-dropdown'
// Mock dependencies
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}))
import { PluginSource } from '../../types'
import OperationDropdown from '../operation-dropdown'
vi.mock('@/context/global-public-context', () => ({
useGlobalPublicStore: <T,>(selector: (state: { systemFeatures: { enable_marketplace: boolean } }) => T): T =>
@ -72,55 +65,55 @@ describe('OperationDropdown', () => {
it('should render info option for github source', () => {
render(<OperationDropdown {...defaultProps} source={PluginSource.github} />)
expect(screen.getByText('detailPanel.operation.info')).toBeInTheDocument()
expect(screen.getByText('plugin.detailPanel.operation.info')).toBeInTheDocument()
})
it('should render check update option for github source', () => {
render(<OperationDropdown {...defaultProps} source={PluginSource.github} />)
expect(screen.getByText('detailPanel.operation.checkUpdate')).toBeInTheDocument()
expect(screen.getByText('plugin.detailPanel.operation.checkUpdate')).toBeInTheDocument()
})
it('should render view detail option for github source with marketplace enabled', () => {
render(<OperationDropdown {...defaultProps} source={PluginSource.github} />)
expect(screen.getByText('detailPanel.operation.viewDetail')).toBeInTheDocument()
expect(screen.getByText('plugin.detailPanel.operation.viewDetail')).toBeInTheDocument()
})
it('should render view detail option for marketplace source', () => {
render(<OperationDropdown {...defaultProps} source={PluginSource.marketplace} />)
expect(screen.getByText('detailPanel.operation.viewDetail')).toBeInTheDocument()
expect(screen.getByText('plugin.detailPanel.operation.viewDetail')).toBeInTheDocument()
})
it('should always render remove option', () => {
render(<OperationDropdown {...defaultProps} />)
expect(screen.getByText('detailPanel.operation.remove')).toBeInTheDocument()
expect(screen.getByText('plugin.detailPanel.operation.remove')).toBeInTheDocument()
})
it('should not render info option for marketplace source', () => {
render(<OperationDropdown {...defaultProps} source={PluginSource.marketplace} />)
expect(screen.queryByText('detailPanel.operation.info')).not.toBeInTheDocument()
expect(screen.queryByText('plugin.detailPanel.operation.info')).not.toBeInTheDocument()
})
it('should not render check update option for marketplace source', () => {
render(<OperationDropdown {...defaultProps} source={PluginSource.marketplace} />)
expect(screen.queryByText('detailPanel.operation.checkUpdate')).not.toBeInTheDocument()
expect(screen.queryByText('plugin.detailPanel.operation.checkUpdate')).not.toBeInTheDocument()
})
it('should not render view detail for local source', () => {
render(<OperationDropdown {...defaultProps} source={PluginSource.local} />)
expect(screen.queryByText('detailPanel.operation.viewDetail')).not.toBeInTheDocument()
expect(screen.queryByText('plugin.detailPanel.operation.viewDetail')).not.toBeInTheDocument()
})
it('should not render view detail for debugging source', () => {
render(<OperationDropdown {...defaultProps} source={PluginSource.debugging} />)
expect(screen.queryByText('detailPanel.operation.viewDetail')).not.toBeInTheDocument()
expect(screen.queryByText('plugin.detailPanel.operation.viewDetail')).not.toBeInTheDocument()
})
})
@ -138,7 +131,7 @@ describe('OperationDropdown', () => {
it('should call onInfo when info option is clicked', () => {
render(<OperationDropdown {...defaultProps} source={PluginSource.github} />)
fireEvent.click(screen.getByText('detailPanel.operation.info'))
fireEvent.click(screen.getByText('plugin.detailPanel.operation.info'))
expect(mockOnInfo).toHaveBeenCalledTimes(1)
})
@ -146,7 +139,7 @@ describe('OperationDropdown', () => {
it('should call onCheckVersion when check update option is clicked', () => {
render(<OperationDropdown {...defaultProps} source={PluginSource.github} />)
fireEvent.click(screen.getByText('detailPanel.operation.checkUpdate'))
fireEvent.click(screen.getByText('plugin.detailPanel.operation.checkUpdate'))
expect(mockOnCheckVersion).toHaveBeenCalledTimes(1)
})
@ -154,7 +147,7 @@ describe('OperationDropdown', () => {
it('should call onRemove when remove option is clicked', () => {
render(<OperationDropdown {...defaultProps} />)
fireEvent.click(screen.getByText('detailPanel.operation.remove'))
fireEvent.click(screen.getByText('plugin.detailPanel.operation.remove'))
expect(mockOnRemove).toHaveBeenCalledTimes(1)
})
@ -162,7 +155,7 @@ describe('OperationDropdown', () => {
it('should have correct href for view detail link', () => {
render(<OperationDropdown {...defaultProps} source={PluginSource.github} />)
const link = screen.getByText('detailPanel.operation.viewDetail').closest('a')
const link = screen.getByText('plugin.detailPanel.operation.viewDetail').closest('a')
expect(link).toHaveAttribute('href', 'https://github.com/test/repo')
expect(link).toHaveAttribute('target', '_blank')
})
@ -182,7 +175,7 @@ describe('OperationDropdown', () => {
<OperationDropdown {...defaultProps} source={source} />,
)
expect(screen.getByTestId('portal-elem')).toBeInTheDocument()
expect(screen.getByText('detailPanel.operation.remove')).toBeInTheDocument()
expect(screen.getByText('plugin.detailPanel.operation.remove')).toBeInTheDocument()
unmount()
})
})
@ -197,7 +190,7 @@ describe('OperationDropdown', () => {
const { unmount } = render(
<OperationDropdown {...defaultProps} detailUrl={url} source={PluginSource.github} />,
)
const link = screen.getByText('detailPanel.operation.viewDetail').closest('a')
const link = screen.getByText('plugin.detailPanel.operation.viewDetail').closest('a')
expect(link).toHaveAttribute('href', url)
unmount()
})

View File

@ -1,7 +1,7 @@
import type { SimpleDetail } from './store'
import type { SimpleDetail } from '../store'
import { act, renderHook } from '@testing-library/react'
import { beforeEach, describe, expect, it } from 'vitest'
import { usePluginStore } from './store'
import { usePluginStore } from '../store'
// Factory function to create mock SimpleDetail
const createSimpleDetail = (overrides: Partial<SimpleDetail> = {}): SimpleDetail => ({

View File

@ -1,13 +1,7 @@
import type { StrategyDetail as StrategyDetailType } from '@/app/components/plugins/types'
import { fireEvent, render, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import StrategyDetail from './strategy-detail'
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}))
import StrategyDetail from '../strategy-detail'
vi.mock('@/hooks/use-i18n', () => ({
useRenderI18nObject: () => (obj: Record<string, string>) => obj?.en_US || '',
@ -93,7 +87,7 @@ describe('StrategyDetail', () => {
it('should render parameters section', () => {
render(<StrategyDetail provider={mockProvider} detail={mockDetail} onHide={mockOnHide} />)
expect(screen.getByText('setBuiltInTools.parameters')).toBeInTheDocument()
expect(screen.getByText('tools.setBuiltInTools.parameters')).toBeInTheDocument()
expect(screen.getByText('Parameter 1')).toBeInTheDocument()
})
@ -141,7 +135,7 @@ describe('StrategyDetail', () => {
}
render(<StrategyDetail provider={mockProvider} detail={detailWithNumber} onHide={mockOnHide} />)
expect(screen.getByText('setBuiltInTools.number')).toBeInTheDocument()
expect(screen.getByText('tools.setBuiltInTools.number')).toBeInTheDocument()
})
it('should display correct type for checkbox', () => {
@ -161,7 +155,7 @@ describe('StrategyDetail', () => {
}
render(<StrategyDetail provider={mockProvider} detail={detailWithFile} onHide={mockOnHide} />)
expect(screen.getByText('setBuiltInTools.file')).toBeInTheDocument()
expect(screen.getByText('tools.setBuiltInTools.file')).toBeInTheDocument()
})
it('should display correct type for array[tools]', () => {
@ -190,7 +184,7 @@ describe('StrategyDetail', () => {
const detailEmpty = { ...mockDetail, parameters: [] }
render(<StrategyDetail provider={mockProvider} detail={detailEmpty} onHide={mockOnHide} />)
expect(screen.getByText('setBuiltInTools.parameters')).toBeInTheDocument()
expect(screen.getByText('tools.setBuiltInTools.parameters')).toBeInTheDocument()
})
it('should handle no output schema', () => {

View File

@ -1,7 +1,7 @@
import type { StrategyDetail } from '@/app/components/plugins/types'
import { fireEvent, render, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import StrategyItem from './strategy-item'
import StrategyItem from '../strategy-item'
vi.mock('@/hooks/use-i18n', () => ({
useRenderI18nObject: () => (obj: Record<string, string>) => obj?.en_US || '',
@ -11,7 +11,7 @@ vi.mock('@/utils/classnames', () => ({
cn: (...args: (string | undefined | false | null)[]) => args.filter(Boolean).join(' '),
}))
vi.mock('./strategy-detail', () => ({
vi.mock('../strategy-detail', () => ({
default: ({ onHide }: { onHide: () => void }) => (
<div data-testid="strategy-detail-panel">
<button data-testid="hide-btn" onClick={onHide}>Hide</button>

View File

@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest'
import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import { NAME_FIELD } from './utils'
import { NAME_FIELD } from '../utils'
describe('utils', () => {
describe('NAME_FIELD', () => {

View File

@ -2,10 +2,6 @@ import { render, screen } from '@testing-library/react'
import * as React from 'react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
vi.mock('@remixicon/react', () => ({
RiArrowDownSLine: () => <span data-testid="chevron-icon" />,
}))
vi.mock('@/app/components/base/app-icon', () => ({
default: ({ size }: { size: string }) => <div data-testid="app-icon" data-size={size} />,
}))
@ -15,11 +11,11 @@ vi.mock('@/utils/classnames', () => ({
}))
describe('AppTrigger', () => {
let AppTrigger: (typeof import('./app-trigger'))['default']
let AppTrigger: (typeof import('../app-trigger'))['default']
beforeEach(async () => {
vi.clearAllMocks()
const mod = await import('./app-trigger')
const mod = await import('../app-trigger')
AppTrigger = mod.default
})
@ -27,7 +23,6 @@ describe('AppTrigger', () => {
render(<AppTrigger open={false} />)
expect(screen.queryByTestId('app-icon')).not.toBeInTheDocument()
expect(screen.getByTestId('chevron-icon')).toBeInTheDocument()
})
it('should render app details when appDetail is provided', () => {
@ -43,9 +38,9 @@ describe('AppTrigger', () => {
expect(screen.getByText('My App')).toBeInTheDocument()
})
it('should show chevron icon', () => {
render(<AppTrigger open={true} />)
it('should render when open', () => {
const { container } = render(<AppTrigger open={true} />)
expect(screen.getByTestId('chevron-icon')).toBeInTheDocument()
expect(container.firstChild).toBeInTheDocument()
})
})

View File

@ -6,12 +6,12 @@ import * as React from 'react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { InputVarType } from '@/app/components/workflow/types'
import { AppModeEnum } from '@/types/app'
import AppInputsForm from './app-inputs-form'
import AppInputsPanel from './app-inputs-panel'
import AppPicker from './app-picker'
import AppTrigger from './app-trigger'
import AppInputsForm from '../app-inputs-form'
import AppInputsPanel from '../app-inputs-panel'
import AppPicker from '../app-picker'
import AppTrigger from '../app-trigger'
import AppSelector from './index'
import AppSelector from '../index'
// ==================== Mock Setup ====================

View File

@ -1,15 +1,9 @@
import type { PluginDetail } from '../../../types'
import type { ModalStates, VersionTarget } from '../hooks'
import type { PluginDetail } from '../../../../types'
import type { ModalStates, VersionTarget } from '../../hooks'
import { fireEvent, render, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { PluginSource } from '../../../types'
import HeaderModals from './header-modals'
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}))
import { PluginSource } from '../../../../types'
import HeaderModals from '../header-modals'
vi.mock('@/context/i18n', () => ({
useGetLanguage: () => 'en_US',
@ -270,7 +264,7 @@ describe('HeaderModals', () => {
/>,
)
expect(screen.getByTestId('delete-title')).toHaveTextContent('action.delete')
expect(screen.getByTestId('delete-title')).toHaveTextContent('plugin.action.delete')
})
it('should call hideDeleteConfirm when cancel is clicked', () => {

View File

@ -1,13 +1,7 @@
import { render, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { PluginSource } from '../../../types'
import PluginSourceBadge from './plugin-source-badge'
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}))
import { PluginSource } from '../../../../types'
import PluginSourceBadge from '../plugin-source-badge'
vi.mock('@/app/components/base/tooltip', () => ({
default: ({ children, popupContent }: { children: React.ReactNode, popupContent: string }) => (
@ -28,7 +22,7 @@ describe('PluginSourceBadge', () => {
const tooltip = screen.getByTestId('tooltip')
expect(tooltip).toBeInTheDocument()
expect(tooltip).toHaveAttribute('data-content', 'detailPanel.categoryTip.marketplace')
expect(tooltip).toHaveAttribute('data-content', 'plugin.detailPanel.categoryTip.marketplace')
})
it('should render github source badge', () => {
@ -36,7 +30,7 @@ describe('PluginSourceBadge', () => {
const tooltip = screen.getByTestId('tooltip')
expect(tooltip).toBeInTheDocument()
expect(tooltip).toHaveAttribute('data-content', 'detailPanel.categoryTip.github')
expect(tooltip).toHaveAttribute('data-content', 'plugin.detailPanel.categoryTip.github')
})
it('should render local source badge', () => {
@ -44,7 +38,7 @@ describe('PluginSourceBadge', () => {
const tooltip = screen.getByTestId('tooltip')
expect(tooltip).toBeInTheDocument()
expect(tooltip).toHaveAttribute('data-content', 'detailPanel.categoryTip.local')
expect(tooltip).toHaveAttribute('data-content', 'plugin.detailPanel.categoryTip.local')
})
it('should render debugging source badge', () => {
@ -52,7 +46,7 @@ describe('PluginSourceBadge', () => {
const tooltip = screen.getByTestId('tooltip')
expect(tooltip).toBeInTheDocument()
expect(tooltip).toHaveAttribute('data-content', 'detailPanel.categoryTip.debugging')
expect(tooltip).toHaveAttribute('data-content', 'plugin.detailPanel.categoryTip.debugging')
})
})
@ -94,7 +88,7 @@ describe('PluginSourceBadge', () => {
expect(screen.getByTestId('tooltip')).toHaveAttribute(
'data-content',
'detailPanel.categoryTip.marketplace',
'plugin.detailPanel.categoryTip.marketplace',
)
})
@ -103,7 +97,7 @@ describe('PluginSourceBadge', () => {
expect(screen.getByTestId('tooltip')).toHaveAttribute(
'data-content',
'detailPanel.categoryTip.github',
'plugin.detailPanel.categoryTip.github',
)
})
@ -112,7 +106,7 @@ describe('PluginSourceBadge', () => {
expect(screen.getByTestId('tooltip')).toHaveAttribute(
'data-content',
'detailPanel.categoryTip.local',
'plugin.detailPanel.categoryTip.local',
)
})
@ -121,7 +115,7 @@ describe('PluginSourceBadge', () => {
expect(screen.getByTestId('tooltip')).toHaveAttribute(
'data-content',
'detailPanel.categoryTip.debugging',
'plugin.detailPanel.categoryTip.debugging',
)
})
})

View File

@ -1,8 +1,8 @@
import type { PluginDetail } from '../../../types'
import type { PluginDetail } from '../../../../types'
import { act, renderHook } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { PluginSource } from '../../../types'
import { useDetailHeaderState } from './use-detail-header-state'
import { PluginSource } from '../../../../types'
import { useDetailHeaderState } from '../use-detail-header-state'
let mockEnableMarketplace = true
vi.mock('@/context/global-public-context', () => ({
@ -18,13 +18,13 @@ let mockAutoUpgradeInfo: {
upgrade_time_of_day: number
} | null = null
vi.mock('../../../plugin-page/use-reference-setting', () => ({
vi.mock('../../../../plugin-page/use-reference-setting', () => ({
default: () => ({
referenceSetting: mockAutoUpgradeInfo ? { auto_upgrade: mockAutoUpgradeInfo } : null,
}),
}))
vi.mock('../../../reference-setting-modal/auto-update-setting/types', () => ({
vi.mock('../../../../reference-setting-modal/auto-update-setting/types', () => ({
AUTO_UPDATE_MODE: {
update_all: 'update_all',
partial: 'partial',

View File

@ -1,11 +1,11 @@
import type { PluginDetail } from '../../../types'
import type { ModalStates, VersionTarget } from './use-detail-header-state'
import type { PluginDetail } from '../../../../types'
import type { ModalStates, VersionTarget } from '../use-detail-header-state'
import { act, renderHook } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import * as amplitude from '@/app/components/base/amplitude'
import Toast from '@/app/components/base/toast'
import { PluginSource } from '../../../types'
import { usePluginOperations } from './use-plugin-operations'
import { PluginSource } from '../../../../types'
import { usePluginOperations } from '../use-plugin-operations'
type VersionPickerMock = {
setTargetVersion: (version: VersionTarget) => void
@ -50,7 +50,7 @@ vi.mock('@/service/use-tools', () => ({
useInvalidateAllToolProviders: () => mockInvalidateAllToolProviders,
}))
vi.mock('../../../install-plugin/hooks', () => ({
vi.mock('../../../../install-plugin/hooks', () => ({
useGitHubReleases: () => ({
checkForUpdates: mockCheckForUpdates,
fetchReleases: mockFetchReleases,

View File

@ -5,7 +5,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
import Toast from '@/app/components/base/toast'
import { ConfigurationMethodEnum, ModelStatusEnum, ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import ModelParameterModal from './index'
import ModelParameterModal from '../index'
// ==================== Mock Setup ====================
@ -159,7 +159,7 @@ vi.mock('@/app/components/header/account-setting/model-provider-page/model-param
),
}))
vi.mock('./llm-params-panel', () => ({
vi.mock('../llm-params-panel', () => ({
default: ({ provider, modelId, onCompletionParamsChange, isAdvancedMode }: {
provider: string
modelId: string
@ -179,7 +179,7 @@ vi.mock('./llm-params-panel', () => ({
),
}))
vi.mock('./tts-params-panel', () => ({
vi.mock('../tts-params-panel', () => ({
default: ({ language, voice, onChange }: {
currentModel?: ModelItem
language?: string

View File

@ -3,7 +3,7 @@ import { fireEvent, render, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
// Import component after mocks
import LLMParamsPanel from './llm-params-panel'
import LLMParamsPanel from '../llm-params-panel'
// ==================== Mock Setup ====================
// All vi.mock() calls are hoisted, so inline all mock data

View File

@ -2,7 +2,7 @@ import { fireEvent, render, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
// Import component after mocks
import TTSParamsPanel from './tts-params-panel'
import TTSParamsPanel from '../tts-params-panel'
// ==================== Mock Setup ====================
// All vi.mock() calls are hoisted, so inline all mock data

View File

@ -8,7 +8,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
// ==================== Imports (after mocks) ====================
import { MCPToolAvailabilityProvider } from '@/app/components/workflow/nodes/_base/components/mcp-tool-availability'
import MultipleToolSelector from './index'
import MultipleToolSelector from '../index'
// ==================== Mock Setup ====================
@ -30,9 +30,9 @@ vi.mock('@/app/components/plugins/plugin-detail-panel/tool-selector', () => ({
onSelectMultiple,
onDelete,
controlledState,
onControlledStateChange,
onControlledStateChange: _onControlledStateChange,
panelShowState,
onPanelShowStateChange,
onPanelShowStateChange: _onPanelShowStateChange,
isEdit,
supportEnableSwitch,
}: {
@ -150,15 +150,15 @@ const createMCPTool = (overrides: Partial<ToolWithProvider> = {}): ToolWithProvi
author: 'test-author',
type: 'mcp',
icon: 'test-icon.png',
label: { en_US: 'MCP Provider' } as any,
description: { en_US: 'MCP Provider description' } as any,
label: { en_US: 'MCP Provider' } as unknown as ToolWithProvider['label'],
description: { en_US: 'MCP Provider description' } as unknown as ToolWithProvider['description'],
is_team_authorization: true,
allow_delete: false,
labels: [],
tools: [{
name: 'mcp-tool-1',
label: { en_US: 'MCP Tool 1' } as any,
description: { en_US: 'MCP Tool 1 description' } as any,
label: { en_US: 'MCP Tool 1' } as unknown as ToolWithProvider['label'],
description: { en_US: 'MCP Tool 1 description' } as unknown as ToolWithProvider['description'],
parameters: [],
output_schema: {},
}],
@ -641,7 +641,7 @@ describe('MultipleToolSelector', () => {
it('should handle undefined value', () => {
// Arrange & Act - value defaults to [] in component
renderComponent({ value: undefined as any })
renderComponent({ value: undefined as unknown as ToolValue[] })
// Assert
expect(screen.getByText('plugin.detailPanel.toolSelector.empty')).toBeInTheDocument()

View File

@ -1,12 +1,12 @@
import { fireEvent, render, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { DeleteConfirm } from './delete-confirm'
import { DeleteConfirm } from '../delete-confirm'
const mockRefetch = vi.fn()
const mockDelete = vi.fn()
const mockToast = vi.fn()
vi.mock('./use-subscription-list', () => ({
vi.mock('../use-subscription-list', () => ({
useSubscriptionList: () => ({ refetch: mockRefetch }),
}))

View File

@ -3,8 +3,8 @@ import type { TriggerSubscription } from '@/app/components/workflow/block-select
import { fireEvent, render, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { TriggerCredentialTypeEnum } from '@/app/components/workflow/block-selector/types'
import { SubscriptionList } from './index'
import { SubscriptionListMode } from './types'
import { SubscriptionList } from '../index'
import { SubscriptionListMode } from '../types'
const mockRefetch = vi.fn()
let mockSubscriptionListError: Error | null = null
@ -16,7 +16,7 @@ let mockSubscriptionListState: {
let mockPluginDetail: PluginDetail | undefined
vi.mock('./use-subscription-list', () => ({
vi.mock('../use-subscription-list', () => ({
useSubscriptionList: () => {
if (mockSubscriptionListError)
throw mockSubscriptionListError
@ -24,7 +24,7 @@ vi.mock('./use-subscription-list', () => ({
},
}))
vi.mock('../../store', () => ({
vi.mock('../../../store', () => ({
usePluginStore: (selector: (state: { detail: PluginDetail | undefined }) => PluginDetail | undefined) =>
selector({ detail: mockPluginDetail }),
}))

View File

@ -2,15 +2,15 @@ import type { TriggerSubscription } from '@/app/components/workflow/block-select
import { render, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { TriggerCredentialTypeEnum } from '@/app/components/workflow/block-selector/types'
import { SubscriptionListView } from './list-view'
import { SubscriptionListView } from '../list-view'
let mockSubscriptions: TriggerSubscription[] = []
vi.mock('./use-subscription-list', () => ({
vi.mock('../use-subscription-list', () => ({
useSubscriptionList: () => ({ subscriptions: mockSubscriptions }),
}))
vi.mock('../../store', () => ({
vi.mock('../../../store', () => ({
usePluginStore: () => ({ detail: undefined }),
}))

View File

@ -1,7 +1,7 @@
import type { TriggerLogEntity } from '@/app/components/workflow/block-selector/types'
import { fireEvent, render, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import LogViewer from './log-viewer'
import LogViewer from '../log-viewer'
const mockToastNotify = vi.fn()
const mockWriteText = vi.fn()

View File

@ -2,12 +2,12 @@ import type { TriggerSubscription } from '@/app/components/workflow/block-select
import { fireEvent, render, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { TriggerCredentialTypeEnum } from '@/app/components/workflow/block-selector/types'
import { SubscriptionSelectorEntry } from './selector-entry'
import { SubscriptionSelectorEntry } from '../selector-entry'
let mockSubscriptions: TriggerSubscription[] = []
const mockRefetch = vi.fn()
vi.mock('./use-subscription-list', () => ({
vi.mock('../use-subscription-list', () => ({
useSubscriptionList: () => ({
subscriptions: mockSubscriptions,
isLoading: false,
@ -15,7 +15,7 @@ vi.mock('./use-subscription-list', () => ({
}),
}))
vi.mock('../../store', () => ({
vi.mock('../../../store', () => ({
usePluginStore: () => ({ detail: undefined }),
}))

View File

@ -2,7 +2,7 @@ import type { TriggerSubscription } from '@/app/components/workflow/block-select
import { fireEvent, render, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { TriggerCredentialTypeEnum } from '@/app/components/workflow/block-selector/types'
import { SubscriptionSelectorView } from './selector-view'
import { SubscriptionSelectorView } from '../selector-view'
let mockSubscriptions: TriggerSubscription[] = []
const mockRefetch = vi.fn()
@ -10,11 +10,11 @@ const mockDelete = vi.fn((_: string, options?: { onSuccess?: () => void }) => {
options?.onSuccess?.()
})
vi.mock('./use-subscription-list', () => ({
vi.mock('../use-subscription-list', () => ({
useSubscriptionList: () => ({ subscriptions: mockSubscriptions, refetch: mockRefetch }),
}))
vi.mock('../../store', () => ({
vi.mock('../../../store', () => ({
usePluginStore: () => ({ detail: undefined }),
}))

View File

@ -2,15 +2,15 @@ import type { TriggerSubscription } from '@/app/components/workflow/block-select
import { fireEvent, render, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { TriggerCredentialTypeEnum } from '@/app/components/workflow/block-selector/types'
import SubscriptionCard from './subscription-card'
import SubscriptionCard from '../subscription-card'
const mockRefetch = vi.fn()
vi.mock('./use-subscription-list', () => ({
vi.mock('../use-subscription-list', () => ({
useSubscriptionList: () => ({ refetch: mockRefetch }),
}))
vi.mock('../../store', () => ({
vi.mock('../../../store', () => ({
usePluginStore: () => ({
detail: {
id: 'detail-1',

Some files were not shown because too many files have changed in this diff Show More