Barazo default frontend barazo.forum
at main 299 lines 8.7 kB view raw
1/** 2 * Tests for PluginSlot component. 3 */ 4 5import { describe, it, expect, vi, beforeEach } from 'vitest' 6import { render, screen } from '@testing-library/react' 7import { axe } from 'vitest-axe' 8import { PluginSlot } from './plugin-slot' 9import { usePlugins } from '@/hooks/use-plugins' 10import { getPluginComponents } from '@/lib/plugins/registry' 11import type { PluginRegistration } from '@/lib/plugins/registry' 12import type { PluginContextValue } from '@/context/plugin-context' 13 14vi.mock('@/hooks/use-plugins', () => ({ 15 usePlugins: vi.fn(), 16})) 17 18vi.mock('@/lib/plugins/registry', () => ({ 19 getPluginComponents: vi.fn(), 20})) 21 22// Test helper components 23function TestPluginComponent({ authorDid }: { authorDid?: string }) { 24 return <div data-testid="test-plugin">Plugin content: {authorDid}</div> 25} 26 27function CrashingPluginComponent() { 28 throw new Error('Plugin crashed!') 29} 30 31function createMockPluginContext(plugins: PluginContextValue['plugins'] = []): PluginContextValue { 32 return { 33 plugins, 34 isPluginEnabled: (name: string) => plugins.some((p) => p.name === name && p.enabled), 35 getPluginSettings: () => null, 36 isLoading: false, 37 refreshPlugins: vi.fn(), 38 } 39} 40 41beforeEach(() => { 42 vi.clearAllMocks() 43 vi.mocked(getPluginComponents).mockReturnValue([]) 44 vi.mocked(usePlugins).mockReturnValue(createMockPluginContext()) 45}) 46 47describe('PluginSlot', () => { 48 it('renders nothing when no plugins are registered for the slot', () => { 49 vi.mocked(getPluginComponents).mockReturnValue([]) 50 51 const { container } = render(<PluginSlot name="post-content" />) 52 expect(container.innerHTML).toBe('') 53 }) 54 55 it('renders fallback when provided and no plugins registered', () => { 56 vi.mocked(getPluginComponents).mockReturnValue([]) 57 58 render(<PluginSlot name="post-content" fallback={<div>Fallback content</div>} />) 59 expect(screen.getByText('Fallback content')).toBeInTheDocument() 60 }) 61 62 it('renders fallback when plugins are registered but none are enabled', () => { 63 vi.mocked(getPluginComponents).mockReturnValue([ 64 { 65 pluginName: 'disabled-plugin', 66 component: TestPluginComponent, 67 }, 68 ] as PluginRegistration[]) 69 70 vi.mocked(usePlugins).mockReturnValue( 71 createMockPluginContext([ 72 { 73 id: '1', 74 name: 'disabled-plugin', 75 displayName: 'Disabled Plugin', 76 version: '1.0.0', 77 description: 'A disabled plugin', 78 source: 'core', 79 enabled: false, 80 category: 'content', 81 dependencies: [], 82 dependents: [], 83 settingsSchema: {}, 84 settings: {}, 85 installedAt: '2026-01-01T00:00:00Z', 86 }, 87 ]) 88 ) 89 90 render(<PluginSlot name="post-content" fallback={<div>Fallback content</div>} />) 91 expect(screen.getByText('Fallback content')).toBeInTheDocument() 92 expect(screen.queryByTestId('test-plugin')).not.toBeInTheDocument() 93 }) 94 95 it('renders plugin component when registered and plugin is enabled', () => { 96 vi.mocked(getPluginComponents).mockReturnValue([ 97 { 98 pluginName: 'test-plugin', 99 component: TestPluginComponent, 100 }, 101 ] as PluginRegistration[]) 102 103 vi.mocked(usePlugins).mockReturnValue( 104 createMockPluginContext([ 105 { 106 id: '1', 107 name: 'test-plugin', 108 displayName: 'Test Plugin', 109 version: '1.0.0', 110 description: 'A test plugin', 111 source: 'core', 112 enabled: true, 113 category: 'content', 114 dependencies: [], 115 dependents: [], 116 settingsSchema: {}, 117 settings: {}, 118 installedAt: '2026-01-01T00:00:00Z', 119 }, 120 ]) 121 ) 122 123 render(<PluginSlot name="post-content" />) 124 expect(screen.getByTestId('test-plugin')).toBeInTheDocument() 125 }) 126 127 it('does not render plugin component when plugin is disabled', () => { 128 vi.mocked(getPluginComponents).mockReturnValue([ 129 { 130 pluginName: 'test-plugin', 131 component: TestPluginComponent, 132 }, 133 ] as PluginRegistration[]) 134 135 vi.mocked(usePlugins).mockReturnValue( 136 createMockPluginContext([ 137 { 138 id: '1', 139 name: 'test-plugin', 140 displayName: 'Test Plugin', 141 version: '1.0.0', 142 description: 'A test plugin', 143 source: 'core', 144 enabled: false, 145 category: 'content', 146 dependencies: [], 147 dependents: [], 148 settingsSchema: {}, 149 settings: {}, 150 installedAt: '2026-01-01T00:00:00Z', 151 }, 152 ]) 153 ) 154 155 render(<PluginSlot name="post-content" />) 156 expect(screen.queryByTestId('test-plugin')).not.toBeInTheDocument() 157 }) 158 159 it('error boundary catches crashing plugin component and shows error message', () => { 160 const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) 161 162 vi.mocked(getPluginComponents).mockReturnValue([ 163 { 164 pluginName: 'crashing-plugin', 165 component: CrashingPluginComponent, 166 }, 167 ] as PluginRegistration[]) 168 169 vi.mocked(usePlugins).mockReturnValue( 170 createMockPluginContext([ 171 { 172 id: '1', 173 name: 'crashing-plugin', 174 displayName: 'Crashing Plugin', 175 version: '1.0.0', 176 description: 'A crashing plugin', 177 source: 'core', 178 enabled: true, 179 category: 'content', 180 dependencies: [], 181 dependents: [], 182 settingsSchema: {}, 183 settings: {}, 184 installedAt: '2026-01-01T00:00:00Z', 185 }, 186 ]) 187 ) 188 189 render(<PluginSlot name="post-content" />) 190 expect(screen.getByText(/crashing-plugin/)).toBeInTheDocument() 191 expect(screen.getByText(/encountered an error/)).toBeInTheDocument() 192 193 consoleSpy.mockRestore() 194 }) 195 196 it('passes context props to plugin components', () => { 197 vi.mocked(getPluginComponents).mockReturnValue([ 198 { 199 pluginName: 'test-plugin', 200 component: TestPluginComponent, 201 }, 202 ] as PluginRegistration[]) 203 204 vi.mocked(usePlugins).mockReturnValue( 205 createMockPluginContext([ 206 { 207 id: '1', 208 name: 'test-plugin', 209 displayName: 'Test Plugin', 210 version: '1.0.0', 211 description: 'A test plugin', 212 source: 'core', 213 enabled: true, 214 category: 'content', 215 dependencies: [], 216 dependents: [], 217 settingsSchema: {}, 218 settings: {}, 219 installedAt: '2026-01-01T00:00:00Z', 220 }, 221 ]) 222 ) 223 224 render(<PluginSlot name="post-content" context={{ authorDid: 'did:plc:abc123' }} />) 225 expect(screen.getByText('Plugin content: did:plc:abc123')).toBeInTheDocument() 226 }) 227 228 it('passes axe accessibility check', async () => { 229 vi.mocked(getPluginComponents).mockReturnValue([ 230 { 231 pluginName: 'test-plugin', 232 component: TestPluginComponent, 233 }, 234 ] as PluginRegistration[]) 235 236 vi.mocked(usePlugins).mockReturnValue( 237 createMockPluginContext([ 238 { 239 id: '1', 240 name: 'test-plugin', 241 displayName: 'Test Plugin', 242 version: '1.0.0', 243 description: 'A test plugin', 244 source: 'core', 245 enabled: true, 246 category: 'content', 247 dependencies: [], 248 dependents: [], 249 settingsSchema: {}, 250 settings: {}, 251 installedAt: '2026-01-01T00:00:00Z', 252 }, 253 ]) 254 ) 255 256 const { container } = render( 257 <PluginSlot name="post-content" context={{ authorDid: 'did:plc:abc123' }} /> 258 ) 259 const results = await axe(container) 260 expect(results).toHaveNoViolations() 261 }) 262 263 it('passes axe accessibility check with error boundary fallback', async () => { 264 vi.spyOn(console, 'error').mockImplementation(() => {}) 265 266 vi.mocked(getPluginComponents).mockReturnValue([ 267 { 268 pluginName: 'crashing-plugin', 269 component: CrashingPluginComponent, 270 }, 271 ] as PluginRegistration[]) 272 273 vi.mocked(usePlugins).mockReturnValue( 274 createMockPluginContext([ 275 { 276 id: '1', 277 name: 'crashing-plugin', 278 displayName: 'Crashing Plugin', 279 version: '1.0.0', 280 description: 'A crashing plugin', 281 source: 'core', 282 enabled: true, 283 category: 'content', 284 dependencies: [], 285 dependents: [], 286 settingsSchema: {}, 287 settings: {}, 288 installedAt: '2026-01-01T00:00:00Z', 289 }, 290 ]) 291 ) 292 293 const { container } = render(<PluginSlot name="post-content" />) 294 const results = await axe(container) 295 expect(results).toHaveNoViolations() 296 297 vi.restoreAllMocks() 298 }) 299})