diff --git a/src/lib/stores/downloads.test.ts b/src/lib/stores/downloads.test.ts index 7745e3b..4a7cb0d 100644 --- a/src/lib/stores/downloads.test.ts +++ b/src/lib/stores/downloads.test.ts @@ -40,6 +40,10 @@ describe("downloads store", () => { vi.clearAllMocks(); mockInvoke.mockReset(); mockListen.mockReset(); + + // Reset modules to get fresh store instances for each test + // This prevents state pollution from affecting subsequent tests + vi.resetModules(); }); describe("initial state", () => { @@ -477,37 +481,42 @@ describe("downloads store", () => { expect(state.downloads[123].status).toBe("completed"); }); - it.skip("should handle failed event and refresh", async () => { - // TODO: Fix mock ordering - the invoke mock needs to handle multiple calls in the correct order - // The test triggers: 1) refresh get_downloads 2) mark_download_failed invoke - // Current issue: Mock queue doesn't preserve order between tests + it("should handle failed event and refresh", async () => { const { downloads, initDownloadEvents } = await import("./downloads"); - mockInvoke.mockResolvedValueOnce({ - downloads: [ - { - id: 123, - itemId: "item-1", - userId: "user-1", - filePath: "/path/to/file.mp3", - status: "downloading", - progress: 0.5, - bytesDownloaded: 500000, - queuedAt: "2024-01-01T00:00:00Z", - retryCount: 0, - priority: 0, - mediaType: "audio", - downloadSource: "user", - }, - ], - stats: { - total: 1, - activeCount: 1, - queuedCount: 0, - completedCount: 0, - failedCount: 0, - pausedCount: 0, - }, + // Setup mock to return downloading download when refresh is called + mockInvoke.mockImplementation((command: string) => { + if (command === "get_downloads") { + return Promise.resolve({ + downloads: [ + { + id: 123, + itemId: "item-1", + userId: "user-1", + filePath: "/path/to/file.mp3", + status: "downloading", + progress: 0.5, + bytesDownloaded: 500000, + queuedAt: "2024-01-01T00:00:00Z", + retryCount: 0, + priority: 0, + mediaType: "audio", + downloadSource: "user", + }, + ], + stats: { + total: 1, + activeCount: 1, + queuedCount: 0, + completedCount: 0, + failedCount: 0, + pausedCount: 0, + }, + }); + } else if (command === "mark_download_failed") { + return Promise.resolve(undefined); + } + return Promise.reject(new Error(`Unexpected command: ${command}`)); }); await downloads.refresh("user-1"); @@ -515,9 +524,6 @@ describe("downloads store", () => { // Initialize event listener await initDownloadEvents(); - // Mock the mark_download_failed call - mockInvoke.mockResolvedValueOnce(undefined); - // Simulate failed event eventHandler!({ payload: { @@ -590,35 +596,319 @@ describe("downloads store", () => { }); describe("derived stores", () => { - it.skip("activeDownloads should filter downloading status", async () => { - // TODO: Fix store singleton state pollution - // The store persists state across tests, causing derived filter tests to fail - // Need to implement a reset mechanism or use a fresh store instance per test + it("activeDownloads should filter downloading status", async () => { + const { downloads, activeDownloads } = await import("./downloads"); + + mockInvoke.mockImplementation((command: string) => { + if (command === "get_downloads") { + return Promise.resolve({ + downloads: [ + { + id: 1, + itemId: "item-1", + userId: "user-1", + filePath: "/path/1.mp3", + status: "downloading", + progress: 0.5, + bytesDownloaded: 500, + queuedAt: "2024-01-01T00:00:00Z", + retryCount: 0, + priority: 0, + mediaType: "audio", + downloadSource: "user", + }, + { + id: 2, + itemId: "item-2", + userId: "user-1", + filePath: "/path/2.mp3", + status: "completed", + progress: 1.0, + bytesDownloaded: 1000, + queuedAt: "2024-01-01T00:00:00Z", + retryCount: 0, + priority: 0, + mediaType: "audio", + downloadSource: "user", + }, + { + id: 3, + itemId: "item-3", + userId: "user-1", + filePath: "/path/3.mp3", + status: "pending", + progress: 0, + bytesDownloaded: 0, + queuedAt: "2024-01-01T00:00:00Z", + retryCount: 0, + priority: 0, + mediaType: "audio", + downloadSource: "user", + }, + ], + stats: { + total: 3, + activeCount: 1, + queuedCount: 1, + completedCount: 1, + failedCount: 0, + pausedCount: 0, + }, + }); + } + return Promise.reject(new Error(`Unexpected command: ${command}`)); + }); + + await downloads.refresh("user-1"); + + const active = get(activeDownloads); + expect(active.length).toBe(1); + expect(active[0].id).toBe(1); + expect(active[0].status).toBe("downloading"); }); - it.skip("completedDownloads should filter completed status", async () => { - // TODO: Fix store singleton state pollution + it("completedDownloads should filter completed status", async () => { + const { downloads, completedDownloads } = await import("./downloads"); + + mockInvoke.mockImplementation((command: string) => { + if (command === "get_downloads") { + return Promise.resolve({ + downloads: [ + { + id: 1, + itemId: "item-1", + userId: "user-1", + filePath: "/path/1.mp3", + status: "downloading", + progress: 0.5, + bytesDownloaded: 500, + queuedAt: "2024-01-01T00:00:00Z", + retryCount: 0, + priority: 0, + mediaType: "audio", + downloadSource: "user", + }, + { + id: 2, + itemId: "item-2", + userId: "user-1", + filePath: "/path/2.mp3", + status: "completed", + progress: 1.0, + bytesDownloaded: 1000, + queuedAt: "2024-01-01T00:00:00Z", + retryCount: 0, + priority: 0, + mediaType: "audio", + downloadSource: "user", + }, + { + id: 3, + itemId: "item-3", + userId: "user-1", + filePath: "/path/3.mp3", + status: "completed", + progress: 1.0, + bytesDownloaded: 1000, + queuedAt: "2024-01-01T00:00:00Z", + retryCount: 0, + priority: 0, + mediaType: "audio", + downloadSource: "user", + }, + ], + stats: { + total: 3, + activeCount: 1, + queuedCount: 0, + completedCount: 2, + failedCount: 0, + pausedCount: 0, + }, + }); + } + return Promise.reject(new Error(`Unexpected command: ${command}`)); + }); + + await downloads.refresh("user-1"); + + const completed = get(completedDownloads); + expect(completed.length).toBe(2); + expect(completed.every((d) => d.status === "completed")).toBe(true); }); - it.skip("pendingDownloads should filter pending status", async () => { - // TODO: Fix store singleton state pollution + it("pendingDownloads should filter pending status", async () => { + const { downloads, pendingDownloads } = await import("./downloads"); + + mockInvoke.mockImplementation((command: string) => { + if (command === "get_downloads") { + return Promise.resolve({ + downloads: [ + { + id: 1, + itemId: "item-1", + userId: "user-1", + filePath: "/path/1.mp3", + status: "pending", + progress: 0, + bytesDownloaded: 0, + queuedAt: "2024-01-01T00:00:00Z", + retryCount: 0, + priority: 10, + mediaType: "audio", + downloadSource: "user", + }, + { + id: 2, + itemId: "item-2", + userId: "user-1", + filePath: "/path/2.mp3", + status: "completed", + progress: 1.0, + bytesDownloaded: 1000, + queuedAt: "2024-01-01T00:00:00Z", + retryCount: 0, + priority: 0, + mediaType: "audio", + downloadSource: "user", + }, + { + id: 3, + itemId: "item-3", + userId: "user-1", + filePath: "/path/3.mp3", + status: "pending", + progress: 0, + bytesDownloaded: 0, + queuedAt: "2024-01-01T00:00:00Z", + retryCount: 0, + priority: 5, + mediaType: "audio", + downloadSource: "user", + }, + ], + stats: { + total: 3, + activeCount: 0, + queuedCount: 2, + completedCount: 1, + failedCount: 0, + pausedCount: 0, + }, + }); + } + return Promise.reject(new Error(`Unexpected command: ${command}`)); + }); + + await downloads.refresh("user-1"); + + const pending = get(pendingDownloads); + expect(pending.length).toBe(2); + expect(pending.every((d) => d.status === "pending")).toBe(true); }); - it.skip("failedDownloads should filter failed status", async () => { - // TODO: Fix store singleton state pollution + it("failedDownloads should filter failed status", async () => { + const { downloads, failedDownloads } = await import("./downloads"); + + mockInvoke.mockImplementation((command: string) => { + if (command === "get_downloads") { + return Promise.resolve({ + downloads: [ + { + id: 1, + itemId: "item-1", + userId: "user-1", + filePath: "/path/1.mp3", + status: "failed", + progress: 0.5, + bytesDownloaded: 500, + queuedAt: "2024-01-01T00:00:00Z", + retryCount: 3, + priority: 0, + mediaType: "audio", + downloadSource: "user", + errorMessage: "Network error", + }, + { + id: 2, + itemId: "item-2", + userId: "user-1", + filePath: "/path/2.mp3", + status: "completed", + progress: 1.0, + bytesDownloaded: 1000, + queuedAt: "2024-01-01T00:00:00Z", + retryCount: 0, + priority: 0, + mediaType: "audio", + downloadSource: "user", + }, + { + id: 3, + itemId: "item-3", + userId: "user-1", + filePath: "/path/3.mp3", + status: "failed", + progress: 0.3, + bytesDownloaded: 300, + queuedAt: "2024-01-01T00:00:00Z", + retryCount: 2, + priority: 0, + mediaType: "audio", + downloadSource: "user", + errorMessage: "Timeout", + }, + ], + stats: { + total: 3, + activeCount: 0, + queuedCount: 0, + completedCount: 1, + failedCount: 2, + pausedCount: 0, + }, + }); + } + return Promise.reject(new Error(`Unexpected command: ${command}`)); + }); + + await downloads.refresh("user-1"); + + const failed = get(failedDownloads); + expect(failed.length).toBe(2); + expect(failed.every((d) => d.status === "failed")).toBe(true); }); }); describe("error handling", () => { - it.skip("should throw error when download_item fails", async () => { - // TODO: Fix mock rejection handling - // The downloadItem function makes two invoke calls (download_item then get_downloads) - // Mock rejection on first call doesn't properly prevent second call execution + it("should throw error when download_item fails", async () => { + const { downloads } = await import("./downloads"); + + mockInvoke.mockImplementation((command: string) => { + if (command === "download_item") { + return Promise.reject(new Error("Backend error: failed to queue download")); + } + return Promise.reject(new Error(`Unexpected command: ${command}`)); + }); + + await expect( + downloads.downloadItem("item-1", "user-1", "/path/to/file.mp3") + ).rejects.toThrow("Backend error: failed to queue download"); }); - it.skip("should throw error when refresh fails", async () => { - // TODO: Fix mock rejection - mockRejectedValueOnce isn't working as expected - // The refresh function isn't properly propagating the error + it("should throw error when refresh fails", async () => { + const { downloads } = await import("./downloads"); + + mockInvoke.mockImplementation((command: string) => { + if (command === "get_downloads") { + return Promise.reject(new Error("Backend error: failed to fetch downloads")); + } + return Promise.reject(new Error(`Unexpected command: ${command}`)); + }); + + await expect( + downloads.refresh("user-1") + ).rejects.toThrow("Backend error: failed to fetch downloads"); }); }); }); diff --git a/vitest.config.ts b/vitest.config.ts index aefce10..3c6274a 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -25,6 +25,7 @@ export default defineConfig({ conditions: ["browser"], alias: { $lib: resolve(__dirname, "./src/lib"), + "$lib/": resolve(__dirname, "./src/lib/"), "$app/environment": resolve(__dirname, "./src/test/mocks/app-environment.ts"), "$app/navigation": resolve(__dirname, "./src/test/mocks/app-navigation.ts"), "$app/stores": resolve(__dirname, "./src/test/mocks/app-stores.ts"),