You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@texera.apache.org by gi...@apache.org on 2026/08/06 05:57:34 UTC

(texera) branch gh-readonly-queue/main/pr-7334-c76d7b3f864808ba95d5847f661731bfcc8fd248 created (now cc83087403)

This is an automated email from the ASF dual-hosted git repository.

github-merge-queue[bot] pushed a change to branch gh-readonly-queue/main/pr-7334-c76d7b3f864808ba95d5847f661731bfcc8fd248
in repository https://gitbox.apache.org/repos/asf/texera.git


      at cc83087403 test(frontend): extend UserVenvComponent template coverage (#7334)

This branch includes the following new commits:

     new cc83087403 test(frontend): extend UserVenvComponent template coverage (#7334)

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(texera) 01/01: test(frontend): extend UserVenvComponent template coverage (#7334)

Posted by gi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/main/pr-7334-c76d7b3f864808ba95d5847f661731bfcc8fd248
in repository https://gitbox.apache.org/repos/asf/texera.git

commit cc8308740361324f7f129657dad6dc395abeeb1c
Author: Meng Wang <me...@uci.edu>
AuthorDate: Wed Aug 5 22:46:01 2026 -0700

    test(frontend): extend UserVenvComponent template coverage (#7334)
    
    ### What changes were proposed in this PR?
    
    Extends `user-venv.component.spec.ts` to render the template branches
    the existing
    class-focused tests never exercised, taking the template from ~36% to
    100%
    (`user-venv.component.html` 102/102). 9 new tests plus one class-guard
    test:
    
    - **list branches** — the empty-state message when there are no
    environments; a
    row per environment (with the `(unnamed)` fallback); clicking a row
    opens its
    draft; the delete icon fires `confirmDeletePve` and stops row-open
    propagation.
    - **modal** — the Create button opens an empty draft; opening the modal
    renders
    the form, the package header, one row per package, and the footer into
    the CDK
    overlay; the add-package / delete-toggle / Save / Close controls are
    driven
    through the DOM; the `nz-modal` cancel (X/mask) output closes the modal.
    - a `saveEnvironment` no-op-without-draft guard test brings the class to
    100% too.
    
    Interactions go through the DOM (`By.css` + `triggerEventHandler` /
    native
    `click`); the `nz-modal` body/footer render into the overlay, flushed
    with
    `ApplicationRef.tick()`. The backing `WorkflowPveService` is mocked; no
    layout
    assertions. No production code was changed.
    
    ### Any related issues, documentation, discussions?
    
    Closes #7331.
    
    ### How was this PR tested?
    
    `ng test --watch=false --include
    src/app/dashboard/component/user/user-venv/user-venv.component.spec.ts`
    — 33 passed, run 3× for determinism (the modal renders into the
    overlay). Coverage
    (`--coverage`) confirms `user-venv.component.html` at 102/102 and the
    component
    class at 89/89. The failure path was verified by breaking an assertion
    (red,
    non-zero exit); eslint and prettier are clean.
    
    ### Was this PR authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Code (Opus 4.8 [1M context])
---
 .../user/user-venv/user-venv.component.spec.ts     | 136 +++++++++++++++++++++
 1 file changed, 136 insertions(+)

diff --git a/frontend/src/app/dashboard/component/user/user-venv/user-venv.component.spec.ts b/frontend/src/app/dashboard/component/user/user-venv/user-venv.component.spec.ts
index 359c687780..c1807a3048 100644
--- a/frontend/src/app/dashboard/component/user/user-venv/user-venv.component.spec.ts
+++ b/frontend/src/app/dashboard/component/user/user-venv/user-venv.component.spec.ts
@@ -17,7 +17,9 @@
  * under the License.
  */
 
+import { ApplicationRef } from "@angular/core";
 import { ComponentFixture, TestBed } from "@angular/core/testing";
+import { By } from "@angular/platform-browser";
 import { NoopAnimationsModule } from "@angular/platform-browser/animations";
 import { DeleteOutline, FileAddOutline, PlusOutline } from "@ant-design/icons-angular/icons";
 import { NzIconModule } from "ng-zorro-antd/icon";
@@ -306,6 +308,14 @@ describe("UserVenvComponent", () => {
       expect(component.currentDraft).not.toBeNull();
       expect(component.pveModalVisible).toBe(true);
     });
+
+    it("is a no-op when there is no draft", () => {
+      component.currentDraft = null;
+      component.saveEnvironment();
+      expect(pveServiceSpy.savePve).not.toHaveBeenCalled();
+      expect(pveServiceSpy.updateUserPve).not.toHaveBeenCalled();
+      expect(component.saving).toBe(false);
+    });
   });
 
   describe("confirmDeletePve", () => {
@@ -377,4 +387,130 @@ 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();
+    };
+    // Assert an element exists so a bad selector fails as "not found" instead of a null deref.
+    const q = <E extends Element>(root: ParentNode, selector: string): E => {
+      const el = root.querySelector(selector);
+      expect(el, `expected to find "${selector}"`).not.toBeNull();
+      return el as unknown as E;
+    };
+    const overlay = (): HTMLElement => q<HTMLElement>(document, ".cdk-overlay-container");
+    // Pick a footer button by its label so the tests survive button reordering.
+    const footerButton = (root: ParentNode, label: string): HTMLButtonElement => {
+      const btn = Array.from(root.querySelectorAll<HTMLButtonElement>(".footer-all button")).find(
+        b => b.textContent?.trim() === label
+      );
+      expect(btn, `expected a footer button labelled "${label}"`).toBeDefined();
+      return btn as HTMLButtonElement;
+    };
+
+    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" }] });
+
+      q<HTMLButtonElement>(o, ".add-btn button").click();
+      flushOverlay();
+      expect(component.currentDraft?.newPackages.length).toBe(2);
+
+      q<HTMLButtonElement>(o, ".package-row .user-package-inputs button").click();
+      expect(component.currentDraft?.newPackages[0].deleteToggle).toBe(true);
+
+      footerButton(o, "Save").click();
+      expect(pveServiceSpy.savePve).toHaveBeenCalledWith("envDrive", {});
+    });
+
+    it("closes the modal from the footer Close button", () => {
+      fixture.detectChanges();
+      const o = openModalWith({ name: "envClose", newPackages: [] });
+      footerButton(o, "Close").click();
+      expect(component.pveModalVisible).toBe(false);
+      expect(component.currentDraft).toBeNull();
+    });
+
+    it("closes the modal on the nz-modal cancel (X / mask) output", () => {
+      fixture.detectChanges();
+      openModalWith({ name: "envCancel", newPackages: [] });
+      fixture.debugElement.query(By.css("nz-modal")).triggerEventHandler("nzOnCancel", null);
+      expect(component.pveModalVisible).toBe(false);
+      expect(component.currentDraft).toBeNull();
+    });
+  });
 });