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-7420-2c57707a762486a7935860d8fee7b8eb0ff89a7e
in repository https://gitbox.apache.org/repos/asf/texera.git

commit 85c5fb2ec52b75a91d4915a61717ced425458a5e
Author: Meng Wang <[email protected]>
AuthorDate: Fri Aug 7 22:02:17 2026 -0700

    test(frontend): cover UserComputingUnitListItemComponent template bindings 
(#7420)
    
    ### What changes were proposed in this PR?
    
    Extends `UserComputingUnitListItemComponent`'s spec to exercise the row
    through
    the DOM. The class file is already at 100%, but the existing tests call
    the
    handlers directly and never click the row's buttons, so
    `user-computing-unit-list-item.component.html` sat at ~52%.
    
    10 added tests render the row and drive it via
    `fixture.debugElement.query(By.css(...))`:
    
    - **Buttons** — the rename button starts inline editing; clicking the
    unit name opens the metadata modal (`NzModalService.create` spied); the
    delete button emits the `deleted` output.
    - **Inline rename** — `Escape` cancels and `Enter` confirms with the
    typed value (both fired as real `KeyboardEvent`s), and a click inside
    the input does not bubble to the row.
    - **Sharing** — the share button is omitted while
    `sharingComputingUnitEnabled` is off, and opens the share-access modal
    when it is on.
    - **Metrics popover** — the CPU/RAM rows render, and the GPU /
    JVM-memory / shared-memory rows appear only when those limits are set
    (covering both arms of their `*ngIf`s).
    
    This lifts the template from **~52% to 100%** (statements *and*
    branches); the
    class stays at 100%.
    
    Two things worth noting, both found by checking rather than assuming:
    
    - The `Escape`/`Enter` tests dispatch **real** `KeyboardEvent`s. With
    `triggerEventHandler("keydown.escape", …)` the handler does fire
    (verified with a spy), but it bypasses Angular's key-filtering, so the
    real dispatch is both more representative and what the coverage
    reflects.
    - The last uncovered line was reported as html:79, but reading the
    coverage `statementMap` showed the uncovered statement actually spans to
    **line 84** — the input's `(click)="$event.stopPropagation()"`. That is
    what the added "click does not bubble" test covers.
    
    **Determinism:** the added tests introduce no *new* `vi.useFakeTimers()`
    usage
    (one pre-existing test in this file already uses fake timers; nothing
    was added
    on top) — the component's `setTimeout` never runs in a synchronous test
    body, and
    layering fake timers over zone.js's patched timers is Node-version
    dependent. No
    layout/geometry assertions. Overlay contents are cleared in an
    `afterEach` so no
    popover DOM leaks into a later test (clearing `innerHTML` rather than
    removing
    the container, since CDK caches that element). The popover is
    opened synchronously via `.injector.get(NzPopoverDirective).show()` +
    `detectChanges()` — the pattern already used in
    `user-dataset-staged-objects-list.component.spec.ts` — and hidden again
    afterwards. The sharing flag is flipped through
    `TestBed.inject(GuiConfigService)`
    (the same DI instance the component holds, whose config object is
    per-instance),
    so nothing leaks between tests. No production code was changed.
    
    ### Any related issues, documentation, discussions?
    
    Closes #7411
    
    ### How was this PR tested?
    
    Extended unit tests, run locally in `frontend/`:
    
    ```
    ng test --watch=false --include 
src/app/dashboard/component/user/user-computing-unit/user-computing-unit-list-item/user-computing-unit-list-item.component.spec.ts
    # Test Files 1 passed (1) | Tests 45 passed (45)   — 3 consecutive runs, 0 
flakes
    # user-computing-unit-list-item.component.html: ~52% -> 100% (statements & 
branches)
    prettier --write <spec>   # formatted
    eslint  <spec>            # clean
    ```
    
    The failure path was verified by deliberately breaking a new assertion
    and
    confirming the suite exits non-zero.
    
    ### Was this PR authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Code (Opus 4.8 [1M context])
---
 ...user-computing-unit-list-item.component.spec.ts | 140 +++++++++++++++++++++
 1 file changed, 140 insertions(+)

diff --git 
a/frontend/src/app/dashboard/component/user/user-computing-unit/user-computing-unit-list-item/user-computing-unit-list-item.component.spec.ts
 
b/frontend/src/app/dashboard/component/user/user-computing-unit/user-computing-unit-list-item/user-computing-unit-list-item.component.spec.ts
index 9fd46da1c1..9fd52ee80d 100644
--- 
a/frontend/src/app/dashboard/component/user/user-computing-unit/user-computing-unit-list-item/user-computing-unit-list-item.component.spec.ts
+++ 
b/frontend/src/app/dashboard/component/user/user-computing-unit/user-computing-unit-list-item/user-computing-unit-list-item.component.spec.ts
@@ -22,6 +22,7 @@ import { ComponentFixture, TestBed } from 
"@angular/core/testing";
 import { HttpClientTestingModule } from "@angular/common/http/testing";
 import { By } from "@angular/platform-browser";
 import { NzModalService } from "ng-zorro-antd/modal";
+import { NzPopoverDirective } from "ng-zorro-antd/popover";
 import { of, throwError } from "rxjs";
 import type { Mocked } from "vitest";
 import { UserComputingUnitListItemComponent } from 
"./user-computing-unit-list-item.component";
@@ -31,6 +32,7 @@ import { ComputingUnitStatusService } from 
"../../../../../common/service/comput
 import { MockComputingUnitStatusService } from 
"../../../../../common/service/computing-unit/computing-unit-status/mock-computing-unit-status.service";
 import { ComputingUnitActionsService } from 
"../../../../../common/service/computing-unit/computing-unit-actions/computing-unit-actions.service";
 import { DashboardWorkflowComputingUnit } from 
"../../../../../common/type/workflow-computing-unit";
+import { GuiConfigService } from 
"../../../../../common/service/gui-config.service";
 import { commonTestProviders } from "../../../../../common/testing/test-utils";
 import { ComputingUnitMetadataComponent } from 
"../../../../../common/util/computing-unit.util";
 
@@ -490,4 +492,142 @@ describe("UserComputingUnitListItemComponent", () => {
       expect(component.showGpuSelection()).toBe(true);
     });
   });
+
+  // ── Rendered-template interactions (the row's bindings) ──
+
+  describe("template interactions", () => {
+    /** The handlers that call `$event.stopPropagation()` need a real-ish 
event. */
+    const clickEvent = () => ({ stopPropagation: vi.fn() }) as unknown as 
MouseEvent;
+
+    /** Show the metrics popover synchronously and return its overlay text. */
+    function openMetricsPopover(): { popover: NzPopoverDirective; text: string 
} {
+      const popover = 
fixture.debugElement.query(By.css(".metrics-container")).injector.get(NzPopoverDirective);
+      popover.show();
+      fixture.detectChanges();
+      return { popover, text: 
document.querySelector(".cdk-overlay-container")?.textContent ?? "" };
+    }
+
+    afterEach(() => {
+      // Clear the overlay contents rather than removing the container element 
itself:
+      // CDK's OverlayContainer caches that element, so removing it would make 
later
+      // overlays render into a detached node.
+      document.querySelectorAll(".cdk-overlay-container").forEach(el => 
(el.innerHTML = ""));
+    });
+
+    it("the rename button starts inline editing", () => {
+      const renameButton = fixture.debugElement.query(By.css(".edit-button 
button"));
+      expect(renameButton).toBeTruthy();
+
+      renameButton.triggerEventHandler("click", clickEvent());
+
+      expect(component.editingNameOfUnit).toBe(1);
+      expect(component.editingUnitName).toBe("unit-1");
+    });
+
+    it("clicking the unit name opens the metadata modal", () => {
+      const createSpy = vi
+        .spyOn(TestBed.inject(NzModalService), "create")
+        .mockReturnValue({} as ReturnType<NzModalService["create"]>);
+
+      
fixture.debugElement.query(By.css(".resource-name")).triggerEventHandler("click",
 null);
+
+      expect(createSpy).toHaveBeenCalledWith(expect.objectContaining({ nzData: 
component.entry }));
+    });
+
+    it("escape on the rename input cancels editing", () => {
+      component.editingNameOfUnit = 1;
+      fixture.detectChanges();
+
+      const input = 
fixture.debugElement.query(By.css("input.unit-name-edit-input"));
+      expect(input).toBeTruthy();
+      input.nativeElement.dispatchEvent(new KeyboardEvent("keydown", { key: 
"Escape", bubbles: true }));
+
+      expect(component.editingNameOfUnit).toBeNull();
+    });
+
+    it("enter on the rename input confirms with the typed value", () => {
+      computingUnitService.renameComputingUnit.mockReturnValue(of({} as 
Response));
+      component.editingNameOfUnit = 1;
+      fixture.detectChanges();
+
+      const input = 
fixture.debugElement.query(By.css("input.unit-name-edit-input"));
+      input.nativeElement.value = "  renamed  ";
+      input.nativeElement.dispatchEvent(new KeyboardEvent("keydown", { key: 
"Enter", bubbles: true }));
+
+      
expect(computingUnitService.renameComputingUnit).toHaveBeenCalledExactlyOnceWith(1,
 "renamed");
+    });
+
+    it("a click inside the rename input does not bubble to the row", () => {
+      component.editingNameOfUnit = 1;
+      fixture.detectChanges();
+      const stopPropagation = vi.fn();
+
+      fixture.debugElement
+        .query(By.css("input.unit-name-edit-input"))
+        .triggerEventHandler("click", { stopPropagation } as unknown as 
MouseEvent);
+
+      expect(stopPropagation).toHaveBeenCalledTimes(1);
+    });
+
+    it("the delete button emits the deleted output", () => {
+      const deletedSpy = vi.fn();
+      component.deleted.subscribe(deletedSpy);
+      const deleteButton = fixture.debugElement.query(By.css(".button-group 
button[title='Delete']"));
+      expect(deleteButton).toBeTruthy();
+
+      deleteButton.triggerEventHandler("click", null);
+
+      expect(deletedSpy).toHaveBeenCalledTimes(1);
+    });
+
+    it("omits the share button while sharing is disabled", () => {
+      expect(fixture.debugElement.query(By.css("button[aria-label='Share 
computing unit']"))).toBeNull();
+    });
+
+    it("the share button opens the share-access modal when sharing is 
enabled", () => {
+      // same DI instance the component holds
+      TestBed.inject(GuiConfigService).env.sharingComputingUnitEnabled = true;
+      fixture.detectChanges();
+
+      const shareButton = 
fixture.debugElement.query(By.css("button[aria-label='Share computing unit']"));
+      expect(shareButton).toBeTruthy();
+      shareButton.triggerEventHandler("click", clickEvent());
+
+      expect(actionsService.openShareAccessModal).toHaveBeenCalledWith(1, 
false);
+    });
+
+    it("the metrics popover renders the CPU and RAM rows", () => {
+      const { popover, text } = openMetricsPopover();
+
+      expect(text).toContain("CPU");
+      expect(text).toContain("RAM");
+      // the default entry has no GPU / JVM / shared-memory limits
+      expect(text).not.toContain("GPU(s)");
+      expect(text).not.toContain("JVM Memory Size");
+      expect(text).not.toContain("Shared Memory Size");
+
+      popover.hide();
+      fixture.detectChanges();
+    });
+
+    it("the metrics popover adds the GPU, JVM and shared-memory rows when 
those limits are set", () => {
+      const base = makeEntry();
+      component.entry = makeEntry({
+        computingUnit: {
+          ...base.computingUnit,
+          resource: { ...base.computingUnit.resource, gpuLimit: "2", 
jvmMemorySize: "2Gi", shmSize: "1Gi" },
+        },
+      });
+      fixture.detectChanges();
+
+      const { popover, text } = openMetricsPopover();
+
+      expect(text).toContain("2 GPU(s)");
+      expect(text).toContain("JVM Memory Size");
+      expect(text).toContain("Shared Memory Size");
+
+      popover.hide();
+      fixture.detectChanges();
+    });
+  });
 });

Reply via email to