This is an automated email from the ASF dual-hosted git repository.

aglinxinyuan pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/texera.git


The following commit(s) were added to refs/heads/main by this push:
     new 502287b75d test: add real specs for left-panel SettingsComponent     
(#4992)
502287b75d is described below

commit 502287b75d216641c7f5b84606bd2a37adcac14b
Author: Matthew B. <[email protected]>
AuthorDate: Sat May 9 15:50:26 2026 -0700

    test: add real specs for left-panel SettingsComponent     (#4992)
    
    ### What changes were proposed in this PR?
    Replaced the placeholder spec at
    
frontend/src/app/workspace/component/left-panel/settings/settings.component.spec.ts
    (previously just a license header) with 10 real test cases for
    SettingsComponent:
    
    - Component creation
      - Form initialization from the current workflow settings
    - dataTransferBatchSize validators (min(1) and required)
    - confirmUpdateDataTransferBatchSize writes through to
    WorkflowActionService and persists when the user is logged in
    - The > 0 guard in confirmUpdateDataTransferBatchSize blocks invalid
    values
    - UserService.isLogin() gate skips persistence when the user is logged
    out
    - updateExecutionMode writes through to the workflow service and
    persists
    - persistWorkflow errors are surfaced via NotificationService.error
    - Form valueChanges propagate both controls to the action service
    - Form valueChanges ignore invalid values
    Also removed settings.component.spec.ts from the placeholder exclusion
    lists in frontend/angular.json (test builder exclude) and
    frontend/src/tsconfig.spec.json (exclude) so the spec is actually
    compiled and run.
    
    ### Any related issues, documentation, or discussions?
    Closes: #4965
    
    ### How was this PR tested?
    Ran the new spec via the Angular/Vitest test runner:
    
    npx ng test --watch=false
    
--include="src/app/workspace/component/left-panel/settings/settings.component.spec.ts"
    
    ### Was this PR authored or co-authored using generative AI tooling?
    Co-Authored with Claude Opus 4.7 in compliance with ASF
---
 frontend/angular.json                              |   1 -
 .../left-panel/settings/settings.component.spec.ts | 172 +++++++++++++++++++++
 frontend/src/tsconfig.spec.json                    |   1 -
 3 files changed, 172 insertions(+), 2 deletions(-)

diff --git a/frontend/angular.json b/frontend/angular.json
index b5e15a2bb7..f5617ecf46 100644
--- a/frontend/angular.json
+++ b/frontend/angular.json
@@ -96,7 +96,6 @@
             "exclude": [
               
"**/app/common/formly/preset-wrapper/preset-wrapper.component.spec.ts",
               "**/app/common/service/user/config/user-config.service.spec.ts",
-              
"**/app/workspace/component/left-panel/settings/settings.component.spec.ts",
               "**/app/workspace/component/menu/menu.component.spec.ts",
               
"**/app/workspace/component/workflow-editor/workflow-editor.component.spec.ts",
               "**/app/workspace/component/workspace.component.spec.ts"
diff --git 
a/frontend/src/app/workspace/component/left-panel/settings/settings.component.spec.ts
 
b/frontend/src/app/workspace/component/left-panel/settings/settings.component.spec.ts
index 51da6c0f2b..c504071e21 100644
--- 
a/frontend/src/app/workspace/component/left-panel/settings/settings.component.spec.ts
+++ 
b/frontend/src/app/workspace/component/left-panel/settings/settings.component.spec.ts
@@ -16,3 +16,175 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+
+import { ComponentFixture, TestBed } from "@angular/core/testing";
+import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
+import { FormsModule, ReactiveFormsModule } from "@angular/forms";
+import { Observable, Subject, of, throwError } from "rxjs";
+
+import { SettingsComponent } from "./settings.component";
+import { WorkflowActionService } from 
"../../../service/workflow-graph/model/workflow-action.service";
+import { WorkflowPersistService } from 
"../../../../common/service/workflow-persist/workflow-persist.service";
+import { UserService } from "../../../../common/service/user/user.service";
+import { StubUserService } from 
"../../../../common/service/user/stub-user.service";
+import { NotificationService } from 
"../../../../common/service/notification/notification.service";
+import { ExecutionMode, Workflow, WorkflowContent, WorkflowSettings } from 
"../../../../common/type/workflow";
+import { commonTestProviders } from "../../../../common/testing/test-utils";
+
+/**
+ * Minimal stand-in for WorkflowActionService covering only the surface
+ * SettingsComponent uses. Avoids constructing the real service (and its
+ * transitive OperatorMetadataService HTTP request) for these unit tests.
+ */
+class StubWorkflowActionService {
+  private settings: WorkflowSettings = {
+    dataTransferBatchSize: 100,
+    executionMode: ExecutionMode.PIPELINED,
+  };
+  private workflowChangedSubject = new Subject<unknown>();
+
+  getWorkflowSettings(): WorkflowSettings {
+    return this.settings;
+  }
+
+  getWorkflowContent(): WorkflowContent {
+    return { operators: [], operatorPositions: {}, links: [], commentBoxes: 
[], settings: this.settings };
+  }
+
+  getWorkflow(): Workflow {
+    return { content: this.getWorkflowContent() } as Workflow;
+  }
+
+  setWorkflowDataTransferBatchSize(size: number): void {
+    if (size > 0 && size != null) {
+      this.settings = { ...this.settings, dataTransferBatchSize: size };
+    }
+  }
+
+  updateExecutionMode(mode: ExecutionMode): void {
+    this.settings = { ...this.settings, executionMode: mode };
+  }
+
+  workflowChanged(): Observable<unknown> {
+    return this.workflowChangedSubject.asObservable();
+  }
+}
+
+describe("SettingsComponent", () => {
+  let component: SettingsComponent;
+  let fixture: ComponentFixture<SettingsComponent>;
+  let workflowActionService: StubWorkflowActionService;
+  let userService: StubUserService;
+  let workflowPersistSpy: { persistWorkflow: ReturnType<typeof vi.fn> };
+  let notificationSpy: { error: ReturnType<typeof vi.fn> };
+
+  beforeEach(async () => {
+    workflowPersistSpy = { persistWorkflow: vi.fn().mockReturnValue(of({})) };
+    notificationSpy = { error: vi.fn() };
+
+    await TestBed.configureTestingModule({
+      providers: [
+        { provide: WorkflowActionService, useClass: StubWorkflowActionService 
},
+        { provide: UserService, useClass: StubUserService },
+        { provide: WorkflowPersistService, useValue: workflowPersistSpy },
+        { provide: NotificationService, useValue: notificationSpy },
+        ...commonTestProviders,
+      ],
+      imports: [SettingsComponent, BrowserAnimationsModule, FormsModule, 
ReactiveFormsModule],
+    }).compileComponents();
+
+    workflowActionService = TestBed.inject(WorkflowActionService) as unknown 
as StubWorkflowActionService;
+    userService = TestBed.inject(UserService) as unknown as StubUserService;
+    fixture = TestBed.createComponent(SettingsComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it("should create", () => {
+    expect(component).toBeTruthy();
+  });
+
+  it("should initialize the form from current workflow settings", () => {
+    const settings = workflowActionService.getWorkflowContent().settings;
+    
expect(component.settingsForm.get("dataTransferBatchSize")!.value).toBe(settings.dataTransferBatchSize);
+    
expect(component.settingsForm.get("executionMode")!.value).toBe(settings.executionMode);
+    expect(component.settingsForm.valid).toBe(true);
+  });
+
+  it("should mark dataTransferBatchSize invalid when below the minimum", () => 
{
+    const control = component.settingsForm.get("dataTransferBatchSize")!;
+    control.setValue(0);
+    expect(control.valid).toBe(false);
+    expect(control.hasError("min")).toBe(true);
+
+    control.setValue(null);
+    expect(control.valid).toBe(false);
+    expect(control.hasError("required")).toBe(true);
+  });
+
+  it("should push dataTransferBatchSize updates to the workflow service and 
persist when logged in", () => {
+    const setBatchSizeSpy = vi.spyOn(workflowActionService, 
"setWorkflowDataTransferBatchSize");
+
+    component.confirmUpdateDataTransferBatchSize(42);
+
+    expect(setBatchSizeSpy).toHaveBeenCalledWith(42);
+    
expect(workflowActionService.getWorkflowSettings().dataTransferBatchSize).toBe(42);
+    expect(workflowPersistSpy.persistWorkflow).toHaveBeenCalledTimes(1);
+  });
+
+  it("should not update or persist a non-positive batch size", () => {
+    const setBatchSizeSpy = vi.spyOn(workflowActionService, 
"setWorkflowDataTransferBatchSize");
+
+    component.confirmUpdateDataTransferBatchSize(0);
+
+    expect(setBatchSizeSpy).not.toHaveBeenCalled();
+    expect(workflowPersistSpy.persistWorkflow).not.toHaveBeenCalled();
+  });
+
+  it("should skip persistWorkflow when the user is not logged in", () => {
+    userService.user = undefined;
+    const setBatchSizeSpy = vi.spyOn(workflowActionService, 
"setWorkflowDataTransferBatchSize");
+
+    component.confirmUpdateDataTransferBatchSize(7);
+
+    expect(setBatchSizeSpy).toHaveBeenCalledWith(7);
+    expect(workflowPersistSpy.persistWorkflow).not.toHaveBeenCalled();
+  });
+
+  it("should update the execution mode on the workflow service and persist", 
() => {
+    const updateModeSpy = vi.spyOn(workflowActionService, 
"updateExecutionMode");
+
+    component.updateExecutionMode(ExecutionMode.MATERIALIZED);
+
+    expect(updateModeSpy).toHaveBeenCalledWith(ExecutionMode.MATERIALIZED);
+    
expect(workflowActionService.getWorkflowSettings().executionMode).toBe(ExecutionMode.MATERIALIZED);
+    expect(workflowPersistSpy.persistWorkflow).toHaveBeenCalledTimes(1);
+  });
+
+  it("should surface a notification error when persistWorkflow fails", () => {
+    workflowPersistSpy.persistWorkflow.mockReturnValueOnce(throwError(() => 
new Error("network down")));
+
+    component.persistWorkflow();
+
+    expect(notificationSpy.error).toHaveBeenCalledWith("network down");
+  });
+
+  it("should propagate form value changes through to the workflow service", () 
=> {
+    const setBatchSizeSpy = vi.spyOn(workflowActionService, 
"setWorkflowDataTransferBatchSize");
+    const updateModeSpy = vi.spyOn(workflowActionService, 
"updateExecutionMode");
+
+    component.settingsForm.get("dataTransferBatchSize")!.setValue(256);
+    
component.settingsForm.get("executionMode")!.setValue(ExecutionMode.MATERIALIZED);
+
+    expect(setBatchSizeSpy).toHaveBeenCalledWith(256);
+    expect(updateModeSpy).toHaveBeenCalledWith(ExecutionMode.MATERIALIZED);
+  });
+
+  it("should ignore form value changes that fail validation", () => {
+    const setBatchSizeSpy = vi.spyOn(workflowActionService, 
"setWorkflowDataTransferBatchSize");
+
+    component.settingsForm.get("dataTransferBatchSize")!.setValue(-5);
+
+    expect(setBatchSizeSpy).not.toHaveBeenCalled();
+  });
+});
diff --git a/frontend/src/tsconfig.spec.json b/frontend/src/tsconfig.spec.json
index 8a5e449e59..eacd678923 100644
--- a/frontend/src/tsconfig.spec.json
+++ b/frontend/src/tsconfig.spec.json
@@ -15,7 +15,6 @@
     // need real test cases written before they can be re-enabled.
     "**/app/common/formly/preset-wrapper/preset-wrapper.component.spec.ts",
     "**/app/common/service/user/config/user-config.service.spec.ts",
-    
"**/app/workspace/component/left-panel/settings/settings.component.spec.ts",
     "**/app/workspace/component/menu/menu.component.spec.ts",
     "**/app/workspace/component/workspace.component.spec.ts",
 

Reply via email to