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-f24dab0030bf4ee0620562ff0d186b3b04de7d51
in repository https://gitbox.apache.org/repos/asf/texera.git

commit ca6316c9d4bd281389e3e7b0e85ad70da12245f1
Author: Luis Fernando Caro Reyna 
<[email protected]>
AuthorDate: Thu Sep 24 18:32:02 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 0b1a8a830e..48007ae949 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 { Observable } from "rxjs";
-import { Comment, OperatorLink, PortDescription, PortProperty } from 
"../../../types/workflow-common.interface";
+import {
+  Comment,
+  OperatorLink,
+  OperatorPredicate,
+  PortDescription,
+  PortProperty,
+} from "../../../types/workflow-common.interface";
 
 describe("WorkflowGraph", () => {
   let workflowGraph: WorkflowGraph;
@@ -824,4 +830,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 16366c8969..a0e5deeb87 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
@@ -76,7 +76,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 {

Reply via email to