mengw15 commented on code in PR #7334:
URL: https://github.com/apache/texera/pull/7334#discussion_r3720293408


##########
frontend/src/app/dashboard/component/user/user-venv/user-venv.component.spec.ts:
##########
@@ -377,4 +387,116 @@ describe("UserVenvComponent", () => {
       expect(component.trackByVeid(1, { name: "", newPackages: [] 
})).toBeUndefined();
     });
   });
+
+  // The class is well covered above; these exercise the template itself — the 
list
+  // branches and the nz-modal body/footer, which render into the CDK overlay.
+  describe("template rendering", () => {
+    type Draft = NonNullable<UserVenvComponent["currentDraft"]>;
+
+    // nz-modal renders into the overlay attached to ApplicationRef, so tick() 
after
+    // detectChanges to flush its embedded view.
+    const flushOverlay = (): void => {
+      fixture.detectChanges();
+      TestBed.inject(ApplicationRef).tick();
+    };
+    const overlay = (): HTMLElement => 
document.querySelector(".cdk-overlay-container") as HTMLElement;

Review Comment:
   `overlay()` now goes through a null-asserting `q()` helper. Cross-test 
overlay contamination is already prevented — the top-level `afterEach` runs 
`fixture.destroy()` and clears `.cdk-overlay-container`, so every test opens 
into a clean overlay.



##########
frontend/src/app/dashboard/component/user/user-venv/user-venv.component.spec.ts:
##########
@@ -377,4 +387,116 @@ describe("UserVenvComponent", () => {
       expect(component.trackByVeid(1, { name: "", newPackages: [] 
})).toBeUndefined();
     });
   });
+
+  // The class is well covered above; these exercise the template itself — the 
list
+  // branches and the nz-modal body/footer, which render into the CDK overlay.
+  describe("template rendering", () => {
+    type Draft = NonNullable<UserVenvComponent["currentDraft"]>;
+
+    // nz-modal renders into the overlay attached to ApplicationRef, so tick() 
after
+    // detectChanges to flush its embedded view.
+    const flushOverlay = (): void => {
+      fixture.detectChanges();
+      TestBed.inject(ApplicationRef).tick();
+    };
+    const overlay = (): HTMLElement => 
document.querySelector(".cdk-overlay-container") as HTMLElement;
+
+    const openModalWith = (draft: Draft): HTMLElement => {
+      component.currentDraft = draft;
+      component.pveModalVisible = true;
+      flushOverlay();
+      return overlay();
+    };
+
+    const seedList = (records: UserPveRecord[]): void => {
+      pveServiceSpy.listUserPves.mockReturnValue(of(records));
+      fixture.detectChanges();
+    };
+
+    it("shows the empty-state message and no list when there are no 
environments", () => {
+      seedList([]);
+      const host = fixture.nativeElement as HTMLElement;
+      
expect(host.querySelector(".python-env-page-empty")?.textContent).toContain("No 
environments yet");
+      expect(host.querySelector("ul.python-env-page-list")).toBeNull();
+    });
+
+    it("opens an empty draft modal from the Create button", () => {
+      fixture.detectChanges();
+      
fixture.debugElement.query(By.css(".create-btn")).triggerEventHandler("click", 
{});
+      expect(component.pveModalVisible).toBe(true);
+      expect(component.currentDraft).toEqual({ name: "", newPackages: [] });
+    });
+
+    it("renders a row per environment (with the unnamed fallback) and opens 
the row on click", () => {
+      seedList([
+        { veid: 1, name: "envA", packages: {} },
+        { veid: 2, name: "", packages: {} },
+      ] as UserPveRecord[]);
+
+      const rows = 
fixture.debugElement.queryAll(By.css("li.python-env-page-item"));
+      expect(rows.length).toBe(2);
+      expect((fixture.nativeElement as 
HTMLElement).textContent).toContain("(unnamed)");
+
+      rows[0].triggerEventHandler("click", {});
+      expect(component.pveModalVisible).toBe(true);
+      expect(component.currentDraft?.name).toBe("envA");
+    });
+
+    it("fires confirmDeletePve from the row delete icon and stops row-open 
propagation", () => {
+      seedList([{ veid: 3, name: "envDel", packages: {} }] as UserPveRecord[]);
+      const stopPropagation = vi.fn();
+      
fixture.debugElement.query(By.css(".python-env-delete-icon")).triggerEventHandler("click",
 { stopPropagation });
+      expect(stopPropagation).toHaveBeenCalled();
+      expect(confirmSpy).toHaveBeenCalledTimes(1);
+      expect(component.pveModalVisible).toBe(false);
+    });
+
+    it("renders the modal form, package header, one row per package, and the 
footer when open", () => {
+      fixture.detectChanges();
+      const o = openModalWith({
+        name: "envForm",
+        newPackages: [
+          { name: "numpy", versionOp: "==", version: "1.2" },
+          { name: "pandas", versionOp: ">=", version: "2.0" },
+        ],
+      });
+
+      expect(o.querySelector(".ve-form")).not.toBeNull();
+      // header row (*ngIf newPackages.length > 0) + one row per package
+      expect(o.querySelectorAll(".package-row").length).toBe(3);
+      expect(o.querySelector(".add-btn button")).not.toBeNull();
+      expect(o.querySelectorAll(".footer-all button").length).toBe(2);
+    });
+
+    it("drives the modal package controls and the Save footer button through 
the DOM", () => {
+      fixture.detectChanges();
+      const o = openModalWith({ name: "envDrive", newPackages: [{ name: "x", 
versionOp: "==", version: "1" }] });
+
+      (o.querySelector(".add-btn button") as HTMLButtonElement).click();

Review Comment:
   Routed through a `q<E>(root, selector)` helper that asserts the element 
exists first, so a missing selector fails as `expected to find "…"` instead of 
a null dereference.



##########
frontend/src/app/dashboard/component/user/user-venv/user-venv.component.spec.ts:
##########
@@ -377,4 +387,116 @@ describe("UserVenvComponent", () => {
       expect(component.trackByVeid(1, { name: "", newPackages: [] 
})).toBeUndefined();
     });
   });
+
+  // The class is well covered above; these exercise the template itself — the 
list
+  // branches and the nz-modal body/footer, which render into the CDK overlay.
+  describe("template rendering", () => {
+    type Draft = NonNullable<UserVenvComponent["currentDraft"]>;
+
+    // nz-modal renders into the overlay attached to ApplicationRef, so tick() 
after
+    // detectChanges to flush its embedded view.
+    const flushOverlay = (): void => {
+      fixture.detectChanges();
+      TestBed.inject(ApplicationRef).tick();
+    };
+    const overlay = (): HTMLElement => 
document.querySelector(".cdk-overlay-container") as HTMLElement;
+
+    const openModalWith = (draft: Draft): HTMLElement => {
+      component.currentDraft = draft;
+      component.pveModalVisible = true;
+      flushOverlay();
+      return overlay();
+    };
+
+    const seedList = (records: UserPveRecord[]): void => {
+      pveServiceSpy.listUserPves.mockReturnValue(of(records));
+      fixture.detectChanges();
+    };
+
+    it("shows the empty-state message and no list when there are no 
environments", () => {
+      seedList([]);
+      const host = fixture.nativeElement as HTMLElement;
+      
expect(host.querySelector(".python-env-page-empty")?.textContent).toContain("No 
environments yet");
+      expect(host.querySelector("ul.python-env-page-list")).toBeNull();
+    });
+
+    it("opens an empty draft modal from the Create button", () => {
+      fixture.detectChanges();
+      
fixture.debugElement.query(By.css(".create-btn")).triggerEventHandler("click", 
{});
+      expect(component.pveModalVisible).toBe(true);
+      expect(component.currentDraft).toEqual({ name: "", newPackages: [] });
+    });
+
+    it("renders a row per environment (with the unnamed fallback) and opens 
the row on click", () => {
+      seedList([
+        { veid: 1, name: "envA", packages: {} },
+        { veid: 2, name: "", packages: {} },
+      ] as UserPveRecord[]);
+
+      const rows = 
fixture.debugElement.queryAll(By.css("li.python-env-page-item"));
+      expect(rows.length).toBe(2);
+      expect((fixture.nativeElement as 
HTMLElement).textContent).toContain("(unnamed)");
+
+      rows[0].triggerEventHandler("click", {});
+      expect(component.pveModalVisible).toBe(true);
+      expect(component.currentDraft?.name).toBe("envA");
+    });
+
+    it("fires confirmDeletePve from the row delete icon and stops row-open 
propagation", () => {
+      seedList([{ veid: 3, name: "envDel", packages: {} }] as UserPveRecord[]);
+      const stopPropagation = vi.fn();
+      
fixture.debugElement.query(By.css(".python-env-delete-icon")).triggerEventHandler("click",
 { stopPropagation });
+      expect(stopPropagation).toHaveBeenCalled();
+      expect(confirmSpy).toHaveBeenCalledTimes(1);
+      expect(component.pveModalVisible).toBe(false);
+    });
+
+    it("renders the modal form, package header, one row per package, and the 
footer when open", () => {
+      fixture.detectChanges();
+      const o = openModalWith({
+        name: "envForm",
+        newPackages: [
+          { name: "numpy", versionOp: "==", version: "1.2" },
+          { name: "pandas", versionOp: ">=", version: "2.0" },
+        ],
+      });
+
+      expect(o.querySelector(".ve-form")).not.toBeNull();
+      // header row (*ngIf newPackages.length > 0) + one row per package
+      expect(o.querySelectorAll(".package-row").length).toBe(3);
+      expect(o.querySelector(".add-btn button")).not.toBeNull();
+      expect(o.querySelectorAll(".footer-all button").length).toBe(2);
+    });
+
+    it("drives the modal package controls and the Save footer button through 
the DOM", () => {
+      fixture.detectChanges();
+      const o = openModalWith({ name: "envDrive", newPackages: [{ name: "x", 
versionOp: "==", version: "1" }] });
+
+      (o.querySelector(".add-btn button") as HTMLButtonElement).click();
+      flushOverlay();
+      expect(component.currentDraft?.newPackages.length).toBe(2);
+
+      (o.querySelector(".package-row .user-package-inputs button") as 
HTMLButtonElement).click();

Review Comment:
   Routed through a `q<E>(root, selector)` helper that asserts the element 
exists first, so a missing selector fails as `expected to find "…"` instead of 
a null dereference.



##########
frontend/src/app/dashboard/component/user/user-venv/user-venv.component.spec.ts:
##########
@@ -377,4 +387,116 @@ describe("UserVenvComponent", () => {
       expect(component.trackByVeid(1, { name: "", newPackages: [] 
})).toBeUndefined();
     });
   });
+
+  // The class is well covered above; these exercise the template itself — the 
list
+  // branches and the nz-modal body/footer, which render into the CDK overlay.
+  describe("template rendering", () => {
+    type Draft = NonNullable<UserVenvComponent["currentDraft"]>;
+
+    // nz-modal renders into the overlay attached to ApplicationRef, so tick() 
after
+    // detectChanges to flush its embedded view.
+    const flushOverlay = (): void => {
+      fixture.detectChanges();
+      TestBed.inject(ApplicationRef).tick();
+    };
+    const overlay = (): HTMLElement => 
document.querySelector(".cdk-overlay-container") as HTMLElement;
+
+    const openModalWith = (draft: Draft): HTMLElement => {
+      component.currentDraft = draft;
+      component.pveModalVisible = true;
+      flushOverlay();
+      return overlay();
+    };
+
+    const seedList = (records: UserPveRecord[]): void => {
+      pveServiceSpy.listUserPves.mockReturnValue(of(records));
+      fixture.detectChanges();
+    };
+
+    it("shows the empty-state message and no list when there are no 
environments", () => {
+      seedList([]);
+      const host = fixture.nativeElement as HTMLElement;
+      
expect(host.querySelector(".python-env-page-empty")?.textContent).toContain("No 
environments yet");
+      expect(host.querySelector("ul.python-env-page-list")).toBeNull();
+    });
+
+    it("opens an empty draft modal from the Create button", () => {
+      fixture.detectChanges();
+      
fixture.debugElement.query(By.css(".create-btn")).triggerEventHandler("click", 
{});
+      expect(component.pveModalVisible).toBe(true);
+      expect(component.currentDraft).toEqual({ name: "", newPackages: [] });
+    });
+
+    it("renders a row per environment (with the unnamed fallback) and opens 
the row on click", () => {
+      seedList([
+        { veid: 1, name: "envA", packages: {} },
+        { veid: 2, name: "", packages: {} },
+      ] as UserPveRecord[]);
+
+      const rows = 
fixture.debugElement.queryAll(By.css("li.python-env-page-item"));
+      expect(rows.length).toBe(2);
+      expect((fixture.nativeElement as 
HTMLElement).textContent).toContain("(unnamed)");
+
+      rows[0].triggerEventHandler("click", {});
+      expect(component.pveModalVisible).toBe(true);
+      expect(component.currentDraft?.name).toBe("envA");
+    });
+
+    it("fires confirmDeletePve from the row delete icon and stops row-open 
propagation", () => {
+      seedList([{ veid: 3, name: "envDel", packages: {} }] as UserPveRecord[]);
+      const stopPropagation = vi.fn();
+      
fixture.debugElement.query(By.css(".python-env-delete-icon")).triggerEventHandler("click",
 { stopPropagation });
+      expect(stopPropagation).toHaveBeenCalled();
+      expect(confirmSpy).toHaveBeenCalledTimes(1);
+      expect(component.pveModalVisible).toBe(false);
+    });
+
+    it("renders the modal form, package header, one row per package, and the 
footer when open", () => {
+      fixture.detectChanges();
+      const o = openModalWith({
+        name: "envForm",
+        newPackages: [
+          { name: "numpy", versionOp: "==", version: "1.2" },
+          { name: "pandas", versionOp: ">=", version: "2.0" },
+        ],
+      });
+
+      expect(o.querySelector(".ve-form")).not.toBeNull();
+      // header row (*ngIf newPackages.length > 0) + one row per package
+      expect(o.querySelectorAll(".package-row").length).toBe(3);
+      expect(o.querySelector(".add-btn button")).not.toBeNull();
+      expect(o.querySelectorAll(".footer-all button").length).toBe(2);
+    });
+
+    it("drives the modal package controls and the Save footer button through 
the DOM", () => {
+      fixture.detectChanges();
+      const o = openModalWith({ name: "envDrive", newPackages: [{ name: "x", 
versionOp: "==", version: "1" }] });
+
+      (o.querySelector(".add-btn button") as HTMLButtonElement).click();
+      flushOverlay();
+      expect(component.currentDraft?.newPackages.length).toBe(2);
+
+      (o.querySelector(".package-row .user-package-inputs button") as 
HTMLButtonElement).click();
+      expect(component.currentDraft?.newPackages[0].deleteToggle).toBe(true);
+
+      (o.querySelectorAll(".footer-all button")[1] as 
HTMLButtonElement).click();

Review Comment:
   Switched to a `footerButton(root, label)` helper that matches by button text 
(`"Save"` / `"Close"`), so the tests survive footer-button reordering.



##########
frontend/src/app/dashboard/component/user/user-venv/user-venv.component.spec.ts:
##########
@@ -377,4 +387,116 @@ describe("UserVenvComponent", () => {
       expect(component.trackByVeid(1, { name: "", newPackages: [] 
})).toBeUndefined();
     });
   });
+
+  // The class is well covered above; these exercise the template itself — the 
list
+  // branches and the nz-modal body/footer, which render into the CDK overlay.
+  describe("template rendering", () => {
+    type Draft = NonNullable<UserVenvComponent["currentDraft"]>;
+
+    // nz-modal renders into the overlay attached to ApplicationRef, so tick() 
after
+    // detectChanges to flush its embedded view.
+    const flushOverlay = (): void => {
+      fixture.detectChanges();
+      TestBed.inject(ApplicationRef).tick();
+    };
+    const overlay = (): HTMLElement => 
document.querySelector(".cdk-overlay-container") as HTMLElement;
+
+    const openModalWith = (draft: Draft): HTMLElement => {
+      component.currentDraft = draft;
+      component.pveModalVisible = true;
+      flushOverlay();
+      return overlay();
+    };
+
+    const seedList = (records: UserPveRecord[]): void => {
+      pveServiceSpy.listUserPves.mockReturnValue(of(records));
+      fixture.detectChanges();
+    };
+
+    it("shows the empty-state message and no list when there are no 
environments", () => {
+      seedList([]);
+      const host = fixture.nativeElement as HTMLElement;
+      
expect(host.querySelector(".python-env-page-empty")?.textContent).toContain("No 
environments yet");
+      expect(host.querySelector("ul.python-env-page-list")).toBeNull();
+    });
+
+    it("opens an empty draft modal from the Create button", () => {
+      fixture.detectChanges();
+      
fixture.debugElement.query(By.css(".create-btn")).triggerEventHandler("click", 
{});
+      expect(component.pveModalVisible).toBe(true);
+      expect(component.currentDraft).toEqual({ name: "", newPackages: [] });
+    });
+
+    it("renders a row per environment (with the unnamed fallback) and opens 
the row on click", () => {
+      seedList([
+        { veid: 1, name: "envA", packages: {} },
+        { veid: 2, name: "", packages: {} },
+      ] as UserPveRecord[]);
+
+      const rows = 
fixture.debugElement.queryAll(By.css("li.python-env-page-item"));
+      expect(rows.length).toBe(2);
+      expect((fixture.nativeElement as 
HTMLElement).textContent).toContain("(unnamed)");
+
+      rows[0].triggerEventHandler("click", {});
+      expect(component.pveModalVisible).toBe(true);
+      expect(component.currentDraft?.name).toBe("envA");
+    });
+
+    it("fires confirmDeletePve from the row delete icon and stops row-open 
propagation", () => {
+      seedList([{ veid: 3, name: "envDel", packages: {} }] as UserPveRecord[]);
+      const stopPropagation = vi.fn();
+      
fixture.debugElement.query(By.css(".python-env-delete-icon")).triggerEventHandler("click",
 { stopPropagation });
+      expect(stopPropagation).toHaveBeenCalled();
+      expect(confirmSpy).toHaveBeenCalledTimes(1);
+      expect(component.pveModalVisible).toBe(false);
+    });
+
+    it("renders the modal form, package header, one row per package, and the 
footer when open", () => {
+      fixture.detectChanges();
+      const o = openModalWith({
+        name: "envForm",
+        newPackages: [
+          { name: "numpy", versionOp: "==", version: "1.2" },
+          { name: "pandas", versionOp: ">=", version: "2.0" },
+        ],
+      });
+
+      expect(o.querySelector(".ve-form")).not.toBeNull();
+      // header row (*ngIf newPackages.length > 0) + one row per package
+      expect(o.querySelectorAll(".package-row").length).toBe(3);
+      expect(o.querySelector(".add-btn button")).not.toBeNull();
+      expect(o.querySelectorAll(".footer-all button").length).toBe(2);
+    });
+
+    it("drives the modal package controls and the Save footer button through 
the DOM", () => {
+      fixture.detectChanges();
+      const o = openModalWith({ name: "envDrive", newPackages: [{ name: "x", 
versionOp: "==", version: "1" }] });
+
+      (o.querySelector(".add-btn button") as HTMLButtonElement).click();
+      flushOverlay();
+      expect(component.currentDraft?.newPackages.length).toBe(2);
+
+      (o.querySelector(".package-row .user-package-inputs button") as 
HTMLButtonElement).click();
+      expect(component.currentDraft?.newPackages[0].deleteToggle).toBe(true);
+
+      (o.querySelectorAll(".footer-all button")[1] as 
HTMLButtonElement).click();
+      expect(pveServiceSpy.savePve).toHaveBeenCalledWith("envDrive", {});
+    });
+
+    it("closes the modal from the footer Close button", () => {
+      fixture.detectChanges();
+      const o = openModalWith({ name: "envClose", newPackages: [] });
+      (o.querySelectorAll(".footer-all button")[0] as 
HTMLButtonElement).click();

Review Comment:
   Switched to a `footerButton(root, label)` helper that matches by button text 
(`"Save"` / `"Close"`), so the tests survive footer-button reordering.



-- 
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