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

commit 03038c798261186501bdbba4acb5cce28c31b541
Author: Xinyuan Lin <[email protected]>
AuthorDate: Wed Jul 22 12:32:14 2026 -0700

    test(frontend): extend TypeCastingDisplayComponent unit test coverage 
(#6741)
    
    ### What changes were proposed in this PR?
    
    Extends `type-casting-display.component.spec.ts` (was a ~31%-covered
    stub) covering input/change handling, the type-casting display
    computation, and guard branches. No existing tests modified.
    
    ### Any related issues, documentation, discussions?
    
    Closes #6734.
    
    ### How was this PR tested?
    
    `ng test --include='**/type-casting-display.component.spec.ts'` -> 12/12
    passing. `yarn format:ci` passes.
    
    ### Was this PR authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Code (Opus 4.8 [1M context])
---
 .../type-casting-display.component.spec.ts         | 206 ++++++++++++++++++++-
 1 file changed, 205 insertions(+), 1 deletion(-)

diff --git 
a/frontend/src/app/workspace/component/property-editor/typecasting-display/type-casting-display.component.spec.ts
 
b/frontend/src/app/workspace/component/property-editor/typecasting-display/type-casting-display.component.spec.ts
index 316900dca1..d8680311db 100644
--- 
a/frontend/src/app/workspace/component/property-editor/typecasting-display/type-casting-display.component.spec.ts
+++ 
b/frontend/src/app/workspace/component/property-editor/typecasting-display/type-casting-display.component.spec.ts
@@ -19,9 +19,10 @@
 
 import { ComponentFixture, TestBed } from "@angular/core/testing";
 import { HttpClientTestingModule } from "@angular/common/http/testing";
+import { Subject } from "rxjs";
 import { WorkflowCompilingService } from 
"../../../service/compile-workflow/workflow-compiling.service";
 
-import { TypeCastingDisplayComponent } from "./type-casting-display.component";
+import { TypeCastingDisplayComponent, TYPE_CASTING_OPERATOR_TYPE } from 
"./type-casting-display.component";
 import { WorkflowActionService } from 
"../../../service/workflow-graph/model/workflow-action.service";
 import { OperatorMetadataService } from 
"../../../service/operator-metadata/operator-metadata.service";
 import { StubOperatorMetadataService } from 
"../../../service/operator-metadata/stub-operator-metadata.service";
@@ -29,10 +30,15 @@ import { JointUIService } from 
"../../../service/joint-ui/joint-ui.service";
 import { UndoRedoService } from "../../../service/undo-redo/undo-redo.service";
 import { WorkflowUtilService } from 
"../../../service/workflow-graph/util/workflow-util.service";
 import { commonTestProviders } from "../../../../common/testing/test-utils";
+import { OperatorPredicate } from "../../../types/workflow-common.interface";
+import { WorkflowGraph, WorkflowGraphReadonly } from 
"../../../service/workflow-graph/model/workflow-graph";
+import { AttributeType, CompilationState, OperatorPortSchemaMap } from 
"../../../types/workflow-compiling.interface";
+import { ValidationWorkflowService } from 
"../../../service/validation/validation-workflow.service";
 
 describe("TypecastingDisplayComponent", () => {
   let component: TypeCastingDisplayComponent;
   let fixture: ComponentFixture<TypeCastingDisplayComponent>;
+  let compilationStateInfoChangedStream: Subject<CompilationState>;
 
   beforeEach(async () => {
     await TestBed.configureTestingModule({
@@ -53,6 +59,14 @@ describe("TypecastingDisplayComponent", () => {
   });
 
   beforeEach(() => {
+    // Control the compilation-state stream the component subscribes to in 
ngOnInit, so tests can
+    // drive it without touching WorkflowCompilingService's private field. 
Must be wired before
+    // detectChanges() triggers ngOnInit.
+    compilationStateInfoChangedStream = new Subject<CompilationState>();
+    vi.spyOn(TestBed.inject(WorkflowCompilingService), 
"getCompilationStateInfoChangedStream").mockReturnValue(
+      compilationStateInfoChangedStream
+    );
+
     fixture = TestBed.createComponent(TypeCastingDisplayComponent);
     component = fixture.componentInstance;
     fixture.detectChanges();
@@ -61,4 +75,194 @@ 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 = (): WorkflowGraphReadonly => 
TestBed.inject(WorkflowActionService).getTexeraGraph();
+
+  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 as WorkflowGraph).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 as WorkflowGraph).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 as WorkflowGraph).operatorPropertyChangeSubject.next({
+        operator: makeOperator("tc1", "ScanSource"),
+      });
+
+      expect(rerenderSpy).not.toHaveBeenCalled();
+    });
+
+    it("rerenders when the workflow compilation state changes", () => {
+      const rerenderSpy = vi.spyOn(component, 
"rerender").mockImplementation(() => {});
+
+      compilationStateInfoChangedStream.next(CompilationState.Succeeded);
+
+      expect(rerenderSpy).toHaveBeenCalled();
+    });
+  });
 });

Reply via email to