Copilot commented on code in PR #6340:
URL: https://github.com/apache/texera/pull/6340#discussion_r3564178402


##########
frontend/src/app/workspace/component/agent/agent-panel/agent-chat/agent-chat.component.spec.ts:
##########
@@ -0,0 +1,926 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { ApplicationRef } from "@angular/core";
+import { ComponentFixture, TestBed } from "@angular/core/testing";
+import { HttpClientTestingModule } from "@angular/common/http/testing";
+import { NoopAnimationsModule } from "@angular/platform-browser/animations";
+import { NzModalService } from "ng-zorro-antd/modal";
+import { MarkdownModule } from "ngx-markdown";
+import { BehaviorSubject, Observable, Subject, of, throwError } from "rxjs";
+import { AgentChatComponent } from "./agent-chat.component";
+import { AgentInfo, AgentService, AgentSettingsApi } from 
"../../../../service/agent/agent.service";
+import { AgentState, ReActStep, ToolOperatorAccess } from 
"../../../../service/agent/agent-types";
+import { WorkflowActionService } from 
"../../../../service/workflow-graph/model/workflow-action.service";
+import { NotificationService } from 
"../../../../../common/service/notification/notification.service";
+import { WorkflowPersistService } from 
"../../../../../common/service/workflow-persist/workflow-persist.service";
+import { Workflow } from "../../../../../common/type/workflow";
+import { commonTestProviders } from "../../../../../common/testing/test-utils";
+
+const AGENT_ID = "agent-1";
+
+/**
+ * Subject-backed double of AgentService. The component's ngOnInit subscribes 
to
+ * the state / steps / HEAD / workflow streams, so each test drives behavior by
+ * calling `next(...)` on the corresponding subject — mirroring how the real
+ * service pushes WebSocket updates through BehaviorSubjects.
+ */
+class MockAgentService {
+  public stateSubject = new BehaviorSubject<AgentState>(AgentState.AVAILABLE);
+  public stepsSubject = new BehaviorSubject<ReActStep[]>([]);
+  public headIdSubject = new BehaviorSubject<string | null>(null);
+  public workflowSubject = new BehaviorSubject<Workflow | null>(null);
+  public scrollToStepSubject = new Subject<{ agentId: string; messageId: 
string; stepId: number }>();
+  public scrollToStep$ = this.scrollToStepSubject.asObservable();
+
+  public ensureWorkflowPolling = vi.fn();
+  public getAgentState = vi.fn((): Observable<AgentState> => 
of(this.stateSubject.getValue()));
+  public getAgentStateObservable = vi.fn((): Observable<AgentState> => 
this.stateSubject.asObservable());
+  public getReActStepsObservable = vi.fn((): Observable<ReActStep[]> => 
this.stepsSubject.asObservable());
+  public getHeadIdObservable = vi.fn((): Observable<string | null> => 
this.headIdSubject.asObservable());
+  public getWorkflowObservable = vi.fn((): Observable<Workflow | null> => 
this.workflowSubject.asObservable());
+  public setHoveredMessage = vi.fn();
+  public sendMessage = vi.fn();
+  public stopGeneration = vi.fn();
+  public clearMessages = vi.fn();
+  public getReActSteps = vi.fn((): Observable<ReActStep[]> => of([]));
+  public getSystemInfo = vi.fn(
+    (): Observable<{
+      systemPrompt: string;
+      tools: Array<{ name: string; description: string; inputSchema: any; 
enabled: boolean }>;
+    }> => of({ systemPrompt: "", tools: [] })
+  );
+  public getAgentSettings = vi.fn((): Observable<AgentSettingsApi> => of({}));
+  public getAvailableOperatorTypes = vi.fn((): Observable<Array<{ type: 
string; description: string }>> => of([]));
+  public updateAgentSettings = vi.fn(
+    (_agentId: string, settings: Partial<AgentSettingsApi>): 
Observable<AgentSettingsApi> =>
+      of(settings as AgentSettingsApi)
+  );
+}
+
+function makeAgentInfo(overrides: Partial<AgentInfo> = {}): AgentInfo {
+  return {
+    id: AGENT_ID,
+    name: "Test Agent",
+    modelType: "gpt-test",
+    isBaselineMode: false,
+    createdAt: new Date("2026-01-01T00:00:00Z"),
+    ...overrides,
+  };
+}
+
+function makeStep(overrides: Partial<ReActStep> = {}): ReActStep {
+  const messageId = overrides.messageId ?? "m1";
+  const stepId = overrides.stepId ?? 0;
+  return {
+    messageId,
+    stepId,
+    timestamp: new Date("2026-01-01T00:00:00Z"),
+    role: "agent",
+    content: "step content",
+    isBegin: false,
+    isEnd: false,
+    id: `${messageId}-${stepId}`,
+    ...overrides,
+  };
+}
+
+describe("AgentChatComponent", () => {
+  let fixture: ComponentFixture<AgentChatComponent>;
+  let component: AgentChatComponent;
+  let agentService: MockAgentService;
+  let reloadWorkflow: ReturnType<typeof vi.fn>;
+  let notification: Record<"success" | "error" | "warning" | "info", 
ReturnType<typeof vi.fn>>;
+  let persist: { setWorkflowPersistFlag: ReturnType<typeof vi.fn> };
+  let createObjectURL: ReturnType<typeof vi.fn>;
+  let revokeObjectURL: ReturnType<typeof vi.fn>;
+  let scrollIntoViewMock: ReturnType<typeof vi.fn>;
+
+  beforeEach(async () => {
+    // jsdom gaps: Element#scrollIntoView and 
URL.createObjectURL/revokeObjectURL
+    // are not implemented; the component calls them from scrollToMessage and
+    // exportReActSteps.
+    scrollIntoViewMock = vi.fn();
+    (Element.prototype as any).scrollIntoView = scrollIntoViewMock;
+    createObjectURL = vi.fn(() => "blob:mock-url");
+    revokeObjectURL = vi.fn();
+    (URL as any).createObjectURL = createObjectURL;
+    (URL as any).revokeObjectURL = revokeObjectURL;

Review Comment:
   beforeEach overwrites Element.prototype.scrollIntoView and 
URL.createObjectURL/revokeObjectURL via direct assignment, which bypasses 
vi.restoreAllMocks(). Capture the original implementations before overwriting 
so afterEach can restore them and avoid leaking mocked globals into other spec 
files.



##########
frontend/src/app/workspace/component/agent/agent-panel/agent-chat/agent-chat.component.spec.ts:
##########
@@ -0,0 +1,926 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { ApplicationRef } from "@angular/core";
+import { ComponentFixture, TestBed } from "@angular/core/testing";
+import { HttpClientTestingModule } from "@angular/common/http/testing";
+import { NoopAnimationsModule } from "@angular/platform-browser/animations";
+import { NzModalService } from "ng-zorro-antd/modal";
+import { MarkdownModule } from "ngx-markdown";
+import { BehaviorSubject, Observable, Subject, of, throwError } from "rxjs";
+import { AgentChatComponent } from "./agent-chat.component";
+import { AgentInfo, AgentService, AgentSettingsApi } from 
"../../../../service/agent/agent.service";
+import { AgentState, ReActStep, ToolOperatorAccess } from 
"../../../../service/agent/agent-types";
+import { WorkflowActionService } from 
"../../../../service/workflow-graph/model/workflow-action.service";
+import { NotificationService } from 
"../../../../../common/service/notification/notification.service";
+import { WorkflowPersistService } from 
"../../../../../common/service/workflow-persist/workflow-persist.service";
+import { Workflow } from "../../../../../common/type/workflow";
+import { commonTestProviders } from "../../../../../common/testing/test-utils";
+
+const AGENT_ID = "agent-1";
+
+/**
+ * Subject-backed double of AgentService. The component's ngOnInit subscribes 
to
+ * the state / steps / HEAD / workflow streams, so each test drives behavior by
+ * calling `next(...)` on the corresponding subject — mirroring how the real
+ * service pushes WebSocket updates through BehaviorSubjects.
+ */
+class MockAgentService {
+  public stateSubject = new BehaviorSubject<AgentState>(AgentState.AVAILABLE);
+  public stepsSubject = new BehaviorSubject<ReActStep[]>([]);
+  public headIdSubject = new BehaviorSubject<string | null>(null);
+  public workflowSubject = new BehaviorSubject<Workflow | null>(null);
+  public scrollToStepSubject = new Subject<{ agentId: string; messageId: 
string; stepId: number }>();
+  public scrollToStep$ = this.scrollToStepSubject.asObservable();
+
+  public ensureWorkflowPolling = vi.fn();
+  public getAgentState = vi.fn((): Observable<AgentState> => 
of(this.stateSubject.getValue()));
+  public getAgentStateObservable = vi.fn((): Observable<AgentState> => 
this.stateSubject.asObservable());
+  public getReActStepsObservable = vi.fn((): Observable<ReActStep[]> => 
this.stepsSubject.asObservable());
+  public getHeadIdObservable = vi.fn((): Observable<string | null> => 
this.headIdSubject.asObservable());
+  public getWorkflowObservable = vi.fn((): Observable<Workflow | null> => 
this.workflowSubject.asObservable());
+  public setHoveredMessage = vi.fn();
+  public sendMessage = vi.fn();
+  public stopGeneration = vi.fn();
+  public clearMessages = vi.fn();
+  public getReActSteps = vi.fn((): Observable<ReActStep[]> => of([]));
+  public getSystemInfo = vi.fn(
+    (): Observable<{
+      systemPrompt: string;
+      tools: Array<{ name: string; description: string; inputSchema: any; 
enabled: boolean }>;
+    }> => of({ systemPrompt: "", tools: [] })
+  );
+  public getAgentSettings = vi.fn((): Observable<AgentSettingsApi> => of({}));
+  public getAvailableOperatorTypes = vi.fn((): Observable<Array<{ type: 
string; description: string }>> => of([]));
+  public updateAgentSettings = vi.fn(
+    (_agentId: string, settings: Partial<AgentSettingsApi>): 
Observable<AgentSettingsApi> =>
+      of(settings as AgentSettingsApi)
+  );
+}
+
+function makeAgentInfo(overrides: Partial<AgentInfo> = {}): AgentInfo {
+  return {
+    id: AGENT_ID,
+    name: "Test Agent",
+    modelType: "gpt-test",
+    isBaselineMode: false,
+    createdAt: new Date("2026-01-01T00:00:00Z"),
+    ...overrides,
+  };
+}
+
+function makeStep(overrides: Partial<ReActStep> = {}): ReActStep {
+  const messageId = overrides.messageId ?? "m1";
+  const stepId = overrides.stepId ?? 0;
+  return {
+    messageId,
+    stepId,
+    timestamp: new Date("2026-01-01T00:00:00Z"),
+    role: "agent",
+    content: "step content",
+    isBegin: false,
+    isEnd: false,
+    id: `${messageId}-${stepId}`,
+    ...overrides,
+  };
+}
+
+describe("AgentChatComponent", () => {
+  let fixture: ComponentFixture<AgentChatComponent>;
+  let component: AgentChatComponent;
+  let agentService: MockAgentService;
+  let reloadWorkflow: ReturnType<typeof vi.fn>;
+  let notification: Record<"success" | "error" | "warning" | "info", 
ReturnType<typeof vi.fn>>;
+  let persist: { setWorkflowPersistFlag: ReturnType<typeof vi.fn> };
+  let createObjectURL: ReturnType<typeof vi.fn>;
+  let revokeObjectURL: ReturnType<typeof vi.fn>;
+  let scrollIntoViewMock: ReturnType<typeof vi.fn>;
+
+  beforeEach(async () => {
+    // jsdom gaps: Element#scrollIntoView and 
URL.createObjectURL/revokeObjectURL
+    // are not implemented; the component calls them from scrollToMessage and
+    // exportReActSteps.
+    scrollIntoViewMock = vi.fn();
+    (Element.prototype as any).scrollIntoView = scrollIntoViewMock;
+    createObjectURL = vi.fn(() => "blob:mock-url");
+    revokeObjectURL = vi.fn();
+    (URL as any).createObjectURL = createObjectURL;
+    (URL as any).revokeObjectURL = revokeObjectURL;
+
+    agentService = new MockAgentService();
+    reloadWorkflow = vi.fn();
+    notification = { success: vi.fn(), error: vi.fn(), warning: vi.fn(), info: 
vi.fn() };
+    persist = { setWorkflowPersistFlag: vi.fn() };
+
+    await TestBed.configureTestingModule({
+      // MarkdownModule.forRoot() backs the <markdown> elements in the chat
+      // bubbles and the system-prompt tab (same wiring as AppModule).
+      imports: [AgentChatComponent, HttpClientTestingModule, 
NoopAnimationsModule, MarkdownModule.forRoot()],
+      providers: [
+        // The declarative <nz-modal> delegates opening to 
NzModalService.create(),
+        // so the real service is required to render the system-info modal.
+        NzModalService,
+        { provide: AgentService, useValue: agentService },
+        { provide: WorkflowActionService, useValue: { reloadWorkflow } },
+        { provide: NotificationService, useValue: notification },
+        { provide: WorkflowPersistService, useValue: persist },
+        ...commonTestProviders,
+      ],
+    }).compileComponents();
+  });
+
+  afterEach(() => {
+    fixture?.destroy();
+    // The system-info modal renders into the CDK overlay attached to
+    // document.body; remove it so document-level queries stay test-local.
+    document.querySelectorAll(".cdk-overlay-container").forEach(el => 
el.remove());
+    vi.restoreAllMocks();
+  });

Review Comment:
   afterEach currently calls fixture?.destroy() unconditionally and then 
vi.restoreAllMocks(), but it doesn't restore the globals that were overwritten 
via direct assignment in beforeEach (and some tests already call 
fixture.destroy()). Restore Element.prototype.scrollIntoView and 
URL.createObjectURL/revokeObjectURL to their original values here, and guard 
fixture.destroy() to avoid double-destroying the same fixture.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to