/** * useFeatureFlags Composable Tests * Verifies feature flag state management, permissions, and overrides */ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import * as useAuthModule from '../../../src/composables/useAuth.js'; import { useFeatureFlags } from '../../../src/composables/useFeatureFlags.js'; // Mock useAuth module with default no-permissions user vi.spyOn(useAuthModule, 'useAuth').mockReturnValue({ user: { isAdmin: false, permissions: [] }, token: null, hasPermission: vi.fn(() => false) }); describe('useFeatureFlags', () => { beforeEach(() => { // Clear localStorage before each test localStorage.clear(); }); afterEach(() => { localStorage.clear(); }); describe('isEnabled', () => { it('returns flag default value when no override exists', () => { const { isEnabled } = useFeatureFlags(); // ENABLE_CACHING defaults to true expect(isEnabled.value('enable-caching')).toBe(true); // DARK_MODE defaults to false expect(isEnabled.value('dark-mode')).toBe(false); }); it('returns false for unknown flag name', () => { const { isEnabled } = useFeatureFlags(); expect(isEnabled.value('nonexistent-flag')).toBe(false); }); it('respects local override when set in same instance', () => { const { isEnabled, toggle } = useFeatureFlags(); expect(isEnabled.value('dark-mode')).toBe(false); toggle('dark-mode'); // Should be toggled in same instance expect(isEnabled.value('dark-mode')).toBe(true); }); it('returns false for flag requiring permission without it', () => { const { isEnabled } = useFeatureFlags(); // GAMEMASTER_DIFF_VIEWER requires 'gamemaster-advanced' permission expect(isEnabled.value('gamemaster-diff-viewer')).toBe(false); }); }); describe('toggle', () => { it('toggles flag override in development mode', () => { const { toggle, isEnabled } = useFeatureFlags(); expect(isEnabled.value('dark-mode')).toBe(false); const result = toggle('dark-mode'); expect(result).toBe(true); expect(isEnabled.value('dark-mode')).toBe(true); }); it('persists override to localStorage', () => { const { toggle } = useFeatureFlags(); toggle('dark-mode'); const stored = localStorage.getItem('feature_flag_overrides'); expect(stored).not.toBeNull(); const overrides = JSON.parse(stored); expect(overrides['dark-mode']).toBe(true); }); it('loads overrides from localStorage on init', () => { // Set override in storage first localStorage.setItem( 'feature_flag_overrides', JSON.stringify({ 'dark-mode': true, 'experimental-search': true }) ); const { isEnabled } = useFeatureFlags(); expect(isEnabled.value('dark-mode')).toBe(true); expect(isEnabled.value('experimental-search')).toBe(true); }); }); describe('reset', () => { it('removes override for specific flag', () => { const { isEnabled, toggle, reset } = useFeatureFlags(); toggle('dark-mode'); expect(isEnabled.value('dark-mode')).toBe(true); reset('dark-mode'); expect(isEnabled.value('dark-mode')).toBe(false); }); it('returns to default after reset', () => { const { isEnabled, toggle, reset } = useFeatureFlags(); // ENABLE_CACHING defaults to true const beforeToggle = isEnabled.value('enable-caching'); toggle('enable-caching'); expect(isEnabled.value('enable-caching')).not.toBe(beforeToggle); reset('enable-caching'); expect(isEnabled.value('enable-caching')).toBe(beforeToggle); }); }); describe('resetAll', () => { it('clears all overrides', () => { const { isEnabled, toggle, resetAll } = useFeatureFlags(); toggle('dark-mode'); toggle('experimental-search'); resetAll(); // Should return to defaults expect(isEnabled.value('dark-mode')).toBe(false); expect(isEnabled.value('experimental-search')).toBe(false); }); it('clears localStorage after reset', () => { const { toggle, resetAll } = useFeatureFlags(); toggle('dark-mode'); resetAll(); const stored = localStorage.getItem('feature_flag_overrides'); expect(stored).toBe('{}'); }); }); describe('getFlags', () => { it('returns array of all flags with status', () => { const { getFlags } = useFeatureFlags(); const flags = getFlags(); expect(Array.isArray(flags)).toBe(true); expect(flags.length).toBeGreaterThan(0); const darkMode = flags.find(f => f.name === 'dark-mode'); expect(darkMode).toBeDefined(); expect(darkMode).toHaveProperty('isEnabled'); expect(darkMode).toHaveProperty('hasOverride'); expect(darkMode).toHaveProperty('requiresPermission'); }); it('marks flags with overrides', () => { const { getFlags, toggle } = useFeatureFlags(); toggle('dark-mode'); const flags = getFlags(); const darkMode = flags.find(f => f.name === 'dark-mode'); expect(darkMode.hasOverride).toBe(true); expect(darkMode.override).toBe(true); }); it('indicates permission requirements for flags', () => { const { getFlags } = useFeatureFlags(); const flags = getFlags(); // Should have flags with and without permissions const withPerm = flags.find(f => f.requiresPermission); const withoutPerm = flags.find(f => !f.requiresPermission); expect(withPerm).toBeDefined(); expect(withoutPerm).toBeDefined(); }); }); describe('setBackendFlags', () => { it('sets flags from backend response', () => { const { isEnabled, setBackendFlags } = useFeatureFlags(); expect(isEnabled.value('dark-mode')).toBe(false); setBackendFlags({ 'dark-mode': true, 'experimental-search': true }); expect(isEnabled.value('dark-mode')).toBe(true); expect(isEnabled.value('experimental-search')).toBe(true); }); it('local overrides take precedence over backend flags', () => { const { isEnabled, toggle, setBackendFlags } = useFeatureFlags(); toggle('dark-mode'); // Local override to true setBackendFlags({ 'dark-mode': false // Backend says false }); // Local override should win expect(isEnabled.value('dark-mode')).toBe(true); }); }); });