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
