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-7335-18a277c3009e71c94c12033bb9af2a9e41089684
in repository https://gitbox.apache.org/repos/asf/texera.git

commit 26aa6caa70067ffaf4493b798460878bada226b5
Author: Meng Wang <[email protected]>
AuthorDate: Thu Aug 6 17:07:12 2026 -0700

    test(frontend): cover ContextMenuComponent template menu-item click 
bindings (#7335)
    
    ### What changes were proposed in this PR?
    
    Extends `ContextMenuComponent`'s spec to cover the template's menu-item
    `(click)`
    bindings. The class file is already at 100%, but the existing tests call
    the
    handlers directly and never render/click the menu, so the template sat
    at ~54%.
    
    13 added tests render the menu (setting the state each item's `*ngIf`
    needs),
    query the `<li nz-menu-item>` by its label via `By.css`, fire
    `.triggerEventHandler("click", null)`, and assert the click reaches the
    handler
    the item declares — either the component method (`onCopy` / `onCut` /
    `onPaste` /
    `onDelete` / `onClickExportHighlightedExecutionResult`, spied) or the
    injected
    `OperatorMenuService` double (`disableHighlightedOperators`,
    `viewResultHighlightedOperators`, `reuseResultHighlightedOperator`,
    `executeUpToOperator`). Covers both variants of the toggle items
    (disable/enable,
    view/remove-view, reuse/remove-reusing) and both delete paths (operators
    vs
    links-only).
    
    This lifts `context-menu.component.html` from ~54% to **97.92%**. The
    single
    remaining line is the hardcoded-`nzDisabled` "reuse result" item's
    `(click)`:
    a disabled `nz-menu-item` genuinely swallows the click (verified —
    clicking it
    does not invoke the handler), so rather than force an unrealistic
    interaction the
    test only asserts that item renders; its handler is identical to
    "remove reusing result", which is clicked and covered.
    
    No production code was changed.
    
    ### Any related issues, documentation, discussions?
    
    Closes #7332
    
    ### How was this PR tested?
    
    Extended unit tests, run locally in `frontend/`:
    
    ```
    ng test --watch=false --include 
src/app/workspace/component/workflow-editor/context-menu/context-menu/context-menu.component.spec.ts
    # Test Files 1 passed (1) | Tests 42 passed (42)   — 3 consecutive runs, 0 
flakes
    # context-menu.component.html: ~54% -> 97.92%
    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])
---
 .../context-menu/context-menu.component.spec.ts    | 169 +++++++++++++++++++++
 1 file changed, 169 insertions(+)

diff --git 
a/frontend/src/app/workspace/component/workflow-editor/context-menu/context-menu/context-menu.component.spec.ts
 
b/frontend/src/app/workspace/component/workflow-editor/context-menu/context-menu/context-menu.component.spec.ts
index 27d740e7df..2903abc1fb 100644
--- 
a/frontend/src/app/workspace/component/workflow-editor/context-menu/context-menu/context-menu.component.spec.ts
+++ 
b/frontend/src/app/workspace/component/workflow-editor/context-menu/context-menu/context-menu.component.spec.ts
@@ -18,6 +18,7 @@
  */
 
 import { ComponentFixture, TestBed } from "@angular/core/testing";
+import { By } from "@angular/platform-browser";
 import { OperatorMetadataService } from 
"src/app/workspace/service/operator-metadata/operator-metadata.service";
 import { StubOperatorMetadataService } from 
"src/app/workspace/service/operator-metadata/stub-operator-metadata.service";
 
@@ -431,6 +432,174 @@ describe("ContextMenuComponent", () => {
       expect(component.highlightedCommentBoxIds).toEqual([]);
     });
   });
+  // ── Template menu-item (click) wiring ──
+  // The class methods are covered above; these render the menu and click each
+  // item so the template's *ngIf-gated (click) bindings are exercised too.
+  describe("menu item click bindings", () => {
+    const norm = (s: string | null) => (s ?? "").replace(/\s+/g, " 
").trim().toLowerCase();
+
+    /** Click the rendered <li nz-menu-item> whose text matches `label` 
exactly. */
+    function clickItem(label: string): void {
+      const items = fixture.debugElement.queryAll(By.css("li[nz-menu-item]"));
+      const item = items.find(li => norm(li.nativeElement.textContent) === 
norm(label));
+      if (!item) {
+        throw new Error(
+          `menu item "${label}" not rendered; present: [${items.map(i => 
norm(i.nativeElement.textContent)).join(" | ")}]`
+        );
+      }
+      item.triggerEventHandler("click", null);
+    }
+
+    it("copy invokes onCopy", () => {
+      highlightedOperatorsSubject.next(["op1"]);
+      fixture.detectChanges();
+      const spy = vi.spyOn(component, "onCopy").mockImplementation(() => {});
+
+      clickItem("copy");
+
+      expect(spy).toHaveBeenCalledTimes(1);
+    });
+
+    it("cut invokes onCut", () => {
+      highlightedOperatorsSubject.next(["op1"]);
+      component.isWorkflowModifiable = true;
+      fixture.detectChanges();
+      const spy = vi.spyOn(component, "onCut").mockImplementation(() => {});
+
+      clickItem("cut");
+
+      expect(spy).toHaveBeenCalledTimes(1);
+    });
+
+    it("paste invokes onPaste", () => {
+      highlightedOperatorsSubject.next([]);
+      highlightedCommentBoxesSubject.next([]);
+      component.isWorkflowModifiable = true;
+      fixture.detectChanges();
+      const spy = vi.spyOn(component, "onPaste").mockImplementation(() => {});
+
+      clickItem("paste");
+
+      expect(spy).toHaveBeenCalledTimes(1);
+    });
+
+    it("disable invokes operatorMenuService.disableHighlightedOperators", () 
=> {
+      operatorMenuService.isDisableOperator = true;
+      operatorMenuService.isDisableOperatorClickable = true;
+      fixture.detectChanges();
+
+      clickItem("disable");
+
+      
expect(operatorMenuService.disableHighlightedOperators).toHaveBeenCalledTimes(1);
+    });
+
+    it("enable invokes operatorMenuService.disableHighlightedOperators", () => 
{
+      operatorMenuService.isDisableOperator = false;
+      operatorMenuService.isDisableOperatorClickable = true;
+      fixture.detectChanges();
+
+      clickItem("enable");
+
+      
expect(operatorMenuService.disableHighlightedOperators).toHaveBeenCalledTimes(1);
+    });
+
+    it("view result invokes 
operatorMenuService.viewResultHighlightedOperators", () => {
+      operatorMenuService.isToViewResult = true;
+      operatorMenuService.isToViewResultClickable = true;
+      fixture.detectChanges();
+
+      clickItem("view result");
+
+      
expect(operatorMenuService.viewResultHighlightedOperators).toHaveBeenCalledTimes(1);
+    });
+
+    it("remove view result invokes 
operatorMenuService.viewResultHighlightedOperators", () => {
+      operatorMenuService.isToViewResult = false;
+      operatorMenuService.isToViewResultClickable = true;
+      fixture.detectChanges();
+
+      clickItem("remove view result");
+
+      
expect(operatorMenuService.viewResultHighlightedOperators).toHaveBeenCalledTimes(1);
+    });
+
+    it("renders the reuse result item (disabled) when marked for reuse", () => 
{
+      // This entry is hardcoded `nzDisabled`, so it can't be clicked; assert 
it renders.
+      // Its (click) handler is the same as "remove reusing result", covered 
by the next test.
+      operatorMenuService.isMarkForReuse = true;
+      operatorMenuService.isReuseResultClickable = true;
+      fixture.detectChanges();
+
+      const rendered = fixture.debugElement
+        .queryAll(By.css("li[nz-menu-item]"))
+        .some(li => norm(li.nativeElement.textContent) === "reuse result");
+      expect(rendered).toBe(true);
+    });
+
+    it("remove reusing result invokes 
operatorMenuService.reuseResultHighlightedOperator", () => {
+      operatorMenuService.isMarkForReuse = false;
+      operatorMenuService.isReuseResultClickable = true;
+      fixture.detectChanges();
+
+      clickItem("remove reusing result");
+
+      
expect(operatorMenuService.reuseResultHighlightedOperator).toHaveBeenCalledTimes(1);
+    });
+
+    it("delete invokes onDelete when operators are highlighted", () => {
+      highlightedOperatorsSubject.next(["op1"]);
+      component.isWorkflowModifiable = true;
+      fixture.detectChanges();
+      const spy = vi.spyOn(component, "onDelete").mockImplementation(() => {});
+
+      clickItem("delete");
+
+      expect(spy).toHaveBeenCalledTimes(1);
+    });
+
+    it("delete invokes onDelete for a links-only selection", () => {
+      highlightedOperatorsSubject.next([]);
+      highlightedCommentBoxesSubject.next([]);
+      
jointGraphWrapperSpy.getCurrentHighlightedLinkIDs.mockReturnValue(["link1"]);
+      component.isWorkflowModifiable = true;
+      fixture.detectChanges();
+      const spy = vi.spyOn(component, "onDelete").mockImplementation(() => {});
+
+      clickItem("delete");
+
+      expect(spy).toHaveBeenCalledTimes(1);
+    });
+
+    it("execute to this operator invokes executeUpToOperator", () => {
+      highlightedOperatorsSubject.next(["op1"]);
+      component.isWorkflowModifiable = true;
+      
jointGraphWrapperSpy.getCurrentHighlightedOperatorIDs.mockReturnValue(["op1"]);
+      validationWorkflowService.validateOperator.mockReturnValue({ isValid: 
true });
+      (workflowActionService.getTexeraGraph() as unknown as 
Mocked<WorkflowGraph>).isOperatorDisabled.mockReturnValue(
+        false
+      );
+      fixture.detectChanges();
+      expect(component.canExecuteOperator()).toBe(true); // item is enabled
+
+      clickItem("execute to this operator");
+
+      expect(operatorMenuService.executeUpToOperator).toHaveBeenCalledTimes(1);
+    });
+
+    it("Export result invokes onClickExportHighlightedExecutionResult", () => {
+      (
+        workflowResultExportService as unknown as { 
hasResultToExportOnHighlightedOperators: boolean }
+      ).hasResultToExportOnHighlightedOperators = true;
+      (component as unknown as { config: { env: Record<string, unknown> } 
}).config.env.exportExecutionResultEnabled =
+        true;
+      fixture.detectChanges();
+      const spy = vi.spyOn(component, 
"onClickExportHighlightedExecutionResult").mockImplementation(() => {});
+
+      clickItem("export result");
+
+      expect(spy).toHaveBeenCalledTimes(1);
+    });
+  });
 });
 
 describe("ContextMenuComponent onDelete with real WorkflowActionService", () 
=> {

Reply via email to