This is an automated email from the ASF dual-hosted git repository. github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/main/pr-6687-43bd4b01ad1796cc18ee9c7397cc006e88c6993a in repository https://gitbox.apache.org/repos/asf/texera.git
commit 368227aea6f1c6972d74ae1512dbeb6840b917a2 Author: Matthew B. <[email protected]> AuthorDate: Tue Jul 21 18:52:03 2026 -0700 test(frontend): cover mutateProperty (#6687) ### What changes were proposed in this PR? - Add unit tests for the static DynamicSchemaService.mutateProperty helper. - Cover top-level replacement with an untouched original, recursion through properties, definitions, and array items, and the no-match path. ### Any related issues, documentation, discussions? Closes: #6686 ### How was this PR tested? - Run: `cd frontend && node --max-old-space-size=8192 ./node_modules/nx/dist/bin/nx.js test gui --watch=false --include=src/app/workspace/service/dynamic-schema/dynamic-schema.service.spec.ts`, expect the suite passing. - Test-only change; no production code is modified. ### Was this PR authored or co-authored using generative AI tooling? Co-authored with Claude Opus 4.8 in compliance with ASF --------- Signed-off-by: Xinyuan Lin <[email protected]> Co-authored-by: Yicong Huang <[email protected]> Co-authored-by: Xinyuan Lin <[email protected]> Co-authored-by: Copilot Autofix powered by AI <[email protected]> --- .../dynamic-schema/dynamic-schema.service.spec.ts | 117 +++++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/frontend/src/app/workspace/service/dynamic-schema/dynamic-schema.service.spec.ts b/frontend/src/app/workspace/service/dynamic-schema/dynamic-schema.service.spec.ts index 75dc21193a..e5997027c9 100644 --- a/frontend/src/app/workspace/service/dynamic-schema/dynamic-schema.service.spec.ts +++ b/frontend/src/app/workspace/service/dynamic-schema/dynamic-schema.service.spec.ts @@ -32,6 +32,7 @@ import { OperatorPredicate } from "../../types/workflow-common.interface"; import { mockScanSourceSchema } from "../operator-metadata/mock-operator-metadata.data"; import { WorkflowUtilService } from "../workflow-graph/util/workflow-util.service"; import { commonTestProviders } from "../../../common/testing/test-utils"; +import { CustomJSONSchema7 } from "../../types/custom-json-schema.interface"; describe("DynamicSchemaService", () => { beforeEach(() => { @@ -142,3 +143,119 @@ describe("DynamicSchemaService", () => { }) ); }); + +describe("DynamicSchemaService.mutateProperty", () => { + const matchByName = (name: string) => (propertyName: string, _: CustomJSONSchema7) => propertyName === name; + const markMutated = (_: string, propertyValue: CustomJSONSchema7): CustomJSONSchema7 => ({ + ...propertyValue, + description: "mutated", + }); + + it("should replace a matched top-level property without mutating the original schema", () => { + const original = { + type: "object", + properties: { + target: { type: "string", description: "original" }, + other: { type: "number" }, + }, + } as CustomJSONSchema7; + + const result = DynamicSchemaService.mutateProperty(original, matchByName("target"), markMutated); + + // the returned schema has the matched property mutated + expect((result.properties!.target as CustomJSONSchema7).description).toEqual("mutated"); + // the non-matching property is left untouched + expect(result.properties!.other).toEqual({ type: "number" }); + // the original schema object is deep cloned and stays unchanged + expect((original.properties!.target as CustomJSONSchema7).description).toEqual("original"); + expect(result).not.toBe(original); + }); + + it("should recurse into nested object properties to find the matched property", () => { + const original = { + type: "object", + properties: { + nested: { + type: "object", + properties: { + deepTarget: { type: "string", description: "original" }, + }, + }, + }, + } as CustomJSONSchema7; + + const result = DynamicSchemaService.mutateProperty(original, matchByName("deepTarget"), markMutated); + + const nested = result.properties!.nested as CustomJSONSchema7; + expect((nested.properties!.deepTarget as CustomJSONSchema7).description).toEqual("mutated"); + }); + + it("should recurse into definitions to find the matched property", () => { + const original = { + type: "object", + definitions: { + target: { type: "string", description: "original" }, + }, + } as CustomJSONSchema7; + + const result = DynamicSchemaService.mutateProperty(original, matchByName("target"), markMutated); + + expect((result.definitions!.target as CustomJSONSchema7).description).toEqual("mutated"); + }); + + it("should recurse into array items and single-schema items to find the matched property", () => { + const original = { + type: "object", + properties: { + listTuple: { + type: "array", + items: [ + { + type: "object", + properties: { + target: { type: "string", description: "original" }, + }, + }, + ], + }, + listSchema: { + type: "array", + items: { + type: "object", + properties: { + target: { type: "string", description: "original" }, + }, + }, + }, + }, + } as CustomJSONSchema7; + + const result = DynamicSchemaService.mutateProperty(original, matchByName("target"), markMutated); + + const listTuple = result.properties!.listTuple as CustomJSONSchema7; + const firstItem = (listTuple.items as CustomJSONSchema7[])[0]; + expect((firstItem.properties!.target as CustomJSONSchema7).description).toEqual("mutated"); + + const listSchema = result.properties!.listSchema as CustomJSONSchema7; + const itemSchema = listSchema.items as CustomJSONSchema7; + expect((itemSchema.properties!.target as CustomJSONSchema7).description).toEqual("mutated"); + }); + + it("should not invoke the mutation function when nothing matches", () => { + const original = { + type: "object", + properties: { + a: { type: "string" }, + b: { type: "number" }, + }, + } as CustomJSONSchema7; + const mutationSpy = vi.fn((_: string, value: CustomJSONSchema7) => value); + + const result = DynamicSchemaService.mutateProperty(original, matchByName("missing"), mutationSpy); + + expect(mutationSpy).not.toHaveBeenCalled(); + // with no match the clone is structurally equal to the original + expect(result).toEqual(original); + expect(result).not.toBe(original); + }); +});
