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-6832-362e6baca217b29d6df411d669e029ab5bc1af7d
in repository https://gitbox.apache.org/repos/asf/texera.git

commit d2ab9668a759d732e41144966a6f85c12ec91630
Author: Meng Wang <[email protected]>
AuthorDate: Thu Jul 23 16:21:15 2026 -0700

    test(frontend): cover OperatorPropertyEditFrameComponent modify-logic and 
subscription handlers (#6832)
    
    ### What changes were proposed in this PR?
    
    Extends the existing `OperatorPropertyEditFrameComponent` spec (codecov
    ~72%) with
    8 deterministic tests for the non-Quill methods it did not reach:
    
    - **Modify-logic gating** — `allowModifyOperatorLogic`, and
    `confirmModifyOperatorLogic`
    across its three paths: applies the change and re-locks the form;
    surfaces a thrown
    error through `NotificationService` while leaving the form editable;
    no-ops when no
      operator is selected.
    - **Subscription handlers** — the display-name handler retitles only the
    current
    operator; the workflow-modification handler mirrors the flag onto the
    form's
    interactivity and stays out of the way when no operator is selected; the
      dynamic-schema handler re-renders only for the current operator.
    - **`rerenderEditorForm`** — early return when no operator is selected.
    
    Already-covered behaviour was deliberately not duplicated: the
    form-change →
    `setOperatorProperty` path is covered by the existing "should change
    Texera graph
    property when the form is edited by the user" test, and
    `formatTaskTitle` /
    `isHuggingFaceOperator` are exercised by the HuggingFace preview tests.
    The Quill
    binding stays out of scope per the issue. No production code was
    changed.
    
    ### Any related issues, documentation, discussions?
    
    Closes #6826.
    
    ### How was this PR tested?
    
    `ng test --watch=false --include
    
src/app/workspace/component/property-editor/operator-property-edit-frame/operator-property-edit-frame.component.spec.ts`
    — 190 passed, 1 skipped, exit 0.
    `eslint` exit 0 and `prettier --check` clean on the spec.
    Failure path verified by breaking two of the new assertions and
    confirming a non-zero exit, then restoring.
    
    ### Was this PR authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Code (Opus 4.8 [1M context])
---
 .../operator-property-edit-frame.component.spec.ts | 126 +++++++++++++++++++++
 1 file changed, 126 insertions(+)

diff --git 
a/frontend/src/app/workspace/component/property-editor/operator-property-edit-frame/operator-property-edit-frame.component.spec.ts
 
b/frontend/src/app/workspace/component/property-editor/operator-property-edit-frame/operator-property-edit-frame.component.spec.ts
index e23aa7ae70..9bb72cea23 100644
--- 
a/frontend/src/app/workspace/component/property-editor/operator-property-edit-frame/operator-property-edit-frame.component.spec.ts
+++ 
b/frontend/src/app/workspace/component/property-editor/operator-property-edit-frame/operator-property-edit-frame.component.spec.ts
@@ -57,6 +57,9 @@ import { FormlyNgZorroAntdModule } from 
"@ngx-formly/ng-zorro-antd";
 import { ComputingUnitStatusService } from 
"../../../../common/service/computing-unit/computing-unit-status/computing-unit-status.service";
 import { MockComputingUnitStatusService } from 
"../../../../common/service/computing-unit/computing-unit-status/mock-computing-unit-status.service";
 import { commonTestProviders } from "../../../../common/testing/test-utils";
+import { DynamicSchemaService } from 
"../../../service/dynamic-schema/dynamic-schema.service";
+import { NotificationService } from 
"../../../../common/service/notification/notification.service";
+import { WorkflowGraph } from 
"../../../service/workflow-graph/model/workflow-graph";
 
 const { marbles } = configure({ run: false });
 
@@ -2084,4 +2087,127 @@ describe("OperatorPropertyEditFrameComponent", () => {
       
expect(realFixture.debugElement.query(By.css(".hf-task-preview-pills"))).toBeNull();
     });
   });
+
+  describe("modify-operator-logic gating", () => {
+    it("allowModifyOperatorLogic re-enables editing", () => {
+      fixture.detectChanges();
+      component.setInteractivity(false);
+
+      component.allowModifyOperatorLogic();
+
+      expect(component.interactive).toBe(true);
+    });
+
+    it("confirmModifyOperatorLogic pushes the change and locks the form 
again", () => {
+      fixture.detectChanges();
+      component.currentOperatorId = mockScanPredicate.operatorID;
+      const modifySpy = vi.spyOn(component.executeWorkflowService, 
"modifyOperatorLogic").mockImplementation(() => {});
+      component.setInteractivity(true);
+
+      component.confirmModifyOperatorLogic();
+
+      expect(modifySpy).toHaveBeenCalledWith(mockScanPredicate.operatorID);
+      expect(component.interactive).toBe(false);
+    });
+
+    it("confirmModifyOperatorLogic reports a failure and leaves the form 
editable", () => {
+      fixture.detectChanges();
+      component.currentOperatorId = mockScanPredicate.operatorID;
+      vi.spyOn(component.executeWorkflowService, 
"modifyOperatorLogic").mockImplementation(() => {
+        throw new Error("cannot modify while running");
+      });
+      const errorSpy = vi.spyOn(TestBed.inject(NotificationService), 
"error").mockImplementation(() => {});
+      component.setInteractivity(true);
+
+      component.confirmModifyOperatorLogic();
+
+      expect(errorSpy).toHaveBeenCalledWith("cannot modify while running");
+      expect(component.interactive).toBe(true);
+    });
+
+    it("confirmModifyOperatorLogic does nothing when no operator is selected", 
() => {
+      fixture.detectChanges();
+      component.currentOperatorId = undefined;
+      const modifySpy = vi.spyOn(component.executeWorkflowService, 
"modifyOperatorLogic");
+
+      component.confirmModifyOperatorLogic();
+
+      expect(modifySpy).not.toHaveBeenCalled();
+    });
+  });
+
+  describe("subscription handlers", () => {
+    it("retitles the form only when the renamed operator is the current one", 
() => {
+      fixture.detectChanges();
+      component.currentOperatorId = mockScanPredicate.operatorID;
+      component.formTitle = "original";
+      // getTexeraGraph() narrows to WorkflowGraphReadonly; the instance 
really is a
+      // WorkflowGraph, whose display-name subject is only pushed by the 
shared-model
+      // handler, so drive it directly to exercise the subscriber.
+      const graph = workflowActionService.getTexeraGraph() as unknown as 
WorkflowGraph;
+
+      graph.operatorDisplayNameChangedSubject.next({
+        operatorID: "some-other-operator",
+        newDisplayName: "ignored",
+      });
+      expect(component.formTitle).toBe("original");
+
+      graph.operatorDisplayNameChangedSubject.next({
+        operatorID: mockScanPredicate.operatorID,
+        newDisplayName: "renamed",
+      });
+      expect(component.formTitle).toBe("renamed");
+    });
+
+    it("mirrors the workflow-modification flag onto the form's interactivity", 
() => {
+      fixture.detectChanges();
+      component.currentOperatorId = mockScanPredicate.operatorID;
+
+      workflowActionService.disableWorkflowModification();
+      expect(component.interactive).toBe(false);
+
+      workflowActionService.enableWorkflowModification();
+      expect(component.interactive).toBe(true);
+    });
+
+    it("ignores the workflow-modification flag while no operator is selected", 
() => {
+      fixture.detectChanges();
+      component.currentOperatorId = undefined;
+      component.setInteractivity(true);
+
+      workflowActionService.disableWorkflowModification();
+
+      expect(component.interactive).toBe(true);
+      workflowActionService.enableWorkflowModification();
+    });
+
+    it("re-renders only when the current operator's dynamic schema changes", 
() => {
+      workflowActionService.addOperator(mockScanPredicate, mockPoint);
+      workflowActionService.addOperator(mockResultPredicate, mockPoint);
+      fixture.detectChanges();
+      component.currentOperatorId = mockScanPredicate.operatorID;
+      const dynamicSchemaService = TestBed.inject(DynamicSchemaService);
+      const rerenderSpy = vi.spyOn(component, 
"rerenderEditorForm").mockImplementation(() => {});
+      // setDynamicSchema is a no-op when the schema is unchanged, so vary it 
to force an emit.
+      const bumped = (schema: typeof mockScanSourceSchema) => ({ ...schema, 
operatorVersion: "bumped" });
+
+      dynamicSchemaService.setDynamicSchema(mockResultPredicate.operatorID, 
bumped(mockViewResultsSchema));
+      expect(rerenderSpy).not.toHaveBeenCalled();
+
+      dynamicSchemaService.setDynamicSchema(mockScanPredicate.operatorID, 
bumped(mockScanSourceSchema));
+      expect(rerenderSpy).toHaveBeenCalled();
+    });
+  });
+
+  describe("rerenderEditorForm", () => {
+    it("is a no-op when no operator is selected", () => {
+      fixture.detectChanges();
+      component.currentOperatorId = undefined;
+      component.formTitle = "untouched";
+
+      component.rerenderEditorForm();
+
+      expect(component.formTitle).toBe("untouched");
+    });
+  });
 });

Reply via email to