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

commit e4205c357a2271b89892eadb72f3efbc7012c37c
Author: Meng Wang <[email protected]>
AuthorDate: Thu Aug 6 18:32:10 2026 -0700

    test(frontend): cover LeftPanelComponent template tab rendering and 
interactions (#7367)
    
    ### What changes were proposed in this PR?
    
    Extends `LeftPanelComponent`'s spec to render the panel's tab lists and
    exercise
    their bindings. The class file is already at 100%, but the existing
    tests call
    the handlers directly and never render/click the tabs, so
    `left-panel.component.html` sat at ~41%.
    
    9 added tests render both the collapsed dock and the expanded panel,
    then drive
    each interactive element via `fixture.debugElement.query(By.css(...))` +
    `.triggerEventHandler(...)`:
    
    - Collapsed dock (`#docked-buttons`): clicking a tab re-opens that
    frame; only the enabled tabs render (disabled ones are omitted); the
    `width && !isDocked` minus button collapses the panel.
    - Expanded dock (`#dock`): clicking a tab switches frames; the
    `isDocked` minus collapses.
    - Return bar (`#return-button`): the reset button re-docks to the return
    position; its minus collapses.
    - Container events: the three menu lists' `(cdkDropListDropped)` reorder
    `order`, and the left container's `(cdkDragStarted)` / `(nzResize)`
    update the docked/size state.
    
    This lifts `left-panel.component.html` from ~41% to **100%** (statements
    and
    branches); the class stays at 100%.
    
    **Determinism (per the issue's constraint):** no `vi.useFakeTimers()` —
    the
    `setTimeout` `ngAfterViewInit` queues never runs in a synchronous test
    body, and
    layering fake timers over zone.js's patched timers is Node-version
    dependent. No
    `getBoundingClientRect`/offset assertions either — the tests assert the
    rendered
    tabs and the active-frame/width state, not measured heights. Tabs are
    located by
    the CDK `cdk-drag` class (the `nz-tooltip` attribute is consumed by the
    directive
    and not present in the DOM). No production code was changed.
    
    ### Any related issues, documentation, discussions?
    
    Closes #7363
    
    ### How was this PR tested?
    
    Extended unit tests, run locally in `frontend/`:
    
    ```
    ng test --watch=false --include 
src/app/workspace/component/left-panel/left-panel.component.spec.ts
    # Test Files 1 passed (1) | Tests 28 passed (28)   — 3 consecutive runs, 0 
flakes
    # left-panel.component.html: ~41% -> 100% (statements & branches)
    prettier --write <spec>   # unchanged
    eslint  <spec>            # clean
    ```
    
    The failure path was verified by deliberately breaking a new assertion
    and
    confirming the suite exits non-zero. (Locally on Node 26 the component's
    `localStorage` reads need `NODE_OPTIONS=--localstorage-file=...`; CI
    runs Node 24
    where jsdom's `localStorage` is present, so no polyfill is added to the
    spec.)
    
    ### Was this PR authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Code (Opus 4.8 [1M context])
    
    ---------
    
    Signed-off-by: Xinyuan Lin <[email protected]>
    Co-authored-by: Xinyuan Lin <[email protected]>
    Co-authored-by: Copilot Autofix powered by AI 
<[email protected]>
---
 .../left-panel/left-panel.component.spec.ts        | 140 +++++++++++++++++++++
 1 file changed, 140 insertions(+)

diff --git 
a/frontend/src/app/workspace/component/left-panel/left-panel.component.spec.ts 
b/frontend/src/app/workspace/component/left-panel/left-panel.component.spec.ts
index 87cd859422..57e65c8c84 100644
--- 
a/frontend/src/app/workspace/component/left-panel/left-panel.component.spec.ts
+++ 
b/frontend/src/app/workspace/component/left-panel/left-panel.component.spec.ts
@@ -18,6 +18,8 @@
  */
 
 import { ComponentFixture, fakeAsync, TestBed, tick } from 
"@angular/core/testing";
+import { By } from "@angular/platform-browser";
+import { DebugElement } from "@angular/core";
 import { LeftPanelComponent } from "./left-panel.component";
 import { mockPoint, mockScanPredicate } from 
"../../service/workflow-graph/model/mock-workflow-data";
 import { VersionsListComponent } from 
"./versions-list/versions-list.component";
@@ -315,6 +317,144 @@ describe("LeftPanelComponent", () => {
     freshFixture.destroy();
   });
 
+  // ── Rendered template: tab lists + their (click)/event bindings ──
+  describe("template tab rendering & interactions", () => {
+    // Draggable tabs render with the CDK `cdk-drag` class; the collapse 
"minus"
+    // button does not. Enabled tabs render in `order` sequence, so the frame's
+    // list position is its index among the currently-enabled frames.
+    const tabForFrame = (containerId: string, frame: number): DebugElement | 
undefined => {
+      const tabs = fixture.debugElement.queryAll(By.css(`#${containerId} 
li[nz-menu-item].cdk-drag`));
+      const pos = component.order.filter(i => 
component.items[i].enabled).indexOf(frame);
+      return pos >= 0 ? tabs[pos] : undefined;
+    };
+    const minusOf = (containerId: string): DebugElement | null =>
+      fixture.debugElement.query(By.css(`#${containerId} 
li[nz-menu-item]:not(.cdk-drag)`));
+
+    it("clicking a tab in the collapsed dock opens that frame", () => {
+      component.width = 0;
+      fixture.detectChanges();
+
+      // collapsed state (width 0) -> #docked-buttons shows the enabled tabs
+      const versionsTab = tabForFrame("docked-buttons", 2);
+      expect(versionsTab).toBeTruthy();
+
+      versionsTab!.triggerEventHandler("click", null);
+
+      expect(component.currentIndex).toBe(2);
+      expect(component.width).toBe(230); // collapsed -> re-opened
+    });
+
+    it("the collapsed dock renders enabled tabs and omits disabled ones", () 
=> {
+      // 1/2/3 are enabled; 4 (Execution History) is disabled in the mock GUI 
config
+      expect(fixture.debugElement.queryAll(By.css("#docked-buttons 
li[nz-menu-item].cdk-drag")).length).toBe(3);
+      expect(tabForFrame("docked-buttons", 1)).toBeTruthy();
+      expect(tabForFrame("docked-buttons", 4)).toBeUndefined();
+    });
+
+    it("the docked-bar minus collapses the panel when it is open and 
undocked", () => {
+      component.width = 300;
+      component.isDocked = false;
+      fixture.detectChanges();
+
+      const minus = minusOf("docked-buttons");
+      expect(minus).toBeTruthy();
+      minus!.triggerEventHandler("click", null);
+
+      expect(component.width).toBe(0);
+      expect(component.currentIndex).toBe(0);
+    });
+
+    it("clicking a tab in the expanded dock switches frames", () => {
+      component.width = 300;
+      component.isDocked = false;
+      fixture.detectChanges();
+
+      const settingsTab = tabForFrame("dock", 3);
+      expect(settingsTab).toBeTruthy();
+      settingsTab!.triggerEventHandler("click", null);
+
+      expect(component.currentIndex).toBe(3);
+    });
+
+    it("the expanded dock's minus collapses the panel when docked", () => {
+      component.width = 300;
+      component.isDocked = true;
+      fixture.detectChanges();
+
+      const minus = minusOf("dock");
+      expect(minus).toBeTruthy();
+      minus!.triggerEventHandler("click", null);
+
+      expect(component.width).toBe(0);
+      expect(component.currentIndex).toBe(0);
+    });
+
+    it("the return button re-docks the panel to its return position", () => {
+      component.width = 300;
+      component.returnPosition = { x: 7, y: 8 };
+      component.dragPosition = { x: 70, y: 80 };
+      component.isDocked = false;
+      fixture.detectChanges();
+
+      const resetBtn = fixture.debugElement.query(By.css("#return-button 
button"));
+      expect(resetBtn).toBeTruthy();
+      resetBtn.triggerEventHandler("click", null);
+
+      expect(component.isDocked).toBe(true);
+      expect(component.dragPosition).toEqual({ x: 7, y: 8 });
+    });
+
+    it("the return bar's minus collapses the panel", () => {
+      component.width = 300;
+      fixture.detectChanges();
+
+      const minus = minusOf("return-button");
+      expect(minus).toBeTruthy();
+      minus!.triggerEventHandler("click", null);
+
+      expect(component.width).toBe(0);
+      expect(component.currentIndex).toBe(0);
+    });
+
+    it("wires the drop-list reorder from every rendered menu list", () => {
+      component.width = 300;
+      fixture.detectChanges();
+
+      for (const id of ["docked-buttons", "dock", "return-button"]) {
+        component.order = [1, 2, 3, 4, 5];
+        fixture.debugElement
+          .query(By.css(`#${id}`))
+          .triggerEventHandler("cdkDropListDropped", { previousIndex: 0, 
currentIndex: 1 });
+        expect(component.order).toEqual([2, 1, 3, 4, 5]);
+      }
+    });
+
+    it("wires the resize and drag-start events from the left container", () => 
{
+      component.width = 300;
+      fixture.detectChanges();
+      const container = fixture.debugElement.query(By.css("#left-container"));
+
+      container.triggerEventHandler("cdkDragStarted", null);
+      expect(component.isDocked).toBe(false);
+
+      const rafSpy = vi
+        .spyOn(window, "requestAnimationFrame")
+        .mockImplementation((cb: FrameRequestCallback): number => {
+          cb(0);
+          return 1;
+        });
+      const cafSpy = vi.spyOn(window, 
"cancelAnimationFrame").mockImplementation(() => {});
+      try {
+        container.triggerEventHandler("nzResize", { width: 345, height: 678 });
+        expect(component.width).toBe(345);
+        expect(component.height).toBe(678);
+      } finally {
+        rafSpy.mockRestore();
+        cafSpy.mockRestore();
+      }
+    });
+  });
+
   it("restores the saved left-container style on its first ngOnInit", () => {
     // Destroy the default fixture so a single #left-container lives in the 
document, set the
     // saved style, then create a fresh component and attach it before its 
FIRST ngOnInit runs

Reply via email to