Skip to content

Commit c009e9d

Browse files
authored
test(frontend): extend unit test coverage for ResultPanelComponent (#6556)
### What changes were proposed in this PR? Extends the existing `ResultPanelComponent` spec (codecov ~57.5%) with 16 tests covering the previously-untested public methods. Existing services are the real ones from the spec's `TestBed`; specific methods are `vi.spyOn`-stubbed: - **visibility** — `openPanel` (default dims + notifies resize service), `closePanel` (collapses), `isPanelDocked` (docked vs. undocked), `clearResultPanel`. - **content frames** — `displayConsole`, `displayError`, and `displayResult` across all three branches (paginated → table frame, non-paginated result → visualization frame, no result → nothing). - **position & resize** — `resetPanelPosition`, `updateReturnPosition` (delta + undefined no-op), and `onResize` (the `requestAnimationFrame` callback is run synchronously via a locally-restored spy so the assertion is deterministic). - **drag** — `handleStartDrag` (hides the viz overlay) and `handleEndDrag` (records the free-drag position). - **rerenderResultPanel** — no-ops while previewing a workflow version. All tests are deterministic (no real timers/network/random; the one rAF is mocked synchronously and restored locally). No production code was changed. ### Any related issues, documentation, discussions? Closes #6549 ### How was this PR tested? Extended unit tests, run locally in `frontend/` (all green; the failure path was verified by breaking an assertion to confirm the suite goes red): ``` ng test --watch=false --include src/app/workspace/component/result-panel/result-panel.component.spec.ts # Tests 20 passed (20) (4 existing + 16 new) eslint <spec> # clean prettier --check # clean ``` (The component reads `localStorage` on init; on Node 26 that needs `NODE_OPTIONS=--localstorage-file=...` locally — CI on Node 24.10.0 provides it.) ### Was this PR authored or co-authored using generative AI tooling? Generated-by: Claude Code (Opus 4.8 [1M context])
1 parent dfb9400 commit c009e9d

1 file changed

Lines changed: 181 additions & 1 deletion

File tree

frontend/src/app/workspace/component/result-panel/result-panel.component.spec.ts

Lines changed: 181 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,19 @@
1818
*/
1919

2020
import { ComponentFixture, TestBed } from "@angular/core/testing";
21+
import { ElementRef } from "@angular/core";
22+
import { CdkDragEnd } from "@angular/cdk/drag-drop";
23+
import { NzResizeEvent } from "ng-zorro-antd/resizable";
2124

22-
import { ResultPanelComponent } from "./result-panel.component";
25+
import { DEFAULT_HEIGHT, DEFAULT_WIDTH, ResultPanelComponent } from "./result-panel.component";
2326
import { ExecuteWorkflowService } from "../../service/execute-workflow/execute-workflow.service";
2427
import { WorkflowResultService } from "../../service/workflow-result/workflow-result.service";
2528
import { WorkflowActionService } from "../../service/workflow-graph/model/workflow-action.service";
29+
import { PanelResizeService } from "../../service/workflow-result/panel-resize/panel-resize.service";
30+
import { ResultTableFrameComponent } from "./result-table-frame/result-table-frame.component";
31+
import { VisualizationFrameContentComponent } from "../visualization-panel-content/visualization-frame-content.component";
32+
import { ErrorFrameComponent } from "./error-frame/error-frame.component";
33+
import { ConsoleFrameComponent } from "./console-frame/console-frame.component";
2634
import { OperatorMetadataService } from "../../service/operator-metadata/operator-metadata.service";
2735
import { StubOperatorMetadataService } from "../../service/operator-metadata/stub-operator-metadata.service";
2836
import { By } from "@angular/platform-browser";
@@ -40,6 +48,7 @@ describe("ResultPanelComponent", () => {
4048
let executeWorkflowService: ExecuteWorkflowService;
4149
let workflowActionService: WorkflowActionService;
4250
let workflowResultService: WorkflowResultService;
51+
let resizeService: PanelResizeService;
4352

4453
beforeEach(async () => {
4554
await TestBed.configureTestingModule({
@@ -63,6 +72,7 @@ describe("ResultPanelComponent", () => {
6372
executeWorkflowService = TestBed.inject(ExecuteWorkflowService);
6473
workflowActionService = TestBed.inject(WorkflowActionService);
6574
workflowResultService = TestBed.inject(WorkflowResultService);
75+
resizeService = TestBed.inject(PanelResizeService);
6676
fixture.detectChanges();
6777
});
6878

@@ -103,4 +113,174 @@ describe("ResultPanelComponent", () => {
103113
expect(component.currentOperatorId).toBeUndefined();
104114
expect(component.operatorTitle).toBe("");
105115
});
116+
117+
describe("visibility", () => {
118+
it("openPanel sets default dimensions and notifies the resize service", () => {
119+
const resizeSpy = vi.spyOn(resizeService, "changePanelSize");
120+
121+
component.openPanel();
122+
123+
expect(component.height).toBe(DEFAULT_HEIGHT);
124+
expect(component.width).toBe(DEFAULT_WIDTH);
125+
expect(resizeSpy).toHaveBeenCalledWith(DEFAULT_WIDTH, DEFAULT_HEIGHT);
126+
});
127+
128+
it("closePanel collapses the panel", () => {
129+
component.openPanel();
130+
131+
component.closePanel();
132+
133+
expect(component.height).toBe(32.5);
134+
expect(component.width).toBe(0);
135+
});
136+
137+
it("isPanelDocked is true only when the drag position matches the return position", () => {
138+
component.returnPosition = { x: 5, y: 7 };
139+
component.dragPosition = { x: 5, y: 7 };
140+
expect(component.isPanelDocked()).toBe(true);
141+
142+
component.dragPosition = { x: 5, y: 8 };
143+
expect(component.isPanelDocked()).toBe(false);
144+
});
145+
146+
it("clearResultPanel empties the frame configs", () => {
147+
component.frameComponentConfigs.set("Result", { component: ResultTableFrameComponent, componentInputs: {} });
148+
149+
component.clearResultPanel();
150+
151+
expect(component.frameComponentConfigs.size).toBe(0);
152+
});
153+
});
154+
155+
describe("content frames", () => {
156+
it("displayConsole registers a console frame", () => {
157+
component.displayConsole("op1", true);
158+
159+
const config = component.frameComponentConfigs.get("Console");
160+
expect(config?.component).toBe(ConsoleFrameComponent);
161+
expect(config?.componentInputs).toEqual({ operatorId: "op1", consoleInputEnabled: true });
162+
});
163+
164+
it("displayError registers a static error frame", () => {
165+
component.displayError("op1");
166+
167+
const config = component.frameComponentConfigs.get("Static Error");
168+
expect(config?.component).toBe(ErrorFrameComponent);
169+
expect(config?.componentInputs).toEqual({ operatorId: "op1" });
170+
});
171+
172+
it("displayResult uses the table frame when a paginated result exists", () => {
173+
vi.spyOn(workflowResultService, "getPaginatedResultService").mockReturnValue(
174+
{} as unknown as ReturnType<typeof workflowResultService.getPaginatedResultService>
175+
);
176+
177+
component.displayResult("op1");
178+
179+
expect(component.frameComponentConfigs.get("Result")?.component).toBe(ResultTableFrameComponent);
180+
});
181+
182+
it("displayResult uses the visualization frame when only a non-paginated result exists", () => {
183+
vi.spyOn(workflowResultService, "getPaginatedResultService").mockReturnValue(undefined);
184+
vi.spyOn(workflowResultService, "getResultService").mockReturnValue(
185+
{} as unknown as ReturnType<typeof workflowResultService.getResultService>
186+
);
187+
188+
component.displayResult("op1");
189+
190+
expect(component.frameComponentConfigs.get("Result")?.component).toBe(VisualizationFrameContentComponent);
191+
});
192+
193+
it("displayResult registers nothing when the operator has no result", () => {
194+
vi.spyOn(workflowResultService, "getPaginatedResultService").mockReturnValue(undefined);
195+
vi.spyOn(workflowResultService, "getResultService").mockReturnValue(undefined);
196+
197+
component.displayResult("op1");
198+
199+
expect(component.frameComponentConfigs.has("Result")).toBe(false);
200+
});
201+
});
202+
203+
describe("position & resize", () => {
204+
it("resetPanelPosition moves the drag position back to the return position", () => {
205+
component.returnPosition = { x: 3, y: 9 };
206+
component.dragPosition = { x: 100, y: 200 };
207+
208+
component.resetPanelPosition();
209+
210+
expect(component.dragPosition).toEqual({ x: 3, y: 9 });
211+
});
212+
213+
it("updateReturnPosition shifts y by the height delta", () => {
214+
component.returnPosition = { x: 4, y: 10 };
215+
216+
component.updateReturnPosition(500, 300); // y + (500 - 300)
217+
218+
expect(component.returnPosition).toEqual({ x: 4, y: 210 });
219+
});
220+
221+
it("updateReturnPosition is a no-op when the new height is undefined", () => {
222+
component.returnPosition = { x: 4, y: 10 };
223+
224+
component.updateReturnPosition(500, undefined);
225+
226+
expect(component.returnPosition).toEqual({ x: 4, y: 10 });
227+
});
228+
229+
it("onResize applies the new size and notifies the resize service", () => {
230+
// Run the requestAnimationFrame callback synchronously so the assertion is deterministic.
231+
// These spies patch the global `window`, so restore them locally to avoid leaking across tests.
232+
const cancelSpy = vi.spyOn(window, "cancelAnimationFrame").mockImplementation(() => {});
233+
const rafSpy = vi.spyOn(window, "requestAnimationFrame").mockImplementation(cb => {
234+
cb(0);
235+
return 1;
236+
});
237+
const resizeSpy = vi.spyOn(resizeService, "changePanelSize");
238+
239+
try {
240+
component.onResize({ width: 900, height: 600 } as NzResizeEvent);
241+
242+
expect(component.width).toBe(900);
243+
expect(component.height).toBe(600);
244+
expect(resizeSpy).toHaveBeenCalledWith(900, 600);
245+
} finally {
246+
rafSpy.mockRestore();
247+
cancelSpy.mockRestore();
248+
}
249+
});
250+
});
251+
252+
describe("drag", () => {
253+
it("handleStartDrag hides the visualization overlay when it is present", () => {
254+
const vizEl = { style: { zIndex: 0 } };
255+
component.componentOutlets = {
256+
nativeElement: { querySelector: () => vizEl },
257+
} as unknown as ElementRef;
258+
259+
component.handleStartDrag();
260+
261+
expect(vizEl.style.zIndex).toBe(-1);
262+
});
263+
264+
it("handleEndDrag records the final free-drag position", () => {
265+
component.componentOutlets = {
266+
nativeElement: { querySelector: () => null },
267+
} as unknown as ElementRef;
268+
const source = { getFreeDragPosition: () => ({ x: 12, y: 34 }) };
269+
270+
component.handleEndDrag({ source } as unknown as CdkDragEnd);
271+
272+
expect(component.dragPosition).toEqual({ x: 12, y: 34 });
273+
});
274+
});
275+
276+
describe("rerenderResultPanel", () => {
277+
it("does nothing while previewing a workflow version", () => {
278+
component.previewWorkflowVersion = true;
279+
const clearSpy = vi.spyOn(component, "clearResultPanel");
280+
281+
component.rerenderResultPanel();
282+
283+
expect(clearSpy).not.toHaveBeenCalled();
284+
});
285+
});
106286
});

0 commit comments

Comments
 (0)