aglinxinyuan commented on code in PR #6363:
URL: https://github.com/apache/texera/pull/6363#discussion_r3565440640


##########
frontend/src/app/workspace/component/workflow-editor/workflow-editor.component.spec.ts:
##########
@@ -968,5 +971,265 @@ describe("WorkflowEditorComponent", () => {
         expect(getStroke(mockScanPredicate.operatorID)).toBe("red");
       });
     });
+
+    /**
+     * Covers the JointJS paper event handlers wired in ngAfterViewInit. Each 
test
+     * drives the real paper by triggering the callback event the handler 
subscribes
+     * to (element:delete, element:*-port, element:magnet:pointerclick, 
cell:pointerdown,
+     * cell:pointerdblclick, link:mouseenter/leave, center-event) and asserts 
the
+     * resulting graph / router / paper state. Mouse-wheel pan/zoom and 
clipboard
+     * copy/cut/paste are intentionally excluded — those need real-browser DOM.
+     */
+    describe("joint paper event handlers", () => {
+      // A predicate whose type exists in the stub metadata but with dynamic 
ports
+      // enabled, so the add/remove-port handlers' addPort calls are accepted.
+      const dynamicPortPredicate: OperatorPredicate = {
+        ...mockMultiInputOutputPredicate,
+        operatorID: "dynamic-port-op",
+        inputPorts: [{ portID: "input-0" }],
+        outputPorts: [{ portID: "output-0" }],
+        dynamicInputPorts: true,
+        dynamicOutputPorts: true,
+      };
+
+      it("deletes the operator when its element:delete button fires", () => {
+        const texeraGraph = workflowActionService.getTexeraGraph();
+        workflowActionService.addOperator(mockScanPredicate, mockPoint);
+        const view = 
component.paper.findViewByModel(mockScanPredicate.operatorID);
+
+        // The `.delete-button` fires `element:delete` (cell view, DOM event, 
x, y);
+        // fromJointPaperEvent only emits the arg array when several args are 
passed.
+        (component.paper as any).trigger("element:delete", view, new 
Event("click"), 0, 0);
+
+        
expect(texeraGraph.hasOperator(mockScanPredicate.operatorID)).toBe(false);
+      });
+
+      it("adds then removes an input port on the matching element port 
events", () => {
+        const texeraGraph = workflowActionService.getTexeraGraph();
+        const opID = dynamicPortPredicate.operatorID;
+        workflowActionService.addOperator(dynamicPortPredicate, mockPoint);
+        const view = component.paper.findViewByModel(opID);
+        expect(texeraGraph.getOperator(opID).inputPorts.length).toEqual(1);
+
+        // The port buttons fire `element:*-port` (cell view, DOM event, x, y);
+        // fromJointPaperEvent only emits the arg array when several args are 
passed.
+        (component.paper as any).trigger("element:add-input-port", view, new 
Event("click"), 0, 0);
+        expect(texeraGraph.getOperator(opID).inputPorts.length).toEqual(2);
+
+        (component.paper as any).trigger("element:remove-input-port", view, 
new Event("click"), 0, 0);
+        expect(texeraGraph.getOperator(opID).inputPorts.length).toEqual(1);
+      });
+
+      it("adds then removes an output port on the matching element port 
events", () => {
+        const texeraGraph = workflowActionService.getTexeraGraph();
+        const opID = dynamicPortPredicate.operatorID;
+        workflowActionService.addOperator(dynamicPortPredicate, mockPoint);
+        const view = component.paper.findViewByModel(opID);
+        expect(texeraGraph.getOperator(opID).outputPorts.length).toEqual(1);
+
+        // The port buttons fire `element:*-port` (cell view, DOM event, x, y);
+        // fromJointPaperEvent only emits the arg array when several args are 
passed.
+        (component.paper as any).trigger("element:add-output-port", view, new 
Event("click"), 0, 0);
+        expect(texeraGraph.getOperator(opID).outputPorts.length).toEqual(2);
+
+        (component.paper as any).trigger("element:remove-output-port", view, 
new Event("click"), 0, 0);
+        expect(texeraGraph.getOperator(opID).outputPorts.length).toEqual(1);
+      });
+
+      it("highlights the clicked port when a port magnet is clicked", () => {
+        const wrapper = workflowActionService.getJointGraphWrapper();
+        workflowActionService.addOperator(mockScanPredicate, mockPoint);
+        const view = 
component.paper.findViewByModel(mockScanPredicate.operatorID);
+        const magnet = { getAttribute: (name: string) => (name === "port" ? 
"output-0" : null) };
+
+        (component.paper as any).trigger("element:magnet:pointerclick", view, 
{ shiftKey: false }, magnet);
+
+        expect(wrapper.getCurrentHighlightedPortIDs()).toContainEqual({
+          operatorID: mockScanPredicate.operatorID,
+          portID: "output-0",
+        });
+      });
+
+      it("supports shift-click multiselect, toggle-off, and blank-area 
unhighlight", () => {
+        const wrapper = workflowActionService.getJointGraphWrapper();
+        workflowActionService.addOperatorsAndLinks(
+          [
+            { op: mockScanPredicate, pos: mockPoint },
+            { op: mockResultPredicate, pos: mockPoint },
+          ],
+          []
+        );
+        
wrapper.unhighlightOperators(...wrapper.getCurrentHighlightedOperatorIDs());
+        const viewA = 
component.paper.findViewByModel(mockScanPredicate.operatorID);
+        const viewB = 
component.paper.findViewByModel(mockResultPredicate.operatorID);
+
+        // plain click highlights only operator A
+        (component.paper as any).trigger("cell:pointerdown", viewA, { 
shiftKey: false });
+        
expect(wrapper.getCurrentHighlightedOperatorIDs()).toEqual([mockScanPredicate.operatorID]);
+
+        // shift-click adds operator B to the selection
+        (component.paper as any).trigger("cell:pointerdown", viewB, { 
shiftKey: true });
+        expect([...wrapper.getCurrentHighlightedOperatorIDs()].sort()).toEqual(
+          [mockScanPredicate.operatorID, mockResultPredicate.operatorID].sort()
+        );
+
+        // shift-clicking an already-highlighted operator toggles it off
+        (component.paper as any).trigger("cell:pointerdown", viewB, { 
shiftKey: true });
+        
expect(wrapper.getCurrentHighlightedOperatorIDs()).toEqual([mockScanPredicate.operatorID]);
+
+        // clicking the blank canvas unhighlights everything
+        (component.paper as any).trigger("blank:pointerdown");
+        expect(wrapper.getCurrentHighlightedOperatorIDs()).toEqual([]);

Review Comment:
   Good catch — the test now dispatches a document `mouseup` after the 
`blank:pointerdown`, so the paper-pan gesture ends and its `document.mousemove` 
listener doesn't leak into later tests (fba6684).



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to