import type { SimpleDetail } from './store' import { act, renderHook } from '@testing-library/react' import { beforeEach, describe, expect, it } from 'vitest' import { usePluginStore } from './store' // Factory function to create mock SimpleDetail const createSimpleDetail = (overrides: Partial = {}): SimpleDetail => ({ plugin_id: 'test-plugin-id', name: 'Test Plugin', plugin_unique_identifier: 'test-plugin-uid', id: 'test-id', provider: 'test-provider', declaration: { category: 'tool' as SimpleDetail['declaration']['category'], name: 'test-declaration', }, ...overrides, }) describe('usePluginStore', () => { beforeEach(() => { // Reset store state before each test const { result } = renderHook(() => usePluginStore()) act(() => { result.current.setDetail(undefined) }) }) describe('Initial State', () => { it('should have undefined detail initially', () => { const { result } = renderHook(() => usePluginStore()) expect(result.current.detail).toBeUndefined() }) it('should provide setDetail function', () => { const { result } = renderHook(() => usePluginStore()) expect(typeof result.current.setDetail).toBe('function') }) }) describe('setDetail', () => { it('should set detail with valid SimpleDetail', () => { const { result } = renderHook(() => usePluginStore()) const detail = createSimpleDetail() act(() => { result.current.setDetail(detail) }) expect(result.current.detail).toEqual(detail) }) it('should set detail to undefined', () => { const { result } = renderHook(() => usePluginStore()) const detail = createSimpleDetail() // First set a value act(() => { result.current.setDetail(detail) }) expect(result.current.detail).toEqual(detail) // Then clear it act(() => { result.current.setDetail(undefined) }) expect(result.current.detail).toBeUndefined() }) it('should update detail when called multiple times', () => { const { result } = renderHook(() => usePluginStore()) const detail1 = createSimpleDetail({ plugin_id: 'plugin-1' }) const detail2 = createSimpleDetail({ plugin_id: 'plugin-2' }) act(() => { result.current.setDetail(detail1) }) expect(result.current.detail?.plugin_id).toBe('plugin-1') act(() => { result.current.setDetail(detail2) }) expect(result.current.detail?.plugin_id).toBe('plugin-2') }) it('should handle detail with trigger declaration', () => { const { result } = renderHook(() => usePluginStore()) const detail = createSimpleDetail({ declaration: { trigger: { subscription_schema: [], subscription_constructor: null, }, }, }) act(() => { result.current.setDetail(detail) }) expect(result.current.detail?.declaration.trigger).toEqual({ subscription_schema: [], subscription_constructor: null, }) }) it('should handle detail with partial declaration', () => { const { result } = renderHook(() => usePluginStore()) const detail = createSimpleDetail({ declaration: { name: 'partial-plugin', }, }) act(() => { result.current.setDetail(detail) }) expect(result.current.detail?.declaration.name).toBe('partial-plugin') }) }) describe('Store Sharing', () => { it('should share state across multiple hook instances', () => { const { result: result1 } = renderHook(() => usePluginStore()) const { result: result2 } = renderHook(() => usePluginStore()) const detail = createSimpleDetail() act(() => { result1.current.setDetail(detail) }) // Both hooks should see the same state expect(result1.current.detail).toEqual(detail) expect(result2.current.detail).toEqual(detail) }) it('should update all hook instances when state changes', () => { const { result: result1 } = renderHook(() => usePluginStore()) const { result: result2 } = renderHook(() => usePluginStore()) const detail1 = createSimpleDetail({ name: 'Plugin One' }) const detail2 = createSimpleDetail({ name: 'Plugin Two' }) act(() => { result1.current.setDetail(detail1) }) expect(result1.current.detail?.name).toBe('Plugin One') expect(result2.current.detail?.name).toBe('Plugin One') act(() => { result2.current.setDetail(detail2) }) expect(result1.current.detail?.name).toBe('Plugin Two') expect(result2.current.detail?.name).toBe('Plugin Two') }) }) describe('Selector Pattern', () => { // Extract selectors to reduce nesting depth const selectDetail = (state: ReturnType) => state.detail const selectSetDetail = (state: ReturnType) => state.setDetail it('should support selector to get specific field', () => { const { result: setterResult } = renderHook(() => usePluginStore()) const detail = createSimpleDetail({ plugin_id: 'selected-plugin' }) act(() => { setterResult.current.setDetail(detail) }) // Use selector to get only detail const { result: selectorResult } = renderHook(() => usePluginStore(selectDetail)) expect(selectorResult.current?.plugin_id).toBe('selected-plugin') }) it('should support selector to get setDetail function', () => { const { result } = renderHook(() => usePluginStore(selectSetDetail)) expect(typeof result.current).toBe('function') }) }) describe('Edge Cases', () => { it('should handle empty string values in detail', () => { const { result } = renderHook(() => usePluginStore()) const detail = createSimpleDetail({ plugin_id: '', name: '', plugin_unique_identifier: '', provider: '', }) act(() => { result.current.setDetail(detail) }) expect(result.current.detail?.plugin_id).toBe('') expect(result.current.detail?.name).toBe('') }) it('should handle detail with empty declaration', () => { const { result } = renderHook(() => usePluginStore()) const detail = createSimpleDetail({ declaration: {}, }) act(() => { result.current.setDetail(detail) }) expect(result.current.detail?.declaration).toEqual({}) }) it('should handle rapid state updates', () => { const { result } = renderHook(() => usePluginStore()) act(() => { for (let i = 0; i < 10; i++) result.current.setDetail(createSimpleDetail({ plugin_id: `plugin-${i}` })) }) expect(result.current.detail?.plugin_id).toBe('plugin-9') }) it('should handle setDetail called without arguments', () => { const { result } = renderHook(() => usePluginStore()) const detail = createSimpleDetail() act(() => { result.current.setDetail(detail) }) expect(result.current.detail).toBeDefined() act(() => { result.current.setDetail() }) expect(result.current.detail).toBeUndefined() }) }) describe('Type Safety', () => { it('should preserve all SimpleDetail fields correctly', () => { const { result } = renderHook(() => usePluginStore()) const detail: SimpleDetail = { plugin_id: 'type-test-id', name: 'Type Test Plugin', plugin_unique_identifier: 'type-test-uid', id: 'type-id', provider: 'type-provider', declaration: { category: 'model' as SimpleDetail['declaration']['category'], name: 'type-declaration', version: '2.0.0', author: 'test-author', }, } act(() => { result.current.setDetail(detail) }) expect(result.current.detail).toStrictEqual(detail) expect(result.current.detail?.plugin_id).toBe('type-test-id') expect(result.current.detail?.name).toBe('Type Test Plugin') expect(result.current.detail?.plugin_unique_identifier).toBe('type-test-uid') expect(result.current.detail?.id).toBe('type-id') expect(result.current.detail?.provider).toBe('type-provider') }) it('should handle declaration with subscription_constructor', () => { const { result } = renderHook(() => usePluginStore()) const mockConstructor = { credentials_schema: [], oauth_schema: { client_schema: [], credentials_schema: [], }, parameters: [], } const detail = createSimpleDetail({ declaration: { trigger: { subscription_schema: [], subscription_constructor: mockConstructor as unknown as NonNullable['subscription_constructor'], }, }, }) act(() => { result.current.setDetail(detail) }) expect(result.current.detail?.declaration.trigger?.subscription_constructor).toBeDefined() }) it('should handle declaration with subscription_schema', () => { const { result } = renderHook(() => usePluginStore()) const detail = createSimpleDetail({ declaration: { trigger: { subscription_schema: [], subscription_constructor: null, }, }, }) act(() => { result.current.setDetail(detail) }) expect(result.current.detail?.declaration.trigger?.subscription_schema).toEqual([]) }) }) describe('State Persistence', () => { it('should maintain state after multiple renders', () => { const detail = createSimpleDetail({ name: 'Persistent Plugin' }) const { result, rerender } = renderHook(() => usePluginStore()) act(() => { result.current.setDetail(detail) }) // Rerender multiple times rerender() rerender() rerender() expect(result.current.detail?.name).toBe('Persistent Plugin') }) it('should maintain reference equality for unchanged state', () => { const { result } = renderHook(() => usePluginStore()) const detail = createSimpleDetail() act(() => { result.current.setDetail(detail) }) const firstDetailRef = result.current.detail // Get state again without changing const { result: result2 } = renderHook(() => usePluginStore()) expect(result2.current.detail).toBe(firstDetailRef) }) }) describe('Concurrent Updates', () => { it('should handle updates from multiple sources correctly', () => { const { result: hook1 } = renderHook(() => usePluginStore()) const { result: hook2 } = renderHook(() => usePluginStore()) const { result: hook3 } = renderHook(() => usePluginStore()) act(() => { hook1.current.setDetail(createSimpleDetail({ name: 'From Hook 1' })) }) act(() => { hook2.current.setDetail(createSimpleDetail({ name: 'From Hook 2' })) }) act(() => { hook3.current.setDetail(createSimpleDetail({ name: 'From Hook 3' })) }) // All hooks should reflect the last update expect(hook1.current.detail?.name).toBe('From Hook 3') expect(hook2.current.detail?.name).toBe('From Hook 3') expect(hook3.current.detail?.name).toBe('From Hook 3') }) it('should handle interleaved read and write operations', () => { const { result } = renderHook(() => usePluginStore()) act(() => { result.current.setDetail(createSimpleDetail({ plugin_id: 'step-1' })) }) expect(result.current.detail?.plugin_id).toBe('step-1') act(() => { result.current.setDetail(createSimpleDetail({ plugin_id: 'step-2' })) }) expect(result.current.detail?.plugin_id).toBe('step-2') act(() => { result.current.setDetail(undefined) }) expect(result.current.detail).toBeUndefined() act(() => { result.current.setDetail(createSimpleDetail({ plugin_id: 'step-3' })) }) expect(result.current.detail?.plugin_id).toBe('step-3') }) }) describe('Declaration Variations', () => { it('should handle declaration with all optional fields', () => { const { result } = renderHook(() => usePluginStore()) const detail = createSimpleDetail({ declaration: { category: 'extension' as SimpleDetail['declaration']['category'], name: 'full-declaration', version: '1.0.0', author: 'full-author', icon: 'icon.png', verified: true, tags: ['tag1', 'tag2'], }, }) act(() => { result.current.setDetail(detail) }) const decl = result.current.detail?.declaration expect(decl?.category).toBe('extension') expect(decl?.name).toBe('full-declaration') expect(decl?.version).toBe('1.0.0') expect(decl?.author).toBe('full-author') expect(decl?.icon).toBe('icon.png') expect(decl?.verified).toBe(true) expect(decl?.tags).toEqual(['tag1', 'tag2']) }) it('should handle declaration with nested tool object', () => { const { result } = renderHook(() => usePluginStore()) const mockTool = { identity: { author: 'tool-author', name: 'tool-name', icon: 'tool-icon.png', tags: ['api', 'utility'], }, credentials_schema: [], } const detail = createSimpleDetail({ declaration: { tool: mockTool as unknown as SimpleDetail['declaration']['tool'], }, }) act(() => { result.current.setDetail(detail) }) expect(result.current.detail?.declaration.tool?.identity.name).toBe('tool-name') expect(result.current.detail?.declaration.tool?.identity.tags).toEqual(['api', 'utility']) }) }) })