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-7366-d80cc6e50155e0cd36cb0e72254d42068309d877 in repository https://gitbox.apache.org/repos/asf/texera.git
commit 76ab7ae41270e21939d95bb65a230a1e50a00ea9 Author: Meng Wang <[email protected]> AuthorDate: Thu Aug 6 20:45:36 2026 -0700 test(frontend): extend ResultPanelComponent template coverage (#7366) ### What changes were proposed in this PR? Extends `result-panel.component.spec.ts` to render the previously-unexercised half of `result-panel.component.html`. The class was already ~99% covered but the template sat at ~45% — the existing tests drive the class, not the DOM. Adds 7 tests that render each template branch (`detectChanges()` + `By.css(...)` + `triggerEventHandler`): - **Docked panel** (`width > 0`) — the content renders; the header close, the in-panel close, and the reset-position button fire their handlers. - **Collapsed panel** (`width = 0`) — only the open button renders (the body is hidden) and it wires `openPanel()`. - **Title interpolation** — `operatorTitle` appended after `: ` when set, and the bare `Result Panel` when not. - **Tabs** — the "No results available" tab when `frameComponentConfigs` is empty, and one tab per frame (`*ngFor` + `*ngComponentOutlet`) when populated. - **Resize handles** — rendered while docked. This takes the template from ~45% to **52/53 lines (98%)**. No production code was changed. ### Any related issues, documentation, discussions? Closes #7364. ### How was this PR tested? `ng test --watch=false --include src/app/workspace/component/result-panel/result-panel.component.spec.ts` — the 7 new tests pass; template coverage confirmed at 52/53 lines via the lcov report. `eslint` and `prettier --check` clean. Failure path verified by breaking a new assertion (→ that test goes red) and restoring. Note: two pre-existing `persistence on unload` tests assert `localStorage.setItem` and fail only under Node 26 locally (the file-backed `--localstorage-file` shim behaves differently from CI's Node 24.10.0 localStorage); they are unrelated to this change and pass on CI. ### Was this PR authored or co-authored using generative AI tooling? Generated-by: Claude Code (Opus 4.8 [1M context]) --- .../result-panel/result-panel.component.spec.ts | 90 +++++++++++++++++++++- 1 file changed, 89 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/workspace/component/result-panel/result-panel.component.spec.ts b/frontend/src/app/workspace/component/result-panel/result-panel.component.spec.ts index d4998ef95c..c4236299b7 100644 --- a/frontend/src/app/workspace/component/result-panel/result-panel.component.spec.ts +++ b/frontend/src/app/workspace/component/result-panel/result-panel.component.spec.ts @@ -18,7 +18,7 @@ */ import { ComponentFixture, TestBed } from "@angular/core/testing"; -import { ElementRef } from "@angular/core"; +import { Component, ElementRef } from "@angular/core"; import { CdkDragEnd } from "@angular/cdk/drag-drop"; import { NzResizeEvent } from "ng-zorro-antd/resizable"; @@ -664,4 +664,92 @@ describe("ResultPanelComponent", () => { expect(setItemSpy).not.toHaveBeenCalledWith("result-panel-style", expect.anything()); }); }); + + describe("template rendering", () => { + // Query, assert the element is present, then dispatch the event (a real MouseEvent + // for clicks so handlers calling stopPropagation()/preventDefault() work). + const fire = (css: string, event: string, payload: unknown): void => { + const el = fixture.debugElement.query(By.css(css)); + expect(el).toBeTruthy(); + el.triggerEventHandler(event, payload); + }; + + it("renders the docked panel chrome and wires the close / reset actions", () => { + component.width = DEFAULT_WIDTH; + fixture.detectChanges(); + const closeSpy = vi.spyOn(component, "closePanel").mockImplementation(() => {}); + const resetSpy = vi.spyOn(component, "resetPanelPosition").mockImplementation(() => {}); + + expect(fixture.debugElement.query(By.css("#content"))).toBeTruthy(); + fire("#result-buttons li[nz-menu-item]", "click", new MouseEvent("click")); // header close + fire("#panel-button button", "click", new MouseEvent("click")); // reset-position + fire("#panel-button li[nz-menu-item]", "click", new MouseEvent("click")); // in-panel close + + expect(closeSpy).toHaveBeenCalled(); + expect(resetSpy).toHaveBeenCalled(); + }); + + it("renders only the collapsed open button and wires openPanel when width is 0", () => { + component.width = 0; + fixture.detectChanges(); + const openSpy = vi.spyOn(component, "openPanel").mockImplementation(() => {}); + + // the panel body is hidden while collapsed + expect(fixture.debugElement.query(By.css("#content"))).toBeNull(); + fire("#result-buttons li[nz-menu-item]", "click", new MouseEvent("click")); // the open item + + expect(openSpy).toHaveBeenCalled(); + }); + + it("interpolates the operator title into the panel title", () => { + component.width = DEFAULT_WIDTH; + component.operatorTitle = "My Operator"; + fixture.detectChanges(); + + const title = fixture.debugElement.query(By.css("#title")).nativeElement as HTMLElement; + expect(title.textContent).toContain("Result Panel: My Operator"); + }); + + it("omits the separator in the title when no operator is selected", () => { + component.width = DEFAULT_WIDTH; + component.operatorTitle = ""; + fixture.detectChanges(); + + const title = fixture.debugElement.query(By.css("#title")).nativeElement as HTMLElement; + expect(title.textContent?.trim()).toBe("Result Panel"); + }); + + it("shows the no-results tab when there are no frames", () => { + component.width = DEFAULT_WIDTH; + component.frameComponentConfigs.clear(); + fixture.detectChanges(); + + expect((fixture.nativeElement as HTMLElement).textContent).toContain("No results available to display."); + }); + + it("renders a tab per frame when frames are present", () => { + component.width = DEFAULT_WIDTH; + component.frameComponentConfigs.set("Result", { component: StubFrame, componentInputs: {} }); + component.frameComponentConfigs.set("Console", { component: StubFrame, componentInputs: {} }); + fixture.detectChanges(); + + const text = (fixture.nativeElement as HTMLElement).textContent ?? ""; + expect(text).toContain("Result"); + expect(text).toContain("Console"); + expect(text).not.toContain("No results available to display."); + }); + + it("renders the resize handles when the panel is docked", () => { + component.width = DEFAULT_WIDTH; + vi.spyOn(component, "isPanelDocked").mockReturnValue(true); + fixture.detectChanges(); + + expect(fixture.debugElement.query(By.css("nz-resize-handles"))).toBeTruthy(); + }); + }); }); + +// A trivial standalone frame so *ngComponentOutlet can instantiate a tab's content +// without pulling in a real result-frame component's dependency graph. +@Component({ standalone: true, template: "" }) +class StubFrame {}
