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


##########
frontend/src/app/workspace/component/property-editor/typecasting-display/type-casting-display.component.spec.ts:
##########
@@ -61,4 +65,195 @@ describe("TypecastingDisplayComponent", () => {
   it("should create", () => {
     expect(component).toBeTruthy();
   });
+
+  afterEach(() => {
+    fixture.destroy();
+    vi.restoreAllMocks();
+  });
+
+  // Build an OperatorPredicate; when typeCastingUnits is supplied it is 
placed under
+  // operatorProperties exactly as the TypeCasting operator descriptor stores 
it.
+  const makeOperator = (
+    operatorID: string,
+    operatorType: string,
+    typeCastingUnits?: ReadonlyArray<{ attribute: string; resultType: 
AttributeType }>
+  ): OperatorPredicate => ({
+    operatorID,
+    operatorType,
+    operatorVersion: "v1",
+    operatorProperties: typeCastingUnits ? { typeCastingUnits } : {},
+    inputPorts: [{ portID: "input-0" }],
+    outputPorts: [{ portID: "output-0" }],
+    showAdvanced: true,
+    isDisabled: false,
+  });
+
+  // The component reads/writes the same graph instance the injected 
WorkflowActionService owns.
+  const getGraph = (): WorkflowGraph => (TestBed.inject(WorkflowActionService) 
as any).texeraGraph as WorkflowGraph;

Review Comment:
   Fixed — getGraph() now uses the public getTexeraGraph() accessor (casting to 
WorkflowGraph only at the subject-push sites).



##########
frontend/src/app/workspace/component/property-editor/typecasting-display/type-casting-display.component.spec.ts:
##########
@@ -61,4 +65,195 @@ describe("TypecastingDisplayComponent", () => {
   it("should create", () => {
     expect(component).toBeTruthy();
   });
+
+  afterEach(() => {
+    fixture.destroy();
+    vi.restoreAllMocks();
+  });
+
+  // Build an OperatorPredicate; when typeCastingUnits is supplied it is 
placed under
+  // operatorProperties exactly as the TypeCasting operator descriptor stores 
it.
+  const makeOperator = (
+    operatorID: string,
+    operatorType: string,
+    typeCastingUnits?: ReadonlyArray<{ attribute: string; resultType: 
AttributeType }>
+  ): OperatorPredicate => ({
+    operatorID,
+    operatorType,
+    operatorVersion: "v1",
+    operatorProperties: typeCastingUnits ? { typeCastingUnits } : {},
+    inputPorts: [{ portID: "input-0" }],
+    outputPorts: [{ portID: "output-0" }],
+    showAdvanced: true,
+    isDisabled: false,
+  });
+
+  // The component reads/writes the same graph instance the injected 
WorkflowActionService owns.
+  const getGraph = (): WorkflowGraph => (TestBed.inject(WorkflowActionService) 
as any).texeraGraph as WorkflowGraph;
+
+  describe("ngOnChanges", () => {
+    it("hides the display and skips rerender when there is no 
currentOperatorId", () => {
+      const rerenderSpy = vi.spyOn(component, 
"rerender").mockImplementation(() => {});
+      component.currentOperatorId = undefined;
+      component.displayTypeCastingSchemaInformation = true;
+
+      component.ngOnChanges();
+
+      expect(component.displayTypeCastingSchemaInformation).toBe(false);
+      expect(rerenderSpy).not.toHaveBeenCalled();
+    });
+
+    it("hides the display and skips rerender when the operator is not a 
TypeCasting operator", () => {
+      const graph = getGraph();
+      vi.spyOn(graph, "getOperator").mockReturnValue(makeOperator("op1", 
"ScanSource"));
+      const rerenderSpy = vi.spyOn(component, 
"rerender").mockImplementation(() => {});
+      component.currentOperatorId = "op1";
+      component.displayTypeCastingSchemaInformation = true;
+
+      component.ngOnChanges();
+
+      expect(component.displayTypeCastingSchemaInformation).toBe(false);
+      expect(rerenderSpy).not.toHaveBeenCalled();
+    });
+
+    it("shows the display and rerenders when the operator is a TypeCasting 
operator", () => {
+      const graph = getGraph();
+      vi.spyOn(graph, "getOperator").mockReturnValue(
+        makeOperator("tc1", TYPE_CASTING_OPERATOR_TYPE, [{ attribute: "age", 
resultType: "string" }])
+      );
+      const compiling = TestBed.inject(WorkflowCompilingService);
+      vi.spyOn(compiling, "getOperatorInputSchemaMap").mockReturnValue({
+        "port-0": [{ attributeName: "age", attributeType: "integer" }],
+      } as OperatorPortSchemaMap);
+      component.currentOperatorId = "tc1";
+
+      component.ngOnChanges();
+
+      expect(component.displayTypeCastingSchemaInformation).toBe(true);
+      expect(component.schemaToDisplay).toEqual([{ attributeName: "age", 
attributeType: "string" }]);
+    });
+  });
+
+  describe("rerender", () => {
+    it("returns early and leaves schemaToDisplay untouched when 
currentOperatorId is undefined", () => {
+      const compiling = TestBed.inject(WorkflowCompilingService);
+      const schemaSpy = vi.spyOn(compiling, "getOperatorInputSchemaMap");
+      const sentinel = [{ attributeName: "keep", attributeType: "string" as 
AttributeType }];
+      component.currentOperatorId = undefined;
+      component.schemaToDisplay = sentinel;
+
+      component.rerender();
+
+      expect(component.schemaToDisplay).toBe(sentinel);
+      expect(schemaSpy).not.toHaveBeenCalled();
+    });
+
+    it("casts attributes listed in typeCastingUnits and passes the remaining 
attributes through", () => {
+      const graph = getGraph();
+      vi.spyOn(graph, "getOperator").mockReturnValue(
+        makeOperator("tc1", TYPE_CASTING_OPERATOR_TYPE, [{ attribute: "age", 
resultType: "string" }])
+      );
+      const compiling = TestBed.inject(WorkflowCompilingService);
+      // Two ports: port-0 carries the schema, port-1 is undefined and must be 
skipped safely.
+      vi.spyOn(compiling, "getOperatorInputSchemaMap").mockReturnValue({
+        "port-0": [
+          { attributeName: "age", attributeType: "integer" },
+          { attributeName: "name", attributeType: "string" },
+        ],
+        "port-1": undefined,
+      } as OperatorPortSchemaMap);
+      component.currentOperatorId = "tc1";
+
+      component.rerender();
+
+      expect(component.schemaToDisplay).toEqual([
+        { attributeName: "age", attributeType: "string" },
+        { attributeName: "name", attributeType: "string" },
+      ]);
+    });
+
+    it("passes every attribute through unchanged when typeCastingUnits is 
absent", () => {
+      const graph = getGraph();
+      vi.spyOn(graph, "getOperator").mockReturnValue(makeOperator("tc1", 
TYPE_CASTING_OPERATOR_TYPE));
+      const compiling = TestBed.inject(WorkflowCompilingService);
+      vi.spyOn(compiling, "getOperatorInputSchemaMap").mockReturnValue({
+        "port-0": [{ attributeName: "x", attributeType: "integer" }],
+      } as OperatorPortSchemaMap);
+      component.currentOperatorId = "tc1";
+
+      component.rerender();
+
+      expect(component.schemaToDisplay).toEqual([{ attributeName: "x", 
attributeType: "integer" }]);
+    });
+
+    it("produces an empty schemaToDisplay when there is no input schema", () 
=> {
+      const graph = getGraph();
+      vi.spyOn(graph, "getOperator").mockReturnValue(
+        makeOperator("tc1", TYPE_CASTING_OPERATOR_TYPE, [{ attribute: "age", 
resultType: "string" }])
+      );
+      const compiling = TestBed.inject(WorkflowCompilingService);
+      vi.spyOn(compiling, 
"getOperatorInputSchemaMap").mockReturnValue(undefined);
+      component.currentOperatorId = "tc1";
+      component.schemaToDisplay = [{ attributeName: "stale", attributeType: 
"string" }];
+
+      component.rerender();
+
+      expect(component.schemaToDisplay).toEqual([]);
+    });
+  });
+
+  describe("stream handlers registered in ngOnInit", () => {
+    // The operator-property-change stream is a shared multicast: the real 
ValidationWorkflowService
+    // also subscribes and calls validateOperator() on the emitted id. These 
synthetic events reference
+    // operators that were never registered in metadata, so neutralize that 
unrelated collaborator to
+    // keep the tests focused on the component's own handler (and free of 
incidental console noise).
+    beforeEach(() => {
+      vi.spyOn(TestBed.inject(ValidationWorkflowService), 
"validateOperator").mockReturnValue({ isValid: true });
+    });
+
+    it("rerenders when the matching TypeCasting operator's properties change", 
() => {
+      const graph = getGraph();
+      const rerenderSpy = vi.spyOn(component, 
"rerender").mockImplementation(() => {});
+      component.currentOperatorId = "tc1";
+
+      graph.operatorPropertyChangeSubject.next({
+        operator: makeOperator("tc1", TYPE_CASTING_OPERATOR_TYPE),
+      });
+
+      expect(rerenderSpy).toHaveBeenCalledTimes(1);
+    });
+
+    it("ignores property changes emitted for a different operator id", () => {
+      const graph = getGraph();
+      const rerenderSpy = vi.spyOn(component, 
"rerender").mockImplementation(() => {});
+      component.currentOperatorId = "tc1";
+
+      graph.operatorPropertyChangeSubject.next({
+        operator: makeOperator("other", TYPE_CASTING_OPERATOR_TYPE),
+      });
+
+      expect(rerenderSpy).not.toHaveBeenCalled();
+    });
+
+    it("ignores property changes for the matching id but a non-TypeCasting 
operator type", () => {
+      const graph = getGraph();
+      const rerenderSpy = vi.spyOn(component, 
"rerender").mockImplementation(() => {});
+      component.currentOperatorId = "tc1";
+
+      graph.operatorPropertyChangeSubject.next({
+        operator: makeOperator("tc1", "ScanSource"),
+      });
+
+      expect(rerenderSpy).not.toHaveBeenCalled();
+    });
+
+    it("rerenders when the workflow compilation state changes", () => {
+      const compiling = TestBed.inject(WorkflowCompilingService);
+      const rerenderSpy = vi.spyOn(component, 
"rerender").mockImplementation(() => {});
+
+      (compiling as 
any).compilationStateInfoChangedStream.next(CompilationState.Succeeded);
+

Review Comment:
   Fixed — the test now emits on a controlled rxjs Subject returned from a 
mocked getCompilationStateInfoChangedStream(), instead of reaching into the 
private stream field.



-- 
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