Fix all skipped downloads store tests (21/21 passing)
Some checks failed
Traceability Validation / Check Requirement Traces (push) Has been cancelled
🏗️ Build and Test JellyTau / Build APK and Run Tests (push) Has been cancelled

This commit is contained in:
Duncan Tourolle 2026-02-14 12:04:19 +01:00
parent a5e065daed
commit cbd5435e26
2 changed files with 340 additions and 49 deletions

View File

@ -40,6 +40,10 @@ describe("downloads store", () => {
vi.clearAllMocks(); vi.clearAllMocks();
mockInvoke.mockReset(); mockInvoke.mockReset();
mockListen.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", () => { describe("initial state", () => {
@ -477,13 +481,13 @@ describe("downloads store", () => {
expect(state.downloads[123].status).toBe("completed"); expect(state.downloads[123].status).toBe("completed");
}); });
it.skip("should handle failed event and refresh", async () => { it("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
const { downloads, initDownloadEvents } = await import("./downloads"); const { downloads, initDownloadEvents } = await import("./downloads");
mockInvoke.mockResolvedValueOnce({ // Setup mock to return downloading download when refresh is called
mockInvoke.mockImplementation((command: string) => {
if (command === "get_downloads") {
return Promise.resolve({
downloads: [ downloads: [
{ {
id: 123, id: 123,
@ -509,15 +513,17 @@ describe("downloads store", () => {
pausedCount: 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"); await downloads.refresh("user-1");
// Initialize event listener // Initialize event listener
await initDownloadEvents(); await initDownloadEvents();
// Mock the mark_download_failed call
mockInvoke.mockResolvedValueOnce(undefined);
// Simulate failed event // Simulate failed event
eventHandler!({ eventHandler!({
payload: { payload: {
@ -590,35 +596,319 @@ describe("downloads store", () => {
}); });
describe("derived stores", () => { describe("derived stores", () => {
it.skip("activeDownloads should filter downloading status", async () => { it("activeDownloads should filter downloading status", async () => {
// TODO: Fix store singleton state pollution const { downloads, activeDownloads } = await import("./downloads");
// 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 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}`));
}); });
it.skip("completedDownloads should filter completed status", async () => { await downloads.refresh("user-1");
// TODO: Fix store singleton state pollution
const active = get(activeDownloads);
expect(active.length).toBe(1);
expect(active[0].id).toBe(1);
expect(active[0].status).toBe("downloading");
}); });
it.skip("pendingDownloads should filter pending status", async () => { it("completedDownloads should filter completed status", async () => {
// TODO: Fix store singleton state pollution 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}`));
}); });
it.skip("failedDownloads should filter failed status", async () => { await downloads.refresh("user-1");
// TODO: Fix store singleton state pollution
const completed = get(completedDownloads);
expect(completed.length).toBe(2);
expect(completed.every((d) => d.status === "completed")).toBe(true);
});
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("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", () => { describe("error handling", () => {
it.skip("should throw error when download_item fails", async () => { it("should throw error when download_item fails", async () => {
// TODO: Fix mock rejection handling const { downloads } = await import("./downloads");
// The downloadItem function makes two invoke calls (download_item then get_downloads)
// Mock rejection on first call doesn't properly prevent second call execution 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}`));
}); });
it.skip("should throw error when refresh fails", async () => { await expect(
// TODO: Fix mock rejection - mockRejectedValueOnce isn't working as expected downloads.downloadItem("item-1", "user-1", "/path/to/file.mp3")
// The refresh function isn't properly propagating the error ).rejects.toThrow("Backend error: failed to queue download");
});
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");
}); });
}); });
}); });

View File

@ -25,6 +25,7 @@ export default defineConfig({
conditions: ["browser"], conditions: ["browser"],
alias: { alias: {
$lib: resolve(__dirname, "./src/lib"), $lib: resolve(__dirname, "./src/lib"),
"$lib/": resolve(__dirname, "./src/lib/"),
"$app/environment": resolve(__dirname, "./src/test/mocks/app-environment.ts"), "$app/environment": resolve(__dirname, "./src/test/mocks/app-environment.ts"),
"$app/navigation": resolve(__dirname, "./src/test/mocks/app-navigation.ts"), "$app/navigation": resolve(__dirname, "./src/test/mocks/app-navigation.ts"),
"$app/stores": resolve(__dirname, "./src/test/mocks/app-stores.ts"), "$app/stores": resolve(__dirname, "./src/test/mocks/app-stores.ts"),