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-8648-70bd3b3caef58febd250765e107811718aa5a5db in repository https://gitbox.apache.org/repos/asf/texera.git
commit 4f670fc9ec70af7315c45489fb7a025f881982ec Author: Luis Fernando Caro Reyna <[email protected]> AuthorDate: Wed Sep 23 07:25:28 2026 +0000 fix(frontend): use locale-independent lowercasing in isSink (#8648) ### What changes were proposed in this PR? `isSink` folded operator types with `toLocaleLowerCase()`, which is locale-dependent. In a Turkish-locale browser an uppercase `I` folds to `ı`, so a type spelled `SINK` becomes `sınk` and stops matching: the operator drops out of the result panel and becomes eligible for the result-view and cache toggles it is meant to be excluded from. An operator type is a machine identifier, so case folding must not depend on the browser's locale. Before: `isSink("SINK")` can return false under a Turkish runtime locale After: `isSink("SINK")` is always true Adds direct unit coverage for `isSink`: the casing contract, negative cases, and a regression test that stubs `toLocaleLowerCase` with Turkish folding — the one case `#8603` cannot pin, since it fails on the old implementation. ### Any related issues, documentation, discussions? Closes #8616 Sequenced on #8603 (test-only coverage for the same helpers, same spec file). Whichever of the two merges first should rebase the other. ### How was this PR tested? - Targeted spec: `yarn ng test --watch=false --include "src/app/workspace/service/workflow-graph/model/workflow-graph.spec.ts"` — 72 passed (regression test proved red before the fix) - Full frontend suite: `yarn test:ci` — 229 files, 6244 passed, 1 skipped (pre-existing) - `eslint` and `prettier --check` clean on changed files ### Was this PR authored or co-authored using generative AI tooling? Yes. Co-authored with Claude Code; reviewed before submission. --- .../workflow-graph/model/workflow-graph.spec.ts | 48 +++++++++++++++++++++- .../service/workflow-graph/model/workflow-graph.ts | 2 +- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/workspace/service/workflow-graph/model/workflow-graph.spec.ts b/frontend/src/app/workspace/service/workflow-graph/model/workflow-graph.spec.ts index 41942cd7a5..bf5d6fd0c7 100644 --- a/frontend/src/app/workspace/service/workflow-graph/model/workflow-graph.spec.ts +++ b/frontend/src/app/workspace/service/workflow-graph/model/workflow-graph.spec.ts @@ -27,9 +27,15 @@ import { mockSentimentPredicate, mockSentimentResultLink, } from "./mock-workflow-data"; -import { WorkflowGraph } from "./workflow-graph"; +import { WorkflowGraph, isSink } from "./workflow-graph"; +import { + Comment, + OperatorLink, + OperatorPredicate, + PortDescription, + PortProperty, +} from "../../../types/workflow-common.interface"; import { Observable } from "rxjs"; -import { Comment, OperatorLink, PortDescription, PortProperty } from "../../../types/workflow-common.interface"; describe("WorkflowGraph", () => { let workflowGraph: WorkflowGraph; @@ -829,4 +835,42 @@ describe("WorkflowGraph", () => { sub.unsubscribe(); }); }); + + describe("isSink", () => { + const buildPredicate = (operatorType: string): OperatorPredicate => ({ + operatorID: "testOperator", + operatorType, + operatorVersion: "v1", + operatorProperties: {}, + inputPorts: [{ portID: "input-0" }], + outputPorts: [], + showAdvanced: true, + isDisabled: false, + }); + + it("should match an operator type that contains sink in any casing", () => { + expect(isSink(buildPredicate("sink"))).toBe(true); + expect(isSink(buildPredicate("SINK"))).toBe(true); + expect(isSink(buildPredicate("SimpleSink"))).toBe(true); + expect(isSink(buildPredicate("dvdpowSinkExec"))).toBe(true); + }); + + it("should not match an operator type without sink", () => { + expect(isSink(buildPredicate("NlpSentiment"))).toBe(false); + expect(isSink(buildPredicate("scanSource"))).toBe(false); + }); + + it("should match a sink type even when the runtime locale folds uppercase I to dotless ı", () => { + const turkishFolding = vi.spyOn(String.prototype, "toLocaleLowerCase").mockImplementation(function ( + this: string + ) { + return this.replace(/I/g, "ı").toLowerCase(); + }); + try { + expect(isSink(buildPredicate("SINK"))).toBe(true); + } finally { + turkishFolding.mockRestore(); + } + }); + }); }); diff --git a/frontend/src/app/workspace/service/workflow-graph/model/workflow-graph.ts b/frontend/src/app/workspace/service/workflow-graph/model/workflow-graph.ts index 9c77ad4170..635f4d33dd 100644 --- a/frontend/src/app/workspace/service/workflow-graph/model/workflow-graph.ts +++ b/frontend/src/app/workspace/service/workflow-graph/model/workflow-graph.ts @@ -78,7 +78,7 @@ export const VIEW_RESULT_OP_TYPE = "SimpleSink"; export const VIEW_RESULT_OP_NAME = "View Results"; export function isSink(operator: OperatorPredicate): boolean { - return operator.operatorType.toLocaleLowerCase().includes("sink"); + return operator.operatorType.toLowerCase().includes("sink"); } export function isPythonUdf(operator: OperatorPredicate): boolean {
