From d3ac20f9fa3d4d68a774d2bf08a58a57d7fd18c6 Mon Sep 17 00:00:00 2001 From: FragginWagon Date: Wed, 28 Jan 2026 22:54:40 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=A7=AA=20Add=20unit=20tests=20for=20Devel?= =?UTF-8?q?operTools=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../unit/components/DeveloperTools.test.js | 335 ++++++++++++++++++ 1 file changed, 335 insertions(+) create mode 100644 code/websites/pokedex.online/tests/unit/components/DeveloperTools.test.js diff --git a/code/websites/pokedex.online/tests/unit/components/DeveloperTools.test.js b/code/websites/pokedex.online/tests/unit/components/DeveloperTools.test.js new file mode 100644 index 0000000..d9b4ed2 --- /dev/null +++ b/code/websites/pokedex.online/tests/unit/components/DeveloperTools.test.js @@ -0,0 +1,335 @@ +/** + * 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 + vi.stubGlobal('process', { + env: { + NODE_ENV: 'development' + } + }); + + wrapper = mount(DeveloperTools, { + global: { + stubs: { + Teleport: false, + Transition: false + } + } + }); + }); + + afterEach(() => { + vi.unstubAllGlobals(); + }); + + 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'); + }); + }); +});