Copilot commented on code in PR #6741:
URL: https://github.com/apache/texera/pull/6741#discussion_r3627053124
##########
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:
`getGraph()` reaches into `WorkflowActionService`'s private `texeraGraph`
field via `as any`. This makes the test brittle against refactors (e.g.,
renaming the private field) even though there is a public `getTexeraGraph()`
API. Prefer using the public accessor and casting to `WorkflowGraph` only where
you need to push to subjects.
##########
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:
This test triggers compilation-state updates by calling `(compiling as
any).compilationStateInfoChangedStream.next(...)`, which relies on a private
field name and bypasses the service’s public API. To keep the test robust, mock
`getCompilationStateInfoChangedStream()` to return a Subject/ReplaySubject you
control and emit on that instead (add a `Subject` import from `rxjs`).
--
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]