From 8d382ce98eeb4c211bfd56f412cb1df77423466a Mon Sep 17 00:00:00 2001 From: anmol thapar Date: Wed, 4 Sep 2024 16:28:33 +0100 Subject: [PATCH 1/2] feat: get csv download done --- app/client-v2/e2e/projectPostRun.spec.ts | 9 ++ .../ProjectView/ProjectFileUpload.spec.ts | 41 ++++++- .../__tests__/utils/projectCsvUtils.spec.ts | 102 ++++++++++++++++++ .../ProjectView/ProjectFileUploadHeader.vue | 12 ++- app/client-v2/src/utils/amrDisplayUtils.ts | 2 +- app/client-v2/src/utils/projectCsvUtils.ts | 38 +++++++ 6 files changed, 201 insertions(+), 3 deletions(-) create mode 100644 app/client-v2/src/__tests__/utils/projectCsvUtils.spec.ts create mode 100644 app/client-v2/src/utils/projectCsvUtils.ts diff --git a/app/client-v2/e2e/projectPostRun.spec.ts b/app/client-v2/e2e/projectPostRun.spec.ts index fc3b5fcb..ce4850c3 100644 --- a/app/client-v2/e2e/projectPostRun.spec.ts +++ b/app/client-v2/e2e/projectPostRun.spec.ts @@ -89,3 +89,12 @@ test("can run project multiple times", async ({ page }) => { await expect(page.getByText("GPSC7")).toBeVisible(); await expect(page.getByText("GPSC4")).toBeVisible(); }); + +test("can export project data as csv", async ({ page }) => { + uploadFiles(page); + + const downloadPromise = page.waitForEvent("download"); + await page.getByLabel("Export").click(); + const download = await downloadPromise; + expect(download.suggestedFilename()).toBe(`${projectName}.csv`); +}); diff --git a/app/client-v2/src/__tests__/components/ProjectView/ProjectFileUpload.spec.ts b/app/client-v2/src/__tests__/components/ProjectView/ProjectFileUpload.spec.ts index ebd1fc46..6656c202 100644 --- a/app/client-v2/src/__tests__/components/ProjectView/ProjectFileUpload.spec.ts +++ b/app/client-v2/src/__tests__/components/ProjectView/ProjectFileUpload.spec.ts @@ -1,14 +1,20 @@ import ProjectFileUpload from "@/components/ProjectView/ProjectFileUpload.vue"; -import { MOCK_PROJECT_SAMPLES_BEFORE_RUN } from "@/mocks/mockObjects"; +import { MOCK_PROJECT_SAMPLES, MOCK_PROJECT_SAMPLES_BEFORE_RUN } from "@/mocks/mockObjects"; import { useProjectStore } from "@/stores/projectStore"; import { createTestingPinia } from "@pinia/testing"; import userEvent from "@testing-library/user-event"; import { fireEvent, render, screen, waitFor } from "@testing-library/vue"; import PrimeVue from "primevue/config"; +import { downloadCsv } from "@/utils/projectCsvUtils"; +import type { Mock } from "vitest"; vitest.mock("primevue/usetoast", () => ({ useToast: vitest.fn() })); +const mockDownloadCsv = downloadCsv as Mock; +vitest.mock("@/utils/projectCsvUtils", () => ({ + downloadCsv: vitest.fn() +})); describe("ProjectFile upload", () => { it("should render drag and drop section when no files uploaded", () => { @@ -187,4 +193,37 @@ describe("ProjectFile upload", () => { expect(screen.queryByRole("progressbar")).toBeNull(); }); + + it("should disable export button if not ready to run analysis", async () => { + const testPinia = createTestingPinia(); + const store = useProjectStore(testPinia); + // @ts-expect-error: Getter is read only + store.isReadyToRun = false; + store.project.samples = []; + render(ProjectFileUpload, { + global: { + plugins: [testPinia, PrimeVue] + } + }); + + expect(screen.getByRole("button", { name: /export/i })).toBeDisabled(); + }); + + it("should export when ready to run analysis and clicked", async () => { + const testPinia = createTestingPinia(); + const store = useProjectStore(testPinia); + // @ts-expect-error: Getter is read only + store.isReadyToRun = true; + store.project.name = "Test Project"; + store.project.samples = MOCK_PROJECT_SAMPLES; + render(ProjectFileUpload, { + global: { + plugins: [testPinia, PrimeVue] + } + }); + + await userEvent.click(screen.getByRole("button", { name: /export/i })); + + expect(mockDownloadCsv).toHaveBeenCalledWith(MOCK_PROJECT_SAMPLES, "Test Project"); + }); }); diff --git a/app/client-v2/src/__tests__/utils/projectCsvUtils.spec.ts b/app/client-v2/src/__tests__/utils/projectCsvUtils.spec.ts new file mode 100644 index 00000000..26aefa7e --- /dev/null +++ b/app/client-v2/src/__tests__/utils/projectCsvUtils.spec.ts @@ -0,0 +1,102 @@ +import type { AMR, ProjectSample } from "@/types/projectTypes"; +import { downloadCsv, convertAmrForCsv, generateCsvContent, triggerCsvDownload } from "@/utils/projectCsvUtils"; +import type { Mock } from "vitest"; + +const mockConvertProbabilityToWord = vitest.fn(); +vitest.mock("@/utils/amrDisplayUtils", () => ({ + convertProbabilityToWord: () => mockConvertProbabilityToWord() +})); +document.createElement = vitest.fn().mockReturnValue({ + click: vitest.fn(), + remove: vitest.fn() +}); + +describe("projectCsvUtils", () => { + beforeEach(() => { + vitest.clearAllMocks(); + }); + + describe("downloadCsv", () => { + it("should generate and trigger CSV download with correct content and filename", () => { + const samples = [ + { + filename: "sample1", + amr: { Penicillin: 0.9, Chloramphenicol: 0.8, Erythromycin: 0.7, Tetracycline: 0.6, Trim_sulfa: 0.5 }, + cluster: "cluster1" + } + ] as ProjectSample[]; + const filename = "test"; + + downloadCsv(samples, filename); + + expect(document.createElement).toHaveBeenCalledWith("a"); + const anchor = (document.createElement as Mock).mock.results[0].value; + expect(anchor.href).toContain("data:text/csv;charset=utf-8,"); + expect(anchor.download).toBe("test.csv"); + expect(anchor.click).toHaveBeenCalled(); + expect(anchor.remove).toHaveBeenCalled(); + }); + }); + + describe("convertAmrForCsv", () => { + it("should convert AMR object to CSV format", () => { + const amr = { + Penicillin: 0.9, + Chloramphenicol: 0.8, + Erythromycin: 0.7, + Tetracycline: 0.6, + Trim_sulfa: 0.5 + } as AMR; + + mockConvertProbabilityToWord.mockReturnValue("word"); + + const result = convertAmrForCsv(amr); + + expect(result).toEqual({ + Penicillin: "word", + Chloramphenicol: "word", + Erythromycin: "word", + Tetracycline: "word", + Cotrim: "word" + }); + }); + }); + + describe("generateCsvContent", () => { + it("should generate CSV content from data array", () => { + const data = [ + { filename: "sample1", Penicillin: "Penicillin-0.9", cluster: "cluster1" }, + { filename: "sample2", Penicillin: "Penicillin-0.8", cluster: "cluster2" } + ]; + + const result = generateCsvContent(data); + + expect(result).toBe( + "filename,Penicillin,cluster\nsample1,Penicillin-0.9,cluster1\nsample2,Penicillin-0.8,cluster2" + ); + }); + + it("should return an empty string for empty data array", () => { + const result = generateCsvContent([]); + + expect(result).toBe(""); + }); + }); + + describe("triggerCsvDownload", () => { + it("should create an anchor element and trigger download", () => { + const csvContent = "filename,Penicillin,cluster\nsample1,Penicillin-0.9,cluster1"; + const filename = "test.csv"; + + triggerCsvDownload(csvContent, filename); + + expect(document.createElement).toHaveBeenCalledWith("a"); + const anchor = (document.createElement as Mock).mock.results[0].value; + + expect(anchor.href).toBe("data:text/csv;charset=utf-8," + encodeURIComponent(csvContent)); + expect(anchor.download).toBe("test.csv"); + expect(anchor.click).toHaveBeenCalled(); + expect(anchor.remove).toHaveBeenCalled(); + }); + }); +}); diff --git a/app/client-v2/src/components/ProjectView/ProjectFileUploadHeader.vue b/app/client-v2/src/components/ProjectView/ProjectFileUploadHeader.vue index 78589d51..ef7e2ee9 100644 --- a/app/client-v2/src/components/ProjectView/ProjectFileUploadHeader.vue +++ b/app/client-v2/src/components/ProjectView/ProjectFileUploadHeader.vue @@ -1,5 +1,6 @@