import { describe, expect, test } from 'vitest' import { getActiveFields, getDefaultState, resetDependentFields, resolveConditional, resolveSteps, } from './connect.resolver' import type { ConditionalValue, ConnectSchema, ConnectState, StepTree } from './Connect.types' // ============================================================================ // resolveConditional Tests // ============================================================================ describe('connect.resolver:resolveConditional', () => { test('should return primitive values directly', () => { expect(resolveConditional('hello', { mode: 'framework' })).toBe('hello') expect(resolveConditional(42, { mode: 'framework' })).toBe(42) expect(resolveConditional(null, { mode: 'framework' })).toBe(null) expect(resolveConditional(true, { mode: 'framework' })).toBe(true) }) test('should return arrays directly', () => { const arr = ['a', 'b', 'c'] expect(resolveConditional(arr, { mode: 'framework' })).toBe(arr) }) test('should resolve single-level conditional based on mode', () => { const conditional: ConditionalValue = { framework: 'Framework Content', direct: 'Direct Content', DEFAULT: 'Default Content', } expect(resolveConditional(conditional, { mode: 'framework' })).toBe('Framework Content') expect(resolveConditional(conditional, { mode: 'direct' })).toBe('Direct Content') expect(resolveConditional(conditional, { mode: 'orm' })).toBe('Default Content') }) test('should resolve nested conditionals (mode -> framework)', () => { const conditional: ConditionalValue = { framework: { nextjs: 'Next.js Content', react: 'React Content', DEFAULT: 'Generic Framework', }, direct: 'Direct Content', } expect(resolveConditional(conditional, { mode: 'framework', framework: 'nextjs' })).toBe( 'Next.js Content' ) expect(resolveConditional(conditional, { mode: 'framework', framework: 'react' })).toBe( 'React Content' ) expect(resolveConditional(conditional, { mode: 'framework', framework: 'vue' })).toBe( 'Generic Framework' ) expect(resolveConditional(conditional, { mode: 'direct' })).toBe('Direct Content') }) test('should resolve deeply nested conditionals (mode -> framework -> variant)', () => { const conditional: ConditionalValue = { framework: { nextjs: { app: 'App Router Content', pages: 'Pages Router Content', DEFAULT: 'Generic Next.js', }, DEFAULT: 'Generic Framework', }, } expect( resolveConditional(conditional, { mode: 'framework', framework: 'nextjs', frameworkVariant: 'app', }) ).toBe('App Router Content') expect( resolveConditional(conditional, { mode: 'framework', framework: 'nextjs', frameworkVariant: 'pages', }) ).toBe('Pages Router Content') expect( resolveConditional(conditional, { mode: 'framework', framework: 'nextjs', frameworkVariant: 'unknown', }) ).toBe('Generic Next.js') }) test('should resolve MCP client-specific content', () => { const conditional: ConditionalValue = { mcp: { cursor: 'Cursor Config', codex: 'Codex Config', 'claude-code': 'Claude Code Config', DEFAULT: 'Generic MCP', }, } expect(resolveConditional(conditional, { mode: 'mcp', mcpClient: 'cursor' })).toBe( 'Cursor Config' ) expect(resolveConditional(conditional, { mode: 'mcp', mcpClient: 'codex' })).toBe( 'Codex Config' ) expect(resolveConditional(conditional, { mode: 'mcp', mcpClient: 'claude-code' })).toBe( 'Claude Code Config' ) }) test('should return undefined when no match and no DEFAULT', () => { const conditional: ConditionalValue = { framework: 'Framework', direct: 'Direct', } expect(resolveConditional(conditional, { mode: 'orm' })).toBe(undefined) }) test('should handle frameworkUi boolean state (as string "true"/"false")', () => { const conditional: ConditionalValue = { framework: { nextjs: { true: 'Shadcn Steps', DEFAULT: 'Regular Steps', }, }, } // When frameworkUi is true (boolean), it gets converted to string "true" expect( resolveConditional(conditional, { mode: 'framework', framework: 'nextjs', frameworkUi: true }) ).toBe('Shadcn Steps') expect( resolveConditional(conditional, { mode: 'framework', framework: 'nextjs', frameworkUi: false, }) ).toBe('Regular Steps') }) }) // ============================================================================ // resolveSteps Tests // ============================================================================ describe('connect.resolver:resolveSteps', () => { const createMockSchema = (steps: StepTree): ConnectSchema => ({ modes: [ { id: 'framework', label: 'Framework', description: '', fields: [] }, { id: 'direct', label: 'Direct', description: '', fields: [] }, ], fields: {}, steps, }) test('should resolve steps array for framework mode', () => { const schema = createMockSchema({ mode: { framework: [ { id: 'step1', title: 'Install', description: 'Install pkg', content: 'install-content' }, { id: 'step2', title: 'Configure', description: 'Configure', content: 'config-content' }, ], direct: [ { id: 'connection', title: 'Connection', description: 'Connect', content: 'direct-content', }, ], }, }) const steps = resolveSteps(schema, { mode: 'framework' }) expect(steps).toHaveLength(2) expect(steps[0].id).toBe('step1') expect(steps[0].title).toBe('Install') expect(steps[1].id).toBe('step2') }) test('should resolve steps for direct mode', () => { const schema = createMockSchema({ mode: { framework: [{ id: 'step1', title: 'Install', description: 'Install', content: 'install' }], direct: [ { id: 'connection', title: 'Connection', description: 'Connect', content: 'direct' }, ], }, }) const steps = resolveSteps(schema, { mode: 'direct' }) expect(steps).toHaveLength(1) expect(steps[0].id).toBe('connection') }) test('should filter out steps with empty content', () => { const schema = createMockSchema({ mode: { framework: [ { id: 'step1', title: 'Valid', description: 'Valid step', content: 'valid-content' }, { id: 'step2', title: 'Empty', description: 'Empty step', content: '' }, { id: 'step3', title: 'Null', description: 'Null step', content: null }, ], }, }) const steps = resolveSteps(schema, { mode: 'framework' }) expect(steps).toHaveLength(1) expect(steps[0].id).toBe('step1') }) test('should resolve conditional step content based on state', () => { const schema = createMockSchema({ mode: { framework: [ { id: 'configure', title: 'Configure', description: 'Configure', content: { nextjs: 'nextjs-content', react: 'react-content', DEFAULT: 'generic-content', }, }, ], }, }) const nextjsSteps = resolveSteps(schema, { mode: 'framework', framework: 'nextjs' }) expect(nextjsSteps[0].content).toBe('nextjs-content') const reactSteps = resolveSteps(schema, { mode: 'framework', framework: 'react' }) expect(reactSteps[0].content).toBe('react-content') const vueSteps = resolveSteps(schema, { mode: 'framework', framework: 'vue' }) expect(vueSteps[0].content).toBe('generic-content') }) test('should return empty array when no steps resolve', () => { const schema = createMockSchema({ mode: { framework: [{ id: 'step1', title: 'Step', description: 'Desc', content: '' }], }, }) const steps = resolveSteps(schema, { mode: 'framework' }) expect(steps).toEqual([]) }) test('should return empty array when steps is not an array', () => { const schema = createMockSchema({ mode: { framework: { framework: {}, }, }, } as any) const steps = resolveSteps(schema, { mode: 'framework' }) expect(steps).toEqual([]) }) test('should append steps from multiple field conditions in order', () => { const schema = createMockSchema({ mode: { framework: { framework: { nextjs: [{ id: 'base', title: 'Base', description: 'Base step', content: 'base' }], }, frameworkUi: { true: [{ id: 'ui', title: 'UI', description: 'UI step', content: 'ui' }], }, }, }, }) const steps = resolveSteps(schema, { mode: 'framework', framework: 'nextjs', frameworkUi: true, }) expect(steps.map((step) => step.id)).toEqual(['base', 'ui']) }) }) // ============================================================================ // getActiveFields Tests // ============================================================================ describe('connect.resolver:getActiveFields', () => { const createSchemaWithFields = ( modes: ConnectSchema['modes'], fields: ConnectSchema['fields'] ): ConnectSchema => ({ modes, fields, steps: [], }) test('should return fields for the current mode', () => { const schema = createSchemaWithFields( [ { id: 'framework', label: 'Framework', description: '', fields: ['framework', 'library'] }, { id: 'direct', label: 'Direct', description: '', fields: ['connectionType'] }, ], { framework: { id: 'framework', type: 'radio-grid', label: 'Framework', defaultValue: 'nextjs', }, library: { id: 'library', type: 'select', label: 'Library', defaultValue: 'brivenjs', }, connectionType: { id: 'connectionType', type: 'select', label: 'Type', defaultValue: 'uri', }, } ) const frameworkFields = getActiveFields(schema, { mode: 'framework' }) expect(frameworkFields).toHaveLength(2) expect(frameworkFields.map((f) => f.id)).toEqual(['framework', 'library']) const directFields = getActiveFields(schema, { mode: 'direct' }) expect(directFields).toHaveLength(1) expect(directFields[0].id).toBe('connectionType') }) test('should filter fields by dependsOn conditions', () => { const schema = createSchemaWithFields( [ { id: 'framework', label: 'Framework', description: '', fields: ['framework', 'frameworkVariant', 'frameworkUi'], }, ], { framework: { id: 'framework', type: 'radio-grid', label: 'Framework', defaultValue: 'nextjs', }, frameworkVariant: { id: 'frameworkVariant', type: 'select', label: 'Variant', dependsOn: { framework: ['nextjs', 'react'] }, }, frameworkUi: { id: 'frameworkUi', type: 'switch', label: 'Shadcn', dependsOn: { framework: ['nextjs', 'react'] }, }, } ) // With nextjs - should show all fields const nextjsFields = getActiveFields(schema, { mode: 'framework', framework: 'nextjs' }) expect(nextjsFields).toHaveLength(3) // With vue - should hide frameworkVariant and frameworkUi const vueFields = getActiveFields(schema, { mode: 'framework', framework: 'vue' }) expect(vueFields).toHaveLength(1) expect(vueFields[0].id).toBe('framework') }) test('should handle multiple dependsOn conditions', () => { const schema = createSchemaWithFields( [ { id: 'direct', label: 'Direct', description: '', fields: ['connectionMethod', 'useSharedPooler'], }, ], { connectionMethod: { id: 'connectionMethod', type: 'radio-list', label: 'Method', defaultValue: 'direct', }, useSharedPooler: { id: 'useSharedPooler', type: 'switch', label: 'Use Shared Pooler', dependsOn: { connectionMethod: ['transaction'] }, }, } ) // Transaction mode - show shared pooler option const transactionFields = getActiveFields(schema, { mode: 'direct', connectionMethod: 'transaction', }) expect(transactionFields).toHaveLength(2) // Direct mode - hide shared pooler option const directFields = getActiveFields(schema, { mode: 'direct', connectionMethod: 'direct' }) expect(directFields).toHaveLength(1) expect(directFields[0].id).toBe('connectionMethod') }) test('should return empty array for invalid mode', () => { const schema = createSchemaWithFields( [{ id: 'framework', label: 'Framework', description: '', fields: ['framework'] }], { framework: { id: 'framework', type: 'radio-grid', label: 'Framework' } } ) const fields = getActiveFields(schema, { mode: 'invalid' as any }) expect(fields).toEqual([]) }) test('should include resolvedOptions for each field', () => { const schema = createSchemaWithFields( [{ id: 'framework', label: 'Framework', description: '', fields: ['framework'] }], { framework: { id: 'framework', type: 'radio-grid', label: 'Framework', options: { source: 'frameworks' }, // Source reference - resolved elsewhere }, } ) const fields = getActiveFields(schema, { mode: 'framework' }) expect(fields[0]).toHaveProperty('resolvedOptions') // Source options are resolved by the hook, not the resolver expect(fields[0].resolvedOptions).toEqual([]) }) }) // ============================================================================ // getDefaultState Tests // ============================================================================ describe('connect.resolver:getDefaultState', () => { test('should return default state with first mode', () => { const schema: ConnectSchema = { modes: [ { id: 'framework', label: 'Framework', description: '', fields: [] }, { id: 'direct', label: 'Direct', description: '', fields: [] }, ], fields: {}, steps: [], } const state = getDefaultState({ schema }) expect(state.mode).toBe('framework') }) test('should include default values from fields', () => { const schema: ConnectSchema = { modes: [{ id: 'framework', label: 'Framework', description: '', fields: ['framework'] }], fields: { framework: { id: 'framework', type: 'radio-grid', label: 'Framework', defaultValue: 'nextjs', }, library: { id: 'library', type: 'select', label: 'Library', defaultValue: 'brivenjs', }, mcpReadonly: { id: 'mcpReadonly', type: 'switch', label: 'Readonly', defaultValue: false, }, }, steps: [], } const state = getDefaultState({ schema }) expect(state.framework).toBe('nextjs') expect(state.library).toBe('brivenjs') expect(state.mcpReadonly).toBe(false) }) test('should fallback to "direct" if no modes defined', () => { const schema: ConnectSchema = { modes: [], fields: {}, steps: [], } const state = getDefaultState({ schema }) expect(state.mode).toBe('direct') }) }) // ============================================================================ // resetDependentFields Tests // ============================================================================ describe('connect.resolver:resetDependentFields', () => { const createSchemaForReset = (): ConnectSchema => ({ modes: [ { id: 'framework', label: 'Framework', description: '', fields: ['framework', 'frameworkVariant', 'frameworkUi'], }, { id: 'direct', label: 'Direct', description: '', fields: ['connectionMethod'] }, ], fields: { framework: { id: 'framework', type: 'radio-grid', label: 'Framework', defaultValue: 'nextjs', }, frameworkVariant: { id: 'frameworkVariant', type: 'select', label: 'Variant', dependsOn: { framework: ['nextjs', 'react'] }, }, frameworkUi: { id: 'frameworkUi', type: 'switch', label: 'Shadcn', dependsOn: { framework: ['nextjs', 'react'] }, }, connectionMethod: { id: 'connectionMethod', type: 'radio-list', label: 'Method', defaultValue: 'direct', }, }, steps: [], }) test('should reset dependent fields when dependency no longer satisfied', () => { const schema = createSchemaForReset() const state: ConnectState = { mode: 'framework', framework: 'vue', // Changed from nextjs to vue frameworkVariant: 'app', // This should be reset frameworkUi: true, // This should be reset } const newState = resetDependentFields(state, 'framework', schema) expect(newState.frameworkVariant).toBeUndefined() expect(newState.frameworkUi).toBeUndefined() }) test('should keep dependent fields when dependency still satisfied', () => { const schema = createSchemaForReset() const state: ConnectState = { mode: 'framework', framework: 'react', // Still in the allowed list frameworkVariant: 'vite', frameworkUi: true, } const newState = resetDependentFields(state, 'framework', schema) expect(newState.frameworkVariant).toBe('vite') expect(newState.frameworkUi).toBe(true) }) test('should handle mode changes', () => { const schema = createSchemaForReset() const state: ConnectState = { mode: 'direct', // Changed mode framework: 'nextjs', frameworkVariant: 'app', } // Note: The current implementation of resetDependentFields for mode changes // looks for fields not in the current mode, but the logic compares against previous mode const newState = resetDependentFields(state, 'mode', schema) // Mode-specific field reset logic is handled expect(newState.mode).toBe('direct') }) test('should not modify state for fields without dependencies', () => { const schema = createSchemaForReset() const state: ConnectState = { mode: 'framework', framework: 'nextjs', } const newState = resetDependentFields(state, 'framework', schema) expect(newState.mode).toBe('framework') expect(newState.framework).toBe('nextjs') }) })