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",