You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@texera.apache.org by gi...@apache.org on 2026/07/19 02:51:27 UTC
(texera) branch gh-readonly-queue/main/pr-6539-10e7cb35000e68f6ab3a21348bba92886acd0152 created (now 99fce9d655)
This is an automated email from the ASF dual-hosted git repository.
github-merge-queue[bot] pushed a change to branch gh-readonly-queue/main/pr-6539-10e7cb35000e68f6ab3a21348bba92886acd0152
in repository https://gitbox.apache.org/repos/asf/texera.git
at 99fce9d655 test(frontend): extend ConsoleFrameComponent coverage with helper, render, and debug-control tests (#6539)
This branch includes the following new commits:
new 99fce9d655 test(frontend): extend ConsoleFrameComponent coverage with helper, render, and debug-control tests (#6539)
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(texera) 01/01: test(frontend): extend ConsoleFrameComponent coverage with helper, render, and debug-control tests (#6539)
Posted by gi...@apache.org.
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-6539-10e7cb35000e68f6ab3a21348bba92886acd0152
in repository https://gitbox.apache.org/repos/asf/texera.git
commit 99fce9d6555a45ca63bac6f9bad7c97f560c5362
Author: Meng Wang <me...@uci.edu>
AuthorDate: Sat Jul 18 19:51:05 2026 -0700
test(frontend): extend ConsoleFrameComponent coverage with helper, render, and debug-control tests (#6539)
### What changes were proposed in this PR?
Extends the existing `ConsoleFrameComponent` spec (which only asserted
`should create`) to cover its previously untested helpers, rendering,
and debug
controls
(`frontend/src/app/workspace/component/result-panel/console-frame/console-frame.component.ts`,
~38% coverage). The five collaborating services
(`ExecuteWorkflowService`,
`WorkflowConsoleService`, `WorkflowWebsocketService`,
`NotificationService`,
`UdfDebugService`) are stubbed, with the execution-state and
console-update
streams driven by test `Subject`s.
18 new tests cover:
- **Pure helpers** — `getWorkerIndex` (trailing numeric token),
`workerIdToAbbr` (`W<index>`), `getWorkerColor` (deterministic hex
color), and `getMessageLabel` (message-type → tag color, with the
unknown-type fallback).
- **Rendering** — `clearConsole` empties the list;
`displayConsoleMessages` loads the operator's messages; `renderConsole`
loads worker ids + messages when an operator is set and is a no-op
otherwise.
- **Debug controls** — `onClickContinue` / `onClickStep` dispatch per
worker; `onClickSkipTuples` / `onClickRetryTuples` forward the worker
ids and surface failures via the notification service;
`submitDebugCommand` targets all workers or a single worker and clears
the input, and is a no-op without an operator id.
- **`registerAutoConsoleRerender`** — clears the console on an
Initializing→Running transition, re-renders on any other state change,
and re-renders on a console-message update.
No production code was changed.
### Any related issues, documentation, discussions?
Closes #6531
### How was this PR tested?
Extended unit tests, run locally in `frontend/` (all green; the failure
path was
verified by deliberately breaking a subscription assertion to confirm
the suite
goes red):
```
ng test --watch=false --include src/app/workspace/component/result-panel/console-frame/console-frame.component.spec.ts
# Test Files 1 passed (1) | Tests 19 passed (19)
prettier --write <spec> # unchanged
eslint <spec> # clean
```
All collaborating services are stubbed, so the suite makes no
network/WebSocket
calls (see `frontend/TESTING.md`).
### Was this PR authored or co-authored using generative AI tooling?
Generated-by: Claude Code (Opus 4.8 [1M context])
---------
Signed-off-by: Meng Wang <me...@uci.edu>
Co-authored-by: Copilot Autofix powered by AI <17...@users.noreply.github.com>
---
.../console-frame/console-frame.component.spec.ts | 247 ++++++++++++++++++++-
1 file changed, 239 insertions(+), 8 deletions(-)
diff --git a/frontend/src/app/workspace/component/result-panel/console-frame/console-frame.component.spec.ts b/frontend/src/app/workspace/component/result-panel/console-frame/console-frame.component.spec.ts
index 0c21cf7503..34205e2ae7 100644
--- a/frontend/src/app/workspace/component/result-panel/console-frame/console-frame.component.spec.ts
+++ b/frontend/src/app/workspace/component/result-panel/console-frame/console-frame.component.spec.ts
@@ -18,7 +18,7 @@
*/
import { ComponentFixture, TestBed } from "@angular/core/testing";
-
+import { Subject } from "rxjs";
import { ConsoleFrameComponent } from "./console-frame.component";
import { OperatorMetadataService } from "../../../service/operator-metadata/operator-metadata.service";
import { StubOperatorMetadataService } from "../../../service/operator-metadata/stub-operator-metadata.service";
@@ -27,35 +27,266 @@ import { NzDropDownModule } from "ng-zorro-antd/dropdown";
import { ComputingUnitStatusService } from "../../../../common/service/computing-unit/computing-unit-status/computing-unit-status.service";
import { MockComputingUnitStatusService } from "../../../../common/service/computing-unit/computing-unit-status/mock-computing-unit-status.service";
import { commonTestProviders } from "../../../../common/testing/test-utils";
+import { ExecuteWorkflowService } from "../../../service/execute-workflow/execute-workflow.service";
+import { WorkflowConsoleService } from "../../../service/workflow-console/workflow-console.service";
+import { WorkflowWebsocketService } from "../../../service/workflow-websocket/workflow-websocket.service";
+import { NotificationService } from "../../../../common/service/notification/notification.service";
+import { UdfDebugService } from "../../../service/operator-debug/udf-debug.service";
+import { ExecutionState } from "../../../types/execute-workflow.interface";
+import { ConsoleMessage } from "../../../types/workflow-common.interface";
+
+function consoleMessage(name: string): ConsoleMessage {
+ return {
+ workerId: "w",
+ timestamp: { nanos: 0, seconds: 0 },
+ msgType: { name },
+ source: "src",
+ title: "title",
+ message: "message",
+ };
+}
+
+type StateEvent = { previous: { state: ExecutionState }; current: { state: ExecutionState } };
describe("ConsoleFrameComponent", () => {
let component: ConsoleFrameComponent;
let fixture: ComponentFixture<ConsoleFrameComponent>;
+ let getWorkerIds: ReturnType<typeof vi.fn>;
+ let getConsoleMessages: ReturnType<typeof vi.fn>;
+ let skipTuples: ReturnType<typeof vi.fn>;
+ let retryExecution: ReturnType<typeof vi.fn>;
+ let send: ReturnType<typeof vi.fn>;
+ let notifyError: ReturnType<typeof vi.fn>;
+ let doStep: ReturnType<typeof vi.fn>;
+ let doContinue: ReturnType<typeof vi.fn>;
+ let executionStateStream: Subject<StateEvent>;
+ let consoleUpdateStream: Subject<void>;
+
beforeEach(async () => {
+ getWorkerIds = vi.fn().mockReturnValue([]);
+ getConsoleMessages = vi.fn().mockReturnValue([]);
+ skipTuples = vi.fn();
+ retryExecution = vi.fn();
+ send = vi.fn();
+ notifyError = vi.fn();
+ doStep = vi.fn();
+ doContinue = vi.fn();
+ executionStateStream = new Subject<StateEvent>();
+ consoleUpdateStream = new Subject<void>();
+
await TestBed.configureTestingModule({
imports: [ConsoleFrameComponent, HttpClientTestingModule, NzDropDownModule],
providers: [
+ { provide: OperatorMetadataService, useClass: StubOperatorMetadataService },
+ { provide: ComputingUnitStatusService, useClass: MockComputingUnitStatusService },
{
- provide: OperatorMetadataService,
- useClass: StubOperatorMetadataService,
+ provide: ExecuteWorkflowService,
+ useValue: {
+ getExecutionStateStream: () => executionStateStream.asObservable(),
+ getWorkerIds,
+ skipTuples,
+ retryExecution,
+ },
},
{
- provide: ComputingUnitStatusService,
- useClass: MockComputingUnitStatusService,
+ provide: WorkflowConsoleService,
+ useValue: { getConsoleMessageUpdateStream: () => consoleUpdateStream.asObservable(), getConsoleMessages },
},
+ { provide: WorkflowWebsocketService, useValue: { send } },
+ { provide: NotificationService, useValue: { error: notifyError } },
+ { provide: UdfDebugService, useValue: { doStep, doContinue } },
...commonTestProviders,
],
}).compileComponents();
- });
- beforeEach(() => {
fixture = TestBed.createComponent(ConsoleFrameComponent);
component = fixture.componentInstance;
- fixture.detectChanges();
+ fixture.detectChanges(); // runs ngOnInit -> registerAutoConsoleRerender
});
it("should create", () => {
expect(component).toBeTruthy();
});
+
+ describe("pure helpers", () => {
+ it("getWorkerIndex parses the trailing numeric token", () => {
+ expect(component.getWorkerIndex("worker-op-3")).toBe(3);
+ expect(component.getWorkerIndex("W-0-12")).toBe(12);
+ expect(component.getWorkerIndex("")).toBe(0);
+ });
+
+ it("workerIdToAbbr prefixes the worker index with 'W'", () => {
+ expect(component.workerIdToAbbr("worker-op-3")).toBe("W3");
+ });
+
+ it("getWorkerColor returns a deterministic hex color", () => {
+ const color = component.getWorkerColor(0);
+ expect(color).toMatch(/^#[0-9a-fA-F]{6}$/);
+ expect(component.getWorkerColor(0)).toBe(color); // deterministic
+ expect(component.getWorkerColor(5)).toMatch(/^#[0-9a-fA-F]{6}$/);
+ });
+
+ it("getMessageLabel maps the message type to its tag color", () => {
+ expect(component.getMessageLabel(consoleMessage("PRINT"))).toBe("default");
+ expect(component.getMessageLabel(consoleMessage("COMMAND"))).toBe("processing");
+ expect(component.getMessageLabel(consoleMessage("DEBUGGER"))).toBe("warning");
+ expect(component.getMessageLabel(consoleMessage("ERROR"))).toBe("error");
+ expect(component.getMessageLabel(consoleMessage("UNKNOWN"))).toBe("");
+ });
+ });
+
+ describe("console rendering", () => {
+ it("clearConsole empties the message list", () => {
+ component.consoleMessages = [consoleMessage("PRINT")];
+ component.clearConsole();
+ expect(component.consoleMessages).toEqual([]);
+ });
+
+ it("displayConsoleMessages loads the operator's messages from the service", () => {
+ const messages = [consoleMessage("PRINT"), consoleMessage("ERROR")];
+ getConsoleMessages.mockReturnValue(messages);
+
+ component.displayConsoleMessages("op1");
+
+ expect(getConsoleMessages).toHaveBeenCalledWith("op1");
+ expect(component.consoleMessages).toEqual(messages);
+ });
+
+ it("renderConsole loads the worker ids and messages when an operator is set", () => {
+ component.operatorId = "op1";
+ getWorkerIds.mockReturnValue(["w-1", "w-2"]);
+ getConsoleMessages.mockReturnValue([consoleMessage("PRINT")]);
+
+ component.renderConsole();
+
+ expect(component.workerIds).toEqual(["w-1", "w-2"]);
+ expect(getConsoleMessages).toHaveBeenCalledWith("op1");
+ });
+
+ it("renderConsole is a no-op without an operator id", () => {
+ component.operatorId = "";
+ getWorkerIds.mockClear();
+ component.renderConsole();
+ expect(getWorkerIds).not.toHaveBeenCalled();
+ });
+ });
+
+ describe("debug controls", () => {
+ it("onClickContinue continues every worker via the debug service", () => {
+ component.operatorId = "op1";
+ component.workerIds = ["w-1", "w-2"];
+
+ component.onClickContinue();
+
+ expect(doContinue).toHaveBeenCalledTimes(2);
+ expect(doContinue).toHaveBeenCalledWith("op1", "w-1");
+ expect(doContinue).toHaveBeenCalledWith("op1", "w-2");
+ });
+
+ it("onClickStep steps every worker via the debug service", () => {
+ component.operatorId = "op1";
+ component.workerIds = ["w-1"];
+
+ component.onClickStep();
+
+ expect(doStep).toHaveBeenCalledWith("op1", "w-1");
+ });
+
+ it("onClickSkipTuples forwards the worker ids and surfaces failures", () => {
+ component.workerIds = ["w-1", "w-2"];
+ component.onClickSkipTuples();
+ expect(skipTuples).toHaveBeenCalledWith(["w-1", "w-2"]);
+
+ skipTuples.mockImplementation(() => {
+ throw new Error("skip failed");
+ });
+ component.onClickSkipTuples();
+ expect(notifyError).toHaveBeenCalledWith("skip failed");
+ });
+
+ it("onClickRetryTuples forwards the worker ids and surfaces failures", () => {
+ component.workerIds = ["w-1"];
+ component.onClickRetryTuples();
+ expect(retryExecution).toHaveBeenCalledWith(["w-1"]);
+
+ retryExecution.mockImplementation(() => {
+ throw new Error("retry failed");
+ });
+ component.onClickRetryTuples();
+ expect(notifyError).toHaveBeenCalledWith("retry failed");
+ });
+
+ it("submitDebugCommand sends the command to every worker when All Workers is targeted", () => {
+ component.operatorId = "op1";
+ component.workerIds = ["w-1", "w-2"];
+ component.targetWorker = component.ALL_WORKERS;
+ component.command = "break";
+
+ component.submitDebugCommand();
+
+ expect(send).toHaveBeenCalledTimes(2);
+ expect(send).toHaveBeenCalledWith("DebugCommandRequest", { operatorId: "op1", workerId: "w-1", cmd: "break" });
+ expect(send).toHaveBeenCalledWith("DebugCommandRequest", { operatorId: "op1", workerId: "w-2", cmd: "break" });
+ expect(component.command).toBe(""); // input cleared after sending
+ });
+
+ it("submitDebugCommand sends only to the selected worker", () => {
+ component.operatorId = "op1";
+ component.workerIds = ["w-1", "w-2"];
+ component.targetWorker = "w-2";
+ component.command = "continue";
+
+ component.submitDebugCommand();
+
+ expect(send).toHaveBeenCalledTimes(1);
+ expect(send).toHaveBeenCalledWith("DebugCommandRequest", { operatorId: "op1", workerId: "w-2", cmd: "continue" });
+ });
+
+ it("submitDebugCommand does nothing without an operator id", () => {
+ component.workerIds = ["w-1"];
+ component.command = "break";
+ // operatorId is left undefined
+ component.submitDebugCommand();
+ expect(send).not.toHaveBeenCalled();
+ });
+ });
+
+ describe("registerAutoConsoleRerender", () => {
+ it("clears the console when execution transitions from Initializing to Running", () => {
+ component.consoleMessages = [consoleMessage("PRINT")];
+
+ executionStateStream.next({
+ previous: { state: ExecutionState.Initializing },
+ current: { state: ExecutionState.Running },
+ });
+
+ expect(component.consoleMessages).toEqual([]);
+ });
+
+ it("re-renders the console on any other execution state change", () => {
+ component.operatorId = "op1";
+ getWorkerIds.mockReturnValue(["w-9"]);
+ getConsoleMessages.mockReturnValue([consoleMessage("DEBUGGER")]);
+
+ executionStateStream.next({
+ previous: { state: ExecutionState.Running },
+ current: { state: ExecutionState.Paused },
+ });
+
+ expect(component.workerIds).toEqual(["w-9"]);
+ expect(component.consoleMessages).toEqual([consoleMessage("DEBUGGER")]);
+ });
+
+ it("re-renders the console when a console message update arrives", () => {
+ component.operatorId = "op1";
+ getWorkerIds.mockReturnValue(["w-5"]);
+ getConsoleMessages.mockReturnValue([consoleMessage("PRINT")]);
+
+ consoleUpdateStream.next();
+
+ expect(component.workerIds).toEqual(["w-5"]);
+ expect(component.consoleMessages).toEqual([consoleMessage("PRINT")]);
+ });
+ });
});