You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@texera.apache.org by gi...@apache.org on 2026/07/22 02:17:39 UTC

(texera) branch gh-readonly-queue/main/pr-6683-5f9a6d2d8a974556293615f2e96970ee429b6e47 created (now d3aa22e93f)

This is an automated email from the ASF dual-hosted git repository.

github-merge-queue[bot] pushed a change to branch gh-readonly-queue/main/pr-6683-5f9a6d2d8a974556293615f2e96970ee429b6e47
in repository https://gitbox.apache.org/repos/asf/texera.git


      at d3aa22e93f test(frontend): cover formly-utils stream and type helpers (#6683)

This branch includes the following new commits:

     new d3aa22e93f test(frontend): cover formly-utils stream and type helpers (#6683)

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(texera) 01/01: test(frontend): cover formly-utils stream and type helpers (#6683)

Posted by gi...@apache.org.
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-6683-5f9a6d2d8a974556293615f2e96970ee429b6e47
in repository https://gitbox.apache.org/repos/asf/texera.git

commit d3aa22e93fecce420595f7b999be60a2bc2ea924
Author: Matthew B. <mg...@uci.edu>
AuthorDate: Tue Jul 21 19:12:28 2026 -0700

    test(frontend): cover formly-utils stream and type helpers (#6683)
    
    ### What changes were proposed in this PR?
    - Append describe blocks to formly-utils.spec.ts covering
    setChildTypeDependency and createOutputFormChangeEventStream.
    - Assert timestamp-attribute collection and the child description
    expression, and the stream debounce, dedupe, and modelCheck gating using
    fake timers.
    ### Any related issues, documentation, discussions?
    Closes: #6682
    ### 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/common/formly/formly-utils.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
---
 .../src/app/common/formly/formly-utils.spec.ts     | 118 ++++++++++++++++++++-
 1 file changed, 117 insertions(+), 1 deletion(-)

diff --git a/frontend/src/app/common/formly/formly-utils.spec.ts b/frontend/src/app/common/formly/formly-utils.spec.ts
index 22ee315217..4bec75a669 100644
--- a/frontend/src/app/common/formly/formly-utils.spec.ts
+++ b/frontend/src/app/common/formly/formly-utils.spec.ts
@@ -18,7 +18,16 @@
  */
 
 import { FormlyFieldConfig } from "@ngx-formly/core";
-import { createShouldHideFieldFunc, getFieldByName, setHideExpression } from "./formly-utils";
+import {
+  createOutputFormChangeEventStream,
+  createShouldHideFieldFunc,
+  getFieldByName,
+  setChildTypeDependency,
+  setHideExpression,
+} from "./formly-utils";
+import { Subject } from "rxjs";
+import { FORM_DEBOUNCE_TIME_MS } from "../../workspace/service/execute-workflow/execute-workflow.service";
+import { PortSchema } from "../../workspace/types/workflow-compiling.interface";
 
 describe("getFieldByName", () => {
   it("returns the field whose key matches the name", () => {
@@ -90,3 +99,110 @@ describe("createShouldHideFieldFunc", () => {
     expect(hide(fieldWithModel({ target: 6 }))).toBe(false);
   });
 });
+
+describe("setChildTypeDependency", () => {
+  it("collects timestamp attribute names across all ports and sets a description expression on the child", () => {
+    const attributes: Record<string, PortSchema | undefined> = {
+      outPort0: [
+        { attributeName: "ts1", attributeType: "timestamp" },
+        { attributeName: "label", attributeType: "string" },
+      ],
+      outPort1: [{ attributeName: "ts2", attributeType: "timestamp" }],
+    };
+    const child: FormlyFieldConfig = { key: "attrValue" };
+    setChildTypeDependency(attributes, "attrName", [{ key: "other" }, child], "attrValue");
+
+    expect(child.expressions).toEqual({
+      "templateOptions.description":
+        "[\"ts1\",\"ts2\"].includes(model.attrName)? 'Input a datetime string' : 'Input a positive number'",
+    });
+  });
+
+  it("emits an empty timestamp list when there are no timestamp-typed attributes", () => {
+    const attributes: Record<string, PortSchema | undefined> = {
+      outPort0: [{ attributeName: "label", attributeType: "string" }],
+    };
+    const child: FormlyFieldConfig = { key: "attrValue" };
+    setChildTypeDependency(attributes, "attrName", [child], "attrValue");
+
+    expect(child.expressions).toEqual({
+      "templateOptions.description":
+        "[].includes(model.attrName)? 'Input a datetime string' : 'Input a positive number'",
+    });
+  });
+
+  it("treats undefined attributes as an empty timestamp list", () => {
+    const child: FormlyFieldConfig = { key: "attrValue" };
+    setChildTypeDependency(undefined, "attrName", [child], "attrValue");
+
+    expect(child.expressions).toEqual({
+      "templateOptions.description":
+        "[].includes(model.attrName)? 'Input a datetime string' : 'Input a positive number'",
+    });
+  });
+
+  it("is a no-op when the named child field is not present", () => {
+    const attributes: Record<string, PortSchema | undefined> = {
+      outPort0: [{ attributeName: "ts1", attributeType: "timestamp" }],
+    };
+    const unrelated: FormlyFieldConfig = { key: "other" };
+    setChildTypeDependency(attributes, "attrName", [unrelated], "missingChild");
+
+    expect(unrelated.expressions).toBeUndefined();
+  });
+});
+
+describe("createOutputFormChangeEventStream", () => {
+  beforeEach(() => {
+    vi.useFakeTimers();
+  });
+
+  afterEach(() => {
+    vi.useRealTimers();
+  });
+
+  it("debounces bursts, emitting only the latest value once the debounce window elapses", () => {
+    const source = new Subject<Record<string, unknown>>();
+    const emissions: Record<string, unknown>[] = [];
+    createOutputFormChangeEventStream(source, () => true).subscribe(v => emissions.push(v));
+
+    source.next({ v: 1 });
+    vi.advanceTimersByTime(FORM_DEBOUNCE_TIME_MS - 1);
+    source.next({ v: 2 });
+    // still within the debounce window relative to the second emission
+    vi.advanceTimersByTime(FORM_DEBOUNCE_TIME_MS - 1);
+    expect(emissions).toEqual([]);
+
+    vi.advanceTimersByTime(1);
+    expect(emissions).toEqual([{ v: 2 }]);
+  });
+
+  it("drops a repeated identical value via distinctUntilChanged", () => {
+    const source = new Subject<Record<string, unknown>>();
+    const emissions: Record<string, unknown>[] = [];
+    createOutputFormChangeEventStream(source, () => true).subscribe(v => emissions.push(v));
+
+    const sameRef = { v: 1 };
+    source.next(sameRef);
+    vi.advanceTimersByTime(FORM_DEBOUNCE_TIME_MS);
+    source.next(sameRef);
+    vi.advanceTimersByTime(FORM_DEBOUNCE_TIME_MS);
+
+    expect(emissions).toEqual([sameRef]);
+  });
+
+  it("gates emissions through the modelCheck predicate", () => {
+    const source = new Subject<Record<string, unknown>>();
+    const emissions: Record<string, unknown>[] = [];
+    const modelCheck = vi.fn((formData: Record<string, unknown>) => formData["keep"] === true);
+    createOutputFormChangeEventStream(source, modelCheck).subscribe(v => emissions.push(v));
+
+    source.next({ keep: false });
+    vi.advanceTimersByTime(FORM_DEBOUNCE_TIME_MS);
+    source.next({ keep: true });
+    vi.advanceTimersByTime(FORM_DEBOUNCE_TIME_MS);
+
+    expect(emissions).toEqual([{ keep: true }]);
+    expect(modelCheck).toHaveBeenCalledTimes(2);
+  });
+});