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

bbovenzi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 57ed6e7bcb6 UI: Add regression tests for Dags list rows remounting on 
refresh (#75005)
57ed6e7bcb6 is described below

commit 57ed6e7bcb692406468e08f1864c04c2b2925f6d
Author: Yuseok Jo <[email protected]>
AuthorDate: Wed Oct 21 05:20:50 2026 +0900

    UI: Add regression tests for Dags list rows remounting on refresh (#75005)
    
    * UI: Add regression tests for Dags list rows remounting on refresh
    
    * Drop the unused page_size from the Dags list refresh test config
---
 .../ui/src/pages/DagsList/DagsList.test.tsx        | 80 +++++++++++++++++++++-
 1 file changed, 79 insertions(+), 1 deletion(-)

diff --git a/airflow-core/src/airflow/ui/src/pages/DagsList/DagsList.test.tsx 
b/airflow-core/src/airflow/ui/src/pages/DagsList/DagsList.test.tsx
index 68da78a137a..96c86fd3524 100644
--- a/airflow-core/src/airflow/ui/src/pages/DagsList/DagsList.test.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/DagsList/DagsList.test.tsx
@@ -19,7 +19,7 @@
 import "@testing-library/jest-dom/vitest";
 import { fireEvent, render, screen, waitFor, within } from 
"@testing-library/react";
 import { delay, http, HttpResponse } from "msw";
-import { afterEach, describe, expect, it } from "vitest";
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
 
 import type { DagSchedulingState } from "openapi/requests/types.gen";
 
@@ -417,3 +417,81 @@ describe("Dags display toggle", () => {
     expect(screen.queryByTestId("table-list")).toBeNull();
   });
 });
+
+const runningDag = {
+  ...successDag,
+  has_unfinished_runs: true,
+  latest_dag_runs: successDag.latest_dag_runs.map((run) => ({ ...run, 
end_date: null, state: "running" })),
+};
+
+// A running Dag keeps the list polling, and its run state counts change on 
every poll like they do
+// while runs progress, so each refresh hands the list new data that must not 
remount its rows.
+const serveRefreshingDagsList = () => {
+  let countsResponses = 0;
+
+  server.use(
+    http.get("/ui/config", () => HttpResponse.json({ auto_refresh_interval: 1 
})),
+    http.get("/ui/dags", () => HttpResponse.json({ dags: [runningDag], 
total_entries: 1 })),
+    http.get("/ui/dags/run_state_counts", () => {
+      countsResponses += 1;
+
+      return HttpResponse.json({
+        dags: [{ dag_id: runningDag.dag_id, state_counts: { running: 
countsResponses } }],
+        state_count_limit: 1000,
+      });
+    }),
+    http.get("/ui/dags/recent_task_instance_state_counts", () => 
HttpResponse.json({ dags: [] })),
+  );
+
+  return async () => {
+    const next = String(countsResponses + 1);
+
+    await waitFor(
+      () =>
+        
expect(screen.getByTestId(`run-state-count-running-${runningDag.dag_id}`)).toHaveTextContent(next),
+      { timeout: 3000 },
+    );
+  };
+};
+
+describe("Dags list auto-refresh", () => {
+  // happy-dom never reports an intersection, which would keep card content 
hidden for good.
+  beforeEach(() => {
+    vi.stubGlobal("IntersectionObserver", undefined);
+  });
+  afterEach(() => {
+    vi.unstubAllGlobals();
+  });
+
+  it.each(["card", "table"])("keeps the %s rows mounted across a refresh", 
async (display) => {
+    const waitForRefresh = serveRefreshingDagsList();
+
+    localStorage.setItem(DAGS_LIST_DISPLAY_KEY, JSON.stringify(display));
+    render(<AppWrapper initialEntries={["/dags"]} />);
+
+    await waitForRefresh();
+    const toggle = screen.getByTestId("toggle-pause");
+
+    await waitForRefresh();
+
+    expect(screen.getByTestId("toggle-pause")).toBe(toggle);
+  });
+
+  it.each(["card", "table"])(
+    "keeps the pause dialog open in the %s view across a refresh",
+    async (display) => {
+      const waitForRefresh = serveRefreshingDagsList();
+
+      localStorage.setItem(DAGS_LIST_DISPLAY_KEY, JSON.stringify(display));
+      render(<AppWrapper initialEntries={["/dags"]} />);
+
+      await waitForRefresh();
+      fireEvent.click(screen.getByTestId("toggle-pause"));
+      const drainButton = await screen.findByTestId("drain-dag");
+
+      await waitForRefresh();
+
+      expect(screen.getByTestId("drain-dag")).toBe(drainButton);
+    },
+  );
+});

Reply via email to