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

commit 97cb8cf699e676e7d9ee1ca643da58d0b3bc8b84
Author: Xinyuan Lin <[email protected]>
AuthorDate: Sun Aug 9 18:16:43 2026 -0700

    test(frontend): render the quota page's result cache tab (#7437)
    
    ### What changes were proposed in this PR?
    
    The quota page's Result Cache tab is never rendered by its spec, which
    drives the quota data and its charts only.
    
    Adds 5 tests. The reported cache size is the centrepiece:
    
    ```html
    {{ formatSize(execution.resultBytes + execution.logBytes + 
execution.runTimeStatsBytes) }}
    ```
    
    Three separate byte counts added together. A dropped or double-counted
    term still produces a plausible-looking size and nothing else in the
    suite would notice, so the fixture uses distinct powers of two — any
    such slip lands on a different total rather than coincidentally
    matching.
    
    Also covered: one collapse panel per workflow headed by its name, the
    executions listed under the opened workflow, the delete button carrying
    the row's **execution** id rather than the workflow id, and the table
    paging rather than listing everything.
    
    **Verified by mutation**, all reverted (template diff empty):
    
    | Mutation | Result |
    |---|---|
    | cache size drops `logBytes` | red |
    | cache size drops runtime statistics | red |
    | cache size double-counts `resultBytes` | red |
    | delete passes the workflow id | red |
    | panel header shows the id | red |
    | execution-id column shows the name | red |
    | pagination widened | red |
    | rows read the raw list instead of the page | red |
    
    `ngOnInit` resets `workflows`, so the first change-detection cycle runs
    before the fixture data is assigned; that is commented in the spec.
    
    No production file is touched.
    
    ### Any related issues, documentation, discussions?
    
    Closes #7434
    
    ### How was this PR tested?
    
    ```
    npx ng test --watch=false --include="**/user-quota.component.spec.ts"
    ```
    
    ```
     Test Files  1 passed (1)
          Tests  16 passed (16)
    ```
    
    5 new on top of the existing 11. `yarn format:ci` passes.
    
    ### Was this PR authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Code (Opus 5)
    
    ---------
    
    Signed-off-by: Xinyuan Lin <[email protected]>
    Co-authored-by: Copilot Autofix powered by AI 
<[email protected]>
---
 .../user/user-quota/user-quota.component.spec.ts   | 96 +++++++++++++++++++++-
 1 file changed, 95 insertions(+), 1 deletion(-)

diff --git 
a/frontend/src/app/dashboard/component/user/user-quota/user-quota.component.spec.ts
 
b/frontend/src/app/dashboard/component/user/user-quota/user-quota.component.spec.ts
index a659317792..e1e3dc92f4 100644
--- 
a/frontend/src/app/dashboard/component/user/user-quota/user-quota.component.spec.ts
+++ 
b/frontend/src/app/dashboard/component/user/user-quota/user-quota.component.spec.ts
@@ -23,8 +23,9 @@ import { UserQuotaService } from 
"../../../service/user/quota/user-quota.service
 import { HttpClientTestingModule } from "@angular/common/http/testing";
 import { commonTestProviders } from "../../../../common/testing/test-utils";
 import { of } from "rxjs";
+import { By } from "@angular/platform-browser";
 import type { Mocked } from "vitest";
-import { ExecutionQuota } from "../../../../common/type/user";
+import { ExecutionQuota, WorkflowQuota } from "../../../../common/type/user";
 import { DatasetQuota } from "../../../type/quota-statistic.interface";
 
 // Real Plotly renders into a DOM element by id; create one and assert on the
@@ -258,4 +259,97 @@ describe("UserQuotaComponent", () => {
       expect(gd.layout.yaxis.title).toMatchObject({ text: "Y Label" });
     });
   });
+  /**
+   * The Result Cache tab is template-only: the suite above drives the 
component's data and charts
+   * and never renders this tab, so the per-execution row — including the size 
it reports and the id
+   * its delete button carries — was unexercised.
+   */
+  describe("result cache tab", () => {
+    /** Selects a tab by its title and returns the host element. */
+    function openTab(title: string): HTMLElement {
+      const host = fixture.nativeElement as HTMLElement;
+      const tab = 
Array.from(host.querySelectorAll<HTMLElement>(".ant-tabs-tab")).find(t =>
+        (t.textContent || "").includes(title)
+      );
+      if (!tab) {
+        throw new Error(`Result Cache spec: tab not found: ${title}`);
+      }
+      tab.click();
+      fixture.detectChanges();
+      return host;
+    }
+
+    /** Expands the collapse panel whose header contains the given text. */
+    function openPanel(host: HTMLElement, header: string): void {
+      const panel = 
Array.from(host.querySelectorAll<HTMLElement>(".ant-collapse-header")).find(h =>
+        (h.textContent || "").includes(header)
+      );
+      panel!.click();
+      fixture.detectChanges();
+    }
+
+    /** Renders the Result Cache tab with the given workflows and opens the 
first panel. */
+    function renderCache(workflows: any[], openHeader = "wf-1"): HTMLElement {
+      // ngOnInit loads the quota and resets `workflows`, so the first cycle 
has to run before the
+      // fixture data is put in place, or it is wiped before anything renders.
+      fixture.detectChanges();
+      component.workflows = workflows;
+      fixture.detectChanges();
+      const host = openTab("Result Cache");
+      openPanel(host, openHeader);
+      return host;
+    }
+
+    const workflow = (workflowId: number, executions: any[]) => ({
+      workflowId,
+      workflowName: `wf-${workflowId}`,
+      executions,
+    });
+
+    it("groups the executions under one panel per workflow", () => {
+      const host = renderCache([workflow(1, [execution(10, 1, 1, 1, 1)]), 
workflow(2, [execution(20, 2, 1, 1, 1)])]);
+
+      const headers = 
Array.from(host.querySelectorAll(".ant-collapse-header")).map(h => 
h.textContent?.trim());
+      expect(headers).toEqual(["wf-1", "wf-2"]);
+    });
+
+    it("lists every execution of the opened workflow", () => {
+      const host = renderCache([workflow(1, [execution(10, 1, 1, 1, 1), 
execution(11, 1, 1, 1, 1)])]);
+
+      const eids = Array.from(host.querySelectorAll("tbody tr")).map(r =>
+        r.querySelectorAll("td")[1]?.textContent?.trim()
+      );
+      expect(eids).toEqual(["10", "11"]);
+    });
+
+    it("reports the cache size as result plus log plus runtime statistics", () 
=> {
+      // Three separate byte counts are added together. Distinct powers of 
two, so dropping or
+      // double-counting any one of them lands on a different total rather 
than coincidentally
+      // matching — which is exactly how a missing term would otherwise go 
unnoticed.
+      const host = renderCache([workflow(1, [execution(10, 1, 1024, 4096, 
2048)])]);
+
+      const sizeCell = host.querySelectorAll("tbody tr td")[2];
+      expect(sizeCell.textContent?.trim()).toBe(component.formatSize(1024 + 
4096 + 2048));
+    });
+
+    it("deletes the collection of the row it was pressed on", () => {
+      // The row carries the execution id; passing the workflow id would 
delete the wrong cache.
+      const spy = vi.spyOn(component, 
"deleteCollection").mockImplementation(() => {});
+      const host = renderCache([workflow(1, [execution(10, 1, 1, 1, 1), 
execution(11, 1, 1, 1, 1)])]);
+
+      const secondRowButton = host.querySelectorAll("tbody 
tr")[1].querySelector("button")!;
+      secondRowButton.click();
+      // nz-popconfirm defers the action to its confirmation, so trigger that 
directly.
+      
fixture.debugElement.queryAll(By.css("button[nz-popconfirm]"))[1].triggerEventHandler("nzOnConfirm",
 null);
+
+      expect(spy).toHaveBeenCalledWith(11);
+    });
+
+    it("pages the executions rather than listing all of them", () => {
+      const executions = Array.from({ length: 5 }, (_, i) => execution(100 + 
i, 1, 1, 1, 1));
+      const host = renderCache([workflow(1, executions)]);
+
+      expect(host.querySelectorAll("tbody tr").length).toBe(3);
+    });
+  });
 });

Reply via email to