import { describe, it, expect, vi, beforeEach } from "vitest"; // Create mock functions using vi.hoisted so they're available during mock setup const { mockTrack, mockRevenue } = vi.hoisted(() => ({ mockTrack: vi.fn().mockResolvedValue(undefined), mockRevenue: vi.fn().mockResolvedValue(undefined), })); // Mock OpenPanel using factory function vi.mock("@openpanel/nextjs", () => { return { OpenPanel: class MockOpenPanel { track = mockTrack; revenue = mockRevenue; constructor() {} }, }; }); // Import after mock is set up import { AnalyticsService } from "@/lib/services/AnalyticsService"; describe("AnalyticsService", () => { beforeEach(() => { vi.clearAllMocks(); }); describe("trackOrderReceived", () => { it("should track order with all details", async () => { await new AnalyticsService().trackOrderReceived({ orderId: "order-123", orderNumber: "1524", total: 5479, currency: "RSD", itemCount: 3, customerEmail: "test@example.com", eventType: "ORDER_CONFIRMED", }); expect(mockTrack).toHaveBeenCalledWith("order_received", { order_id: "order-123", order_number: "1524", total: 5479, currency: "RSD", item_count: 3, customer_email: "test@example.com", event_type: "ORDER_CONFIRMED", }); }); it("should handle large order values", async () => { await new AnalyticsService().trackOrderReceived({ orderId: "order-456", orderNumber: "2000", total: 500000, // Large amount currency: "RSD", itemCount: 100, customerEmail: "bulk@example.com", eventType: "ORDER_CONFIRMED", }); expect(mockTrack).toHaveBeenCalledWith( "order_received", expect.objectContaining({ total: 500000, item_count: 100, }) ); }); it("should not throw if tracking fails", async () => { mockTrack.mockRejectedValueOnce(new Error("Network error")); await expect( new AnalyticsService().trackOrderReceived({ orderId: "order-123", orderNumber: "1524", total: 1000, currency: "RSD", itemCount: 1, customerEmail: "test@example.com", eventType: "ORDER_CONFIRMED", }) ).resolves.not.toThrow(); }); }); describe("trackRevenue", () => { it("should track revenue with correct currency", async () => { await new AnalyticsService().trackRevenue({ amount: 5479, currency: "RSD", orderId: "order-123", orderNumber: "1524", }); expect(mockRevenue).toHaveBeenCalledWith(5479, { currency: "RSD", order_id: "order-123", order_number: "1524", }); }); it("should track revenue with different currencies", async () => { // Test EUR await new AnalyticsService().trackRevenue({ amount: 100, currency: "EUR", orderId: "order-1", orderNumber: "1000", }); expect(mockRevenue).toHaveBeenCalledWith(100, { currency: "EUR", order_id: "order-1", order_number: "1000", }); // Test USD await new AnalyticsService().trackRevenue({ amount: 150, currency: "USD", orderId: "order-2", orderNumber: "1001", }); expect(mockRevenue).toHaveBeenCalledWith(150, { currency: "USD", order_id: "order-2", order_number: "1001", }); }); it("should log tracking for debugging", async () => { const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {}); await new AnalyticsService().trackRevenue({ amount: 5479, currency: "RSD", orderId: "order-123", orderNumber: "1524", }); expect(consoleSpy).toHaveBeenCalledWith( "Tracking revenue: 5479 RSD for order 1524" ); consoleSpy.mockRestore(); }); it("should not throw if revenue tracking fails", async () => { mockRevenue.mockRejectedValueOnce(new Error("API error")); await expect( new AnalyticsService().trackRevenue({ amount: 1000, currency: "RSD", orderId: "order-123", orderNumber: "1524", }) ).resolves.not.toThrow(); }); it("should handle zero amount orders", async () => { await new AnalyticsService().trackRevenue({ amount: 0, currency: "RSD", orderId: "order-000", orderNumber: "0000", }); expect(mockRevenue).toHaveBeenCalledWith(0, { currency: "RSD", order_id: "order-000", order_number: "0000", }); }); }); describe("track", () => { it("should track custom events", async () => { await new AnalyticsService().track("custom_event", { property1: "value1", property2: 123, }); expect(mockTrack).toHaveBeenCalledWith("custom_event", { property1: "value1", property2: 123, }); }); it("should not throw on tracking errors", async () => { mockTrack.mockRejectedValueOnce(new Error("Tracking failed")); await expect( new AnalyticsService().track("test_event", { test: true }) ).resolves.not.toThrow(); }); }); describe("Singleton pattern", () => { it("should return the same instance", async () => { // Import fresh to test singleton using dynamic import const { analyticsService: service1 } = await import("@/lib/services/AnalyticsService"); const { analyticsService: service2 } = await import("@/lib/services/AnalyticsService"); expect(service1).toBe(service2); }); }); describe("Error handling", () => { it("should log errors but not throw", async () => { const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); mockTrack.mockRejectedValueOnce(new Error("Test error")); await new AnalyticsService().trackOrderReceived({ orderId: "order-123", orderNumber: "1524", total: 1000, currency: "RSD", itemCount: 1, customerEmail: "test@example.com", eventType: "ORDER_CONFIRMED", }); expect(consoleErrorSpy).toHaveBeenCalled(); expect(consoleErrorSpy.mock.calls[0][0]).toContain("Failed to track order received"); consoleErrorSpy.mockRestore(); }); }); });