Copilot commented on code in PR #6359:
URL: https://github.com/apache/texera/pull/6359#discussion_r3565408223


##########
frontend/src/app/dashboard/component/user/user-workflow/user-workflow.component.spec.ts:
##########
@@ -357,4 +366,343 @@ describe("SavedWorkflowSectionComponent", () => {
     expect(testWorkflowFileNameConflictEntries[0].checked).toBe(true);
     expect(testWorkflowFileNameConflictEntries[2].checked).toBe(true);
   });
+
+  describe("additional UserWorkflowComponent behaviors", () => {
+    const VIEW_MODE_KEY = "texera.userWorkflow.viewMode";
+
+    const makeDashboardWorkflow = (wid: number | undefined, name: string): 
DashboardWorkflow => ({
+      workflow: {
+        wid,
+        name,
+        description: "desc",
+        content: testWorkflowContent([]),
+        creationTime: 0,
+        lastModifiedTime: 0,
+        isPublished: 0,
+        readonly: false,
+      },
+      isOwner: true,
+      ownerName: "Texera",
+      accessLevel: "Write",
+      projectIDs: [],
+      ownerId: 1,
+    });
+
+    const makeEntry = (wid: number | undefined, name: string, checked = 
false): DashboardEntry => {
+      const entry = new DashboardEntry(makeDashboardWorkflow(wid, name));
+      entry.checked = checked;
+      return entry;
+    };
+
+    const setEntries = (entries: DashboardEntry[]): void => {
+      component.searchResultsComponent.entries = entries;
+    };
+
+    afterEach(() => {
+      vi.restoreAllMocks();
+      localStorage.removeItem(VIEW_MODE_KEY);
+    });
+
+    describe("deleteWorkflow", () => {
+      it("deletes an entry with a wid and removes it from the results", () => {
+        const persist = TestBed.inject(WorkflowPersistService) as any;
+        persist.deleteWorkflow = vi.fn().mockReturnValue(of(null));
+        const target = makeEntry(5, "to delete");
+        setEntries([target, makeEntry(6, "keep")]);
+
+        component.deleteWorkflow(target);
+
+        expect(persist.deleteWorkflow).toHaveBeenCalledWith([5]);
+        expect(component.searchResultsComponent.entries.map(e => 
e.name)).toEqual(["keep"]);
+      });
+
+      it("does nothing when the entry has no wid", () => {
+        const persist = TestBed.inject(WorkflowPersistService) as any;
+        persist.deleteWorkflow = vi.fn();
+
+        component.deleteWorkflow(makeEntry(undefined, "no wid"));
+
+        expect(persist.deleteWorkflow).not.toHaveBeenCalled();
+      });
+    });
+
+    describe("onClickDuplicateSelectedWorkflows", () => {
+      it("duplicates checked wids without a pid and prepends the new entries", 
() => {
+        const persist = TestBed.inject(WorkflowPersistService) as any;
+        persist.duplicateWorkflow = vi
+          .fn()
+          .mockReturnValue(of([makeDashboardWorkflow(101, "dup a"), 
makeDashboardWorkflow(102, "dup b")]));
+        component.pid = undefined;
+        setEntries([makeEntry(1, "wf 1", true), makeEntry(2, "wf 2", true), 
makeEntry(3, "wf 3", false)]);
+
+        component.onClickDuplicateSelectedWorkflows();
+
+        expect(persist.duplicateWorkflow).toHaveBeenCalledWith([1, 2]);
+        expect(component.searchResultsComponent.entries.map(e => 
e.name)).toEqual([
+          "dup a",
+          "dup b",
+          "wf 1",
+          "wf 2",
+          "wf 3",
+        ]);
+      });
+
+      it("passes the pid to duplicateWorkflow when the section belongs to a 
project", () => {
+        const persist = TestBed.inject(WorkflowPersistService) as any;
+        persist.duplicateWorkflow = 
vi.fn().mockReturnValue(of([makeDashboardWorkflow(101, "dup a")]));
+        component.pid = 9;
+        setEntries([makeEntry(1, "wf 1", true), makeEntry(2, "wf 2", true)]);
+
+        component.onClickDuplicateSelectedWorkflows();
+
+        expect(persist.duplicateWorkflow).toHaveBeenCalledWith([1, 2], 9);
+      });
+
+      it("early-returns without calling the service when a checked entry has 
no wid", () => {
+        const persist = TestBed.inject(WorkflowPersistService) as any;
+        persist.duplicateWorkflow = vi.fn();
+        setEntries([makeEntry(1, "wf 1", true), makeEntry(undefined, "wf 2", 
true)]);
+
+        component.onClickDuplicateSelectedWorkflows();
+
+        expect(persist.duplicateWorkflow).not.toHaveBeenCalled();
+      });
+
+      it("alerts on a duplication error", () => {
+        const persist = TestBed.inject(WorkflowPersistService) as any;
+        persist.duplicateWorkflow = vi.fn().mockReturnValue(throwError(() => 
"boom"));
+        const alertSpy = vi.spyOn(window, "alert").mockImplementation(() => 
{});
+        component.pid = undefined;
+        setEntries([makeEntry(1, "wf 1", true)]);
+
+        component.onClickDuplicateSelectedWorkflows();
+
+        expect(alertSpy).toHaveBeenCalledWith("boom");
+      });
+    });
+
+    describe("handleConfirmDeleteSelectedWorkflows", () => {
+      it("deletes checked wids and keeps undefined-wid entries", () => {
+        const persist = TestBed.inject(WorkflowPersistService) as any;
+        persist.deleteWorkflow = vi.fn().mockReturnValue(of(null));
+        setEntries([
+          makeEntry(1, "a", true),
+          makeEntry(2, "b", true),
+          makeEntry(undefined, "c", false),
+          makeEntry(3, "d", false),
+        ]);
+
+        component.handleConfirmDeleteSelectedWorkflows();
+
+        expect(persist.deleteWorkflow).toHaveBeenCalledWith([1, 2]);
+        expect(component.searchResultsComponent.entries.map(e => 
e.name)).toEqual(["c", "d"]);
+      });
+
+      it("early-returns when a checked entry has no wid", () => {
+        const persist = TestBed.inject(WorkflowPersistService) as any;
+        persist.deleteWorkflow = vi.fn();
+        setEntries([makeEntry(undefined, "a", true)]);
+
+        component.handleConfirmDeleteSelectedWorkflows();
+
+        expect(persist.deleteWorkflow).not.toHaveBeenCalled();
+      });
+
+      it("alerts on a deletion error", () => {
+        const persist = TestBed.inject(WorkflowPersistService) as any;
+        persist.deleteWorkflow = vi.fn().mockReturnValue(throwError(() => 
"delfail"));
+        const alertSpy = vi.spyOn(window, "alert").mockImplementation(() => 
{});
+        setEntries([makeEntry(1, "a", true)]);
+
+        component.handleConfirmDeleteSelectedWorkflows();
+
+        expect(alertSpy).toHaveBeenCalledWith("delfail");
+      });
+    });
+
+    describe("onClickOpenDownloadZip", () => {
+      it("does not call the download service when nothing is checked", () => {
+        setEntries([makeEntry(1, "a", false)]);
+
+        component.onClickOpenDownloadZip();
+
+        
expect(downloadServiceSpy.downloadWorkflowsAsZip).not.toHaveBeenCalled();
+      });
+
+      it("logs an error when the download fails", () => {
+        setEntries([makeEntry(1, "a", true)]);
+        
downloadServiceSpy.downloadWorkflowsAsZip.mockReturnValue(throwError(() => 
"dlfail"));
+        const errorSpy = vi.spyOn(console, "error").mockImplementation(() => 
{});
+
+        component.onClickOpenDownloadZip();
+
+        
expect(downloadServiceSpy.downloadWorkflowsAsZip).toHaveBeenCalledWith([{ id: 
1, name: "a" }]);
+        expect(errorSpy).toHaveBeenCalledWith("Error downloading workflows:", 
"dlfail");
+      });
+    });
+
+    describe("workflow uploads", () => {
+      it("imports a valid .json workflow, appending an entry and toasting 
success", async () => {
+        const persist = TestBed.inject(WorkflowPersistService) as any;
+        persist.createWorkflow = 
vi.fn().mockReturnValue(of(makeDashboardWorkflow(42, "wf")));
+        const searchSpy = vi.spyOn(component, 
"search").mockResolvedValue(undefined);
+        const successSpy = vi
+          .spyOn(TestBed.inject(NotificationService), "success")
+          .mockImplementation(() => undefined as any);
+        const content = testWorkflowContent([]);
+        const file = new File([JSON.stringify(content)], "wf.json");
+        setEntries([]);
+
+        const result = await 
firstValueFrom(component.onClickUploadExistingWorkflowFromLocal(file as any));
+
+        expect(result).toBe(false);
+        expect(persist.createWorkflow).toHaveBeenCalledWith(content, "wf");
+        expect(component.searchResultsComponent.entries.map(e => 
e.name)).toContain("wf");
+        expect(searchSpy).toHaveBeenCalledWith(true);
+        expect(successSpy).toHaveBeenCalledWith("Upload Successful");
+      });
+
+      it("toasts an error and errors the stream when the file is not JSON", 
async () => {
+        const errorSpy = vi
+          .spyOn(TestBed.inject(NotificationService), "error")
+          .mockImplementation(() => undefined as any);
+        const file = new File(["this is not json"], "bad.json");
+
+        await 
expect(firstValueFrom(component.onClickUploadExistingWorkflowFromLocal(file as 
any))).rejects.toThrow();
+
+        expect(errorSpy).toHaveBeenCalledWith(
+          "An error occurred when importing the workflow. Please import a 
workflow json file."
+        );
+      });
+
+      it("falls back to DEFAULT_WORKFLOW_NAME when the stripped name is 
empty", async () => {
+        const persist = TestBed.inject(WorkflowPersistService) as any;
+        persist.createWorkflow = 
vi.fn().mockReturnValue(of(makeDashboardWorkflow(1, "x")));
+        vi.spyOn(component, "search").mockResolvedValue(undefined);
+        const content = testWorkflowContent([]);
+        const file = new File([JSON.stringify(content)], ".json");
+
+        await 
firstValueFrom(component.onClickUploadExistingWorkflowFromLocal(file as any));
+
+        expect(persist.createWorkflow).toHaveBeenCalledWith(content, 
DEFAULT_WORKFLOW_NAME);
+      });
+
+      // handleZipUploads is intentionally not covered here: its first 
statement is
+      // `new JSZip()` against the `import * as JSZip from "jszip"` namespace, 
which
+      // crashes under Vitest ("... is not a constructor"). The same 
limitation is
+      // documented and skipped in download.service.spec.ts. nameWorkflow's 
dedup
+      // logic is exercised below with a lightweight fake that mimics 
`zip.files`.

Review Comment:
   This block states `handleZipUploads` (the .zip import path) is intentionally 
not covered due to the `new JSZip()`/`import * as JSZip from "jszip"` 
constructor crash in Vitest, but the PR description and issue #6357 both list 
“zip import (real JSZip)” / `handleZipUploads` coverage as an explicit goal and 
the PR is marked `Closes #6357`. As written, the zip-import behavior is still 
untested, so the PR metadata/closure appears premature.
   
   Consider either (1) enabling zip-import coverage by fixing the underlying 
JSZip import/constructor usage (so `new JSZip()` works in Vitest and at 
runtime) and then adding the `.zip` test, or (2) updating the PR description 
and removing the issue closure until zip-import coverage is actually added.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to