/**
* Tests for PluginSlot component.
*/
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { render, screen } from '@testing-library/react'
import { axe } from 'vitest-axe'
import { PluginSlot } from './plugin-slot'
import { usePlugins } from '@/hooks/use-plugins'
import { getPluginComponents } from '@/lib/plugins/registry'
import type { PluginRegistration } from '@/lib/plugins/registry'
import type { PluginContextValue } from '@/context/plugin-context'
vi.mock('@/hooks/use-plugins', () => ({
usePlugins: vi.fn(),
}))
vi.mock('@/lib/plugins/registry', () => ({
getPluginComponents: vi.fn(),
}))
// Test helper components
function TestPluginComponent({ authorDid }: { authorDid?: string }) {
return
Plugin content: {authorDid}
}
function CrashingPluginComponent() {
throw new Error('Plugin crashed!')
}
function createMockPluginContext(plugins: PluginContextValue['plugins'] = []): PluginContextValue {
return {
plugins,
isPluginEnabled: (name: string) => plugins.some((p) => p.name === name && p.enabled),
getPluginSettings: () => null,
isLoading: false,
refreshPlugins: vi.fn(),
}
}
beforeEach(() => {
vi.clearAllMocks()
vi.mocked(getPluginComponents).mockReturnValue([])
vi.mocked(usePlugins).mockReturnValue(createMockPluginContext())
})
describe('PluginSlot', () => {
it('renders nothing when no plugins are registered for the slot', () => {
vi.mocked(getPluginComponents).mockReturnValue([])
const { container } = render()
expect(container.innerHTML).toBe('')
})
it('renders fallback when provided and no plugins registered', () => {
vi.mocked(getPluginComponents).mockReturnValue([])
render(Fallback content} />)
expect(screen.getByText('Fallback content')).toBeInTheDocument()
})
it('renders fallback when plugins are registered but none are enabled', () => {
vi.mocked(getPluginComponents).mockReturnValue([
{
pluginName: 'disabled-plugin',
component: TestPluginComponent,
},
] as PluginRegistration[])
vi.mocked(usePlugins).mockReturnValue(
createMockPluginContext([
{
id: '1',
name: 'disabled-plugin',
displayName: 'Disabled Plugin',
version: '1.0.0',
description: 'A disabled plugin',
source: 'core',
enabled: false,
category: 'content',
dependencies: [],
dependents: [],
settingsSchema: {},
settings: {},
installedAt: '2026-01-01T00:00:00Z',
},
])
)
render(Fallback content} />)
expect(screen.getByText('Fallback content')).toBeInTheDocument()
expect(screen.queryByTestId('test-plugin')).not.toBeInTheDocument()
})
it('renders plugin component when registered and plugin is enabled', () => {
vi.mocked(getPluginComponents).mockReturnValue([
{
pluginName: 'test-plugin',
component: TestPluginComponent,
},
] as PluginRegistration[])
vi.mocked(usePlugins).mockReturnValue(
createMockPluginContext([
{
id: '1',
name: 'test-plugin',
displayName: 'Test Plugin',
version: '1.0.0',
description: 'A test plugin',
source: 'core',
enabled: true,
category: 'content',
dependencies: [],
dependents: [],
settingsSchema: {},
settings: {},
installedAt: '2026-01-01T00:00:00Z',
},
])
)
render()
expect(screen.getByTestId('test-plugin')).toBeInTheDocument()
})
it('does not render plugin component when plugin is disabled', () => {
vi.mocked(getPluginComponents).mockReturnValue([
{
pluginName: 'test-plugin',
component: TestPluginComponent,
},
] as PluginRegistration[])
vi.mocked(usePlugins).mockReturnValue(
createMockPluginContext([
{
id: '1',
name: 'test-plugin',
displayName: 'Test Plugin',
version: '1.0.0',
description: 'A test plugin',
source: 'core',
enabled: false,
category: 'content',
dependencies: [],
dependents: [],
settingsSchema: {},
settings: {},
installedAt: '2026-01-01T00:00:00Z',
},
])
)
render()
expect(screen.queryByTestId('test-plugin')).not.toBeInTheDocument()
})
it('error boundary catches crashing plugin component and shows error message', () => {
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
vi.mocked(getPluginComponents).mockReturnValue([
{
pluginName: 'crashing-plugin',
component: CrashingPluginComponent,
},
] as PluginRegistration[])
vi.mocked(usePlugins).mockReturnValue(
createMockPluginContext([
{
id: '1',
name: 'crashing-plugin',
displayName: 'Crashing Plugin',
version: '1.0.0',
description: 'A crashing plugin',
source: 'core',
enabled: true,
category: 'content',
dependencies: [],
dependents: [],
settingsSchema: {},
settings: {},
installedAt: '2026-01-01T00:00:00Z',
},
])
)
render()
expect(screen.getByText(/crashing-plugin/)).toBeInTheDocument()
expect(screen.getByText(/encountered an error/)).toBeInTheDocument()
consoleSpy.mockRestore()
})
it('passes context props to plugin components', () => {
vi.mocked(getPluginComponents).mockReturnValue([
{
pluginName: 'test-plugin',
component: TestPluginComponent,
},
] as PluginRegistration[])
vi.mocked(usePlugins).mockReturnValue(
createMockPluginContext([
{
id: '1',
name: 'test-plugin',
displayName: 'Test Plugin',
version: '1.0.0',
description: 'A test plugin',
source: 'core',
enabled: true,
category: 'content',
dependencies: [],
dependents: [],
settingsSchema: {},
settings: {},
installedAt: '2026-01-01T00:00:00Z',
},
])
)
render()
expect(screen.getByText('Plugin content: did:plc:abc123')).toBeInTheDocument()
})
it('passes axe accessibility check', async () => {
vi.mocked(getPluginComponents).mockReturnValue([
{
pluginName: 'test-plugin',
component: TestPluginComponent,
},
] as PluginRegistration[])
vi.mocked(usePlugins).mockReturnValue(
createMockPluginContext([
{
id: '1',
name: 'test-plugin',
displayName: 'Test Plugin',
version: '1.0.0',
description: 'A test plugin',
source: 'core',
enabled: true,
category: 'content',
dependencies: [],
dependents: [],
settingsSchema: {},
settings: {},
installedAt: '2026-01-01T00:00:00Z',
},
])
)
const { container } = render(
)
const results = await axe(container)
expect(results).toHaveNoViolations()
})
it('passes axe accessibility check with error boundary fallback', async () => {
vi.spyOn(console, 'error').mockImplementation(() => {})
vi.mocked(getPluginComponents).mockReturnValue([
{
pluginName: 'crashing-plugin',
component: CrashingPluginComponent,
},
] as PluginRegistration[])
vi.mocked(usePlugins).mockReturnValue(
createMockPluginContext([
{
id: '1',
name: 'crashing-plugin',
displayName: 'Crashing Plugin',
version: '1.0.0',
description: 'A crashing plugin',
source: 'core',
enabled: true,
category: 'content',
dependencies: [],
dependents: [],
settingsSchema: {},
settings: {},
installedAt: '2026-01-01T00:00:00Z',
},
])
)
const { container } = render()
const results = await axe(container)
expect(results).toHaveNoViolations()
vi.restoreAllMocks()
})
})