/** * Unit Tests for Analytics Service * Tests the analyticsService for proper event tracking and user property management */ import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'; import { analyticsService, AnalyticsEvents, AnalyticsUserProperties } from '../../src/services/analyticsService'; // Mock gtag function const mockGtag = vi.fn(); // Setup gtag in global window before tests if (typeof window !== 'undefined') { (window as any).gtag = mockGtag; } describe('Analytics Service', () => { beforeEach(() => { vi.clearAllMocks(); // Re-enable analytics for each test analyticsService.setEnabled(true); }); describe('Event Tracking', () => { it('should track user signup event', () => { analyticsService.trackSignup('frodo_baggins'); expect(mockGtag).toHaveBeenCalledWith('event', AnalyticsEvents.USER_SIGNUP, { username: 'frodo_baggins', }); }); it('should track user login event', () => { analyticsService.trackLogin('gandalf'); expect(mockGtag).toHaveBeenCalledWith('event', AnalyticsEvents.USER_LOGIN, { username: 'gandalf', }); }); it('should track user logout event', () => { analyticsService.trackLogout(); expect(mockGtag).toHaveBeenCalledWith('event', AnalyticsEvents.USER_LOGOUT, {}); }); it('should track quest creation', () => { analyticsService.trackQuestCreated(1, 'Find the Ring', 'The Ring'); expect(mockGtag).toHaveBeenCalledWith('event', AnalyticsEvents.QUEST_CREATED, { quest_id: 1, quest_title: 'Find the Ring', quest_type: 'The Ring', }); }); it('should track quest completion', () => { analyticsService.trackQuestCompleted(5, 'Destroy the Ring', 'Trivia'); expect(mockGtag).toHaveBeenCalledWith('event', AnalyticsEvents.QUEST_COMPLETED, { quest_id: 5, quest_title: 'Destroy the Ring', game_type: 'Trivia', }); }); it('should track game completion', () => { analyticsService.trackGameCompleted('memory', 95); expect(mockGtag).toHaveBeenCalledWith('event', AnalyticsEvents.GAME_COMPLETED, { game_type: 'memory', score: 95, }); }); it('should track game failure', () => { analyticsService.trackGameFailed('reaction', 'timeout'); expect(mockGtag).toHaveBeenCalledWith('event', AnalyticsEvents.GAME_FAILED, { game_type: 'reaction', reason: 'timeout', }); }); it('should track bargain initiation', () => { analyticsService.trackBargainInitiated(); expect(mockGtag).toHaveBeenCalledWith('event', AnalyticsEvents.BARGAIN_INITIATED, {}); }); it('should track successful bargain', () => { analyticsService.trackBargainCompleted('Mithril Coat', 500); expect(mockGtag).toHaveBeenCalledWith('event', AnalyticsEvents.BARGAIN_COMPLETED, { item_name: 'Mithril Coat', bargain_value: 500, }); }); it('should track item purchase', () => { analyticsService.trackItemPurchased('Sword of Elendil', 250, 42); expect(mockGtag).toHaveBeenCalledWith('event', AnalyticsEvents.ITEM_PURCHASED, { item_name: 'Sword of Elendil', item_id: 42, price: 250, currency: 'gold', }); }); it('should track NPC chat initiation', () => { analyticsService.trackNpcChatInitiated('Gandalf'); expect(mockGtag).toHaveBeenCalledWith('event', AnalyticsEvents.NPC_CHAT_INITIATED, { character: 'Gandalf', }); }); it('should track NPC message sent', () => { analyticsService.trackNpcMessageSent('Frodo', 42); expect(mockGtag).toHaveBeenCalledWith('event', AnalyticsEvents.NPC_MESSAGE_SENT, { character: 'Frodo', message_length: 42, }); }); it('should track inventory viewed', () => { analyticsService.trackInventoryViewed(8); expect(mockGtag).toHaveBeenCalledWith('event', AnalyticsEvents.INVENTORY_VIEWED, { item_count: 8, }); }); it('should track inventory item added', () => { analyticsService.trackInventoryItemAdded('Mithril Coat', 1); expect(mockGtag).toHaveBeenCalledWith('event', AnalyticsEvents.INVENTORY_ITEM_ADDED, { item_name: 'Mithril Coat', quantity: 1, }); }); it('should track page view', () => { analyticsService.trackPageView('/dashboard', 'The Council Chamber'); expect(mockGtag).toHaveBeenCalledWith('event', 'page_view', { page_title: 'The Council Chamber', page_location: window.location.href, }); }); it('should track map viewed', () => { analyticsService.trackMapViewed(); expect(mockGtag).toHaveBeenCalledWith('event', AnalyticsEvents.MAP_VIEWED, { page_title: 'Middle-earth Map', }); }); it('should track dashboard viewed', () => { analyticsService.trackDashboardViewed(); expect(mockGtag).toHaveBeenCalledWith('event', AnalyticsEvents.DASHBOARD_VIEWED, { page_title: 'The Council Chamber', }); }); it('should track quests page viewed', () => { analyticsService.trackQuestsPageViewed(); expect(mockGtag).toHaveBeenCalledWith('event', AnalyticsEvents.QUESTS_PAGE_VIEWED, { page_title: 'Quests', }); }); }); describe('User Properties', () => { it('should set user properties', () => { analyticsService.setUserProperties({ character_name: 'Frodo', user_type: 'fellowship_member', }); expect(mockGtag).toHaveBeenCalledWith('config', { character_name: 'Frodo', user_type: 'fellowship_member', }); }); it('should set user ID', () => { analyticsService.setUserId(123); expect(mockGtag).toHaveBeenCalledWith('config', { 'user_id': '123', }); }); }); describe('Analytics Management', () => { it('should check if analytics is enabled', () => { expect(analyticsService.isAnalyticsEnabled()).toBe(true); }); it('should enable/disable analytics', () => { analyticsService.setEnabled(false); expect(analyticsService.isAnalyticsEnabled()).toBe(false); analyticsService.setEnabled(true); expect(analyticsService.isAnalyticsEnabled()).toBe(true); }); }); describe('Default Values', () => { it('should handle game completion with default score', () => { analyticsService.trackGameCompleted('trivia'); expect(mockGtag).toHaveBeenCalledWith('event', AnalyticsEvents.GAME_COMPLETED, { game_type: 'trivia', score: 0, }); }); it('should handle inventory without item count', () => { analyticsService.trackInventoryViewed(); expect(mockGtag).toHaveBeenCalledWith('event', AnalyticsEvents.INVENTORY_VIEWED, { item_count: 0, }); }); it('should handle quest creation without type', () => { analyticsService.trackQuestCreated(1, 'A Quest'); expect(mockGtag).toHaveBeenCalledWith('event', AnalyticsEvents.QUEST_CREATED, { quest_id: 1, quest_title: 'A Quest', quest_type: 'unknown', }); }); }); describe('Error Handling', () => { it('should handle gtag errors gracefully', () => { mockGtag.mockImplementationOnce(() => { throw new Error('GTags error'); }); // Should not throw expect(() => { analyticsService.trackLogin('user'); }).not.toThrow(); }); it('should log errors to console', () => { const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); mockGtag.mockImplementationOnce(() => { throw new Error('GTags error'); }); analyticsService.trackLogin('user'); expect(consoleSpy).toHaveBeenCalledWith( 'Error tracking analytics event:', expect.any(Error) ); consoleSpy.mockRestore(); }); }); });