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

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


The following commit(s) were added to refs/heads/main by this push:
     new 8633188757 test(frontend): rewrite sync-texera-model spec (#5194)
8633188757 is described below

commit 8633188757e255f29cf43a9122aaef4dc7b8085a
Author: Yicong Huang <[email protected]>
AuthorDate: Sun May 24 16:47:42 2026 -0700

    test(frontend): rewrite sync-texera-model spec (#5194)
    
    ### What changes were proposed in this PR?
    `sync-texera-model.spec.ts` was carrying ~344 lines of dead test code:
    four `it()` blocks commented out against a `SyncTexeraModel` constructor
    that took an `OperatorGroup` third argument. `OperatorGroup` was removed
    from the codebase long ago, the constructor is now two-arg, and
    `SyncTexeraModel` no longer handles operator-delete events at all — so
    each commented test depended on a class that doesn't exist and exercised
    behavior that's been moved elsewhere. One nearby live test claimed to
    assert the operator-delete error path but never instantiated the SUT and
    made no assertions; it was vacuous.
    
    Drop the four commented blocks, their preceding doc blocks, the vacuous
    live test, and the now-orphaned `getJointOperatorValue` helper. Add a
    `describe("getOperatorLink", …)` that hits the static helper's two
    `throw` branches directly — the stream-handler tests can't reach them
    because `isValidJointLink` filters those cases out upstream.
    
    ### Any related issues, documentation, discussions?
    
    Closes #5193.
    
    ### How was this PR tested?
    
    `yarn ng test --watch=false --include=…` runs 9 tests, all green. `yarn
    lint` and `yarn format:ci` both clean. v8 line coverage of
    `sync-texera-model.ts` rises from 94 % (31/33) to 100 % (33/33); the
    spec itself shrinks 747 → 449 lines.
    
    ### Was this PR authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Opus 4.7
---
 .../workflow-graph/model/sync-texera-model.spec.ts | 394 +++------------------
 1 file changed, 50 insertions(+), 344 deletions(-)

diff --git 
a/frontend/src/app/workspace/service/workflow-graph/model/sync-texera-model.spec.ts
 
b/frontend/src/app/workspace/service/workflow-graph/model/sync-texera-model.spec.ts
index 09bcd62943..5bb54e856d 100644
--- 
a/frontend/src/app/workspace/service/workflow-graph/model/sync-texera-model.spec.ts
+++ 
b/frontend/src/app/workspace/service/workflow-graph/model/sync-texera-model.spec.ts
@@ -43,44 +43,18 @@ describe("SyncTexeraModel", () => {
   let jointGraph: joint.dia.Graph;
   let jointGraphWrapper: JointGraphWrapper;
 
-  /**
-   * Returns a mock JointJS operator Element object (joint.dia.Element)
-   * The implementation code only uses the id attribute of the object.
-   *
-   * @param operatorID
-   */
-  function getJointOperatorValue(operatorID: string): joint.dia.Element {
-    return {
-      id: operatorID,
-    } as joint.dia.Element;
-  }
-
   /**
    * Returns a mock JointJS Link object (joint.dia.Link)
    * It includes the attributes and functions same as JointJS
-   *  and are used by the implementation code.
    * @param link
    */
   function getJointLinkValue(link: OperatorLink): joint.dia.Link {
-    // getSourceElement, getTargetElement, and get all returns a function
-    //  that returns the corresponding value
     return {
       id: link.linkID,
       attributes: {
         source: { id: link.source.operatorID, port: link.source.portID },
         target: { id: link.target.operatorID, port: link.target.portID },
       },
-      // getSourceElement: () => ({ id: link.source.operatorID }),
-      // getTargetElement: () => ({ id: link.target.operatorID }),
-      // get: (port: string) => {
-      //   if (port === 'source') {
-      //     return { port: link.source.portID };
-      //   } else if (port === 'target') {
-      //     return { port: link.target.portID };
-      //   } else {
-      //     throw new Error('getJointLinkValue: mock is inconsistent with 
implementation');
-      //   }
-      // }
     } as any as joint.dia.Link;
   }
 
@@ -94,8 +68,6 @@ describe("SyncTexeraModel", () => {
    * @param link an operator link, but the target operator and target link is 
ignored
    */
   function getIncompleteJointLink(link: OperatorLink): joint.dia.Link {
-    // getSourceElement, getTargetElement, and get all returns a function
-    //  that returns the corresponding value
     return {
       id: link.linkID,
       getSourceElement: () => ({ id: link.source.operatorID }),
@@ -131,158 +103,6 @@ describe("SyncTexeraModel", () => {
     jointGraphWrapper = new JointGraphWrapper(jointGraph);
   });
 
-  // The delete event will not happen from JointJS.
-  // /**
-  //  * Test JointJS delete operator `getJointElementCellDeleteStream` event 
stream handled properly
-  //  *
-  //  * Add one operator
-  //  * Then emit one delete operator event from JointJS
-  //  *
-  //  * addOperator
-  //  * jointDeleteOperator: ---d-|
-  //  *
-  //  * Expected:
-  //  * The workflow graph should not have the added operator
-  //  * The workflow graph should have 0 operators
-  //  */
-  // it(
-  //   "should delete an operator when the delete operator event happens from 
JointJS",
-  //   marbles(m => {
-  //     // add operators
-  //     texeraGraph.addOperator(mockScanPredicate);
-  //
-  //     // prepare delete operator event stream
-  //     const deleteOpMarbleString = "---d-|";
-  //     const deleteOpMarbleValues = {
-  //       d: getJointOperatorValue(mockScanPredicate.operatorID),
-  //     };
-  //     vi.spyOn(jointGraphWrapper, 
"getJointElementCellDeleteStream").mockReturnValue(
-  //       m.hot(deleteOpMarbleString, deleteOpMarbleValues)
-  //     );
-  //
-  //     // construct the texera sync model with spied dependencies
-  //     const syncTexeraModel = new SyncTexeraModel(
-  //       texeraGraph,
-  //       jointGraphWrapper,
-  //       new OperatorGroup(
-  //         texeraGraph,
-  //         jointGraph,
-  //         jointGraphWrapper,
-  //         TestBed.inject(WorkflowUtilService),
-  //         TestBed.inject(JointUIService)
-  //       )
-  //     );
-  //
-  //     // assert workflow graph
-  //     jointGraphWrapper.getJointElementCellDeleteStream().subscribe({
-  //       complete: () => {
-  //         
expect(texeraGraph.hasOperator(mockScanPredicate.operatorID)).toBeFalsy();
-  //         expect(texeraGraph.getAllOperators().length).toEqual(0);
-  //       },
-  //     });
-  //   })
-  // );
-
-  // The delete event will not happen from JointJS.
-  // /**
-  //  * Test JointJS delete operator `getJointElementCellDeleteStream` event 
stream handled properly
-  //  *
-  //  * Add two operators
-  //  * Then emit one delete operator event from JointJS
-  //  *
-  //  * addOperator
-  //  * jointDeleteOperator: -----d-|
-  //  *
-  //  * Expected:
-  //  * Only the deleted operator should be removed.
-  //  * The graph should have 1 operators and 0 links.
-  //  */
-  // it(
-  //   "should delete an operator and not touch other operators when the 
delete operator event happens from JointJS",
-  //   marbles(m => {
-  //     // add operators
-  //     texeraGraph.addOperator(mockScanPredicate);
-  //     texeraGraph.addOperator(mockResultPredicate);
-  //
-  //     // prepare delete operator
-  //     const deleteOpMarbleString = "-----d-|";
-  //     const deleteOpMarbleValues = {
-  //       d: getJointOperatorValue(mockScanPredicate.operatorID),
-  //     };
-  //     vi.spyOn(jointGraphWrapper, 
"getJointElementCellDeleteStream").mockReturnValue(
-  //       m.hot(deleteOpMarbleString, deleteOpMarbleValues)
-  //     );
-  //
-  //     // construct the texera sync model with spied dependencies
-  //     // construct the texera sync model with spied dependencies
-  //     const syncTexeraModel = new SyncTexeraModel(
-  //       texeraGraph,
-  //       jointGraphWrapper,
-  //       new OperatorGroup(
-  //         texeraGraph,
-  //         jointGraph,
-  //         jointGraphWrapper,
-  //         TestBed.inject(WorkflowUtilService),
-  //         TestBed.inject(JointUIService)
-  //       )
-  //     );
-  //
-  //     jointGraphWrapper.getJointElementCellDeleteStream().subscribe({
-  //       complete: () => {
-  //         
expect(texeraGraph.hasOperator(mockScanPredicate.operatorID)).toBeFalsy();
-  //         
expect(texeraGraph.hasOperator(mockResultPredicate.operatorID)).toBeTruthy();
-  //         expect(texeraGraph.getAllOperators().length).toEqual(1);
-  //         expect(texeraGraph.getAllLinks().length).toEqual(0);
-  //       },
-  //     });
-  //   })
-  // );
-
-  /**
-   * Test JointJS delete operator `getJointElementCellDeleteStream` event 
stream handled properly
-   *
-   * Add two operators
-   * Delete on operator
-   *
-   * Then if the SyncTexeraModel Service should explicitly throw and error
-   *  if the JointModelService emits an operator delete event on the nonexist 
operator (should not happen),
-   *  then TexeraSyncService should explicitly throw an error (this case 
should not happen).
-   *
-   * Expected:
-   * delete an nonexit operator, error is thrown
-   */
-  it(
-    "should explicitly throw an error if the JointJS operator delete event 
deletes a nonexist operator",
-    marbles(m => {
-      // add operators
-      texeraGraph.addOperator(mockScanPredicate);
-      texeraGraph.addOperator(mockResultPredicate);
-
-      texeraGraph.deleteOperator(mockScanPredicate.operatorID);
-
-      // prepare delete operator
-      const deleteOpMarbleString = "-----d-|";
-      const deleteOpMarbleValues = {
-        d: getJointOperatorValue(mockScanPredicate.operatorID),
-      };
-      // mock delete the operator operation at the same time frame of jointJS 
deleting it
-      //  but executed before the handler
-      vi.spyOn(jointGraphWrapper, 
"getJointElementCellDeleteStream").mockReturnValue(
-        m.hot(deleteOpMarbleString, deleteOpMarbleValues)
-      );
-
-      // construct the texera sync model with spied dependencies
-
-      // TODO: expect error to be thrown
-      // const syncTexeraModel = new SyncTexeraModel(texeraGraph, 
jointGraphWrapper,
-      // new OperatorGroup(texeraGraph, jointGraph, jointGraphWrapper, 
TestBed.inject(WorkflowUtilService),
-      // TestBed.inject(JointUIService)));
-
-      // this should throw an error when the model is constructed and the
-      //  delete is called for second time on the same operator by the delete 
stream
-    })
-  );
-
   /**
    * Test JointJS add link `getJointLinkCellAddStream` event stream handled 
properly
    *
@@ -311,7 +131,6 @@ describe("SyncTexeraModel", () => {
         m.hot(addLinkMarbleString, addLinkMarbleValues)
       );
 
-      // construct the texera sync model with spied dependencies
       // construct the texera sync model with spied dependencies
       const syncTexeraModel = new SyncTexeraModel(texeraGraph, 
jointGraphWrapper);
 
@@ -580,168 +399,55 @@ describe("SyncTexeraModel", () => {
   );
 
   /**
-   * Test JointJS link change `getJointLinkCellChangeStream` event stream 
handled properly,
-   *  when the link change involves logical link delete,
-   *  and a later change event involves a logical link add.
-   *
-   * Add three operators (1, 2, 3) and link 1 -> 3
-   * Then the user *gradually* drags the target port from operator 3's input 
port
-   *  to operator 2's input port. (1 -> 3) changed to (1 -> 2)
-   * The link is detached, then move around the paper for a while, then 
re-attached to another port
-   *
-   * changeLink:  ---------q-r-s-t-| (q: link detached with target being a 
point, r: target moved to another point,
-   *    s: target moved to another point, t: target re-attached to another 
port)
-   *
-   * Expected:
-   * the link should be changed to the new target.
-   *
-   * TODO: finish change link test stream to compare to streams
-   * TODO: This test's functionality is okay but content needs to be changed 
with the introduction of shared editing.
-   * TODO: because SyncJointModel is needed.
+   * `SyncTexeraModel.getOperatorLink` is the static converter the link-event
+   * handlers route every valid joint link through. Its two `throw` branches
+   * (missing source / target) are unreachable from the stream-handler tests
+   * because `isValidJointLink` filters those cases out beforehand, so they
+   * need direct invocation to be covered.
    */
-  // it(
-  //   "should remove then add link if link target port is detached then 
dragged around then re-attached",
-  //   marbles(m => {
-  //     // add operators
-  //     texeraGraph.addOperator(mockScanPredicate);
-  //     texeraGraph.addOperator(mockSentimentPredicate);
-  //     texeraGraph.addOperator(mockResultPredicate);
-  //
-  //     // add links
-  //     texeraGraph.addLink(mockScanResultLink);
-  //
-  //     // create a mock changed link using another link's source/target
-  //     // but the link ID remains the same
-  //     const mockChangedLink = {
-  //       ...mockScanSentimentLink,
-  //       linkID: mockScanResultLink.linkID,
-  //     };
-  //
-  //     // prepare change link (link detached from target port)
-  //     const changeLinkMarbleString = "---------q-r-s-t-|";
-  //     const changeLinkMarbleValues = {
-  //       q: getIncompleteJointLink(mockScanResultLink),
-  //       r: getIncompleteJointLink(mockScanResultLink),
-  //       s: getIncompleteJointLink(mockScanResultLink),
-  //       t: getJointLinkValue(mockChangedLink),
-  //     };
-  //     vi.spyOn(jointGraphWrapper, 
"getJointLinkCellChangeStream").mockReturnValue(
-  //       m.hot(changeLinkMarbleString, changeLinkMarbleValues)
-  //     );
-  //
-  //     // construct the texera sync model with spied dependencies
-  //     const syncTexeraModel = new SyncTexeraModel(
-  //       texeraGraph,
-  //       jointGraphWrapper,
-  //       new OperatorGroup(
-  //         texeraGraph,
-  //         jointGraph,
-  //         jointGraphWrapper,
-  //         TestBed.inject(WorkflowUtilService),
-  //         TestBed.inject(JointUIService)
-  //       )
-  //     );
-  //
-  //     jointGraphWrapper.getJointLinkCellChangeStream().subscribe({
-  //       complete: () => {
-  //         expect(texeraGraph.getAllLinks().length).toEqual(1);
-  //         expect(texeraGraph.hasLink(mockChangedLink.source, 
mockChangedLink.target)).toBeTruthy();
-  //       },
-  //     });
-  //
-  //     // assert link delete stream: delete original link
-  //     const linkDeleteStream = texeraGraph.getLinkDeleteStream();
-  //     const expectedDeleteStream = m.hot("---------q---", {
-  //       q: { deletedLink: mockScanResultLink },
-  //     });
-  //     m.expect(linkDeleteStream).toBeObservable(expectedDeleteStream);
-  //
-  //     // assert link add stream: changed link after its re-attached 
(original link is added synchronously in the begining)
-  //     const linkAddStream = texeraGraph.getLinkAddStream();
-  //     const expectedAddStream = m.hot("---------------t-", {
-  //       t: mockChangedLink,
-  //     });
-  //     m.expect(linkAddStream).toBeObservable(expectedAddStream);
-  //   })
-  // );
-
-  // The delete event will not happen from JointJS.
-  // /**
-  //  * Test JointJS delete operator `getJointElementCellDeleteStream` event 
stream handled properly,
-  //  *  when the operator delete causes its connected links being deleted as 
well
-  //  *
-  //  * Add three operators
-  //  * Then add a link from operator 1 to operator 2 and a link from operator 
2 to operator 3
-  //  *
-  //  * addOperators + addLinks: 1 -> 2 -> 3
-  //  * jointDeleteOperator:  ---------d-| (delete operator 2)
-  //  * jointDeleteLink:      ---------(gh)-| (mock event triggered 
automatically at the same time frame by jointJS)
-  //  *
-  //  * Expected:
-  //  * There will be 2 operators left
-  //  * There will be no links left
-  //  * Texera Operator Delete stream should emit event when the operator is 
deleted
-  //  * Texera Link Delete Stream should emit event twice when the operator is 
deleted
-  //  *
-  //  */
-  // it(
-  //   "should remove an operator and its connected links when that operator 
is deleted from jointJS",
-  //   marbles(m => {
-  //     // add operators
-  //     texeraGraph.addOperator(mockScanPredicate);
-  //     texeraGraph.addOperator(mockSentimentPredicate);
-  //     texeraGraph.addOperator(mockResultPredicate);
-  //
-  //     // add links
-  //     texeraGraph.addLink(mockScanSentimentLink);
-  //     texeraGraph.addLink(mockSentimentResultLink);
-  //
-  //     // prepare the delete oprator event
-  //     const deleteOperatorString = "---------d-|";
-  //     const deleteOperatorValue = {
-  //       d: getJointOperatorValue(mockSentimentPredicate.operatorID),
-  //     };
-  //     vi.spyOn(jointGraphWrapper, 
"getJointElementCellDeleteStream").mockReturnValue(
-  //       m.hot(deleteOperatorString, deleteOperatorValue)
-  //     );
-  //
-  //     /**
-  //      * once the operator is deleted, JointJS will automatically delete 
connected links
-  //      *  and will trigger delete link events at the same timeframe
-  //      */
-  //     const deleteLinkString = "---------(gh)-|";
-  //     const deleteLinkValue = {
-  //       g: getJointLinkValue(mockScanSentimentLink),
-  //       h: getJointLinkValue(mockSentimentResultLink),
-  //     };
-  //
-  //     vi.spyOn(jointGraphWrapper, 
"getJointLinkCellDeleteStream").mockReturnValue(
-  //       m.hot(deleteLinkString, deleteLinkValue)
-  //     );
-  //
-  //     // construct texera model
-  //     const syncTexeraModel = new SyncTexeraModel(
-  //       texeraGraph,
-  //       jointGraphWrapper,
-  //       new OperatorGroup(
-  //         texeraGraph,
-  //         jointGraph,
-  //         jointGraphWrapper,
-  //         TestBed.inject(WorkflowUtilService),
-  //         TestBed.inject(JointUIService)
-  //       )
-  //     );
-  //     jointGraphWrapper.getJointElementCellDeleteStream().subscribe({
-  //       complete: () => {
-  //         
expect(texeraGraph.hasOperator(mockSentimentPredicate.operatorID)).toBeFalsy();
-  //         
expect(texeraGraph.hasOperator(mockScanPredicate.operatorID)).toBeTruthy();
-  //         
expect(texeraGraph.hasOperator(mockResultPredicate.operatorID)).toBeTruthy();
-  //         expect(texeraGraph.getAllOperators().length).toEqual(2);
-  //         
expect(texeraGraph.hasLinkWithID(mockScanSentimentLink.linkID)).toBeFalsy();
-  //         
expect(texeraGraph.hasLinkWithID(mockSentimentResultLink.linkID)).toBeFalsy();
-  //         expect(texeraGraph.getAllLinks().length).toEqual(0);
-  //       },
-  //     });
-  //   })
-  // );
+  describe("getOperatorLink", () => {
+    it("transforms a fully connected joint link into the matching 
OperatorLink", () => {
+      const jointLink = getJointLinkValue(mockScanResultLink);
+
+      expect(SyncTexeraModel.getOperatorLink(jointLink)).toEqual({
+        linkID: mockScanResultLink.linkID,
+        source: {
+          operatorID: mockScanResultLink.source.operatorID,
+          portID: mockScanResultLink.source.portID,
+        },
+        target: {
+          operatorID: mockScanResultLink.target.operatorID,
+          portID: mockScanResultLink.target.portID,
+        },
+      });
+    });
+
+    it("throws when the joint link has no source attribute", () => {
+      const linkWithNoSource = {
+        id: "no-source-link",
+        attributes: {
+          source: null,
+          target: { id: "op-target", port: "input-0" },
+        },
+      } as unknown as joint.dia.Link;
+
+      expect(() => SyncTexeraModel.getOperatorLink(linkWithNoSource)).toThrow(
+        "Invalid JointJS Link: no source element"
+      );
+    });
+
+    it("throws when the joint link has no target attribute", () => {
+      const linkWithNoTarget = {
+        id: "no-target-link",
+        attributes: {
+          source: { id: "op-source", port: "output-0" },
+          target: null,
+        },
+      } as unknown as joint.dia.Link;
+
+      expect(() => SyncTexeraModel.getOperatorLink(linkWithNoTarget)).toThrow(
+        "Invalid JointJS Link: no target element"
+      );
+    });
+  });
 });

Reply via email to