/** * DeveloperTools Component Tests * Verifies developer tools panel rendering and interactions */ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { mount } from '@vue/test-utils'; import DeveloperTools from '../../../src/components/DeveloperTools.vue'; vi.mock('../../../src/composables/useAuth.js', () => ({ useAuth: () => ({ user: { isAdmin: true, permissions: ['admin', 'gamemaster-edit'] }, token: 'test-token-xyz123' }) })); vi.mock('../../../src/composables/useFeatureFlags.js', () => ({ useFeatureFlags: () => ({ getFlags: () => [ { name: 'dark-mode', description: 'Enable dark mode theme', isEnabled: false, hasOverride: false, requiresPermission: false, hasPermission: true }, { name: 'experimental-search', description: 'Enable experimental search', isEnabled: false, hasOverride: false, requiresPermission: false, hasPermission: true }, { name: 'admin-panel', description: 'Show admin panel', isEnabled: false, hasOverride: false, requiresPermission: true, hasPermission: true } ], toggle: vi.fn(), resetAll: vi.fn() }) })); describe('DeveloperTools', () => { let wrapper; beforeEach(() => { // Mock process.env globally const originalEnv = process.env.NODE_ENV; process.env.NODE_ENV = 'development'; wrapper = mount(DeveloperTools, { global: { stubs: { Teleport: true, Transition: true } } }); // Restore after test afterEach(() => { process.env.NODE_ENV = originalEnv; }); }); describe('visibility in development mode', () => { it('renders trigger button in development mode', () => { const trigger = wrapper.find('.dev-trigger'); expect(trigger.exists()).toBe(true); }); it('button displays developer tools emoji', () => { const trigger = wrapper.find('.dev-trigger'); expect(trigger.text()).toBe('🛠️'); }); }); describe('opening/closing panel', () => { it('opens panel when trigger is clicked', async () => { const trigger = wrapper.find('.dev-trigger'); expect(wrapper.find('.developer-tools').exists()).toBe(false); await trigger.trigger('click'); expect(wrapper.find('.developer-tools').exists()).toBe(true); }); it('closes panel when close button is clicked', async () => { const trigger = wrapper.find('.dev-trigger'); await trigger.trigger('click'); expect(wrapper.find('.developer-tools').exists()).toBe(true); const closeBtn = wrapper.find('.close-btn'); await closeBtn.trigger('click'); expect(wrapper.find('.developer-tools').exists()).toBe(false); }); it('toggles panel state on repeated clicks', async () => { const trigger = wrapper.find('.dev-trigger'); await trigger.trigger('click'); expect(wrapper.find('.developer-tools').exists()).toBe(true); await trigger.trigger('click'); expect(wrapper.find('.developer-tools').exists()).toBe(false); await trigger.trigger('click'); expect(wrapper.find('.developer-tools').exists()).toBe(true); }); }); describe('keyboard shortcut', () => { it('opens panel on Ctrl+Shift+D', async () => { const event = new KeyboardEvent('keydown', { ctrlKey: true, shiftKey: true, code: 'KeyD' }); window.dispatchEvent(event); await wrapper.vm.$nextTick(); // Should open the panel expect(wrapper.vm.isOpen).toBe(true); }); it('closes panel on second Ctrl+Shift+D', async () => { // First press let event = new KeyboardEvent('keydown', { ctrlKey: true, shiftKey: true, code: 'KeyD' }); window.dispatchEvent(event); await wrapper.vm.$nextTick(); expect(wrapper.vm.isOpen).toBe(true); // Second press event = new KeyboardEvent('keydown', { ctrlKey: true, shiftKey: true, code: 'KeyD' }); window.dispatchEvent(event); await wrapper.vm.$nextTick(); expect(wrapper.vm.isOpen).toBe(false); }); it('prevents default on Ctrl+Shift+D', () => { const event = new KeyboardEvent('keydown', { ctrlKey: true, shiftKey: true, code: 'KeyD' }); const preventDefaultSpy = vi.spyOn(event, 'preventDefault'); window.dispatchEvent(event); expect(preventDefaultSpy).toHaveBeenCalled(); }); it('ignores shortcut if missing Ctrl key', async () => { const event = new KeyboardEvent('keydown', { ctrlKey: false, shiftKey: true, code: 'KeyD' }); window.dispatchEvent(event); await wrapper.vm.$nextTick(); expect(wrapper.vm.isOpen).toBe(false); }); it('ignores shortcut if missing Shift key', async () => { const event = new KeyboardEvent('keydown', { ctrlKey: true, shiftKey: false, code: 'KeyD' }); window.dispatchEvent(event); await wrapper.vm.$nextTick(); expect(wrapper.vm.isOpen).toBe(false); }); }); describe('feature flags display', () => { it('displays all available feature flags when panel is open', async () => { const trigger = wrapper.find('.dev-trigger'); await trigger.trigger('click'); const flags = wrapper.findAll('.flag-item'); expect(flags.length).toBe(3); }); it('shows flag names and descriptions', async () => { const trigger = wrapper.find('.dev-trigger'); await trigger.trigger('click'); const firstFlag = wrapper.find('.flag-item'); expect(firstFlag.text()).toContain('dark-mode'); expect(firstFlag.text()).toContain('Enable dark mode theme'); }); it('toggles flag when checkbox is clicked', async () => { const trigger = wrapper.find('.dev-trigger'); await trigger.trigger('click'); const checkbox = wrapper.find('.flag-item input[type="checkbox"]'); await checkbox.trigger('change'); // Verify toggle was called (would be called via actual composable) // In real scenario, the flag state would change }); it('shows override badge for flags with overrides', async () => { // This would need a flag with hasOverride: true // Component should display override badge }); it('shows locked badge for flags requiring unavailable permissions', async () => { const trigger = wrapper.find('.dev-trigger'); await trigger.trigger('click'); // Find flag requiring permission with hasPermission: true (unlocked) // Find flag requiring permission with hasPermission: false (locked) }); it('disables checkbox for flags with permission constraints', async () => { const trigger = wrapper.find('.dev-trigger'); await trigger.trigger('click'); const checkboxes = wrapper.findAll('.flag-item input[type="checkbox"]'); // The admin-panel flag requires permission, so check its state expect(checkboxes.length).toBeGreaterThanOrEqual(1); }); }); describe('auth info display', () => { it('shows authentication status when authenticated', async () => { const trigger = wrapper.find('.dev-trigger'); await trigger.trigger('click'); expect(wrapper.text()).toContain('✅ Authenticated'); }); it('shows admin role when user is admin', async () => { const trigger = wrapper.find('.dev-trigger'); await trigger.trigger('click'); expect(wrapper.text()).toContain('👑 Admin'); }); it('displays user permissions as tags', async () => { const trigger = wrapper.find('.dev-trigger'); await trigger.trigger('click'); const tags = wrapper.findAll('.tag'); expect(tags.length).toBeGreaterThan(0); expect(wrapper.text()).toContain('admin'); expect(wrapper.text()).toContain('gamemaster-edit'); }); it('displays truncated token', async () => { const trigger = wrapper.find('.dev-trigger'); await trigger.trigger('click'); expect(wrapper.text()).toContain('test-token-xyz...'); }); }); describe('environment info display', () => { it('shows current environment mode', async () => { const trigger = wrapper.find('.dev-trigger'); await trigger.trigger('click'); expect(wrapper.text()).toContain('development'); }); it('displays app version', async () => { const trigger = wrapper.find('.dev-trigger'); await trigger.trigger('click'); expect(wrapper.text()).toContain('App Version'); }); }); describe('reset functionality', () => { it('calls resetAll when reset button is clicked', async () => { const trigger = wrapper.find('.dev-trigger'); await trigger.trigger('click'); // Mock window.confirm to return true vi.stubGlobal('confirm', () => true); const resetBtn = wrapper.find('.btn-secondary'); if (resetBtn.exists()) { await resetBtn.trigger('click'); // resetAllOverrides would be called } vi.unstubAllGlobals(); }); }); describe('responsive behavior', () => { it('uses Teleport to render at body level', () => { // DeveloperTools uses Teleport to body // This prevents z-index and positioning issues expect(wrapper.vm.$el).toBeDefined(); }); it('has responsive grid layout for info display', async () => { const trigger = wrapper.find('.dev-trigger'); await trigger.trigger('click'); const infoGrid = wrapper.find('.info-grid'); expect(infoGrid.exists()).toBe(true); expect(infoGrid.classes()).toContain('info-grid'); }); }); });